BIND 10 trac2300, updated. 46b7d9d9e329ea3ed9a338924e0cec4e3c83ee04 [2300] document in the man page about statistics socket counters introduced in Xfrin
BIND 10 source code commits
bind10-changes at lists.isc.org
Fri Aug 30 10:37:08 UTC 2013
The branch, trac2300 has been updated
discards c9906b3b0b5944052658fd0623c5bc681a6cccfb (commit)
discards 0287da2e85540074924e6a14fec05a21ff6b434b (commit)
discards 7523530ef80c74dd62e15f826afde9a841187fdb (commit)
discards cbb1fa16b1a6611f72a3530a0e5af59e33b18083 (commit)
discards b8fda9467b27df0e96d994f452890919077261fe (commit)
discards cc59e5d43a66997b833b4dc17efefa817f09b1ce (commit)
discards 1a43c2c78b116962cb35d6fe0e78a309bb32959b (commit)
discards d8e80fd0b37991a4da1f5b89fa725c380d846959 (commit)
discards 25f1bda1f7c720afa115bd139a8b6dfc1f53abd3 (commit)
discards 58f1e8ea5fe79f825efe46085d07ae9eacb502c7 (commit)
discards 638bd452d479d026944d96776a2ff9c9da8aad84 (commit)
discards fbbfe7536dad25f5c4ab279d4206e1cf4fc462d3 (commit)
discards c857fa8399b10debda6a5f9d2560841d86169257 (commit)
discards b22b644d17f5f9a5fe920fb701c383729132b4bc (commit)
discards 48980e51b29c6ddeb46c6a641f4317b09fa4c3de (commit)
discards fbcc78a9968a459c2f18124fffcbdbdded3cecef (commit)
discards 1c2ae58d7be75f8c500a1c83ea5ae8de1aad28db (commit)
discards 4cf6ba29f0ee6abc8212c93168fd5b90528eef4a (commit)
discards 2cc556f7d5cddc7fe7a437f1ef91a92e83adcfd6 (commit)
via 46b7d9d9e329ea3ed9a338924e0cec4e3c83ee04 (commit)
via 5b63f9693efd47c600139b8522c37ea2f01666a0 (commit)
via 499e6f2e3f5bd141459ba46f90359d9dbc72c125 (commit)
via dc10e21d3ace93577e654885981ace4cae92ee09 (commit)
via 90b12d797d33c23ec96cc877ecf4c84a42f6214a (commit)
via f7a34944f319ebffaced2e1ea8a7dd1c420a4c99 (commit)
via d9ee65e374924d0f1cde75f4aa9c242218d1ad85 (commit)
via 715419df2abeba0aeea9664b3a4b832c669f84a2 (commit)
via 8fbdef450e15140f9a9544c2f5ee3630fdf229fe (commit)
via 442c5bd67499bbeec768570b5d3188a56da58e30 (commit)
via db1635daea11d94213b8a7cedaa2a621331b5286 (commit)
via f42ab59973d66783958e114de14e49fab63882ae (commit)
via e2f9d2e4c1b36f01eb5bfa2c4f8d55cf139c7e02 (commit)
via 15776db27f4a077e46ef8c30953c8e2e0b2eb674 (commit)
via 37af28303d1cd61f675faea969cd1159df65bf9d (commit)
via 0607a3acafcb7561b95577a56474d83b9c2da2c8 (commit)
via 39194524eb9a6ce9fa53814516c1ceaffda3b9c3 (commit)
via 968bc96429fc12aea18e5e4186fcafcb5f29dc58 (commit)
via 8ce213f2e0eb1cfd313897288e08be41047cdbd7 (commit)
via 0207a121a00d0e419a6d067d75696a08b1d1e679 (commit)
via 3669c95989ed36429f32a593d74f58dd917663b2 (commit)
via 5207d193067cb884e6aa4ad63d1a96d52ef04f96 (commit)
via 8c203f0a835b8b5a4f6e40603ed2c7ff6ed86706 (commit)
via 21b9e729efc4ba70e36c69cb223f61d0fb0f81a9 (commit)
via f004912dd236a53ee63ab51e937de7988681a22f (commit)
via 343252ae26f8e68890143fda004008e304dee087 (commit)
via 4e92be8690b5b439d1d022083d6677a5c1a313d7 (commit)
via 8ab644fc53d264555f4896cf05fdf28ea8753d80 (commit)
via fd47f18f898695b98623a63a0a1c68d2e4b37568 (commit)
via c3473496d36e6509c797fd6f55e9b9acd2ba8638 (commit)
via e40e2e96bee958e49a7bb389435add5d980419f5 (commit)
via 0d22246092ad4822d48f5a52af5f644f5ae2f5e2 (commit)
via 803bd5a3f260b5412a58ee61bf450211d4b4e061 (commit)
via f17c3559e3353f42150d8fa742e1658285811d47 (commit)
via fe33a56cfbcd0f846e09a6151313bbb510f76352 (commit)
via 55235c63e0aaa87d8a6ef8e42ee98f7f99c532bd (commit)
via 5d1ee7b7470fc644b798ac47db1811c829f5ac24 (commit)
via 7fef2d7ffdb3b89a18d6a56799564514bffbd0cb (commit)
via 8abbdb589f6205c51f461541778f40dd355db60b (commit)
via 9a1036278055840ef554c42d398478430c8ba188 (commit)
via 28684bcfe5e54ad0421d75d4445a04b75358ce77 (commit)
via 9fab3d3a0124810b18c32b181b90da2737cae477 (commit)
via 18dc9853c008b26940ea70167daec3a26b382a79 (commit)
via 231fed6c34f32ce87f15be0f79bcc258f900a783 (commit)
via 3b8241154843db71a970ebc30b04fdc3a5e73d33 (commit)
via bf6d6bcddd460b7fa2c77314bccb4e14ab856609 (commit)
via 078965388c8160137cfb8ddbe05104d90f3bcf31 (commit)
via a8bb19e3163331d8b1137869d4ddd96353d654c1 (commit)
via 8dcab488bb0d93ec9b97075abecf679db04907ae (commit)
via 1f4b736004977ec0e089424298d9b5dbf49a3f92 (commit)
via f164402c6ddfc870959bf99ea9e5bd4422b7108e (commit)
via 3bc0e24e4d1692c73124fe9727141cc2552e5aa3 (commit)
via e3d5529c2df4ae726877c1ede5f4d5133e6ef2a4 (commit)
via 37d09d3125a73a52a30a418d6f532bfeb8c46b34 (commit)
via 5b38a1e7569c88f5bafc91504c42e16404356f9a (commit)
via a2f074a799b7bdf781b6562f57ecbf9dee991872 (commit)
via 9ddb7eded5402f31b606b7f22f275b890c0904a7 (commit)
via 949fc23ebbadfc8a9beaee6fdefb1dfc661d819c (commit)
via 3bcbbb7f26e1a36ad152912d8bea1a1013c622c9 (commit)
via 830fa71dc9dff20fe98af49455ff639ab39bbce8 (commit)
via 209f3964b9f12afbf36f3fa6b62964e03049ec6e (commit)
via aad0b789f4f82e50e219debc7d6d52675d7c85e0 (commit)
via 57f5c253848314ed4e6e1b8685128b29d8dc8240 (commit)
via 1b434debfbf4a43070eb480fa0975a6eff6429d4 (commit)
via 586809ac7369b85efe135f62b6281e141d88bfdb (commit)
via 0ece621e566a76a6129425c43792f136f9d857f8 (commit)
via 4cd6015489d77e842735a1ecd431ea68af1820bd (commit)
via 507da494700d05fe55a928ad57757a4af3d5f2cb (commit)
via 55f0a8f3f54c91686f910669b5183e4e95b51ae0 (commit)
via f5c6cae3cdd1a3fafa8266575a5cc8a082ef8643 (commit)
via 6c968df2fedfc4c0b464fde5e1772dd24e37602e (commit)
via 16ba5d5390af42d05d3d877ec6a476f3938b5e4a (commit)
via 622e72a46501094b0e1f91b17452448856bced79 (commit)
via e9beef0b435ba108af9e5979476bd2928808b342 (commit)
via c39eb9bbe30285a2b19fea86473b63ddb758506b (commit)
via 0b694071801534d824468dff5ac073a29ba3bdba (commit)
via e4d2d0f7380ba3a9cb62ddc87bd766e3c811f5b0 (commit)
via c8013666dc2f01d4a5c258eacd1baf8d0bab6627 (commit)
via a5c0db852ec1d854979be9c3abf24e1732fd6f4c (commit)
via a8dfa35d56e421fa565efb4de12bff2ee2813aa5 (commit)
via cc2866a7dd9a2eb30d328a24d3257f3179229a8d (commit)
via ef9de155bd156391ed1e21a024fa9bd3835bad18 (commit)
via 51e47dbf6cb9f1b78321d96c0548f7aae140c583 (commit)
via 679445b96b4603fd8f6975aeb526abae8922c2bb (commit)
via 04f940ed5e4f796ce09e1cf216da863a9c9a0d9d (commit)
via aba2bd5baaffc9a69ff99ebbc84c71907138c59c (commit)
via a635a6d6ce769d71109e683f0e0480f73b818112 (commit)
via 33edd2ed3d657798c02a75f236c087527f8137ad (commit)
via e15d409632ae5408531051b3164c2e2b851b214e (commit)
via 4aab881a61571329ebfbf9ebae3f945772c72761 (commit)
via 6ce7437c65b714361b3c100154af4c352859bd13 (commit)
via aff6b06b2490fe4fa6568e7575a9a9105cfd7fae (commit)
via f73cd9bc4a0bbc14f106cec09d3843bfec1971e9 (commit)
via abdd9be83ca3ec7267e4d04f014584fa823f7f6c (commit)
via f80c824d8b1745a0cedbbb238b0e988f7acb10ee (commit)
via ad4c8681d9700216ccdcd347823c5131f2796d60 (commit)
via c185e035984740f3914a28f6a93f9d47ae8e75b2 (commit)
via da34264f7277725b59764e73be597ee7cc513a90 (commit)
via 83645c8498c57f27907113a4d062fe47f74739d2 (commit)
via f8e9c2933fd726cb1499fc03c480c0518e23a89a (commit)
via 334e3b332c8ad84b4e4c04453a43303cd2706a33 (commit)
via d7173396ffa2c262f1a3e587152bef7ad323e4cb (commit)
via 2fbb93f713ace228c5d73fc38e2b3c53eb8cea1d (commit)
via 1463b5017b2c3a0d512ac54f8e644156864452c6 (commit)
via 95e7d858ada42f6180d677d176b79b3b91219376 (commit)
via b177e3f9b587dafa15133ef0b8fe621cfbf95f2e (commit)
via 785e97d51905a7d7d7d32d08ac7d82ceee272fc2 (commit)
via 4e902ba369b16a1b3202a4c079af9db429295c97 (commit)
via b92b89134b7393e24e9bfbe16deee9ca99d352e1 (commit)
via 8838f82a224a2aeb7479bc2c4bc3c44c3c20454b (commit)
via 9823dc9e0a2dc618d3eb6eacbfe6ee29cdaf0500 (commit)
via 3edad954ddf775772818ff87221bde52350bebc5 (commit)
via 646a4738545ebe5a44c6922a2950296a0f3f7c55 (commit)
via 72077a1d5b36a470ee5c16599171a2d7cac11ac3 (commit)
via d581f79243723a01240da4b7b5adb8028d016e76 (commit)
via f5f07c976d2d42bdf80fea4433202ecf1f260648 (commit)
via 278c59a0b5db80c61ed08550da6d065af8aead90 (commit)
via 22b29c570a68a8a719f10e178aa27210b2927f57 (commit)
via ed7586fa45a5aac074e8f38a01e8ff5a48ffefaa (commit)
via 598e458c7af7d5bb81131112396e4c5845060ecd (commit)
via 511912d01bb7b2019f0a36100c524859a6de23b6 (commit)
via 69024d03d0cebda395388fdcec2ad559c093f8aa (commit)
via 544668f9aac4823519cee2b10b52315c0df4c259 (commit)
via 578910f51f1dc96876a67a02d353c396cc4d2ec8 (commit)
via 1745301647282b8db81d5618543aabe66b255dd8 (commit)
via 35a7432feeb263c42eae96d6388729476f20b6f2 (commit)
via cf7cb62d777a1d74db1abca0753e5fb2cfe0c310 (commit)
via 535abe75debe26e48284244d0e856b9cad4eeec6 (commit)
via d07c725e9a441e363a18e66965ff7e76f4de526e (commit)
via 0eefdc946964638e619c686604c7a275919e2b80 (commit)
via 7660c95d4f700d1324eca7cff26bbfa16444c4e8 (commit)
via 2092528ae67cf42002c2aef17140d95cbcab8a54 (commit)
via 806ec4bfdf1561f4e59c67550363caf10c41e10e (commit)
via 08a44d8edc6c12dbbd54b9d8b68350732ed57668 (commit)
via 21bc5facd59af83c64bea54baafa025e8ec63fa3 (commit)
via fe763709d177243f6bc22d849d6277423b71c32f (commit)
via 71d687c3666c413e0460ee6d2b992d212547e01d (commit)
via f59e953a20f34118e276c215ed5d75f0e0611c7f (commit)
via 290fb63c1c7640c8fdc8801b93c36f8ca0432b96 (commit)
via cf24637df0959a2b68f2bde161790a73e3270abd (commit)
via ecd94090d4f063d308854041c4343775ab60108e (commit)
via 3483361dfe0d9b0a4448fd7e53a7c3845e66be67 (commit)
via 623eb1b10deb087d9d0dc6c6372140f09933f85f (commit)
via 39db0756e576730953d3ff526c8c124103f5b5df (commit)
via b1a9280bd41ac9de63ebad586033f6ba1326b8b7 (commit)
via 1596f2ccf2273eb5be25ff92ab196f9bd714c0c4 (commit)
via 5498431f4deb81a175135d8334d5f6011455ea1f (commit)
via b2a59752080f1aa7dbcfd677c5bfb8f65239c4c1 (commit)
via fef265b2e3bfae238c91bfd820bed760efc748aa (commit)
via 4fbb85cb991a8f3f412c2c386dcbe154fa5f04f2 (commit)
via 516bb5392ff827f17b7c91d42b6c4f3151a2bf44 (commit)
via f98caa5f314fd63d66e74461bbdd9b33910d850c (commit)
via 516417bc1e769a0fb7e0d965ec9cfee51c342624 (commit)
via 695d7c75d5b307bd0eebf042b78904396e3adfcf (commit)
via fafe5cb09e43b29a3b014a77bb0cf3c4c1ba47a9 (commit)
via 88636c950bc80ce218aa5a98bb6cefc70b653fd2 (commit)
via 81c1c17fd56ac7b087579ee46205349623f6be41 (commit)
via 39588e5fbd781cec7344e07ddadda835aa74d8fb (commit)
via db29b326905b7915c5e1723b565900f06659eb30 (commit)
via 0271260016349e40ec8d366b82077d93aac3ff47 (commit)
via e3dec9b39035a59bbdb561c12817872d66f34fbd (commit)
via ffbcf9833ebd2f1952664cc0498608b988628d53 (commit)
via 1ed6c5843426b22dffaad30ed5403e034896a5ac (commit)
via 10d9ff6b5b241f41939bee5bc5304d24b3147e15 (commit)
via 73372e3a10aef030bb33f2834357fe2f4a3bb481 (commit)
via 1ceca44c4f118ea3d0246a7ac4b67331fcf4241c (commit)
via 34081cbfbc9f0be52330015f8b58dfbbc7997ef2 (commit)
via 540dd0449121094a56f294c500c2ed811f6016b6 (commit)
via 1b46569fab6d606359ada85f6736951c969e8eb0 (commit)
via d72675617d6b60e3eb6160305738771f015849ba (commit)
via 0fc5b23ed78c9b9ed16437a5b53e45881d413e77 (commit)
via 9c88046f2b021dded1981966b930029e765b5915 (commit)
via feeb79f56b2fd672d8ba20607ed26164d362d0e1 (commit)
via ff0b9b45869b1d9a4b99e785fbce421e184c2e93 (commit)
via e5b3471d579937f19e446f8a380464e0fc059567 (commit)
via 6faea427392fa8809269171dc7d98d42177af846 (commit)
via 4f2cfbfa1b3edf01bab9c4d8b38a651ab857f52d (commit)
via 40b956102de5751498e0acc27dbf99a7c554d627 (commit)
via f4d4e14ab8c0756bd19db78365b4eb1593a91867 (commit)
via 1176cc4839f9813cd49733a077715b5b2a767f8a (commit)
via ebed5cb932b16588af7a8fafbd6fc31321154f3e (commit)
via 307eb1cc6cd90c831d95af0511424b5b468e4d37 (commit)
via 72bcad54040078acf2e30d2f31aa177616a570df (commit)
via a970f6c5255e000c053a2dc47926cea7cec2761c (commit)
via 508b40fdd135122a0e006524ac882794d28cfc9f (commit)
via ce9289d1ef31525a60dccbe0daa72e8f8db1be63 (commit)
via 0efb9524bf1782a3e5875982a844a86e6b40460f (commit)
via 067550e38d9bba5831375e9cbbd93674f1353ee2 (commit)
via 26a805c7e49a9ec85ee825f179cda41a2358f4c6 (commit)
via c79031b28b835ccaf0d683b80ae877a5c94c12d4 (commit)
via 029dc6aa03d7d72d60cb424f1121d916064cd6a4 (commit)
via 6674553cc7932dee920e06a4936ba28916552e25 (commit)
via 869ac09027a0fc146ac995e7b1dcc875e6184608 (commit)
via 2babc7e39394de13cf72b0009e47c3c994f346c8 (commit)
via b68e8f32a7128d99e69a9ed42f9d8d21d6a68706 (commit)
via 09f557d871faef090ed444ebeee7f13e142184a0 (commit)
via 2b589c6418e5882bd14bc9471d292d6fc21247d2 (commit)
via 9c3f96ecec2fe57a90680b80fbee8079c94c87f3 (commit)
via 7c0bad1643af13dedf9356e9fb3a51264b7481de (commit)
via 07f62405839389c802c751ddac3ca0dd43ceb754 (commit)
via 59a4d0243d77ae0a12a8832e383892485157447a (commit)
via 22cd67d216fb454b2aae9086a7a0afdec3c79c32 (commit)
via 91cc21601ba76a1a99a42b31530a55948d7a9c93 (commit)
via c322cadf5a251ee14444a059e6ae66611db97488 (commit)
via 36c92588ee9973fe6400547dd38a18fe62161c2e (commit)
via 9388cae1ccc1d3e5c6934c47c857a61ab17cf52e (commit)
via 27d057a79de7e769cef2a87e59e08807491bf44e (commit)
via b8fa96e3498580f2c01b6baa1d38e649edee418b (commit)
via ccdb49aa255c988dd3f03be21b9511794af288f5 (commit)
via f36f677e841adddbaa762480d0cf8b961c098df1 (commit)
via c386d06ac5239ae26446c3bb75143ac54820698b (commit)
via 56f41442f0a647c959e23d8b96ef949607ce98d4 (commit)
via f6fd9cc583267c0ddf779ee0d0fce7ffa697c554 (commit)
via e323b235b5cd03fda009b976fa7765f032e4ed35 (commit)
via 063f70ad570dfc5d8697341bf8643ebe3eb80d7c (commit)
via 363e37a380d7bbfca480e2008b3c135029647d69 (commit)
via ee0e068ef274d482659b11f444d67fa5d0b06de7 (commit)
via d5c2bffa4970fbf53178e4a1975a0e476d3bda32 (commit)
via 26b0ae80ecf50d3868ca059a5965ecb275bb135b (commit)
via e3e92f0134210e040c537753fb32e36679f4b1b0 (commit)
via 2e4d7c7f734e5d10881698e05a26193199558fc5 (commit)
via ceb084b6e3c728d23154c5ea4ae8d10bb3b91477 (commit)
via 35c53d9ab3422ba82ebd2c4f9afb73f1a5dc58dc (commit)
via 6606b6d02a352c15fa60f842bef9ccfecab93c41 (commit)
via 2b38d4d369e3519b835cda94f1f167b4b96d5a0f (commit)
via 940eae0ca5d303a9420747a5c84358f91515075c (commit)
via 8ddaa8da24109d602cb7aced60e57f8422d0c529 (commit)
via 4bca9c538b8deddfc49694d35bebd44c0bbdfc81 (commit)
via b920504363ea51053de5d287dbc68bd271a39f90 (commit)
via c9f3fca8e79716778c0b296a8a0b78db6571f4f2 (commit)
via 8acbf043daf590a9f2ad003e715cd4ffb0b3f979 (commit)
via f964dbc5497ea2aef884251c708007e97185df2e (commit)
via c989ea1255cfb2086655c86234270b10c55efdff (commit)
via 51e0b7424c7b3992e1bec9d524c7554ab33f45ab (commit)
via 775f572c3c6cedcd8f1985c5cf969cb18dbe2e9c (commit)
via 434edbb7276472a4ac31ccb63257fda00698baab (commit)
via 9b71351b47386012f178803b56b71f90a972e9b2 (commit)
via c46e16711aa802046edb5557dfe89afb04be229e (commit)
via 3056c7dd61a56b7053d04819b0b778e0297ffa25 (commit)
via f395f315a51a23ac058456ec15580231f145bd75 (commit)
via 63d34705073649a2de35b2f85cf66c1bf64aaddf (commit)
via d03c9fce131acc35740a52e63894761c77915c0c (commit)
via 7b94068163e1d90e4c10a19ec5a847b614f31f99 (commit)
via ed713618c11b1e63b4a6e10fb487a4bf3339c2ac (commit)
via 9694e88402b35b3df56880c656270c1108e0df13 (commit)
via 3b2674982d558f22f9a2db3b43c922e29ba7520c (commit)
via 2459cd14aeff5cfccac711b6e6de4a4cf42817bb (commit)
via 2d47b144b1a760c4338f534848b9b801fd709964 (commit)
via 943e416451f87977b7aa8ab63c99ccf05865436b (commit)
via a33cd74d9c8c8b2d2789e98db9ad2c94ecafdafd (commit)
via b62e62a49012ae039ecace4c4d68ad196e151cc1 (commit)
via b16f3317c3fe5fab8334f8d87ab7aa905dddb0d0 (commit)
via a00b45ec9cdb590b973e5c00135f087ebdcb9aa6 (commit)
via 3d39bccaf3f0565152ef73ec3e2cd03e77572c56 (commit)
via df4d44fb1d1b127847a61a84f5cb529a7a4cd324 (commit)
via 87e43d5dfdb2b3c470e6f3c81e786b49a9a621c5 (commit)
via cffabd52cf16cb75e896abb29ee194e6e90223b9 (commit)
via 3eb5c11e8b91bd09f8d7111cbeb8df74deb2ea20 (commit)
via df8447e4fa4445aaa26fcdd9c3a09e3cdd15fc1d (commit)
via bb117dc8ada64fe650b090a693af1df45f1c805f (commit)
via 99e8782450496aba1595fbee4058d0f1e5a4477d (commit)
via 478d2eda7aca5a6e7f4a0e017192854f100641b5 (commit)
via 8f0b820ed66c3dea6e9045bd8568af26cfa8d36e (commit)
via 7a6cf1357a4c85a794388475ddfcde9ff92cb6ea (commit)
via 8015b408d16d9d5e69c678a1201a9ec0649697a5 (commit)
via 29228dab743c022c07585dca775ee732a42b5571 (commit)
via a6cd22451be6684f4bebbc34d5344371afdeaa4b (commit)
via aac023f8e10f6922400400d614d0a0b141e79c53 (commit)
via 786a742bc321666586ee923cf15e2d2624ddee42 (commit)
via c7b293f631c53764081428f4723b8c30b5a85fa8 (commit)
via a3e596573657218631e0efba4432a4c07a05c593 (commit)
via e11e8c1488cf3e418fc264f5121d1ef711118264 (commit)
via 649a0f8f2fbfa14b4b9ef9d233fc3290aaf7386e (commit)
via cbdbafbef1e3eaebcf057e15afdef9e6b387e058 (commit)
via f0b494fa3a9ead7321864d5f28c81092226fabf0 (commit)
via fc5e5746a148b1194f5a4d623bb96d1a8d9f085c (commit)
via be68664ae1064cad2e9440fe89511b6166755424 (commit)
via ee4bbde1015148df78d7d5dbed07852866ba99b0 (commit)
via b54530b4539cec4476986442e72c047dddba7b48 (commit)
via 3c285e7ef1b8890caa12ea83353b2e03f2bec23e (commit)
via d165e1c0d34e34a24b4519106af41af74d92b197 (commit)
via 9643f2fc5610a68278c7a1ea7cc167991467122e (commit)
via bc799623e178c6d3e979df3630b8dc65c92fbb62 (commit)
via 105e9affbca307c53328b34302020d27435153f8 (commit)
via c9874eac89f0e49c56c3911446e2e2d0f6d48657 (commit)
via fb7ab395e160cdaefa9ecffb4a70343d24cb80b6 (commit)
via 7d4a5388296c225d11014d6e123cf0ee2cc8eb9f (commit)
via c706b28ae11ad17fde72a2f06a2cdf17f62a5514 (commit)
via 762f2fe1566c467eab8d48444f3d0c0c388d9e9b (commit)
via 35f65cf6f3644e5bc97d058ceffe87b2253305be (commit)
via f8d312a623932ae279f24e6ec9587696ab2418a1 (commit)
via c5c64939c80687faa5692fef51c8435209736d55 (commit)
via c183ac1349ddc9f4d76d1426cbdcd80028cb43ed (commit)
via 988bb28c2391905a35e6f0c9113f78feb8f8bc97 (commit)
via 979f2981bc3518e349e036f92f5058aac2b1faf8 (commit)
via 39a853b0f561e12604fcb31a2b3746d5d577071a (commit)
via c845a96005b28acde2d62a93ee8c046162da4cb4 (commit)
via 456a43775ebd7982ba98f57286702c420f983cf5 (commit)
via 5c56a768045e78576cc71346a5cf4de9f2a35a13 (commit)
via e21cb1dba24a85e7afea480562d2b7277050e0dc (commit)
via 0f845ed94f462dee85b67f056656b2a197878b04 (commit)
via 4f96d3ab2e65356e9cb778ba140a9b8793f8e879 (commit)
via 77e3dfce7c96bc924de4f1ca7060287c147e53e5 (commit)
via 724e39ef6eaba76229a8208d2bf33449f16f3c01 (commit)
via 941c1d8bfc0468b585039c92eba3165e20f6d08d (commit)
via 40471d97b2dbe22189147f2f46f5956c587fa34f (commit)
via fe2331e7e0cadd2e70dfecdb9cf5a67f6484a5bd (commit)
via 135b47d64fba42eda8cd194f257ed77efb441797 (commit)
via 7915732b2210e5540a74041eb759b13af0f6c405 (commit)
via 6f5ce2f575e16f71a88ac35ec93c07ecd254d231 (commit)
via b74b04e1944680e73163cb8f229111d7be3df2c3 (commit)
via 8525babb4efb54926675d2d1a1398638cbc6676d (commit)
via 4b0906418973479e742cf831d5acdb291384014e (commit)
via 3e90051aa1a97eb1c450ba223ef2659220e97a45 (commit)
via 0a3773e5df10cc118dfd9dab3ce35bc612ca8ae7 (commit)
via 07c566bffc7e726945d1c3fc58988c0186d5e0c0 (commit)
via 616213225fee89adc57c8ea19a2195f8273dc5fa (commit)
via 6f304d9ee4e85db2e3aa708ee31135c4e42ef347 (commit)
via d8183b83d92016696106a6665fde369fa7d49779 (commit)
via 9cccfbbc4e13274cc1c5750704f03fbc577189ef (commit)
via 61363a11804b7838e848b8f07cba13dce80e078f (commit)
via f1c0d7070d9a3968ff900663bffcba20e53851b0 (commit)
via feebb25811b38ebfee97c2a11a1470f7aaaf6696 (commit)
via b05bfd741c5b4ec90ada47017af8a299f8a8b4d6 (commit)
via 589f61f8a630643bdee4d8bb2be68a79fba64112 (commit)
via be65cfba939a6a7abd3c93931ce35c33d3e8247b (commit)
via 770c05222d92752594605e1fd16e9527e1d079cb (commit)
via 76aa17ae5f1b89efac30211da457196d79ab49e4 (commit)
via f4dcd7d3757902c90bfa3e7b7db1600c624ea510 (commit)
via a1df84adccebfc6e3f6625aef58f1c9f31cd3eea (commit)
via 946804f3beabb2367474b15bcb173c1fc53265d6 (commit)
via 4ebc8cf417703e5350d68ccee409a8f1f9daef4e (commit)
via 831bd66ffc6def8d0db2c0ab0761bfe0ef0259f4 (commit)
via d71ab2e5570d20ec6259b764472687c81009be24 (commit)
via f5e72732b725a2e20eabe2846a85ac8abb499f76 (commit)
via 8c1e879aeb6043f2a26ba83a07b5a3ee87ae1855 (commit)
via 17a2fab7ec8ccc6886e887f9d9bff9e51fe080a9 (commit)
via f48a3bff3fbbd15584d788a264d5966154394f04 (commit)
via 4084c5f2a9a24009d645def53697b139bf989124 (commit)
via 89d6be56eefa1ed79d9c0cfb8112243e1435beb6 (commit)
via 088a0124e731718b6e8be8092a47a087436ec1dc (commit)
via 6b65ea8a618d32efcdc0697cf0acc534b6f13404 (commit)
via 0d4abba272e2e77132b374a657aa96f65505f26c (commit)
via ca5c18086bcc1fb712a33c0f4f96c27cb76348cd (commit)
via 92ebabdbcf6168666b03d7f7fbb31f899be39322 (commit)
via d803eeaf3f71b0f68d26fde29802adf2a55fa31d (commit)
via d7c133c2e26b0174c96ef48781352feec5f9e03b (commit)
via 949a25220c81c7bd4faed53e0434446c18171ec9 (commit)
via 8b1aa00503272f26a9f0a0271cdea93aab3b3295 (commit)
via 473c1e672ab88928d1f9bda61a1c2690393c018b (commit)
via da1318c5b12541c6a101408e49108b41c2d4aff1 (commit)
via a765638850588cc8980c622351124f1319c46365 (commit)
via bf471e6c6228edca829deb48f810944ea650a19b (commit)
via 0657510f32552e1d2510ec34e807d41c1a0f964e (commit)
via 5b097c33037c8891a50d77f71ee0518e9c7c955a (commit)
via 18dd133a37d37df043d36008286bb769838617be (commit)
via f683ad20d4a1936679ebfbc3f7eabedee1ff5182 (commit)
via 1f09795960479dcbe6e53ae9ead9f969c0705107 (commit)
via cd9d3e1a07cba7cabf1118ffec7f7dacebc0ecfe (commit)
via 9c54e830d9f3b18232de8304edc490f08ceb4f0e (commit)
via 27a4c2199989c2802b0a5002000c32e6c4e00220 (commit)
via e61c29836e74f2fa7b0767b44d274ee36e8dd6ea (commit)
via 7c6999a010297cde490304a97570aa48e88b2099 (commit)
via b3d42b39d85fbd34e67c00c35ae87f823a311373 (commit)
via 22f0a1d193780fdec50bb92a041ec12c8b28ca73 (commit)
via 39f2d9ed855810f30c8bee76c0e8bf1bb7fdeea4 (commit)
via 95701a2c5bed358c1eb3f2e8d2424b9a88d8612e (commit)
via 049d11fa1b6b08e8ad866686e57726e5906ced1c (commit)
via ecd9691c1791c973b9afabe17b95c108596be0f2 (commit)
via 2cbcb533f792251d19339c8b38ac7b87db94b28a (commit)
via 02141f2524808a5ccf1ac9b620fc702e37607e31 (commit)
via 07074c5295156356a0608fbaf8d706f5eaaaaef8 (commit)
via a076d799d926d23e67f495df40812cead7425aa4 (commit)
via 01bb436c45f4d92a3575ff4ad2f5c1cb348ce399 (commit)
via a8a8909b511278b2abe1679a3c801bd1fd1ece74 (commit)
via 15471a94d7cd89204b1a7de89398255662590458 (commit)
via ca9233574b5bea413b38916a0e3593ff8c581b1c (commit)
via 5b10e071de6d724d42e2f6eba4cc78bb7fbacd23 (commit)
via 1626b91e6d458b0dfe8fb71434b52273edaeb795 (commit)
via 0f4c53e60316f5b7006d96627d6236645285fe8c (commit)
via 272122f27fdf10a479c94e6270326f6ab028939f (commit)
via d3c480a5ef3803787564cb8f1bbd92df4b7185d9 (commit)
via 3c1f096e54e42bb79fec8a28abc6528b3c5fc857 (commit)
via 8e13f44e871bde3f44ffc080a1996149d61fc13d (commit)
via 56393640a8c2d3b11b549f1c1133991452c7a088 (commit)
via a6c4e80483303e6fb4deabb586a357987b79b194 (commit)
via 0f8f7995fa2fd6f340d405305783a97b34307ca9 (commit)
via 87ba16e0b1db1a831e394650c7713b028a7d42c8 (commit)
via d6de376f97313ba40fef989e4a437d184fdf70cc (commit)
via 14018a4e7e240d9770dfd565284abdf2f82f9b30 (commit)
via 2221a1de37ddc5c35e52ad0b8b99d7dec430b00c (commit)
via 0d8bee5c0cc3cf65a0a14890d7ff76de328e4316 (commit)
via 2aa3e8e3ad750691dc97badcac032ef1ca92737f (commit)
via 661dae35e38a4b4a7d3f22b785509ceb528003e1 (commit)
via 7276a2eddfc2fefb354a4848aa4376b9dbeb675e (commit)
via 3194a2019cc116c9c668cf4adbf879e06b927879 (commit)
via 7cd60567cc43fe83a74a7799373e5e6410d435f3 (commit)
via d6c9e292ff83ad12b82b8aa73e59ae05d23ee65a (commit)
via 617aa41e35a33420f00dae8bb9f39918208dc1a9 (commit)
via cb814de47219dbc310f277838106d5b4d7e17668 (commit)
via 228807659e177e95f4a3dbedc6978dcb8a02a2c3 (commit)
via ef523ea6235d16e0676d0263c964570134a59750 (commit)
via eb01e3dce1476254ec6720798c861e709d32c9be (commit)
via 87c3781cf964f63b044153a39b05518f72d8b80e (commit)
via 02e0859b64a98789b422bc57c457825bd991749f (commit)
via d26eac8dfc0780bd6211ff90f9d922bbe2bf7edb (commit)
via 36815d6a2501933007ae6e8f823be08a64064058 (commit)
via 37753c0dc215875538463720a5bdd68afdb4fc4e (commit)
via cca0075f103ef62c5b8413bb19a90c03d7bd4e04 (commit)
via 75df517663d7743adfa1295e87f8a77ca97a059d (commit)
via 39fa196b6b7eda249caa0501feb6d049cdfd9123 (commit)
via 98c85cb61b5bcbf948e1ea0923adcfcdca753cbb (commit)
via abad8addef410474f0bb6bf6e15831902017e579 (commit)
via 78fdf6daaf77f5e341e921bb1cb3ac7084060c75 (commit)
via 4337b26b8553e2815c61e979a3c6442b38687f8c (commit)
via 9f8d746079cad652e014d482327b6a73d5ee2f12 (commit)
via 0a5d26dfc2ee6a4a84d596cbcb39e894e7432f3c (commit)
via 465c3330313daf9f80471056dc41eb53eaaa3531 (commit)
via bb7465ef1ba3853c069c44f8f1ab6491782fcd5f (commit)
via 377e88195e5f86ad60bf02bf02dcbd202daa9fa0 (commit)
via d9b56777aaa3a4e70c9b80ecde1b69aab30cb0ee (commit)
via 7027266480db82481529f3680c506a23289ea460 (commit)
via 9517e24b3bc274e4a8f080cb15358db8017c3f44 (commit)
via 42407bf17bcaad492cfb9264dd713ad0e0c4f84c (commit)
via 2ca4520e166d0d07232d0fb9cc31587f77176960 (commit)
via 42de7b42fd0fd6653489efe5412b34365d3a4a77 (commit)
via 01f159a6b4277ccbbb76b18e1de811a0a71b80cd (commit)
via c2dfa3796558372cb69c9bdad6963835c18b3937 (commit)
via 339d60a7b7a5b83a2b9c5064af5d621e3047582f (commit)
via 54c8dec80aa3198a5f6dd7123c1ca415a85a27f8 (commit)
via 009e306698d3c8018fc2a83aa2c9cb6bd787a875 (commit)
via 6f1a5fd22d80b90a5854d4e9c03f1aed13f32bac (commit)
via 0ee5175c54020abcdcbafe078cc2e2dbb53e8dcb (commit)
via bcaf721d44f6805b6b73ab92c317597066e7f752 (commit)
via 7110eb5282cb4f028867d7da7ffdccb5fc503653 (commit)
via 62bbaa215675fbf0e1d7ca34219d5771a04eae4c (commit)
via f3d988ec344e2ef369534c86952b35d7ed0a1471 (commit)
via ffdc326d413b2eb9d95dc73f3948788ca7ece728 (commit)
via 2de17f71bab2fe5b02ca8563477441dc2805661c (commit)
via 72b06693a049e925d240971d92dd8427d3fa8f73 (commit)
via 721eff49ce716e79b898ad578e21efb96e684684 (commit)
via ed363b36987c46c37452c3e83b9efce0a5117cf1 (commit)
via 8c65dc3a4cc369eab2235b0ebc6d56dae4589c5a (commit)
via fd70aa81aeebbb2768d89185d1859520cd535b58 (commit)
via dd01c785f0d8eb616ee4179299dc53e0d2c0015c (commit)
via 82c997a72890a12af135ace5b9ee100e41c5534e (commit)
via 0d98fbf5f919f2ca5a5134bd6d3527bed6606d10 (commit)
via 4ae6b8af324d8e7bcd13f3e6ce5a632f3d704ccf (commit)
via 5a67a8982baa1fd6b796c063eeb13850c633702c (commit)
via 67a39b06a802b161dd6c4c93892bf3309d30d892 (commit)
via 3430cf9ae922d5af87fef9ca4c202db482f29391 (commit)
via e746e58a9fec019dd8a944baeed75c685a6cdf28 (commit)
via 196c9f80b51bb0c4d9e9f6540fc0c5258b69cd06 (commit)
via 7f4bf2e2f9b9eaf3410bf22f918478cf4a5f0ec2 (commit)
via e45f325ec7438e0ecd09d37a7d938348232b994d (commit)
via ec4df20fd7827f622fd9aecf21faae072337b32d (commit)
via 4ef6830ed357ceb859ebb3e5e821a064bd8797bb (commit)
via 016c6b97353dd57158d1da0f574912f2823e74e1 (commit)
via d406f5702264d689e6c1b7dd6c285b7d5ab06db0 (commit)
via 682cac0da7b318a81746d93424a638faa867ee7c (commit)
via cc508f6c28f0fa48720d1d78dc89bc4c287e207d (commit)
via bfcf73f7b86036c774b90e1df948e12399269385 (commit)
via a8a6595db650be70a46d4cd78f9cf8202b2c74a1 (commit)
via 5b7b932c171d48009f722ea20ecdbe852ad2b8aa (commit)
via bc25129780f807429a8c9f4cec569ed35f6b066c (commit)
via 58ad4fd28118d01c5207fcb00422f3a88e50cfab (commit)
via 48aa711b39c5c57d569d6738257ed9e5b4d34995 (commit)
via 644a03f0da719b01456ea0115cc0bd661ff9e636 (commit)
via 89a447a134798d2d59ae7a5565a9f07271ed6e9e (commit)
via 292f28a54f954f44b4771f359c9a3cc4753da22a (commit)
via 1bbc1906476bee5320eee4acb954af7b47c2b2d6 (commit)
via af62ea00182e18f2bfa448870ca37490eb830f09 (commit)
via a84352bebaacb3f414e5a01b1b25a3c7d3e50bbf (commit)
via d4fba54ddee752b447164bbed089735f2999f28b (commit)
via fd38783ce713c1ee718805cd5cc7bd33b07dbc4c (commit)
via d474c8d06214a2ef3dfc991dbd7b73dbfa6572b8 (commit)
via e7855781ad5d287d91c883143bbe2552bcc29f8d (commit)
via 59c4a138d18a6fb2a0adc0daff488406b6a6ad09 (commit)
via af0db15b1727fb986a5a08819624b231f97999e8 (commit)
via 3401846345cc6654eec63649e6bf1c71261b8b4f (commit)
via e68217dd349bb078aae6029b95e182ed165e7bcf (commit)
via c6e820b3078f39bfc19c40afb1c0294c15c16707 (commit)
via 3506d2445092477473a9f15134b8de39dd91aa4d (commit)
via dbabac08932ffda4307f7176be732b8aea655e41 (commit)
via 4bebf8b48d3e6b9b9b1b50618671e772b7e7ebab (commit)
via 29748e7583d0646b4d668c9b70c8c07af49adf98 (commit)
via e1b4215d69747ee068893e167e96428dfdbab1ce (commit)
via 30e7a2c7499274f1feb8a02fe37ab35144c20689 (commit)
via 0202fc5aeaa4e339d55c93eeaf83cdfaafda559a (commit)
via b9c531514beef9d0ee0d706f2850a30ba20c5cd1 (commit)
via 19c93b0845947d0bb5ff60c602d5fd3f64c531f2 (commit)
via 18f8355aae2aabb011a57176ec7381b656e52260 (commit)
via 4d6b150d0605cec1086f05c7557c7990e4524188 (commit)
via bdeeb21d2f97586d4bf754b4a1156412810ef441 (commit)
via 8df3463968ed8d79144303a9ec82accc7f7302db (commit)
via e29189407016909a302b2c7d4e9fb77f2ffb50e2 (commit)
via 894180b8bfb30d7dfd7619cccffd0a2b4c3d4a2f (commit)
via a06a52e6609159bb97b49169192b19d9657aceff (commit)
via 4c9ef2963127e8d887a2ca38435e61a28f1c7c63 (commit)
via 43ea555f6ae497ec40672b951d8a00c437b89c0a (commit)
via 04e36988a16fb36ab84ec548a174b148924ed0d7 (commit)
via d2cf6a85554d5d9e02f1073bf41c61206a82effc (commit)
via 32044b839f83bba409fad29df68e94247e7a94a0 (commit)
via 6ad2bdd472dd5ef1156577667c09bc0bd90d3f39 (commit)
via 7076a02b24e63b0fa542af760585e6ad95cc421f (commit)
via 73656261a319aad29c3b9c405e8a7a6d1b9445f6 (commit)
via 64b9c9084608a24a11c4acd75bb2bdd1b231cdcc (commit)
via d0d6ad3146dcecd50b276f26ce4ebaa3e6fa84a2 (commit)
via 43e5ea02d51f60045318fde277b77d3603f5194f (commit)
via c0153581c3533ef045a92e68e0464aab00947cbb (commit)
via 962c4c299ee7fe733267e7ffdb1cf3f6ff3ec394 (commit)
via 0583652b5a391ec078361254b6d473fa8ffdb4a2 (commit)
via 79154442df1acbc7b00e57091b94a97086134a71 (commit)
via 81ddf65784bb1089f4c6761f018ed1ada5a45ad2 (commit)
via 675e72e818b0a0e61ee8e4c62611ce41e1b5fd98 (commit)
via 6cc73bde44fc16387788cf53906d1c9ecb4fd947 (commit)
via 2cf3214c48f9825347dad3b3bca6e6e6d8b8b6e2 (commit)
via b61e894db16aa458c9d3d405210c357e1fdde657 (commit)
via dc342c8d7ff4eff388bc596e625e54ac9617c09c (commit)
via 326b3dc5b7383a9357dfe2f9d75e1e05b780153e (commit)
via ca87835d1ec6e3393a98dcad72b6c96d43331e39 (commit)
via 497bf6025e70ca767c703dc192a11d968c0c031d (commit)
via 5d8b4032ea2c69f3487d5af8627350e9e35e619a (commit)
via ca3f4ecf2c5dc37dab7d9e89f4bc41ee000f12c1 (commit)
via fcea4de8a92427f8b0910048ecb3c7c420d6daf9 (commit)
via 8288bf44eb68ee5f7f098beeff11e9372065b757 (commit)
via 6d1f4e151097981b21acb32175756afadf432fe6 (commit)
via d3c2b9a2fde279369ebac19e23153963fa855d1b (commit)
via 313bac393cc4a097be0bdeaac4e21bbef1ba0aa2 (commit)
via 4d0494f1618492afa57a70d036d63a6ac67ee894 (commit)
via 3945e7047b3e94919601d9d4bfca36e54310c43c (commit)
via f623694374d70db2e5cae9f78b44fc74d4580f9e (commit)
via cf1828a29fe1afd7382ebf41417b98c027ac5bfd (commit)
via 4ab333e8a81f1d83d03a1f78100823293ff3415b (commit)
via e6bd8c1c59c87dbbed3160350e063031839d6425 (commit)
via 680e748149d99afa27cf4a5a7de83d1652db06f9 (commit)
via 2ae1f990955e7a2b5aabef0ec93448c6c7e8c89f (commit)
via 338cfe287476bdbb2867cf3e745738435a0e2462 (commit)
via 23059244dc6933f7eed78bf46b6f7f8233301618 (commit)
via 5c03ce929bbda1f0fb271b35b5b3dbe1a12696c9 (commit)
via 6d40ecfc230408137edcc43f2653c70fe12589d6 (commit)
via ed2fb666188ea2c005b36d767bb7bb2861178dcc (commit)
via f33bdd59c6a8c8ea883f11578b463277d01c2b70 (commit)
via a3e8b0ad37ebb999e29c390332045bc9babfdc3c (commit)
via 45e404c1b58487d180e91d4b4eb0f5059902b190 (commit)
via c04fb71fa44c2a458aac57ae54eeb1711c017a49 (commit)
via e773dac499b952107dfc5e34290ec421b8bbf7c6 (commit)
via a50149705d6fe5e9b50ab963560ae9eee7458c45 (commit)
via 5e61d601d9424aac561c0661061e19188ed8d93f (commit)
via 57a25a015e23dca348c1e1a3c5821d64981277b1 (commit)
via 3ce8c82c2a3ef441510d2c7b0765169c2177f358 (commit)
via d1c359da0db07875b967e616b07e5d56bcc51588 (commit)
via 41d5b2ead11a334e89a7f04ab38571e2795083d0 (commit)
via d432cd1889063c53a6d5249be9bf9c6029736364 (commit)
via 6917e1335e35a4465b9e242a6c3e9b38c909d201 (commit)
via 2e7cd3c5a4935c2957d800c9e9f29e191e9bcece (commit)
via 0fc40f1677c6bdaf1e2920c6f86b2bc6c15b4cb0 (commit)
via 1a0742f1fd61234a6ec395729b0c4c4cad64aaac (commit)
via e7c52fec35a1f6a49351f58877d21fd43b4082e0 (commit)
via eac5e751473e238dee1ebf16491634a1fbea25e2 (commit)
via 351b82d0ed7d964803be52b433706377d7b2f780 (commit)
via e42db3d2834457c04e19c256a67f4059cd36860d (commit)
via 971e77005e139347d5d4fedf0cc87bfb26b2685f (commit)
via 6e99f2cb85dd4722f7a5c2d15ebf2458cecc7b32 (commit)
via 18b3c00bb06e51f6a3b16fa634983a43f65b8e97 (commit)
via 79fe7149c3d2cc20d4bef791d27d5316702de53b (commit)
via fa4e16aaa9ee6e680606f0211c71c92669b8e74f (commit)
via 61dcb03fbe2c9a4e3df6e8afe269c0d94f00d925 (commit)
via 60a57d2997cfd0bfc27d580df175b31589fdedbf (commit)
via bf1529f69378453d8fb0b75cabb5d560e278d1c3 (commit)
via ed780cbba41428341c5956ff5b032d829d059cab (commit)
via 58f2a7f08242fad52c397bbf7681b209dc84c896 (commit)
via 407655388fe055d2a8a950ca31e36a9102b59682 (commit)
via 41e40c5cb4a62d32889fbbaf704792add24b02d2 (commit)
via 1949be3727367e2b4f9a399f3678a8fa1e969bc1 (commit)
via ee58a132fe6313d7d2d9ecc7eba8b455975ebac7 (commit)
via e15f18aff64c8f7481546e877f059290653b9926 (commit)
via 6916c32a3f5f409a01157d1957ebd3c9e296ed99 (commit)
via ee90ee28b431aac9b513330e31032a20d2c0ac27 (commit)
via c3ef6db1a2b58b3732f1d2aa169127ade79fcb77 (commit)
via 5fe6fd9977984ddc71d71266afb41f963012ddf9 (commit)
via a4ff0d97db3b04c67d3bdd246c04d1dc4c47fb4d (commit)
via 4afd4b896c591245f51b992296039ffd5b3991b5 (commit)
via 489316fd7418436e94de387ce9477c18e2b4da59 (commit)
via 8b4019ff1577d1d32e9400c2baba77c948d0bfe4 (commit)
via 603b0180bc4c1e9b7501c9e3292cad6f3427bf65 (commit)
via ac446f02802bff0a91af4caa6e6717b8f0ff350c (commit)
via dc6150ca4dd1d7dc44bcadb34acecb2d9176a9eb (commit)
via c4004066611a83ea5589af57bf07172da5a4aa94 (commit)
via aa0265f874883431407a6ed6a2f0d543fc4e8507 (commit)
via d8991bf8ed720a316f7506c1dd9db7de5c57ad4d (commit)
via 95d6573794927ff46242625736d894ca4758f55a (commit)
via 464664e3237b5dcb61deaf9ba4b5224bb11f412e (commit)
via d05d7aa36d0f8f87b94dba114134b50ca37eabff (commit)
via cef64b540e2353c080a8938ccf1353a11eced58a (commit)
via 4507063fc988329516dccad11ff28936aac8a08e (commit)
via 1095506737bb6cc7cf8b0c00ea0803ffcbc98ad5 (commit)
via 0d3ac3452be35e57346715a296e05deb48a03ccd (commit)
via d878168fcd942b66b3f88ffd162010dd86ab85a1 (commit)
via 8354cca4763eceb0dfd3270823012511c14512cc (commit)
via c6b3ce6d16f92b80db77487900fb57a7a67b3203 (commit)
via 454dbb49604074ec1ee9136598b3ef0ccf3af256 (commit)
via 20b0efb43cff8020da949df03ce13d58ee6e4eac (commit)
via 9785efa0afab6667a61d322f9a3a3dee048dd0dc (commit)
via 7f644c2532ecf0f3dab826a091fbe900dc5352f4 (commit)
via 5b0a2957d3a806ce84ad86dd7789edf3ef495d92 (commit)
via df2ee17ce15c5259ec2632ebf536f9f117419c88 (commit)
via 2ba18911d1daf85d52fea1e380f9425b7e8c3101 (commit)
via a81a52389a3ce477db992da27cf09cb1adc0e49e (commit)
via 6d42b333f446eccc9d0204bcc04df38fed0c31db (commit)
via b4f1cd645efbda886e123943b5e14222b40e7a9c (commit)
via 204733844d2984ed49eb8e2da89666f6a1729732 (commit)
via d399683b0d058126c2889d090b6067dcac0de859 (commit)
via b178f2a38b94200e35758f6e8d7e570a0cfc41ec (commit)
via aa6d3a22e29241cb6b8efb228821a99fd0b26240 (commit)
via c18cb310851ea2f558fd2b6103c9f5badd4cd337 (commit)
via 6e42b90971b377261c72d51c38bf4a8dc336664a (commit)
via 382705e83e65a261d1dc3b98407226c1340bc90d (commit)
via fcbb1a926c62a986d97566002bf318822161a5e4 (commit)
via f1c399008dea27eed1200e392afd08929c5f9f96 (commit)
via 213d905ed8661b9d1f5ce214c6f391e702d518d7 (commit)
via 334cb51d057d58fc24049e8605a136096be8dc3f (commit)
via d2846e4ecf8679ec53b2cd16e543b7b2488f2692 (commit)
via ef242ca52d93c62bfc2ef42260ebf5069d31cde5 (commit)
via 5e4b2f06b8dfe4cf1eb7f26836942d10e3c719e4 (commit)
via 2a26b3fed052b62aaa2064faa7e9ce9196bbceca (commit)
via c5b11e54a93c528d549a66206f0ff2c2ddf4273d (commit)
via 5eea9d893ec4ac21e89f8b3b0f3e68af625eed48 (commit)
via 5d9916e93986dbeb890cbce2782d67b9f535adb4 (commit)
via cdb34f7debacbbbc4adec956f682c07e0a77d706 (commit)
via 5482fb745b8fc0f3a3f80b25416752682c345211 (commit)
via f999dcb8704206c2cc1ea09ff9a4ca76a9e5affd (commit)
via 6d1d496ef090cfc188842a1fd1f9d0ca1b695826 (commit)
via a63b3077dd102512b9002e204a955f5b8d783ff4 (commit)
via 16156f814b54dffdc0304016aa41b0746c613d3a (commit)
via 361853d37c33abd413b48f9bc1c80578840aa901 (commit)
via 328ab84c1ddd6cf3f7e45a1b6fb6e1552bb26f68 (commit)
via b4b1eca7131cedca72a8973718881c1f18d2010f (commit)
via 5e8a8cc152ef5cd151f99389f475b2a16832fb9c (commit)
via d63e340ac318b68e1a04458ba9669f159d6eb508 (commit)
via 1954874770a322999e04315397c25a56cd1f1b3a (commit)
via 570fc14873220c13f62e622fd3f227a73ae9a106 (commit)
via c77a043a8a6f6216499cc9697d5881c65bd9ad2d (commit)
via 58875c2089c61e5174db855530e9de237920c669 (commit)
via 2bde65105b86a512aa798cac5bcad723bafb38c9 (commit)
via 418f35f58458de0480d6a58299f06721481d9196 (commit)
via 010bda2e96f5763e6eec226ac1cee96bce2588e5 (commit)
via ba91cff481836c5c8c1993174727646213cea60b (commit)
via d72ba709f11dee0d0bb90868fc042bd8caaf8626 (commit)
via b4bf719aa55ad684dbfa5faa39aa93ebf89bac18 (commit)
via befc536e55419f950791c6b32398ed6416d1abdd (commit)
via fac95b174e553ac8eba5805c47541530e9fbb2e1 (commit)
via dd35de3a43c6aec1cb67135da6195e8eabf028e8 (commit)
via 0af5b54f1c40328d94b053bbac09811bbdf3b116 (commit)
via 1e82366eb6afcaf56fefd13f3158487f02935e13 (commit)
via 7341eefceabf675af89a4514b89f0a45ba4a711d (commit)
via 29827fea890f2657705256b70bd38e6295d37588 (commit)
via b99e16aa62aae9000905822f4a76bec8fff3591a (commit)
via 7a55d6839c96bc41cb440e30822e219911ed2ea1 (commit)
via df2c166741be35284c9a7711b28513fa0f5a128c (commit)
via ee27d69e45c9632a482d610b28268f22af85ab5f (commit)
via de12157bbf027dd4bdf90201a4699c1a7287a819 (commit)
via 3b778c9a251f470985f330efa1a314869f5cca1d (commit)
via edf1ae8f84a9bd9ff5725c80c9f61872c8658fc3 (commit)
via 49ee4eaf84ad72a044cf4957af016d1e8e15cab0 (commit)
via 87fc0368a47156638c189fbf071471ee0d9c4463 (commit)
via eff9543714f70df752ab02db33b7a7b579b6a225 (commit)
via db3f2d81e975c2c82e575ba2947340f520f763a9 (commit)
via 2b6d8c99eedfc5e77477461963bff7ae031192bf (commit)
via cc8e7feaf34f1028438854453b4811d7283852c0 (commit)
via 8b7ae5faff5a12fb2841e8b35b43253c0d67c22f (commit)
via b021224a6314396eccfb9c7e0af223474079e864 (commit)
via 18155a69d96d21d845f990010d4e6fdf9589fd6a (commit)
via d1da93e647dfe42cebf516f34d1e5eb732f9cfc5 (commit)
via 5f57054ae173dcbf802f1d7c03a485d7d72e1814 (commit)
via 9a3ddf1e2bfa2546bfcc7df6d9b11bfbdb5cf35f (commit)
via 7c20771f4296820b7efc22b19a0783e7d226736a (commit)
via ae6f706d85ae90ca971d2cfef2abfe7e5bc7856d (commit)
via 9a306b863a628351a84cfafdabe5700d4840be6a (commit)
via 42e6736faa21f98de53a4ac0bc52306041581d91 (commit)
via dc50d73a4980ad1900e0445a1ea0dd46bf37245c (commit)
via 348d7dcb7f31831e5db9c282c213aec0795d1ff2 (commit)
via 19e016eb1b80db43645aca734b1f511ff2b142fb (commit)
via c730f9ab1b00b3611d82d0c00a2f0faf56b8162a (commit)
via 253803efbad0e147f37f6ce7a07949bbb79909f5 (commit)
via 9df9a672eb672bd201e3762f3f321082e7e1d17c (commit)
via ced15db4ec415ce080457136acebf66bebd7bf7d (commit)
via db2f7c545136ec475f2afda44ff182b195e2ed28 (commit)
via f57669ee937a15733a04e069aae01f3fff920f0d (commit)
via c7115f5b90dc167552fe2de1178def62a948afe6 (commit)
via 017278ca8084b9420fa94a2e9cc44aac9ebcae04 (commit)
via e61a5182aa295c0a96428d09e1d259c9b8b09d0d (commit)
via 8e94ab0a3baabd2b20f181243626168df4a56ec1 (commit)
via 390c1a763df3dbfcf0f24394464c0db89aaede9f (commit)
via 38ff6231fc72640e2d67de0583a6af1bb98a1e3d (commit)
via f3599de81907489d828748988c660d644257f0fe (commit)
via 968625c32a07347edd50550da5fcf13c17b61d87 (commit)
via ca59c5c8d5f309657b3f24f9edcc7100fd9d07a5 (commit)
via e9ac9994ee59019ec0689b94ef4d5cb15c0e5699 (commit)
via 53f6ad9e37ee9df633af7256bcda6bafd3cdc1a2 (commit)
via 32f82dfe9ca5b9a66104b0cec748a16dc648a0d0 (commit)
via 78981c6ff6b82a580786f332e58b5d3cfeed4e2c (commit)
via c25adf51ab0d73e162e520592daaa3e70ead8c15 (commit)
via e09ae1968e2c225fb42175e3962168953d4399e1 (commit)
via a57ce9dae4185a4542401a5ff6212f0bdf752488 (commit)
via 51861a16ff9aa86bda48baaca5cac0c37eb10dfc (commit)
via 0507bba4a8dcbd4a3e7f0f6ad03eb5df6068201f (commit)
via 7e343de52c027ac35f231f906cfdd19500e8b7c1 (commit)
via 46544d453c97522254fcbef13fb57c67be622cb5 (commit)
via bdf66e1261cd868e356392e478b0361fcca349b8 (commit)
via d6b484dc1a2689bf7fc8f1cfe2454ddb025b2a01 (commit)
via be6122f520871344324be1ceb9f118195d508bba (commit)
via 8cb8350f576413a4a5f0cf6fda366219ab316b89 (commit)
via 472b197fab494c18fa3ed25018176997da20c22a (commit)
via 8c4f48f99c0a25c37b15f7819aa5c01a3a1b6844 (commit)
via bd4a777c870eb78b1cdfc6df2c8e13ea93480c12 (commit)
via 5485e7248939b67d6dc94b18925cee753df14390 (commit)
via df956ae5a69124cdfd7704ef0582adfe254df802 (commit)
via 7c2793d41b23377529c667f89ca1afb69238a7a1 (commit)
via 0a4612cfe59ee2149b885550e467f4de42236bbb (commit)
via 780a3b3bd3492e500154e53e6835d82eb2eb3131 (commit)
via 5b41c700bd362d76ce19b032c75662f64b5a9ca8 (commit)
via 8f594ed3b5bcd0d29c1ddb2fcc0f91854503606e (commit)
via 3a7fba8e0764537e4782b8625b548bd199e30e28 (commit)
via 7f026c48836d9366316469190e0f82b882b228c0 (commit)
via 03db051a722775e59b803bd44e02d1faad2a0a9a (commit)
via 5b6251fb9bb2cd793fd9f86719d1e359aa427cca (commit)
via 6e6cff79121a939cc961c8f36a489cbb2d0b7074 (commit)
via 1a8db0437ffc3367de7762447cfa1f4efa83cfd7 (commit)
via ff10ffe92269cc3cdf85826fd81e2c01dd909801 (commit)
via b6f5648870e85b8451b624391ad22bcb04a219d2 (commit)
via 43fae7e83655a6bdb1e1b57654436ffaa8fad8c2 (commit)
via 007341505307a53b43802c747fe09435346bbf46 (commit)
via f57c476f33531dfdbffe892b0462338872f64870 (commit)
via 7abee20ad8cfd20c3e33f58c414798276b48484b (commit)
via 7ef14b47d3bd773155e4b6d9c5a739b0213e8f28 (commit)
via 253dc4476ac2052dcd9af515ea4955b09c91cc7d (commit)
via 741e96ebecac59ad257288094eab660661344b39 (commit)
via fc951496b21edbdb9c6ac53ab8ed596035177d54 (commit)
via 430c0c7e0c2b84bbb8d8ef5a2d27daecb5af69a9 (commit)
via d6b2905297b0ab859ed0dfcf67eaff340e7eb1dc (commit)
via 2419d18342284ebc4e7099e65f9a8ce2d3dce15c (commit)
via d2b107586db7c2deaecba212c891d231d7e54a07 (commit)
via b8679425f97da697e4a8766b773fb9d400af6d79 (commit)
via bee3ea9fabdc724d9751c073ecf6f71f7492c286 (commit)
via e5d3eeb0edd4f47af44fee8a5e7ec1b165d8db30 (commit)
via 55f676522e6df80e3293ac5c0f845805f622f3a8 (commit)
via 9368ade9509ac5a2278c52d4c36c3a84d872531e (commit)
via 77c1644d0d1dc8f3c1589138c335323b6a4e5c35 (commit)
via 45087a113407dbb6b657f6e410dcf8158219effe (commit)
via 3a874c84baf65289d49be69c041b8340c946617a (commit)
via 51c802b42eafb9d5da4c7f681051cb5fcfbd3df1 (commit)
via b037963e65fcc56bd6ffd847bfb41d172ad1af1e (commit)
via a9d957c18a7428dfa6d57e00baea49652302b4f0 (commit)
via 8780867306315f587012fd7ec9c623a0c439ffa4 (commit)
via a5c5c9e3456d94ee0465a6665dc1c2aafeb21091 (commit)
via ad2c0ba137e17bcf43b9be5a286d99696fd3009b (commit)
via 78af6ba875985a8c33eb1dd54adbe35fc720574b (commit)
via a0b8bc3685dcffca7713bea4d6d738827c045be0 (commit)
via c855ce32bf071dae01559afa743e757100d9a441 (commit)
via 11cdeec052d94483964f379d822e58eb58ffe522 (commit)
via 40d9424ef15d3c6d484a03772fa15d57781161f8 (commit)
via 7ffc29a5c462f9359d6059e6b14b33653f6da0ad (commit)
via a169232953e34690749708a29b9f6c22e94b2dbf (commit)
via 65b0c0b8a22a6f066ed6d9cae81f1de3cbca4682 (commit)
via 7775ce49e0e07406db6d7dc158b7c01060aa93dd (commit)
via 3c7a0170392994d0c6c67b40cfa8b0d902d5312f (commit)
via b63fddd9ded87b34a2f9072d118bbfaca420c045 (commit)
via 922c6e4ecb69334bdcdac0c9fa82c4615c828609 (commit)
via a2a6bcce71abf5bbd7ee1da2d375a85c297ee21e (commit)
via a9f39355b94fe20924aa02f849e35e7000c67631 (commit)
via 2cd1c10416f27e6b054c3fe4726ea771f63d0bb2 (commit)
via 2652e68f004f5c329b4979e93bbc020fb59af32d (commit)
via 421e22c7f1437c632a7e9aa904662a9344b13446 (commit)
via 0fdc68bb68b017223f35a3dc39e5255a5f255d10 (commit)
via 3a98bed98afbf27ec36a941936d1bf11c656178d (commit)
via 668a507bdb0dd52368621fe7b8aed41b796a9529 (commit)
via b14e0fb808dd161c89a094924f153393b3fc1348 (commit)
via fc735da7e6210f87e4e5a36fce91eb990b8bbbc8 (commit)
via 3a0d2b90ef3766a8a105a8a9b04c92b8620eaeb1 (commit)
via edfad4a9bb65049599adaf9cf68baedf7bc072b7 (commit)
via e93ef80d1c7e0f431f37f75c68f45c3aa78b70f7 (commit)
via 0f00c3880a0cb7498cd5adb86831f779d57d321f (commit)
via 696f1e012969f47880ecf8c5be0c5935a99e3501 (commit)
via ea778b2bd22edec0b971791ae59008328163542f (commit)
via bd74cbbdfc7828d5517089d1c9d4f2768b7bc7de (commit)
via 7448e48ddae3c48408bc95457e21d0ca189a9ea2 (commit)
via 310c6201416fe8b57c691a9bbb045d781c601fea (commit)
via 32f964bd9bf8ab5ee58b409a3006779b9a504b03 (commit)
via ea28774bc7f3586c2ef33028a88f2c51cfa9b691 (commit)
via 3d291f42cdb186682983aa833a1a67cb9e6a8434 (commit)
via 3174827557c878ee62a0624aa3ac93cc3fd37bef (commit)
via 95cb383de7f191fdd4e8796ef52b469be90e5523 (commit)
via b2f0388d5396ee32c6e8b6593130fe87e26153ae (commit)
via efe8fae1cf4f3a272c8da437113f1c7b90c9607a (commit)
via 9206de78991d2dcbbfec451e896dba0006bc5b71 (commit)
via a43f9f2860f156daa1a8caf95d6c0ff1a95323a9 (commit)
via ebe5c03858d0e41ae7e3a8477399d0f54712569a (commit)
via 1d9ea23f74983eb952733a9cacd5cdc6d99db5d4 (commit)
via a66549d4455e23d4486d9287157790ccd49a942c (commit)
via 007b1f5cd7b48bccf501aa624325388e360aa828 (commit)
via e65eaed0d786f8939852b6368de827f7294d396c (commit)
via 983f11cfdd11153b234ff7626c5ff1dccb1baeb2 (commit)
via ab3f32eee4e5eb1d696ac88392497e52b994fbbb (commit)
via 11de0085d24cdb5dfcc5a158cd30699cfde90159 (commit)
via 81847206409e7cb4bb69bda8a22f7fc54a7a6b2a (commit)
via 2ca7ef10889eccf7ebe70f1662fed83c33f62c19 (commit)
via 2987b196ad617e5cc8f759717edae50866162888 (commit)
via 554c01b75c2e1a16bebd00a2d1476b0c85f70ef3 (commit)
via 054d97ceb5c12455b1272e00a860e111f67b6ce9 (commit)
via 425062cfead674420744cee41784e4d894f9ac2d (commit)
via 0fa66c2662c0f8eac63ed44668f90d8b687859c4 (commit)
via 1b1c061696198766e9f6821e69cb1658f83c2030 (commit)
via 2379e411e9671d4ecbee71ca5a9663053a40d091 (commit)
via 94f2a0246ef707722666dc81c68cb58ac85414d4 (commit)
via 8ec2caa761eac2681485217645e7e328924c3c7f (commit)
via c27421b7e7b41247b41aca0de903f328048ab920 (commit)
via dac0b87d5b0466e3aaf0f49ec7b2362606c03415 (commit)
via e626839cc9e9a74aa7c7b560c4fc2997394f24fc (commit)
via c8050a3b7d7e3060c59f14c777af5a8ad675f234 (commit)
via dde7e27a9effb9702e99f2145254ae13821d7667 (commit)
via 638087de4edc54e2ef26fa7e444b743c433b44cd (commit)
via bd3f52daf52aeeb006cc7ac420f11bdc9cd284ed (commit)
via 157d04befe4e1b7a3a9d3fe34d2a39af5709ef1a (commit)
via 21034c5291df20ab7b9ebf79c16336af25e16594 (commit)
via 3ffa4a979da3b91b0c721d931f0d9ad333f076a8 (commit)
via 9f797b9baa3972fc391a6bfbd2a6a9bae6fcf375 (commit)
via d0080895a3a34b3e6d02c38021700e7a02245afc (commit)
via c16bf9a2b17f26c73b5be8aa9a7b4f3570b8f3c1 (commit)
via 3cc9fc1217967770551c6acb3ba8f6c6a0387864 (commit)
via 56e4d09d7b49fa6322c3ed52a2c38898d8a27046 (commit)
via fbe7def71c07f6f36af8f3d71f1bc83cb14c9e3a (commit)
via f7b60d9d56ce0447fad6f6b6ac241477535e7235 (commit)
via ed508fe48ff9a13463ef0bf800a74f7a9dec14f6 (commit)
via 5543f4c43126a4477807ab2c043907cab8f7b04b (commit)
via 6e4e55d9952dd59962c161d2fc2552176f805244 (commit)
via d1bbd4878ba48cd9ebc623b1e8666f425c86e84b (commit)
via 49305bca03037ecf7cc2f91f75c4fd1c2d8bd5d1 (commit)
via 056ca56102ee79dbc19629cdc6720d864b7a7e8d (commit)
via 797308ff2aedadc0281c8ca9c84daaa3c6d6d42c (commit)
via 74625ee6146a2b28c4caaacef2fb9c9ba9876dd6 (commit)
via dc488cb9679c44617fdac1aebb6a821bddd39736 (commit)
via e05af734b0416593a0978c3ab22bfd2ee1d991ed (commit)
via 88ada22821235de67f0d3efa32394b83d11e3b79 (commit)
via e81de23719368f0fcea05d1607c2ffafb76f0359 (commit)
via 67704c85b785065865a1b3963b73ba2dc70e5998 (commit)
via 6ce96d1b10bd6300d193df65cd17c4976c0533ce (commit)
via 9bbd16649e750e9a4c7ce5fec0265f91b10cc4fb (commit)
via 926a9b1b81f91338aa03abcbed25b962ca86d209 (commit)
via 27dcf35fa3b3d65a031df6c434343db2d5ddb8f8 (commit)
via 355bc5c22b7c83f6d24f0e4268b1eff7d1274655 (commit)
via 02c64383d4f2bd7a7c878f05df3ee2fbb4d10ce3 (commit)
via d09952e117b38c8f55a4abbb2f50ddba4874d86b (commit)
via ea8be8f804ea9d99f49d299b90ff6cb735254c32 (commit)
via 5aec5fb20b0486574226f89bd877267cb9116921 (commit)
via 567853b380a1365f0e3fe16a79062ee710f2e167 (commit)
via ab5994c2e41c297aab91789dc18896beeca96063 (commit)
via d6c6fe485c6ed9a4ed9a2efb4792870a58b33c7a (commit)
via 9d30cc97c91754a94f15ae288220401fb11cfc06 (commit)
via 15810521bfeea89e77334df66512863911a4d03c (commit)
via af91bbf6f4104b9731264270e2f1356b0ec74981 (commit)
via 171e04fdad35ccb630a4255efba73af01536f9de (commit)
via 779d5913cc017664e72d4e6008b1e7dced1a6ae4 (commit)
via 523e679dd68695f1815cfb5e0a31cc7bf4f742f6 (commit)
via 41439e23c82d769ae7e1bb04453723a8770e6c9a (commit)
via e12bb75e5e456ecaaa738936aea36fc255d342f7 (commit)
via c7c8f0af322c42e62e6f5168e554e56776230a79 (commit)
via 7035e189a7f94e8284c1da4eb538e6db24fafca4 (commit)
via c049cba60db67b2ab0beeaccb690d77bd0c63689 (commit)
via dcef553fe87fd8c1be039394ad258a20aa8cd0b0 (commit)
via dcd88c02239b954f9617f998852c4399ff1f9a75 (commit)
via 9d2bd293b53d41efc0eb27c3f7e6cff01d264c8d (commit)
via 2cba268b3ae90e44b92227ebbbfa15e0fd3bdb5b (commit)
via 7028594590cdbe96b995ffb104a79551844999ee (commit)
via 827f143ed26198b3726d0921590f8e91fae11b4c (commit)
via 45baa7aba39e666d0eed45e140567ce3be62066e (commit)
via a786fa02df431025c8419ccc7d0e8f4023e6eaba (commit)
via 25fec05f27988d1bf5dbdd87ab67ee61e6ff15f0 (commit)
via 92ebcb9a94e233362660b3d3f29df319da919286 (commit)
via 3a6139c33b7db517e76723d631ee49d47dc691dd (commit)
via 0df2936c8f985071e97fb17d9685266fc0d443a5 (commit)
via b1c398bbc0d7b7b066347cff13ea8fb310bf4f10 (commit)
via b9fefbedb20b98f3a8a39550a9a1183208eb10ea (commit)
via 777c26581b971935e9d77cc1b70f0909ab3534e6 (commit)
via 7212536c15e7374828bb74657d6f6d30cac4160d (commit)
via 347a2846eaacb84791fedcda6537d2fd8be6160f (commit)
via bc6a657a16a55350992cbe251f010d0a46d11dea (commit)
via 8b82620d477ff1eac478407debdef85286f34f79 (commit)
via 946f8117826ae2df528c76901f6e1426cdf614e0 (commit)
via a555e12ddf75657f9ba417f1bc9366c8ba094854 (commit)
via db63f2e09dfa7e2742d06dc72f7bb4d52c3442a1 (commit)
via 4ee45a9b6fe66798c2aa22ecfc6b9522b9ba8d37 (commit)
via ab1890f4594c873aca0b7f483234de5cd3e5e01c (commit)
via 71376fdfd9be70f5dc7f28fcec5bb75fbeb27ccd (commit)
via 6849529f7e974888fcbc49c303b709d7c65002af (commit)
via 984abbf9ed6f4b7fb8b0ea884fd5e3ea38469d6f (commit)
via 270d14dcd9c376ec207f61bb736eb6ef8e9b1a2d (commit)
via 226b4d1a1e9b94d5eb9eb92e6a35f24a1ea1d0bf (commit)
via a41cac582e46213c120b19928e4162535ba5fe76 (commit)
via 3511d115d6565b995ec092037359933eecb7c704 (commit)
via fae036eecd789c8815f8f52f5954835ef9fcfa82 (commit)
via efea64180e770421ac70e4a607e0b5587433b55a (commit)
via cbd8f4ddcdff128ec9f545b35151a8c64affb063 (commit)
via f61a5e73721ee68e4fb330f59a15fe5baf8fadb2 (commit)
via 3d96ea46c8191bff66e8ec93b00414f6a78a66f5 (commit)
via 358984aecfd6a7944624577f931e09c3fd53febb (commit)
via 86d1383a1a5998ce4fa7cac17b9f15bde7d19712 (commit)
via 38beb040c82df7d6502f3f25b52773aeecad6041 (commit)
via c5352f69e7fe7b9071066b4ce40d47c2d7948a1f (commit)
via 71eec22b3e41413651107079d9f9b5cf34d2c7ca (commit)
via 9b1ceb841266b4df04ac717186c86b22f77fe314 (commit)
via b18071c40b073c7f38643dfe19c7d10f2127abfc (commit)
via fe14d0cfc32360547afc55ceded8c69123a70214 (commit)
via faffa474e24d5e3bedc06088a388a0965d5059af (commit)
via 9163e6f7a614dddc6d1cbb7b9e879b802fda14ce (commit)
via 58335cecf1afa60e325b5ce952b23b40416bd4b9 (commit)
via 632a5596298f9d253693631606e026e01d8e7fdd (commit)
via 1ceeb74c1eba0ce65ea05a55fc86149d3fc733f8 (commit)
via 31a77cdd4fb12c30e9222e9e045fb998487b0844 (commit)
via 8191aec04c5279c199909f00f0a0b2b8f7bede94 (commit)
via 35c2010ce56c5fafebad8574184d083d1182534d (commit)
via 19030355897c6185f75193f299c8666b595970ae (commit)
via b3befd1fe7fed482817a7ccf05baa6292fd7a56a (commit)
via 031ae7d0a205b4a82593ba4767e8e4741b3f8081 (commit)
via 9a2922daaac4af5b378144b518a1b1fec767c798 (commit)
via f49e54056a5485a18ab7181256233c3af7f1a73f (commit)
via 2ff30d5d4b5fec6fab22c0ef4021bbc85c9250e2 (commit)
via 6f11ce8d8e06bababd2f706e2affc915e3ed1e02 (commit)
via 99d9be31ae3323ee9f539b01bc74f3797d968e3b (commit)
via c68b2f25695d5fc4c64d8a45a7414695a8281c53 (commit)
via 71de47f8d14d274ae2223e56423d6022a3cf0369 (commit)
via 5cc7f89d8f2fc1ebb563556fa7e250ceb3d4aedb (commit)
via bb08c0bcb4791c3c39f68ae8cefc2e9402ae9b92 (commit)
via d2460249eb2a7340b8adbdf12306744ba255eb6d (commit)
via 003aa4bedf13cd9d7b2e73983aa593deb6281435 (commit)
via 21f3ea90672b5bf84886e58f08f97bbcc63dfd6d (commit)
via 2935657cc5e260f54be045b28d7184adb406d3d7 (commit)
via 77010a2090eb1cade512f03e880df4cca13e9eb4 (commit)
via d340d8278228a062664eda13926f5af3daef8b35 (commit)
via f26eae2533b0b2c6591ad6aa9116dc9ba00c462a (commit)
via 70c715efff4255744322403c03a7be1e64d99c5e (commit)
via 6a4ee80ea2995124b737bd718f16f1baadb5c7dd (commit)
via 508e3554d5167a27ed1287a3903f851bfaf11a17 (commit)
via b881b03e1c8138fc7385ee3f98a34ef3a1489bb0 (commit)
via 95af51c86a9d480562526421863e47df7260e6a3 (commit)
via 2fc57fd693aa0f849d91f39881006099769c6026 (commit)
via ca04c553327800d4a5719dfc1b1343927a9ffa13 (commit)
via 09e6babe2643d0222b4ff99517834cdb07d80aae (commit)
via b02f05774d65cac0b87452dfea6a65be73e08069 (commit)
via 1ce2bb2f000d2a11a2e1e299e62ee567d81c8684 (commit)
via 3bd3919f267d320eec044a8fb8d2aed62f176e08 (commit)
via b33bcfe2c8c1b651f5da66e03ea7fdbf1f499c36 (commit)
via c269cdc11c02affd7b191a4aabcc56bf6894ef3a (commit)
via 24d49c3c491cf82fb5e0134d28e8eecc2428851e (commit)
via 547094b71f9306e1130926ccb76160141e2b080e (commit)
via 98bcbd53342827be7a5be66f8ba69f7e37559c2c (commit)
via d97165b66927e4a58979db9558e2b2f37fea17e9 (commit)
via 9b95e643da6c3b1be36b0700e8198b9fec870515 (commit)
via 53d462aedda7e6f2527dff34a6da25ef58ec298b (commit)
via 1867eb5f882da2cc6cb5c8be897d9affcf125e28 (commit)
via f24dff697d824255a76586d0ff879e8445087d08 (commit)
via 429d200e948f44dd4d7f668c2f8d46883f576205 (commit)
via 4ca40253f70421e0133e2bcc18dde133b4851f67 (commit)
via 525021e0a8f3c00757b66588208bc10528544416 (commit)
via e51d6fd38a9ecdb6d8ff200fc152aaf5bf1e0cf5 (commit)
via edc1911c2dd4b941d4658aa0e677f4a8ef4ce819 (commit)
via 1c5051938f58da3dda1b327d373b4df7f09ca7de (commit)
via 7a0ffbd7818d721d06b1864fe840f89be6f65b8b (commit)
via 43c9101c5e4a721fde8f6699dde7f45f12b80823 (commit)
via dd34e731a3998c656995b178988e12cf0bd5c2b7 (commit)
via 5da8f8131b1224c99603852e1574b2a1adace236 (commit)
via 77def77e9ab95155f91dcd9506de0f8ea1001d8a (commit)
via 61a6cfae29c657daedd334fc2a157d57e85948a3 (commit)
via c188affb416fc02d45a0932fec1e788303425d88 (commit)
via 64dec90474339bab08323f8b76cde47ddd2d8f73 (commit)
via ab78760e3dacab5fe2d4683fe991c2643fdf9877 (commit)
via b6925301471d003931305efa8e39c5704e1413c5 (commit)
via e58e863991709132601c263b8c04ea4e5a0593c5 (commit)
via 4bed4a498354aab3faf234bc76b3f0fbc5a94fe3 (commit)
via 156c5f32bb38cdb94361726fe4f0d40adf39bbdd (commit)
via 587f61464634214c7cdbbb204652eb63b11eea3b (commit)
via 1ee8b5efc468540d5542b300bb01bd0ae50fb339 (commit)
via 67b935d7e7bfd22d0b7341c8e81908c9b5dd19d3 (commit)
via b222f7faadc7ed7898a936a2b97b2a385c6a2a1b (commit)
via 159d0dd5e07877315eaa9690cfe141f3dac1f1c6 (commit)
via 6ebe6bc5875d21ad1d822c251462cebe3e769aee (commit)
via 6bcd96718c01bdbe0d563f6c67c46b441147ba60 (commit)
via c1d7e3f9bfa473478a1070467bf0773f02e54b6e (commit)
via a8186631e5cd811b2ebf8df6d837182ea39177ea (commit)
via 42213653b829ee68eae11af916dbd2164c96d27f (commit)
via 2ed196723ce382b98667029ae24dae42361197df (commit)
via f62ca4a0b64ab0d417063105458f409b4ac027d0 (commit)
via 8b143c88e7413b43086ca538e84af945f7754447 (commit)
via 36cb12b086ee2a1e42f106ce9677eae708532933 (commit)
via 8118f8e4e9c0ad3e7b690bbce265a163e4f8767a (commit)
via 1f72551a31350aebe5709f011f58e2e0ce5b524d (commit)
via 5e08967f518e716ecd929249f7366a2d7faf6306 (commit)
via cd6f223e8f3da567fc320ca4b65948eae9e806ef (commit)
via 946d6e879a77f482a8887d47deb797990dd66544 (commit)
via 09c5415d6a9db4161f280bf8172d21fd796af068 (commit)
via 717f7ead4cfc365fac6c8d95aa79f835885b5f90 (commit)
via 397c814090c29f55eafc869702d00e0c15bac0fd (commit)
via 68e2c05b1f696136e683765c4d40170a45f721b5 (commit)
via 7917c9005e71b17be80a4ff27e21858d82a53e6b (commit)
via e3a4ca39f16eaaf8107d688c03f878ef1cbb68b9 (commit)
via 0332ecca45c768907451a893663f526139ffc2e2 (commit)
via 8c0de3883e79cab57fb1dbabd56b8f7515823288 (commit)
via 9c25723eb98513953e93a16a5bb5b83cdaad015e (commit)
via 2a2f47d37519d5ec4cd310899d7df482145f5feb (commit)
via 60a17382794076ae299eed542eec4b2f8825c120 (commit)
via cac71bc308843f84ef9d1b427f1d401f036ef1b7 (commit)
via 73f842441ce26df57c3af30f526b59e3eba884cb (commit)
via d43299cb0e6680458c2e831048aaa16acc0d3017 (commit)
via e532e4f5648f23bec3bc01d01bec47abd966e07f (commit)
via 537970619d87f2556abcb7d8cb03eff47eda0d54 (commit)
via c612e8e22e27210ce85ff4b1f2924686567aada1 (commit)
via 1ab87fb58246a94e4172188ac80061cddcaad3a2 (commit)
via f714c4ca5abfd11559854398f6f47ec403a3f7de (commit)
via 4acf38036ce68ae59b2da053af471bd0ec40ecae (commit)
via 7e575b706d7cd4d9d15bc9bb6a623ea9b9077252 (commit)
via 80d16e0dfbaeb8053cbd29d6b0512d75351a2c25 (commit)
via fd911f4775865a204a9a67dfcf290d8395e0734d (commit)
via 36f8c6c15ea3e8a659989df7249715df0e0d2ac4 (commit)
via dbe4772246039a1257b6492936fda2a8600cd245 (commit)
via 6ca9ce763678cd8ce47680d083eeee299889427a (commit)
via 5ed66be3e60157ada3a26fc2e53cac3790661c7d (commit)
via 40e0617397c03de943d81e05541c4de17885f6f0 (commit)
via 33352a6b9af110cb06c40c8cfbecc26b5e3e89c2 (commit)
via e567e51a23beed32f03870d9885e462e1bdc276c (commit)
via d65d61499f0f2ca9547d90e3c36fcd83d55d8ba8 (commit)
via 8c00b35f6427ba14e85e7c335367418b28599154 (commit)
via e6a262a1e5a0091b06840a46a20a53eed38eee42 (commit)
via 64bc2cfab72d67bf9e4f3cd19fd6ebb586208e67 (commit)
via 71007940646f888480f85dc2780c0d600f64d179 (commit)
via 439ab563402b77e745db3181af1751ebcac519f3 (commit)
via c1ca91c677addbb7a546ea8e36cc67e0717f1fb7 (commit)
via cab5acfd0cd42260988424e06d926d22e37e1e11 (commit)
via e37828dd1c90541e6e6d7f8d373d95a25ce6bbea (commit)
via 16f7fffa1820f7e65d28e807a81d89dd59390037 (commit)
via 923462dbabee6917d6693e62c80bd0f7c8fd980b (commit)
via 807607ead100bba4afa0c5af2697ebe6dd2f4ec7 (commit)
via e686a5339d6927d4ef7bf35a8e5d9c2ddfa98149 (commit)
via d4389a32b03dafe3c369e9d10d0001687ec74f55 (commit)
via 72b428b6f9178a08d8e5bf68062b4082bf0b8429 (commit)
via 01e1910c9db5d1e5dcf2b5ea01afd7aa8075ebc4 (commit)
via 89b1ca9494abdcaf3ef2beec3fcb015b3328fea7 (commit)
via d3a4dc9a80f85583f71c789555deba8fae2f0381 (commit)
via 1ad0a17cfbc48bec8620958e3495720355b34f81 (commit)
via a292c65c3c68b9bccb2a30169e4bcb613da1ce68 (commit)
via ba0004b0092cf2d7652b9a66212774d19f91517f (commit)
via 29488dd4130d944487bb2fb813401773ea155428 (commit)
via ece829ff0b6b5117ecdfe22439c24b64a8523683 (commit)
via 127d2f3f9faeebcde23f77f64336e7551af525dc (commit)
via 9deba8e6b563fbeeb98af85b89aee5c9f4878a03 (commit)
via 26f53e4d06284cf0854ec06d14b5071b26996347 (commit)
via d219feca6015a99293d37d030ad791263432f533 (commit)
via bf1e13564ab29539beb465d212a51e895e640915 (commit)
via 63e17482bdd2e68e56f2642267169a101af9e012 (commit)
via 6ac0a0914155c3379e6608d3b6bc884ba1e59527 (commit)
via 82679565adb6001d2b445cc932935d9a0561fafc (commit)
via be23568ad3e10931387612fa90f8d2568e9878e7 (commit)
via 42073d9d8c264a33ca883c9e80310cb91de706c6 (commit)
via ea97070cf6b41299351fc29af66fa39c6465d56a (commit)
via 199269b851a76501bba4ab7bd6f8c7e088b45e74 (commit)
via a6f80229344ffd81fdc3c6642b3ab5740dde4bf6 (commit)
via a9b9f641970b7f2db1b1183fab3deaec454b5b75 (commit)
via d7fe718898c7a8d1fbcebd4a4d47548d08ae1d98 (commit)
via 33f75a53137bb954ed660f41043c6887c94e398f (commit)
via e4a817403b39c77a0b3a7c015e1f77f241c21c3a (commit)
via 85fcde63692e73b4ac783d2300851b94578575c9 (commit)
via 9440c155bc74679a5a9526c1507384790a62a057 (commit)
via 425ee821bac86ed616075e2ac89946863132b46a (commit)
via 66564c104a5c8a918a39e7da4cde9976160362f1 (commit)
via bf2a2aab27a29e23699f42c393ac31b324c7f096 (commit)
via 26f8bf358b2eceec472e341f1f380bf9474913de (commit)
via 4eb6ec7a740b1ec21334cc69b0272883c8e874cf (commit)
via 56ee9810fdfb5f86bd6948e6bf26545ac714edd8 (commit)
via aafb86a4203109142c6f888a2548fe57dd0dd701 (commit)
via bde0e94518469557c8b455ccbecc079a38382afd (commit)
via a29c0e18ea2b70d6e4c2b2bd48199240f4c909db (commit)
via 64bc0886402d587b65343fe6faecf35ed59eb029 (commit)
via 8ed9ddfe7f17d01d29cdaed8ee800487413e8632 (commit)
via c226db710c71f94fe3506a588bcd4566cab5b2dd (commit)
via b70a49c18a153f40aaf129d6d3f25039e052e887 (commit)
via e5094ef98ad34b001152d14ea2ea4c6ae9fbd85e (commit)
via 8244c9cb7152aa47be60e0f86f02289fd67a6239 (commit)
via 47a2fe257cc5d3d3902a7db667d1b5456bddc447 (commit)
via 572c201a7d17bde8990ede8d986bf45e53d22842 (commit)
via e08a21c3bdc637a447d68da727fa3f4cb6a26112 (commit)
via 419690bf77f83061c4df46291b27a2a5769e1758 (commit)
via 12164374c127f1e9484c63f2f365ff7395768599 (commit)
via c2d40e3d425f1e51647be6a717c4a97d7ca3c29c (commit)
via 1248e88ace533f61e4f8865fafa5f0c89470c6cd (commit)
via e8a9043a2508edd209a01facb71fe4bac4d9eceb (commit)
via cd146974efff9794c989296158c0e05997da1edf (commit)
via 42569ae1408198a605b93aa1369e32fb62dc8ee2 (commit)
via 14fa32e0dcb2d77ebc7d8a5938b37e36324b8bef (commit)
via 018e2a97c77a95d84505c45f3e2d106e753b18f7 (commit)
via c1d838b4ef7ed6a20b43987713981ff521aec274 (commit)
via 41aef4f8135de17e94f3d703ff8f461bfeedd94e (commit)
via e7e1564b312b70ed4d0c04e59025e337f0210729 (commit)
via 9b41a2252bf1db1fe686ed4cb8b20d3fbbf1d5e6 (commit)
via c13f4a4ab9ddf938d9b5fd62e08574dd8dc210b1 (commit)
via c82208ea06ca7d72186cc606cc4038bb35b7afc1 (commit)
via f34eec70bf82d42c27a0df4709b0682abf2f7b2a (commit)
via 454afa9877926214539d135f239035706bbec035 (commit)
via 4b68c9bb855e1cee112ed6a0c3dd398048930824 (commit)
via cddad16de73088a9e11330746e33a1faed947f64 (commit)
via 32931752aee5c1c61bf6758a3ad34a77eec04064 (commit)
via 9346c7a0f4d9dce50b673e66b5cc992f9b32d18b (commit)
via fafa0dd4a0c97aeef84b7c95ed624c3873ca4cb4 (commit)
via 85dff79c7ec72f5079dea7646baa557de2e98c5b (commit)
via 2ae9ac7bb5a19741eb21b0ccec951588c5672d1d (commit)
via e7591db5df9c7694e1e0e3f7dd3e2d77753999c1 (commit)
via 453f362d6c7bba06582e713b9fa243fa0b8ef5b7 (commit)
via 96b1a7eb31b16bf9b270ad3d82873c0bd86a3530 (commit)
via 704e1a50cae62f61fe2e09d5bcbf59d30c4aae97 (commit)
via 9a49846cb4456b17526c30799df03c9a06c8a318 (commit)
via 18665064e7946e537931caccde60c180e9796127 (commit)
via 7e0b47d099a071d438c45e7cde177d89a0f3151e (commit)
via c738282e2dabd4004dd451edc1d5dd36b4e59df7 (commit)
via ccf4516112346e4722125b2004138b7a78d701e0 (commit)
via fde6aa002772b6529f185148ce22cdbfabfe6a49 (commit)
via 87a710de67996ae01dcee1c2e05f09c9c392f82d (commit)
via 74c286503e39fe3f70a52a0f8b7b7c206508a494 (commit)
via 7acdd8d445081e1a4c70ed58298367a96ccb55fd (commit)
via c39edd26a68da49eb507b3d842b8929a57f2710b (commit)
via 2f0e203d44adda18249239ff33a75102a8477e82 (commit)
via 46d8b0039515a169b9a0297ff530aea7a25f0a54 (commit)
via 16dea270c9125075b74983e10cf1af7b8851f37c (commit)
via 88fe64ea760b7c2f4e2f5d4fdcc8c298481b46b7 (commit)
via 143264e9d77c446e9f5d9f2611198bb73de82243 (commit)
via f3cd2b49392a4883751e2dbce2d8677e46d1f167 (commit)
via f45931cc8a5c0924d77fb6c6681eeb467dc91302 (commit)
via da3579feea036aa2b7d094b1c260a80a69d2f9aa (commit)
via e7a483b3f53323d03b64f49385d173ceb595c39c (commit)
via 5162bd7739ccfab83a0e81e44ba5e46314eae0cf (commit)
via 54c8596393d90528dad115ccf111a5dbc51c5b73 (commit)
via 367da9bd6d92f7fcef7505bf8c54b1a702146eef (commit)
via 14398c533d5f036d1c005565d9236f6721390c5d (commit)
via 62b6680e17683955b24149d63fd9a3f329e0636c (commit)
via 6f344e77217625e36e924f41c24d88ac0ba685be (commit)
via f4130b056637b3b14635d8211ad7d720a74ae9c2 (commit)
via 192787144f8338dfc6443e6907ce2274fda95ca9 (commit)
via 0ae219d8e8bfeacdfe026ad1e3576ab401d79724 (commit)
via 946442d00fdfdc6657b5d068b6aacd859c80383a (commit)
via 2500db669d6463c3ee2f607be63ac0690cf7205e (commit)
via 6994c90205b1c8b7233a47a6ad8b07060ed6b9bd (commit)
via 1a488b6c5ef9970422880a4a6a348d89a66805d7 (commit)
via 45d2c5e07fc8eb3b4a9493470118fe5765164365 (commit)
via 58639475bb07feaf4755b83942a572abb2943743 (commit)
via 9d7bd1274b7ff4578e108c8c13bad81cc2867d14 (commit)
via fededac895f51f0d3aebe33486e0c7303812eb34 (commit)
via c8d00f7c62ecf19879d70e6dcfdc0ab14551b6c9 (commit)
via fdb231c9e31422dea878922607a0268675f50ec6 (commit)
via 5fe71727421c85412f6df8c77f644f69ee36c6f4 (commit)
via 861f24ea145df8212c1e9bd7bbd601fa474c9e33 (commit)
via a66bb4b7adcdf3299fbed50196db06c8dbf16058 (commit)
via cc489915a21327fff39fad6767649c6b2b55ec79 (commit)
via f91e51c212f329dffb26d50643d41a7d27552227 (commit)
via 94bd413ac2b2223802b2185855e7532f6a799dec (commit)
via f5b81693da196d63c248d8004ff02176b23882a3 (commit)
via a5f749af5340f59c4ba00ad0179c4d5be7d6b8f1 (commit)
via 498c4d68645d93aa18da63605b2fd6845a88b886 (commit)
via cb05a97990d769abd3c4e866d8a8f2ed4b03c804 (commit)
via 3a7105d6768c244c486696535da59cf8da25cf5c (commit)
via 8f4a847270d7d8b5585e30d9c01230c8c79e10c5 (commit)
via a8594f39c30f93b0a0968e89779f24e358e20211 (commit)
via cc51cced3f953affb8c87baae5683542b368da2b (commit)
via cfeb578adedf7f6a98b293507d446c886d9b3525 (commit)
via 214d50fb389c4439614e56aad6a252c87edec4b3 (commit)
via cfbf803bb49ad944bef4070fec9f2c999a98cb78 (commit)
via a2b82fc102f6a4f987bf9ba59b05a336dfde8ac4 (commit)
via 0b2800af6eedeba3b71108765fd4e04195c0a4ad (commit)
via ea07789d058ca92c5617c2aca304fb8ad8c69c18 (commit)
via 1e1f87c3540b2f9de576a0267c694e8ff19a1db1 (commit)
via 2afcb422fee3bbb9d3c8403f98e489f195184471 (commit)
via 38a875a2d8c9337c1019b11473f989519e16a778 (commit)
via 16ad2c551b5a2e90d0931644a07caf02b254fae7 (commit)
via 29b82a61f1826a72597adcd7ff38e153a135379e (commit)
via dc25eefe48a9c76b9889dde024ce8e88ee1eb363 (commit)
via a41cac4287573ff826bdad6e9ec61b5991c757e1 (commit)
via d5f2a0d0954acd8bc33aabb220fab31652394fcd (commit)
via 7831eb91e8d50e8cdf9f17487c86584efbfe4855 (commit)
via 994dfcd88af7dbc2a8a6221cb662285faab3f6a3 (commit)
via fcca4e8c09253602530fe66a04e8e9b71bbf4739 (commit)
via 76b4abce83b333c61b596df651dfc2f9e3d72100 (commit)
via 592db047a5a82c5af9e837dc2fc9a9df102af8ed (commit)
via e488b9b74d562517bf430b1de30747d575966b07 (commit)
via 72e89527efb75c3aaec0843bf81b08581c3b1f58 (commit)
via b9b2b5e9a5aba9c87939ff116d103c8e9d42c550 (commit)
via 1519438c8f9c74286b84c7258ea9ede4a5a03a7d (commit)
via e817fc7e97fff519a1e6b7a0ef56fd966a274039 (commit)
via e0d3a70defab35a0dd3f1beffc3495b8965883d6 (commit)
via 4ca74b26c112156ec41226073e8e70ea26d1ee0b (commit)
via 8fa04182fc57c74999d148bc14e5b6a6b3929c4c (commit)
via 72fca133ddc102924bef14d4281314309fb51201 (commit)
via 4bc06a779cbe53dd5ad91ef4fe0c0262e430f03e (commit)
via 9204d1f5951ea88ce351297885c83be3ceb33344 (commit)
via c077699ff43ddc2dff552b616044dab306f23666 (commit)
via 2ab76e6181396639a60994b5c78493fa2235b304 (commit)
via e41be59e113c11dbad91634d47c832dffa628e8f (commit)
via c7e380374b96d23f682e81a8ff159c8e39538103 (commit)
via 48bbc35c15b307c83ade8ee4dedbf2217aee3530 (commit)
via a569e3510c94ab28efa0a6a50ec657ae37035d43 (commit)
via 8cb0a56a019c3cbaacc8ccdeebaf3906eeda8a74 (commit)
via 0b690e4c4a93543a1f0e0a2aff0206d605ac1d33 (commit)
via 7f8feaab9ff4bdfa54c61e4cdcc5e9216fab3c3c (commit)
via 686768228ffccfa16bc367776cff3eb6dd9f86dc (commit)
via 03ef197742328752227b359e5c10cd12de97a967 (commit)
via 433488548230ab8742f9d7b71fa33bc56484ec86 (commit)
via ce84abcf310cc0470a310621688f0947609e6732 (commit)
via 392c5ec5d15cd8c809bc9c6096b9f2bfe7b8c66a (commit)
via 8b47d98c888f29facede10aecc8bf4d39d8f21ce (commit)
via 8da51f50f03d1b84d9be0bb73d4091ddf32149fb (commit)
via 52588836afbc4f6d66fd93d30bc37867bfeb2eb8 (commit)
via 02fb830f3e15239a263d109e8bda0256418401bb (commit)
via a5eb53f5b3fcf01e6df58325e11269e62e259197 (commit)
via 0dcc4f4feab96a9a76d33137adeadc488884b0ec (commit)
via ee886b45eb503cf048abf8416ce7b37430f6fb25 (commit)
via a79234992101f76634568d1128e51655ab45b42a (commit)
via 7c7bf1d126cddc59ca3ad540e2c2015853509a51 (commit)
via 49ef97d8a1b783a35a81db555eadbea985b84a11 (commit)
via b48e4ef0e4f09fdaa48f11dd97e1c378007cebaf (commit)
via ab579f5245e995718410e2f59bb7a82814f138a0 (commit)
via 943f4ea870d5d00101dcdb4dfb5a6892ed3eb634 (commit)
via ef6d94432720eaf1fb73d65d78915a23aada2a25 (commit)
via 9eab41529f7da65498b241f988a8ca096a4286c8 (commit)
via a2500cf8f53c5beaa5e9f872458f10650f91b95d (commit)
via da4f84b70faf79d279dd1ad978ee007b9fbd6447 (commit)
via adc3e2f94dcb38c4594408774599d946605f3cdb (commit)
via 60c3b4447c47bd08d990d91a3a412ac6b329ff08 (commit)
via dc4cf0c6d071ef5661203ef42894ff8ecc6a90e4 (commit)
via 1a3909427319f08fb5dacef02baa8c89b0b97408 (commit)
via c8aff6addd2137edd3b5b727ff40c3f7f6b00e71 (commit)
via 320f10a5395ec1799f11d172ad6936da5467e815 (commit)
via 323d64cc1465f7a1ef8813fa7a541f495c5e9dba (commit)
via 2b5052e66a62634e8da2495a0d97018a144b7919 (commit)
via 3d234878c77fab6f7bb172c5ef2bd21cde768a72 (commit)
via 935c7cc4033d440f270cb2d62114024e3578a4d3 (commit)
via d83205544b88dc4a9c5584212034c03a4f9e32fc (commit)
via 98a1e1dd654b98ccd453f38618f1ee0ca0c33a2a (commit)
via 1438cd564759a281add9fa7c8ed04280848f6ad4 (commit)
via 7f652b9b12ac550b784db4fd52df91f672ab4d94 (commit)
via 85b0a75a0499f2a7a08d6f597e3f90ee4f7be636 (commit)
via 7185b57ec3dfe44fb5ed0188e6234725cfb8fff0 (commit)
via 1910b46df9c17fd817dd38ccf6748cdc49c6e821 (commit)
via da5d0b9b61f06c13ecfad23fd0dd4b4fc0734c77 (commit)
via 3f5fb3767356584c73a7de584adbc4da6159683a (commit)
via e1ff4dda628311346a699da5adf402d859a517cb (commit)
via 929f85f8deb68ba58610354c8161c0e1c0806006 (commit)
via c7b8894303b7e88629c0b838080bfc6097c05ec8 (commit)
via b3dda76df66adecf3af2ba419a23b4169bfe9541 (commit)
via 39387427187c807bdb47830b19224e6dc9dff52f (commit)
via d35ac0a1d9375ec6e4c01db0d2107ecf177b7ab8 (commit)
via aa528462f0f90a589d7773e8d952c51226fc0b96 (commit)
via e561cb2ebdabe6a41ee3e702f407c853a88857ff (commit)
via 0f3dd4e12dc74c1c5985e9b701b59fe2355af931 (commit)
via ac01dfd069e66c85409e93b40385a393bb5c5a59 (commit)
via 61e6c39d6f14c8d06956e92002b53f9448453b4d (commit)
via 598d81ecdbc43b8e6869ba65c60b1d7b4fb155e3 (commit)
via cdb6afba87fa03187512682b8348e65dc5e24a0f (commit)
via 5dc80a3df42d7725b56cc33d33249363a7a14279 (commit)
via e67c6142d40cfde0a9c04c0ae491b8f8754bf69d (commit)
via 2a1a86b1b753b7fdc7db4bc48e8f0838a9521187 (commit)
via b9c101fd307c164f7551d00068358828af4ed549 (commit)
via 025659c532f8521d8a36476d2f1a14593c9690b5 (commit)
via 7482587271347cc071f95cf1709a0370c0087fd7 (commit)
via 8c06e54ac7cdefa669326da0280294ea88196575 (commit)
via a154032687e8ba7c5c5cff880f34cd37166688c9 (commit)
via 6620f7a7d1c0d952ad6da4b1441608cf56c2702b (commit)
via c832257c559d982c221595331f75e16df2e0b5f5 (commit)
via 4be4af38b1179f61740628da95a5c0853e1a17ea (commit)
via 79117eccd04c2a99d80026f7c0df14a8f6bccad0 (commit)
via e43764fda36554cd8c8f7a8f6aaec51ddc48014a (commit)
via 969260c87bd0916bfa951f4801bc7e4e60872fe2 (commit)
via 1e39232770b629d8e944f62c5f01bfa6c3b09384 (commit)
via f9028be22e3a0ad08b2f0e51d823bdf868621019 (commit)
via e448bbba3ecae68b261612954aa9777edc384be4 (commit)
via 4df6d1597b68281fee3999542219c18029b168eb (commit)
via 066c983e091831645218810fb921986a659ccb1b (commit)
via 95c6f676d504950bab0964fe9297893f36d4fac6 (commit)
via a3d4fe8a32003534150ed076ea0bbf80e1fcc43c (commit)
via 650c6ce282800e712fb378741638ac66240d59c0 (commit)
via 7ad877ff1d3b88b2b8430a80f6102590837c2e74 (commit)
via 73b25590661745a2e39c6235698ee93bf00ddba3 (commit)
via 2df5585f42f1af03b89204c7cc22569720a26ae4 (commit)
via 298fd19fe3405ccc76d5d7ab8f91d80c53bcf44b (commit)
via c312fa4f4866b21aca35bc4987f8f0e83b198ef9 (commit)
via 69cdc5f74d067196ec35daad43f842f716897bcc (commit)
via b3c562f2bd85fa9f825d59f8601ba99e04e661f3 (commit)
via 18a4180734c1896f12afadd40a2a658f3c5c795f (commit)
via e62bdea81b790e8694075cad0255244226a00e5d (commit)
via 423740919b7a050dc1255a0ebdc6ab6b71ea777f (commit)
via 504f4708e55050fb6f9dfa7f784dbcceb5e1fe16 (commit)
via f0229b977d354a137df49137655eb3ec56d96f97 (commit)
via 2cab68004571463fdaeefe5659380fbbf9c54e01 (commit)
via 807f3b5cd6b5f7daed92413e9ab771ec500b6536 (commit)
via d7fa28172fa3b2481e1f1c1ddec05ff7ae21bce5 (commit)
via 772de981b14ae73731b54e6dcc5b84935584f1ce (commit)
via bd04d06c2d51af45ed818e90ef500936dafca1c4 (commit)
via 44160bfeffd56f5ccd6c8400eb8c3e883ac10b19 (commit)
via 29f6d05f66afabcd46c532622b10b370b65d3090 (commit)
via f941f042dc2c953390354434e9a4563cb67b14e0 (commit)
via 54b12f14d032796a8d6638cdbbf6ba5d63835478 (commit)
via 689aa37ea4cc087ad0affeedb2b4f8de5b29919d (commit)
via 211389e129c7c27129fda481deda0dd649630a98 (commit)
via de6e68cf258f9349795368f5bf773f8911a79acf (commit)
via 3c98ade56f07c44740f1bde2e37f8fdc9842bd18 (commit)
via bf30f052334922f729c8805f109f838ec1a126b9 (commit)
via f4ca083e427788a22a4979a104b517a5a4ef3df9 (commit)
via 3a6ae66c2a737475ed55fb28cd3cd49b49e7295e (commit)
via 7184eb4185f5bd6577dda90e5100c13e238ed8fe (commit)
via 2f808b5a9d101109016361c6143b1add3887d1b6 (commit)
via 17590957db6a352e86c208c3520a340349b340ea (commit)
via e2889aabb93e201bfaf49046472ffc42df8b4b77 (commit)
via 9e44958839b3f9d8fbe6cbd1f0c8f711763396fb (commit)
via 76e6edb548bfa6eb3df302ddd74ecff71caee3eb (commit)
via 6bff7db76bded2e571e2ee827c776c45bdcd6c6d (commit)
via 5bfdcfaa4a495e36255de546354d1673e4581d3c (commit)
via 46bf6c05bd7c5aa7cc79af834843db68cb6b5471 (commit)
via c6cadebf7b86855a7da91f14f66bd7d605ee13ea (commit)
via 2f220f9ff1a51348ea225d72a75d58d5d81031aa (commit)
via 8ba0a7bc496c135f80c71bf035fcca0687a16e27 (commit)
via a87cf3103eb903132064550a8e75715d9b644620 (commit)
via db8cec0cd9505bd0ab091e6258822dcb2891338c (commit)
via 3471a0a39ec45a25c96905ce634e142fd4a06902 (commit)
via 2f81b4961e70a653a09dc24d8b07bd9eac1ecd04 (commit)
via eacf6593309211e0cdc1867d69d4eeae86bc450c (commit)
via 25e6b90892b1b93c129606f175a905c30ec62a6f (commit)
via 04c159afd7ae18b98b50d1a3d2e5f1eefdb3bae8 (commit)
via c3f6b67fa16a07f7f7ede24dd85feaa7c157e1cb (commit)
via d5a472119f28ad0f7f15756997c741ba25208ab9 (commit)
via bc3d482930fad8d97305f07e116093ace8110e9c (commit)
via 32d750c6674613bd630eb1e724adc82b8ec8a622 (commit)
via 7bea7dd8c15ce158908b8c42c93533089d12da8a (commit)
via 4cac3ddb232eac0e52451c0aff718f3207fc7977 (commit)
via 56099e3711191e22b41cc6f404a69569c1f3b7e1 (commit)
via eda6761d2607385c10bb6a9e8bdc5c1209405093 (commit)
via 8d9a833a05515867753578af948c537a4d8ad127 (commit)
via 14a35130eaf81efdc8483fdfb1b8eabbc97653be (commit)
via cea0d1058d5b96e616ce8f71afd0b45a1f81b103 (commit)
via 3ebd4b039b2596328c1c112398a9e629c9a73001 (commit)
via 89e0087450e73ea2336f56c0f7024f4741eaeba0 (commit)
via 0404a592b185c5473343a3faa25de18845cd192c (commit)
via 6ed2d962ac93a626c7fe4494a0a060858c4125d0 (commit)
via 00cd04ff09dd403968633193ad51ce81bbd1c5d1 (commit)
via 55f6c351193ff5aa17a2729bd58d6e74fcd17933 (commit)
via 0ef74c3696b50b84cc1613fbdff09acb775c3e10 (commit)
via 654ec49fc5f8976cc6567b31ab8277d392f79b76 (commit)
via c65fde7c57736e089aa9d83f82d340d7dfb167e2 (commit)
via d4a8366000c44fe71b70dcee0615d38253b4f932 (commit)
via 4d67c32a6d5ea8a09f9a9dc995b33619f56db5fd (commit)
via 2cb89753827b015cd183fff26b52e7c28154727f (commit)
via 211025a21adc6230894092b606c8f86cef3dc913 (commit)
via 427bc05eee647c9c5afb013f07c0cc73ff17aecc (commit)
via 4f5e8221042782288e57974e38d4ab7d9de54740 (commit)
via a6949cd6433d089e240c6bdec095e12c4230214d (commit)
via be918f9a8b657005605fc8856ed037fafc2374e9 (commit)
via c6603decaadcd33ccf9aee4a7b22447acec4b7f6 (commit)
via 0857d558e83343a266e8bf67f835017c07f773c0 (commit)
via 03a458983cf7d345ffa7d9d1b2cad0b509b3be54 (commit)
via 299f1f5ff94bb77306019c1d26d12e57a30a9ab4 (commit)
via ad0e3e26471403d9b019352132cbc04b2c1c055c (commit)
via 2015f7356237f5004c6aa9b678b442575476681b (commit)
via a7dac3f7ff64093a4958710cc45a7dc28f727182 (commit)
via e6d1be1a6718874ab37b0f403c3e266c90bcdf87 (commit)
via 148c5bbf6132ac5b149a0a97286f13ec212443cb (commit)
via 4542e8960d4accbef74ac41e5082209f3776b5b8 (commit)
via 56d109e8a0c5d46ac542870723ba324aa2425b88 (commit)
via 315d2846dec8b2f36df1aa8ed23feddfc621a356 (commit)
via 131a89175e97714ac87f14f95703cd76df7a965c (commit)
via bc6af598cfc62d68cc4a80c0aa10d8573d63cfc0 (commit)
via 468687262b865e264f2bdc235ec4aff45fffaf29 (commit)
via f1dd9da6c3582b6dcb876609a142f9b6c80389e1 (commit)
via d5d0d09842cd041653044e90bb6f2db0525c3f33 (commit)
via 5fcf58d12137139ba41af390550a0b73ea8a9ef3 (commit)
via 9bfdf12f2fd3108a4b3b772c20e9b78396164042 (commit)
via 4daf58b65f126c84a0d68d102ad899e260ea5662 (commit)
via 7ece6fb69d2791ea263c080083a725c36e7a12ea (commit)
via 20a8d4adbdcc32b17fabc43ef1e6af4c4b7c67d2 (commit)
via 65b7adb8a1578ae6f070b9c877149d0db77aefda (commit)
via 7e2d747ba455749e7ccee8eec36fe5f7e51ed749 (commit)
via d2861efce4cf71c924de470c48594727e0eba80f (commit)
via 70066be6e51afc479dc485dfdf9ab79169a9d3c1 (commit)
via 9d90db228e26173b0781d090ea2b0e078c7969f6 (commit)
via fc2a9e9876510fd425d693aaa5b93230606d26ec (commit)
via a19cdcff7accbce09eab6bf7ea359a830564c1f8 (commit)
via 5306dddc94101bb87bcad59d1c29adc7bef4fe75 (commit)
via b1b78de66931aa416fda81f136b584f515561afd (commit)
via d003ebd54cb9b0454e60af6270177e202c766892 (commit)
via c630ff8a3861c5597a5c77a81eee52c09aaef643 (commit)
via 36f7b7fa7bddd07da90c9346d5d62dbed77c2a49 (commit)
via 9b930275a2a875d91aa425cf8bcbe552c5bee453 (commit)
via 45e7d86237a1b483d27819c5f5fe0d5c6ebb6dc4 (commit)
via e1a0ea8ef5c51b9b25afa111fbfe9347afbe5413 (commit)
via d36c276d69d8897175964b8a2002405c53630663 (commit)
via f7cfb3332667e260a60660af23290087fd0c4fbc (commit)
via 7ccddd64de7f7928d20f7eff23053b64c644349d (commit)
via 5bdee1a5056c8e3517d2dd3de954d40323027ee1 (commit)
via 034e1d3b1490054f265c4e4ac20cfa43127a5625 (commit)
via aedbd11c6780fb36fbaa8c6c8c4521caf24fa5b4 (commit)
via dac1394a30be534559eda0cfeaa140eace9000a6 (commit)
via 885821d16691e4179678e4d7f61d550774c1a7a6 (commit)
via 225a1e04ba13d2a48bbf3e1f958ce4e3f86aed49 (commit)
via caefbb6f9016955ad62391d799fd69886aa363b4 (commit)
via b308ba3bf71ccc768f302022e7ab571ff2cfbdfd (commit)
via 72604e2508ea78f0f0fcce74b6e34838e75675a2 (commit)
via c519aa88c8cf4edb25a53ec761364b076a6ae497 (commit)
via 8aa47f6c343499103fcf86bdbe4c3918253a810a (commit)
via d7eecc60198a4bdd4cdf23dda9663f9d0d803f9b (commit)
via 0475216923e0c60294c0901ae3e6af09f5e961ae (commit)
via 8f4a354b90fbf0be2ddc5c54f72325d52f0eff2e (commit)
via 1fd14dfefcd2339d9dea25b30dc9204bd10548f4 (commit)
via 39fba2a15cb200dbe77ae3c9a4d894496f9eee66 (commit)
via 557794895c01555c32906ad8422d5d6fbb878fe8 (commit)
via 3af2603f0f08ad709590766051eea370c470642f (commit)
via 03ce449ee10c5a8d8f60a68cb42839b9ae63ec00 (commit)
via 9784a1841ff7a5f08c92a0c646bbc4ff9a0b4ccb (commit)
via 5082c255bbeb8c83f9ab08d4e28429cc271b2fec (commit)
via 3599c07417b1bcf40ae8e59959278b4a59ede827 (commit)
via 043b3d2952712cd61e7ffb4f73f3253d11ce6bd2 (commit)
via b5cd040334461d9f5ab0f1267d505bc7ac3e0ca9 (commit)
via 54a1d02e8816b53e0d4ab77817380946ddf156d4 (commit)
via 7d1a2ca88d0daac2779ecccf50c4308eb0914ebd (commit)
via ad233427c14503e34980780761700596f13e14f4 (commit)
via 6713fdc5fb825c90598bbc48029027ed3f2515e0 (commit)
via 6dd94680bed03c6423d1674bc4193c64a7fed4a1 (commit)
via 9377868e58e5303d06688010224ae325972a1547 (commit)
via 8efaec345efaf37272cd7098177b60ea787e3ee7 (commit)
via 48c33c6c5adbe7e46d5a7853c1ee0f7085fcab6f (commit)
via 74c2ed970512562b5ea53004d3e152e2d8519329 (commit)
via 2d3ed739129c40c2473a927f51e37a445b47fee5 (commit)
via 3e678f3bda8a502c8a4fe483e12166bf1c9077d3 (commit)
via 73e1a3cf8920bfb4e7cd8f7672b3f82600f2766f (commit)
via 31075ad95c5c27f30d2b9812ead3dd8b6edff236 (commit)
via 721708db656444bf46451aeed3500bf3580b3466 (commit)
via 5045f86bbd1c7ffaf07c95f7c6ce4d64b1e1c467 (commit)
via 94d255e2f177399d659e53ae06d622f2576b7e4c (commit)
via b9608c20b42ed6db6592a1f7851c91efce974b32 (commit)
via f18722f2e329c939584ff1b45936eed172fb16e3 (commit)
via 214b2747a3213b7267a4a8a597d07851f00fb1c8 (commit)
via f3259cf183a8bea65acad690b50982057f24abe5 (commit)
via 0dccb67c0e49f3448e7fc3da7a5e414113bf36dc (commit)
via 622d3e7123555d0115149e3f4527dbe5f11da864 (commit)
via 6bb2e2de9bc899e31c8233c1ef74e8ff79c1bccc (commit)
via 366cf13a9ca23ea0ace26677754d1968106c277b (commit)
via 186195174b0d8256da12f91395f8e57c17ea6a7c (commit)
via 6548cfede8e4f8fb9457eb62b0c458969a358155 (commit)
via 42a5a7952572019c06a8dddec9994cdbe54e0d4a (commit)
via bc8cc9cf605cfbe065d0e27e8a081e5f54c6a4cd (commit)
via fd6c9be84e460ead7e1fd3e0888828854adccb4f (commit)
via 71be4dc12e4ffee2641ea54f70bd26f16bdc1460 (commit)
via 673bbeb9b8b1ecd0629c0807578624705c58085b (commit)
via 02c2f7f80e6d858233a595cae04842666fbd29c8 (commit)
via fc4e4e5c2c469c8c464146bfe56cda99effd4593 (commit)
via e3c5c430e69e4b3b2211421be2dc23723c4c40eb (commit)
via ca15d37a2e8d6eff63b52eb5fef0e8d3bdb3e2c2 (commit)
via 10386454c422828a5d46a3b927b1c284e6216372 (commit)
via f41e4c3104480b6a8a6aa08cc5c28cf71a13a3bc (commit)
via b30eb9348603cb2181794ca00acbc1bcffa54db7 (commit)
via ac473294894ef65cd01174114183a42657d6151d (commit)
via 3456e30bce45c286b120d176a517ca88e9e21b1c (commit)
via 4325f3dfd81569be4ef09a84cf0fb74246a5b666 (commit)
via 74bad1ad71292fa998f14ad3953236f7e979efac (commit)
via 035fbe1da0e77a45d108046e5399c5a676e098a8 (commit)
via 5d65c74c127243790200028375786fced2bd1ad7 (commit)
via 2b96d4487c517ab209a1ca5fce4af877fecae0a7 (commit)
via 9f92c87372c0a3b37fabb3ee1963dca5409ccde5 (commit)
via 943453b21cb4a91ada2f0da363906e29d3659421 (commit)
via 271a74c31ae50d5f713dd6bccc2127f56f950466 (commit)
via 96f3f46af4cba730dfccaf13ea8350529dc71bb6 (commit)
via 30004e59d00244ebea35e8df0abce51acb2cdef2 (commit)
via 340c8a6ce05ef5c72e81801066191c18875bbec1 (commit)
via 46af17adcadf913b3ee26a70394b8faaf5dd7b36 (commit)
via f85b274b85b57a094d33ca06dfbe12ae67bb47df (commit)
via 79fe8bd49de676e941f804350bde4115ab454124 (commit)
via 36d8374051ef3df79035debd0bcdf63016d0aa85 (commit)
via bec8813a793940089543c8875afc3ea475662459 (commit)
via e8655ab138c37f22582c7370677a4361cb6d6363 (commit)
via 23db9e9d61274b5c8c4d23b493cbd582eb9eda4a (commit)
via 2a6431ad41175316c82c7d13c0bffa0de9a44a7b (commit)
via 05504fc9e53521a9e5f55f6dbfc619813a08c733 (commit)
via 70994c681382b8c8b8c220af97999c887e01a423 (commit)
via 339c94b4664819a817eda8008778efd2c114ae93 (commit)
via 6d3e0f4b36a754248f8a03a29e2c36aef644cdcc (commit)
via 69abb657226d2002827b238503ef0f31a81d66c0 (commit)
via 1815740f4154dea79aa15ec64cffe2f5d010769d (commit)
via a752e1aeacc9470ff3dfb172bf3f9064eefc3161 (commit)
via 0b7af64f6b3a567371a38ceb83001008dcc8f4b2 (commit)
via a1d81937904fd5f646f40c655975855aea5985d4 (commit)
via 26326a8f0780a2603c7ff7268998fbcce7bc5dbc (commit)
via d42136528ea0edc6c7ea45c3c4d4c542217f9d33 (commit)
via 7a49270e4d0117b5432dfedec70e41a9c399745a (commit)
via c45efc1b129fc4b64d79040e0bec33f08658b170 (commit)
via 4a5506ec8f3474513c7de10969628385cec138b2 (commit)
via e646d8af58ff6b9863a89ae65b6244275405bce0 (commit)
via 1024b43c68506682dd55cf1cf9c5fc9dbc8cd96b (commit)
via 68343c845a46a9be3f38c38388ec02a8bb4ef884 (commit)
via 31df868ff405b57c6767fad73a7ba82ac0544e4a (commit)
via 51c2df1a2974002d93335c5d762353795ba8ac92 (commit)
via 10f2ae4625a238a4899372432df13d29eca9f9b3 (commit)
via 8d6493eb98994ac01c4305d12a148d78d048b681 (commit)
via f182b85e7ab01bae5c9c7a181a2b9db7977a9948 (commit)
via bd7e56727b6d8228f2fdc685bff3a18349415c69 (commit)
via 85873bf8615dc643db8619414e417dffef827c4f (commit)
via 909a05ac4185589eb2304d815b5ccd108f92f4b7 (commit)
via 1453084c9e0ecc7c3663678031f3d19c5f91adff (commit)
via fa4c5b912c4e4deab0e42af46eaeb6ef3e41df72 (commit)
via 5c82cc617522160d1c3d248aa215d84031b2de9c (commit)
via 75235e6f7c1b9b646d68c6d6238b2c38df9814d8 (commit)
via b5b6b43f34a3901dff343367515ea08f09a9d6ce (commit)
via 4f3776c2b1d06bca0e7e02feeba79caba01ac865 (commit)
via bf4f7d8e7a3404deafa3901720e58a524e178a18 (commit)
via 03cdf37097aac9e1f10f0f349e6c0aa83f6debe9 (commit)
via 702512a6bbb48421042b69fba312bb41296eab61 (commit)
via 53a1410acc5726fe8ed83ded19b65be7c9002ee1 (commit)
via 832eddac5d9724af4b228cb123a84ed7ddae0473 (commit)
via c055314da48eaadb3f88b64eda17263fc6b387fa (commit)
via d86119932d1b6f2674f3685515c35be147f2d327 (commit)
via 846996720639e0a37682312641bd6ed504870488 (commit)
via fa72716d183569839306fa8bde1d7ec451ce8919 (commit)
via 1a7685ff5054b4a51178ae24064d8cf41e817ced (commit)
via d3896075acb7d3b5dcfd8441d9ec9c40f3c44f4b (commit)
via dc507ec0a035a393304313f21184c9a9c804e46e (commit)
via 6df5353aa49ff281af8c5931b8b556595cd70c6b (commit)
via e4390d4f07ed2af66782abfa41ecfdbd7b1966cd (commit)
via 9c2f41d0ce4250623b36966eb04698d55eac7509 (commit)
via 7ba811ceb844372d04d8f0ca62f112a377889ae9 (commit)
via 97e41248dcb4ec3285d1e5f6d0f7d922952c63a6 (commit)
via 8f8bfc92e622717d75684b8bd36181b525a22309 (commit)
via 1d139b0a312bae07267448c6e679b50ce24e5fc2 (commit)
via 55dd08057c52c3f0baf3f1b34f23f87c2ee7ff35 (commit)
via 6e966c8f89b249a3f1c1a87201f24db656a06720 (commit)
via 882b1c68c77a1e3e8261fd7a8ff9d654313963b2 (commit)
via 4cef89808213a50c9da3aa469d6253ff939f254b (commit)
via 63e9d90b093a7938f0b7cdb34e9e71379a15d5de (commit)
via ac3da26ee0505f828be081d284b4216f5ada820a (commit)
via 79d4b470181b50699125d1cf3394e11071f6178d (commit)
via b6de5342bd648c4f38e892df6c93177c243ec993 (commit)
via 7ff403a459b34457e433a4cf87ee9fdc64ba5af1 (commit)
via bb0d8b35c3f52e2900abccda0f52995683eee4ab (commit)
via 33e10c84c8b6ab274c157816ba81e6d2fc8ba628 (commit)
via 3a382331f1308099930d6b653dd6d11b70da8e22 (commit)
via 0a89b921bb8cb268721e8ca38d99f7ea967fdf8c (commit)
via c0acd591b18839863e5c46be32daeebc07c162c4 (commit)
via 5ee122cfa69f8bd6be3c4861407a3da35cb72bdb (commit)
via 5ee0c3b143458e52d59e3e162c5d9cd4e11e0d1f (commit)
via 2a8863be72c4fdf0b9aba1e05e525de7fe18d4b4 (commit)
via 58a2bdc6b084fdb4ac57726b305f49463370cc2c (commit)
via 861a1055e8d48f682b5aed9942f1eb1963d93a55 (commit)
via e9fa118cd1b9d4d1c47ddb94244ddf78266c9dbd (commit)
via 08f6a3e96b00395c6132b7558ca8c3e0764e7adf (commit)
via a6328cdc2907b9becde3d261373521dc1a3b963f (commit)
via 1c295702731e5fb76d8ea39c14104347fb0a8b91 (commit)
via 51be92846c3bf4881701f17e869eb9f12f9cd239 (commit)
via 35df226d16838f37390270d9d3d1a5e736cfcd12 (commit)
via 2aaa162fd5f5921aac58baeeb0dd026b7a0a2fbb (commit)
via db5ddcf7b62eb21d9cf0f057ee45ec31adc832f3 (commit)
via 125dd1132b09275fbb528ebdd4358f5977f17402 (commit)
via f82f47b299e9f48c86c6c8f7967f29c381d5df06 (commit)
via ca3018c0273b41dac1f67892cdf59e3673a183f5 (commit)
via 7d6c238e01f66ff90e073c458312e732b9ce3f72 (commit)
via 719a1cb238f32588f5b891dc3f5d1461efff6905 (commit)
via 9f0934c0e2ca79fb5b1c03c57cc850fb9bdc22f8 (commit)
via 73bdf354a07a3e7ad4585881c434fd71c36ca775 (commit)
via 8f4d5d803a3fad8019f8b4b638731d533c6dae16 (commit)
via 1d3b43a5f11c82ea80c9c8469cb8aee4f504cb20 (commit)
via 4f47c644ff2ba7b3d062e51d4b7eef4f87417301 (commit)
via 3c9911b4fde96eeec42bdf951ac6beb003eb0184 (commit)
via eecd53050bae78469caad312532cbf404244f1e2 (commit)
via cb324f11a8b5ac623b9c4ed491d7ec01551531cf (commit)
via d20b1f2ce6030846d6de80f6addf2c5ce53d564f (commit)
via e825b22e4ea06a8f0ecc8645fbd42af230d05151 (commit)
via 8250e0313cf3e7f81646e8dd978f4a16f0578222 (commit)
via 4933a1c6284498e2656909d15a84657b5b6ecb8b (commit)
via d24f908722f73071dd220b3457123d3e9dd2deae (commit)
via 6b983412d569f2e7e81f6e6bebe37720f146d9b2 (commit)
via b9be6a9538f9d11946c5291e7063aaa0db990261 (commit)
via 39aa6489f86c5aa33e07da05c0f0be4ca2d9d9ae (commit)
via bf06401cd681a172444c7a07b8f99083be86a508 (commit)
via fa392e8eb391a17d30550d4b290c975710651d98 (commit)
via 85d28a85b04c5c6a6905c6e018dc9a899afc4494 (commit)
via 80f01bc608cbd779c76a6e5d5ca8dade8b37dbdd (commit)
via 27b1c2767aa9f7e7b74a640b548aa099605b28c4 (commit)
via 0c84643032d36c4b28fcc730872f8866dacebe2c (commit)
via de20a4c519712d5076b3e26c6eb1e4a40f689ed8 (commit)
via b47cd4c09f84751609f52a4a989889a7aa475b2a (commit)
via 88e8eec059a9e91725f8a12ff76e6f9c3af40155 (commit)
via 69dfb4544d9ded3c10cffbbfd573ae05fdeb771f (commit)
via 199beea87baee4dd3d4339ec20c32d0d2cb47f40 (commit)
via f3ca4f39256d94a48ed38f886b70d8195f89c83d (commit)
via b4093345f5b38af66fc7b2630baeaf843a817f74 (commit)
via a354d4ed95f1368c961efabea0eb2e1db95675c3 (commit)
via 0a2277b4be0ef6e326d170ad7a0836a17b82c0f4 (commit)
via 6b3b78fa009e557f65deee688302c573106a3ef1 (commit)
via 4c8efe3ad82e6f62f663579c4e792f70db7012bc (commit)
via 3bfd98d48a7b8e132931a950cf5d52ae1b324159 (commit)
via d06ff546486bc123c1fafbe8b20bb5e60557970c (commit)
via b154498887ecdedb12bcde74d91d2e76a3f57e1a (commit)
via db2e9d7c08423014a3aba97ffe3d48d23b3ec28a (commit)
via 4ee02ef54b1056af032347aca528dd1d78c610e9 (commit)
via 65bbffe7c89169fc958fad642185684154c85aa4 (commit)
via abd65c5b6ee4df38adc9dd03207525b805067544 (commit)
via a6e56d56d0a24ba1b8ce4c77f3ae7eddf56c7d24 (commit)
via e2d04f2d4343bf4c9bf8384f44187145c76d1717 (commit)
via f0f403fc8e950db97ddb71a0fe1ba2fc9ab13b3f (commit)
via c66811b7635660c28041c947cbd75e6dcc322168 (commit)
via 4c45f29f28ae766a9f7dc3142859f1d0000284e1 (commit)
via 9a2bcae912446f9ebe821c1e226f9629f89fa8fd (commit)
via d6cae3d58234c7fabfc8d1c9bc5b1b59c7c1e3cf (commit)
via 91a4343fe608ab91dda3f8caf05fef277a2eb215 (commit)
via 1ca6a3ac00cdf22e6fea1157bc887dd44e1f87a7 (commit)
via 8b0cb0ded8035fcfb9cdd483ab3936db6690b97e (commit)
via 7b3ab876a7fb697245c81d03bd477f81136355e8 (commit)
via 4963031b007d9e9f15cacfd058edc20a6d33bf37 (commit)
via b4e44a1c5f4f1fa5c16af5bcb2a3ed48a29c1da6 (commit)
via 73feee77d8a05f458c90990c8ff95f3338c54d72 (commit)
via fa9feb90e8588508daabccf397cc3fd4751a0e70 (commit)
via ae0620edd4c9cb477066e5f29f1d983b79554f4a (commit)
via d2b40a13c798fbbc64ef8aa8d02f6572405ab160 (commit)
via 31f138b96f8713469feb16ff361e40334ec8b83d (commit)
via 4b79543220fa1f81ffaf16bdcfb79845587f51cd (commit)
via c7fd8cf0745b7a13c3ddf0e8309b64c7bdb18f8c (commit)
via 11d850685bf9e0d6a46b434fde322463a056c95a (commit)
via bf60255cc1f000a7251436f2461cd8e9b31e52ff (commit)
via bf807435010663e517215f69a5777b76f903927b (commit)
via 6287fdfff0892a1a71312e0cd2109a03c00cdaea (commit)
via 6700ecd585dac224bd92008fef7f6ee8a275d04b (commit)
via e7f74b8e4b4b8b63d1ebddac464cf713f18e8a15 (commit)
via 953b4dcd1d40790aafa513ed9be7409da2128ae9 (commit)
via e0f000ed71e1eb4a83b8db1b28ff96b6d13b6481 (commit)
via a16cb0089b11bb4a2cbc3230ee6870693c469fc7 (commit)
via 104820a94bef55a4fa1de729723cffc0e3801ac5 (commit)
via 81d23e305105f215c3d6142f750445f7e2ce0fe2 (commit)
via 1e763b3d4808e0f722e2f971b726cae5aefde463 (commit)
via 146f2e6c679ee84783d3b260cf96dac35de9511b (commit)
via 757a5b14ef9101d99c47e7155a1b19b526d09389 (commit)
via c6302d0c4226e13753428df87e7d7864e86ed778 (commit)
via cacb5d70d80379f908b368f933b72815393beed0 (commit)
via b9c84e0e73848c17bc4e5e43a31677d0d84dc6d8 (commit)
via 05e0a3a8a9d6b142b8a6a554c9ce29e531904edd (commit)
via 8076ec59d2a4efa5e7bc2b28d47749819acdb32f (commit)
via 27cae0d6fd4b8f5c0fa205aa67e236e9572b8e64 (commit)
via 23b8228fb4b4fc431a12f1f13668f16c2c7517a2 (commit)
via 29b74f8137e19df1e0389df99d9b20f0fa231f35 (commit)
via f217f1e62497393d63fde870191c9aeda6d75d14 (commit)
via 3b86761e66244e8536b3fbb46ba1d74bbb8dd526 (commit)
via 858d5facb7af996524fed919231d5ff55ad54aa4 (commit)
via 2fdd3551e1012e61c414c475741f0013fce9e11e (commit)
via b91e4297ed4ba93adcb9af382f00c301876bf8de (commit)
via f5b9768aba32bb7673458eeeb14f0f56734427bc (commit)
via 2b67b0507c180a32d14f843d26b99614cb1a88ae (commit)
via decee38f00f3efac436fa899bf6160fff787e318 (commit)
via 35b62ac905d85ea7216a2d61b1cfb62a890bdea6 (commit)
via d8595ab6bd85996c00019da0b086fdb40374e1f9 (commit)
via eab6ead64d7f7d78b024988bc29e95bb28426a37 (commit)
via edd0b0623481ea70b4af23a0cbd2bb4ef797d10f (commit)
via 45e29ee0eeeced40dd047ec9da32da674124c55e (commit)
via a2fcb467b96cb4229e35330a9ca72db8065953bb (commit)
via d70fad8d81f6fb7a4dcadcad45043c76e85f58af (commit)
via 329d5b2238df1813bde496a44a31c0e57dbbac00 (commit)
via 0f6cfdfd2a611d2a440fcb01596c4f6173e9ee04 (commit)
via f85cdd324c2d51046c2030587d8d5053b4a0ebbd (commit)
via 754c38ee58da3a9d9667072c16b58ab53d45e368 (commit)
via 3877992c69722447eeb9e4819bdadc64fb44754c (commit)
via 059b6a3e1f0c3c9a5d4526664d272f16bb664903 (commit)
via 510d6d2e9f16f9bf1dfff251d84e311b685fd454 (commit)
via e41c43c1cc533686b04734fa35377bbaebf356ea (commit)
via e3b63fb33c64d7cafbc59895c803b5ed7ddeb82a (commit)
via 8297b3a7425d2a0de1f1f0f5cb1a4572a1780820 (commit)
via 6eae3579a0dc0ed27f33c3e36cec2ad64be7329d (commit)
via 74b59ef97880f4bba6e660b32c45fb1a8027c727 (commit)
via e9556924dcd1cf285dc358c47d65ed7c413e02cf (commit)
via d43543b1fea006c3ac4bca51749e7cffbcc84085 (commit)
via 2a70916c9d0949f710a3f289ef13925bdf12dd5d (commit)
via 3469f1a8757e2b49c29aa93513c64a5167dff400 (commit)
via 408b43df795d7dbb4ee9d4a47cb3e4ae9ae1bba6 (commit)
via 52c3dae27c25e3aa3e32ae300013ddecec5ff190 (commit)
via c936d8ec97816a35aafd54aa4c06fb8e5b5689eb (commit)
via c1465e69b3d272bf4867d99e47c63659f41c4db3 (commit)
via 36389eed513783f9ccb0b7248fe0c5909ef2a36d (commit)
via 5728c385420fdb47dd463c7737ee0cf447609c81 (commit)
via 946b1dfec880e605bc154bc963f4b3bd860c1b55 (commit)
via caa37a99fc07eec6e88c6a829d000b2cb1baf4d6 (commit)
via df3ee4b9e04459e2ba6dbc50702b3c7f29c0ceb7 (commit)
via af37bfe0c400e0dd923f57e9775d67a8f17af802 (commit)
via b82db9d608868c11c68d692458769e9afb47f871 (commit)
via 784caaf1d2a386777df43178579417107c6492e4 (commit)
via c16ff5e78c1a0f80c57ce8fb7235674b839fef81 (commit)
via 9036a1857725ba935a78b4aeb9750741df9f1287 (commit)
via 430bc7504e0b6d439440376bc6b1c56fc69280f7 (commit)
via c6c92db7bae905d428a876996d8414ee1d278fa1 (commit)
via f00928ec99591b2cabde3d213f7756e90b1e6f50 (commit)
via 33ffc9a750cd3fb34158ef676aab6b05df0302e2 (commit)
via 8544c8e88d5519acf996ef6c4659b75b52ce6839 (commit)
via 5db914470d01eeb3ebde4b71f13647b093df3138 (commit)
via 1cef6595d167a14adcec8674ec51d6059baa06fc (commit)
via 04e248bf10707b003b4584a2c06e9733f6729d28 (commit)
via 61ec72fa83146fe30e2165e02639eab082531712 (commit)
via 084f1f59a6da39c03e5de0ef079216ef94e277dc (commit)
via a50e0a4671586daa8142b17d4f1a7d2ecd9e44bf (commit)
via 07326330d220e74b0f84572d601d50d83ce48945 (commit)
via 71e0b2b77c475c41ff73668455d1dc447f01e8e5 (commit)
via ff74508f934161e722d3a9d64158c061b46039fc (commit)
via ba0d6139cadca933ebbaaa7f5016480154331ae6 (commit)
via 9c36d2e22389ed7c9ae61fc91eb3f2946b10c169 (commit)
via 00b7af245214cfdc54f522d719f23406d80d68ad (commit)
via d17b3c006757eecc1fab20cec90ead3e23b5962b (commit)
via 08a77e0ef2096158eff12b5d20e9af9e46c93c45 (commit)
via e01ee176ce2071c076833c842a9cd946a5008130 (commit)
via 53ceb71e36d310a2a4cd5fef7ae9fa5d410f078a (commit)
via f22ba824385dd587b9e5b54491a225c192d823e0 (commit)
via be5ebe7673a8335d929803553bf3fce041a76bce (commit)
via bafbce54b6b042ff5fcf7bafc18b316c030f08c0 (commit)
via da33bc422e11f1cbf33427659a14291a32256833 (commit)
via 2772477dcf13e7c524cfdaf4e39323f0716404dc (commit)
via faca56aea56b9eafd1093d06234650ea311a667d (commit)
via 54fdd6d60c068faaac34d573acb095378672f47e (commit)
via 9e95314aa024cdd3ef97d17d48e68cd638d2be84 (commit)
via 000b191c2224d9117d02636ececcfaa23ba69447 (commit)
via 44851acb14ff52295eaa9f3f68ffedc04c19de84 (commit)
via 371ff753b03a8d5149041076b5732e659cae80ea (commit)
via a622140d411b3f07a68a1451e19df36118a80650 (commit)
via 69bdbf32c693ddae8b595b45b3ae78582211c553 (commit)
via ccf16f3b82314b94b3aaffe2f58eac7464f404a0 (commit)
via 66d1631eddb33c06140fc22f65b974562a8ccadb (commit)
via 96b66c0c79dccf9a0206a45916b9b23fe9b94f74 (commit)
via 7a7dc17a59cb4d66ec4f7f4cf96afa81284b3040 (commit)
via 937635d75665f331226774ca1b7c5bff791b9d69 (commit)
via 1b9e3430ec2b9a378107877e5470bf567ce17601 (commit)
via 0a9abd16730fa7708967c4b17dd1a36b7c75f363 (commit)
via 33bd949ac7288c61ed0a664b7329b50b36d180e5 (commit)
via 0b18c4d0297e5e37ef0584b5e56912cc02715021 (commit)
via c52e7d7d0cfbdfbf4d7342f32024fdf4cae567c3 (commit)
via 8599372b7fcf5a21c873d43189684b681b63307d (commit)
via c9095ff3de68440efa427a536985bb01e0125504 (commit)
via 870cc9bca34feab530b12cb870fd92f365513571 (commit)
via ecc1ba7e65eaa8371cfdd8b83946c6a3bc2e5ee8 (commit)
via b38ed8f456895a7cb262b1b8cadadddc68964e6c (commit)
via 5ab82e00091c504afeb54037a60671d344233512 (commit)
via 3c93e56397f0af33064d449da82ca69d535a251a (commit)
via 81e57da07f3b1a718fdd81991692e703f083219d (commit)
via b8d905c9dffd7f3973854d9f1bc7eac7086f4aa2 (commit)
via 693068bdb12a36936d221f432e447afae6b05dc1 (commit)
via f6dae94a7b648f7fc766109c4f4c13152d45aa9b (commit)
via 0f5e915ace1384308c9976c5f20dd49d5b2d05ab (commit)
via 10d88f05a9705a4fb14afd6ceb89e976e3c195d0 (commit)
via 617f1e6fc0da5415c244c45c211cae4f01b68585 (commit)
via 378613249f14406399304f3464f249933108417b (commit)
via 8ddec7d1255e11ffaf69c7c827b42aeb69f95872 (commit)
via 384a5661d18a3521169bc96d90b9bce087b0c62b (commit)
via 3f7c07ffda7d4435c0bf241396f914cb336ddfcd (commit)
via ee44f96ae1e8556fa04077f708287cb017dbbd0e (commit)
via 1779eb23b4a147af7585f1c7d090f7c00ecaff6d (commit)
via cac02e9290600407bd6f3071c6654c1216278616 (commit)
via eacf5511e4a005c343b78430c9f749ba46a84c3a (commit)
via 3c704fca950603ec97ad259462500fe6fbd549c9 (commit)
via fdb64790c2d2e104630ffe208439048f0abc50d4 (commit)
via 403c7178590825e101e0822715fa557403ff5c33 (commit)
via 68863a7847788b6dfa9de464c5daf7e48db6a273 (commit)
via e744b4b23d47a3467cb0a69a9ace6baa1576ba5f (commit)
via 88e7a72ae29daf1dfd87cd4fb6ae31bbc6fb9684 (commit)
via 30fc969527cf3ae250cc6d2ee0ec0248c4b943d0 (commit)
via 91a0d273da950aac6348bbc900ceca502a6974db (commit)
via 21d0817bae3a1aded59cf3809bff5856dbba792a (commit)
via c7d8adb3fb0903c2b3a8627de14e85742f338d83 (commit)
via 2b7e75de166cb416a60080ad34d8acbed4fd8119 (commit)
via b215e58d58791c27f6116f65b66243b7a54ec14b (commit)
via 58825bde4cbb00eb52ff5bc31a1d170594a26d87 (commit)
via 5d1b066a7702c075d7934a3ce88181b1c02da878 (commit)
via b610eee2708d9c1783384c850896c6203ccc3dd2 (commit)
via 58f99ec6977a07a6df8f7053d2e03201245491fd (commit)
via f8205314f85fa56bd7a8b783764c9e4ef050ad6c (commit)
via 9a045959025fb0961c7bf2eb09603f7604d5cbee (commit)
via 82fc64bf97eb7d50e22edcac28cefe0b93b9a7ff (commit)
via e8b5540d1dbc1e80eb98d15e1dab3b9c26fe7ccb (commit)
via 3f9a24a0ddf821e8e16b2c99d2e7335f2c40fa09 (commit)
via 65f68f7b9235307f8af029b37ebc7c2a309ed7e9 (commit)
via 3b1551b28913ae83e62997de0c8c4dbd28c25217 (commit)
via 23af78de3f2162aa27920e0ccc2784d76e0def8d (commit)
via 22eb335a0216d642194450947b28ad2ac3f771f0 (commit)
via dd7864295bd032ad3a832a6ba7d35d13b89dc922 (commit)
via fdb992f0a2699cdad3bcaef79c2ca35f8e700c16 (commit)
via 0d8a7d1e48a1b1df5fc09a65bacd550a4bba6d00 (commit)
via 0e61367a4241444a7465b25ee461adb7e829acc2 (commit)
via e9e336900ee014c251ad4b25a8e19d36da3a9a2e (commit)
via 7ed3411dd5f0045b2eb877579a715de9892fc501 (commit)
via 86c303a0291366a0f3dc7918255f2708ff1409e9 (commit)
via 29b1d8dc4aafc4f644b3acd36dd6ca1704c07543 (commit)
via e4870b2ea373a314214f1ff575aa675496557b00 (commit)
via 135070d34df3a8c70cfaec68fcdd43704e017fb7 (commit)
via e893a8d8b65af8f4b4d01ddf0388fb2bceb52ea0 (commit)
via ef8e4e281a59bd4e3f5396533f96e54b7ffd1434 (commit)
via bcabe81c90f6e6294b714b4aef589f60f6b6cca1 (commit)
via 8c2655df05e51decb5cb0ff7ec48d484341b5f6e (commit)
via 223e1a93c3a17e229194899151cd728d3f6e72a1 (commit)
via 9ac31c2a7d7d2ee78ef2de4d56d3f57f6f45d75a (commit)
via fa304f6d472075939f45a1e59a45c6a6c82d8ac7 (commit)
via ad304b978c110bee6e97ef3010776bbd8733e813 (commit)
via 44890bb7db4879313473787c726ae25dd6eac335 (commit)
via aef76a4ff2d463f09a2d82e304d6003025646a5a (commit)
via 5305956b8ed2e5983eb4255e959ffc26c6017693 (commit)
via 5629c01345df4fd78a544169fac69e2afc9322a9 (commit)
via 5dc9b2f5c86bab3a7aec205a2f5cf91b2b6398e1 (commit)
via 23fa01352842de0a16314734ac061f87c81b662a (commit)
via 21ef42e572becd4001baa3fcd509b955e077c656 (commit)
via 06602f5a998ac727aadd8e094e633c569487ac8f (commit)
via 493b6d66358602da6281641df2c80833f2b10b94 (commit)
via 1b437a9f90cf46154bd7cc3c0c63315293d8860c (commit)
via 2ca8aec80f6a9dcb4da01a9057e9abb9717ab93e (commit)
via 9a160a14fcb4d4b781ee79fdd4aa60f8e56918b3 (commit)
via 69127fa628cf8cf44362b9334ae1c0e6c6ee5475 (commit)
via 1eb1178479503a5daf0f0ba43bf729d64c6ecd7c (commit)
via 639532ca50d317fdee78374be0e05c0c43ee2a0d (commit)
via e1126a32146e14b8816b7a3fb733f3836ca61379 (commit)
via 252e7844e40e29ba6a7a30498bf525b75deb8219 (commit)
via 29c3f7f4e82d7e85f0f5fb692345fd55092796b4 (commit)
via d538f9ed878a0abf65a91b1ab70d86d58aaad9aa (commit)
via 3f3bc96b8f17677c90dc7cb049a36666164ef908 (commit)
via 43ad3049d20a10c2a96ae85b167694aaaaadb6be (commit)
via 25b5b6cabd4eb505b0a003718c30b3114440a16d (commit)
via 1032577a333784acde807d02124854a6ca57de71 (commit)
via 53c635403b6bd8a23c6f0b24bab357c6a355f277 (commit)
via c3a8a2254eb9d4226910524936292aa053fa39f7 (commit)
via 3b6cd417ea4ade9d616b12e7cb0577f892bf18b6 (commit)
via 9d00667f84006fc001b04953519e78b1ee5f0010 (commit)
via d7885ecddb52be49de729021a70b42e60aca2cea (commit)
via 63df2f7cdb5672540d176054f5fda0d3a93707e2 (commit)
via 8bc0766533f5f78c4ef8bcd9438f816e58c3aa83 (commit)
via 1f578c9b208b0fb2e58b101d537a9d07f66ad690 (commit)
via 4f63e23b7aac425bafd4e0c6e5c4099b157206c5 (commit)
via a4b1c8d4bb37b273c9027f41b00eeae54ad2f83d (commit)
via 4a8316ddc1bff361a289ed8c406bbe90fde79e16 (commit)
via dd0c9ba580d2b87324e3063de6f69d2bc8be5687 (commit)
via 5c0c1cb8f090edf516c21cf03506075bd866915c (commit)
via f8af3f0612dd39b81283af83b5a930d0754dacf8 (commit)
via 583b148b75060920bf8f394239973caa3868993f (commit)
via 67d05b954c47821d6a29ba093632604c49fb794c (commit)
via 2f6c7ab36c4c2c88bc19503a2bf5577d148cb71b (commit)
via 8a09c89d47dd66c641809fadca16d29eb79c25f4 (commit)
via 229916f0c84ee62b3c0f3c204d6b20dd3e8d3062 (commit)
via 15783473147022e77c16fea2c314abb5b1db0ade (commit)
via 8662231f00b931f1d72c260c1d82fd598caaf840 (commit)
via b8387e4c0167f8521dc34ab14214bc79ac847c4b (commit)
via bf94c3c6175e20a65ff1418e4cba692abbae159b (commit)
via ba37fe42bf0e97653780bddc8ecb47a090c3ccc9 (commit)
via 41a8bec7700a7792e1f24883add4e542977c111e (commit)
via c37383f57aa3d6b6cd4911279a143091912c0e32 (commit)
via d253c3c481194eff6ae52861624d0d0fdf44b4b3 (commit)
via 854d608fd26a2b9c1cee43f5ce48d75ac7223e22 (commit)
via 83652c539b9e0fc04202bef8c6abe6fffad1f58c (commit)
via bb5f0f9677e89c5fa45a6e54c2176a2496cfa6a8 (commit)
via 97f58d9418e14b7076850c529619dcd1efa41145 (commit)
via 4a27055866a0ec36350d20422713174d61543015 (commit)
via a5ec1487b162ef818186ae92b54e5c73d8757bd1 (commit)
via e7971923e90e58921feac425b7ec9073f4a22c95 (commit)
via 962447aa16c4f4accd42fc326d9408cdf75d2dde (commit)
via b7a8ae05f15fb10c0e2215565f160889390c3c8d (commit)
via e346959d031b0ce05939f801e83f653892757e49 (commit)
via 60b242c2d161e30a05236fb6dae3ff21daa0b6b8 (commit)
via 307e3c7a946a593945050b9ea8159df85f5cf28d (commit)
via 2e97db1d7db5a65bc19f6909269ae930d2e8da18 (commit)
via 4d5c20e29fd4a74db31024bdcdaa99d431af3219 (commit)
via 20df0b0ce702241ce8265690388483c5ece3eb16 (commit)
via ff8d95f4e0afc3d2b8fc5f3e75eb41edee1f969e (commit)
via bd56a0715a100c1fc0b643bf14fd0467a5ede80f (commit)
via 1db082c00f5f3760c0959e4f4008ba5e5d0e491e (commit)
via c9de713dfe88ac13976d31dafb28a928d95fb239 (commit)
via 212ccdc84508f1c03b2ae3790067011e1bde0ba2 (commit)
via 613a57b59957e8603286230e8ad92ba8ee317081 (commit)
via 99747ec274d20a83fca6d230d2f384560acbe54a (commit)
via ec52d37b4554e01d70afa67c64d203c445a9aac6 (commit)
via ce1d57a6341ca867ce6624424043b13cfa3d5f39 (commit)
via c3c0784a907aca8b915221c63e9228a81e93c346 (commit)
via 785ade580637cc0d78139edbda05ed13e1675dea (commit)
via 85655172ba34a2d3c44731880f0e439d8933d277 (commit)
via 49316890b86159c3ed72ef0913c5a58d4e8e5657 (commit)
via 2630be5587b6548b0217f16fb78bc80935013a69 (commit)
via 43fa9db5aecf34e77a211c43c4cc2cc6e27e7530 (commit)
via 492b1ef54094f220280d58b42ee0749ef829a045 (commit)
via 7c0534d4b3dcd4b318dca8ebdcb1f1424c14931b (commit)
via ce9fdb302d9d7eac83eb2bb8f03c5b5b239cc23d (commit)
via 4f3d61ec6d64807a8f174c4d028154732c4bad77 (commit)
via d919490c1df6f34147d38e3d0af91d8b836fe25f (commit)
via d9b1da77a397df06221431acaa3ba3caba40bb7a (commit)
via bd65ec46d25bb56ab77f50ebf8d6e431fda2d478 (commit)
via f5f3e572144232518b47dba3ed32a6cded151a47 (commit)
via f5a9aeba7e7b55411834315f192c77d98d3d82d5 (commit)
via f0f75ecda5cb7e51fbebbcf35ca40f3d38b4b535 (commit)
via 70a919f029b159119a50ac6d6fded6b843167ef1 (commit)
via 2b431168cf2961d0a04b07cfb2928b3778057d6c (commit)
via dfd5516cd85bfda7bab56d161082d2ce74330e2b (commit)
via 46168c33b8ff9bdc8b5448c350f2037453e0c2d9 (commit)
via 339e9f7077179abbeeb3a3f16a6f1f95cb22786c (commit)
via 32c1b0777e781501e347ac160e176d2ab6ddcfad (commit)
via 9a8bc5b083049e6d42fe38397ea44375d8634020 (commit)
via dfe737706fed6ca7aac475fb91d988222799b21b (commit)
via 62c273605b092d2c6f8058a31c7fd318eeff0b8b (commit)
via 3c941835b81c1ad7869f38bbf74d8e0a7fd567a7 (commit)
via 4778557e13c231f1bd2cde5d0950b68de920d8cd (commit)
via 7b2ee20fb35a475916a817c30668d31d15fc8907 (commit)
via 61dcaac0cfc5b764e0d5ca144128b1aa7d5a311f (commit)
via 0363b4187fe3c1a148ad424af39e12846610d2d7 (commit)
via 1a11b2d51ccff009c2caf4e132ac18e5bf92b11b (commit)
via 63c8beeb9a4751f512ade6c72e9dc1ce7ed5f158 (commit)
via d9f12c0e4d904fed50e43d5602c4d97563d701d7 (commit)
via 517ca63bd5adda6a2a2f40c889a87f1bf8f7832e (commit)
via 80d60b6b5653d581c6cfe1fb05a4128eacc4e1d6 (commit)
via 879fb8f585a5fe1af737ad41ea81e35a4cc0cfa4 (commit)
via e8137e183ee0b94ccc82d47d1489a470d551bff6 (commit)
via 347f81283182675265cda722d1def0bccedf4c7b (commit)
via 94a17934a24d94741b53857d17fcfb70a0cb1ed6 (commit)
via 766d7f47299cea598fd47bb510b9366de6da4356 (commit)
via 00519912eb96e6fe6aad91376be073977b350f4b (commit)
via 15962b4badfed5d448826d2d55e8542314498dee (commit)
via ff8f84b695de0e5350af325c675ab2bdbab79cc0 (commit)
via 281c6ab13bf40a06055a64b3e5f69c0406d33db0 (commit)
via 8b106d20e3afa459ef9ca13401145395d8a6dcc3 (commit)
via 8117b69a1c1c8c569032951254e5a094763d74e9 (commit)
via ea8936112837ab22263edabe5c44f1f40fa0ef6a (commit)
via e42c4c83835acbb17075759f9dd206dcff4a8d37 (commit)
via 40de1c10db09295fb9c0014c5ff4413b44fe8aa9 (commit)
via 70fd53cf6bbfc536163e70d4621659f840fed83a (commit)
via ce7f53b2de60e2411483b4aa31c714763a36da64 (commit)
via 678965be5a2ca3ba12981884e48911028953f853 (commit)
via 71e772824618ff3399ed6b2c6d1e24103fb8bc29 (commit)
via d47e9e7db10822b6197d7be175a0146e8252ead0 (commit)
via de096122c7d18fae6f0afed28ccdf0bbfcf65395 (commit)
via 2cc9047529f689aa7e1cb08e2d6321c2e1b76049 (commit)
via 8e3eb1576a4f04736aeb7d44d9b13d19729a7062 (commit)
via edd462bc9e8f727b1ebdf14b3f7cb6c844dee35a (commit)
via cd17ffda53e213f39c0e02f38b415b31806c331d (commit)
via 119eed9938b17cbad3a74c823aa9eddb7cd337c2 (commit)
via 7e9c676a0a800322d77ecac81bf223405011442c (commit)
via 147db3ecd8be5a6fe692c07c3d4d7597f8f2fceb (commit)
via 49726757166a3a3e1374091f76f12b4203782c98 (commit)
via 84076d913d1e974e504ddac1c7b8a22c9172bc9b (commit)
via 122da90997b11cee73dd63291216aa4c6c6ecd11 (commit)
via 400ddf1d85976eb07188e6fa20ae0a83274895fc (commit)
via 479c0eeabb8cdb874385fd9ef2160bddf8a0a80f (commit)
via 836fabb60589aaf7b901176892b6c28ab464e8d6 (commit)
via d2805586d949f91b9ca649206a1f4b03d7da4193 (commit)
via b6b3a1a1073b2f674c8ba189635069d3ecadc8bd (commit)
via 29ec8dce0f9312be9cddb3712fbd107fb62e33d3 (commit)
via 5156bc4dbb05a5803b87e57751ffc7ba94e4ac98 (commit)
via 68e9b6e28b981e394f1fd55de00840e861695044 (commit)
via c70bba06b9157645d6493b16ef0ee414277ce7b1 (commit)
via ad6d16107655d744062af1e0570abc2023ee1ee2 (commit)
via b5d7336d2353cd552c2b253a358f612b54b56e54 (commit)
via 745c28556fd0d85718e7228a4957448b2803a348 (commit)
via 905f265367c7b4ee5486886faf2951cb655fa60e (commit)
via 201899adaf91b1c8f7affff546c0c00de7746eab (commit)
via 4d3f1c82605bc45fd390ee3829dff1c3bff0cab6 (commit)
via b59d780f0b9fd66c06eb7d06082e41249b778f94 (commit)
via ad797b98b8ae4f0f003374c13649147aef3c1a83 (commit)
via 8802d414240a4b453b13573e1bbc381eb0d14343 (commit)
via b321b2e7708069b82bae12eb1b4461f4eb9e4a03 (commit)
via fe630ca4be50f0f0bd048eec264b8a21df9c6b38 (commit)
via b41986aa9d6b0d78642082abeb8186ef5d7bf431 (commit)
via 0a2f3129eb9f3c9319a3b203fe1bb8bc386ef212 (commit)
via cdb550b4be0db3a37ca40426d288672615335cee (commit)
via 7f62c19c5d561e755570b8aeab625dd275ecae3a (commit)
via 4c90b6cf817c2087052ea8b22e91a3b7f8ae39f0 (commit)
via eb8aaf5c3b048047cd1cc138ee4951405bb59ce3 (commit)
via c97e9105e1c25d4708d2f153c93c1a29a73c9537 (commit)
via 30c5683babdc96906117dfd677c3313d3a82fea9 (commit)
via aaace1a1f1579d5cb2a657c11115e601ef1d4b7e (commit)
via a852fc05305f5c062ecf9d3c9ff6a5b836370d6d (commit)
via 53e18a35628a7a595d0de637ae4a045e41fe6b64 (commit)
via a1ce8481248f5859f8319ae85d9e25eaa0ace2db (commit)
via 0107d1f09fb6075c9fc2ea6a162c85d4a89a8e85 (commit)
via bcefe1e95cdd61ee4a09b20522c3c56b315a1acc (commit)
via 37583984483d4027f5e2fece88e3f7e12ac42fe8 (commit)
via ad11aede925c7307dd3a6528be0d1e8e4723d162 (commit)
via d72fddedb47c7dfae908e2c5bfc9c6ca1d574420 (commit)
via 37e3ef0278c0f52a2e211b72d9b39e7e25262b43 (commit)
via 75af5cd564de8cec47f2ed5ab3b5cb028acdd0ca (commit)
via 1073338ae5e5ea9fa385abf5b87ff3421f51ab97 (commit)
via 1450d8d486cba3bee8be46e8001d66898edd370c (commit)
via 88d5e6c5a78f2b55fd5518a865bb138c52e910b0 (commit)
via 2d0fc10cb714c3e34bc670a86ba645b42d3bd777 (commit)
via 451d16ebbbf619ad5b420b7a837bf95ec23ab686 (commit)
via 393534ac4698e6dfeee32ab4bdf99e25eade4a76 (commit)
via 954430281cec4d8f6382285f761a1859ca9508f7 (commit)
via 98b10b7662c77074dc26b27a4db1023b18821942 (commit)
via 93cb134fd8ff6fc6c9d63d0a9f6ca3defed43721 (commit)
via da42298dc1ad39a5073bb8b221e05ccf8fb5464c (commit)
via 94aade43c190a9dccca5469009b2604efb7c5664 (commit)
via f3507e17b575821adf1076236e2855c1d7be1f26 (commit)
via f290b63a15ebe4d4f26908c2c9948b43188c4a15 (commit)
via ed58ec0a82ec0b9df0e0f9bd53f1a32123c9fff6 (commit)
via 03b6bd8461d7aee84ac34da1c737fed4a3c16c22 (commit)
via cbaab234b0fe61e09b559c7623e3f29bc89ba1b9 (commit)
via c5f8c4c31d08049b5b8b1e7ab43011d62293f18b (commit)
via 0ecc1e01cde7133bfcb183ced46b011b4eb9558a (commit)
via 09fff7c3f6d538b80e8a1760673bc4a3ac9f62d2 (commit)
via 2045020b58739c4f1ba8c6d39ca118513f8b2e35 (commit)
via d3a0ac2204c90e1bb6b3ccb474f7224f1154a0db (commit)
via 945103ccf34fe1140877c1dda05a98eb4ee79d98 (commit)
via acbf4d8c9e3deb825a7c193a26738b12ef870166 (commit)
via b1a6659c841d75b8e918dc3a1e165188d97d56fe (commit)
via bc9d924de6ba3c7ddf25e3c77b2470b17667d068 (commit)
via 9181c52cd0d827a7c62538b1590e1a061a11c07f (commit)
via 89d7de8e2f809aef2184b450e7dee1bfec98ad14 (commit)
via 4660c86fe6aeb2c77427ca8cacd1b7f840d4e554 (commit)
via 946126b36db6b9cd21c194a84a61417dd6bd567c (commit)
via 8c6afd880d47f33174b656ed12a2e5e4c821e14b (commit)
via 22407c48db5999e77a6fbd6e4f42ba2a3fe36762 (commit)
via bc87d1113832e7900d3627663c0a1203c3e7b2da (commit)
via 6fadd98cb5a9beeb79c58ba0a2768d9ad895c210 (commit)
via 9a3d9604b0fd9dd3dcc5f00b06ebf08e4e4f68a5 (commit)
via 029681ebd000727c546f8d387df270e9c1415bd4 (commit)
via 9a551ccda02b841bf296ecbce5c0bbf04721024a (commit)
via 2f3d1fe8c582c75c98b739e0f7b1486b0a9141ca (commit)
via 506fdf871f529b20b295c8b3a18286c5f295a806 (commit)
via fbfd9a49b562e3f5532e87ad801baf82c6b7a912 (commit)
via 14ee53dc65a150d46f51e4cf6d666c38187ce075 (commit)
via 9344c41c186e020cddf191880ff5bef4e653b461 (commit)
via b448dabbe9de31366ae5a20c0aae423f6257d953 (commit)
via c11f99307c27f07ad5b8ab9abb80f6ee5be32efc (commit)
via 0eec3b5ce47348b8fdf8eae9ffc61dcb4db0ccb0 (commit)
via c37fd56ccccd540bde166eb7f29c2c691bbff154 (commit)
via be426ec46dfd7aa3573872059ac9420dd4fd7d75 (commit)
via 208c318edbba5b98ebbd865f15c31bd77626328c (commit)
via bae4e4960422f2f86af1fc48d1f1d1a8f23a1b95 (commit)
via b0f2acac75969ee9773b3ec3eb584e017cbfb117 (commit)
via 5ac7de770cd112d0bc3842dd79c612bae3acc611 (commit)
via 716006fe1be4edaf841b94d488cd1d8cbbda116e (commit)
via ba20d4a58b6d7ea93c69681372bd6f1064491d1a (commit)
via 459cdc4a0dd14c3c21a8ce1e51bd99fd53026a21 (commit)
via 11cdfbb065c969dc5638c8c146666da4b3195677 (commit)
via 522bc3b62d92f30094428d580392ff427725cb0c (commit)
via 43c537178a4024702196d28c6408917c611c3a66 (commit)
via 0b8dd6b36af411f1aac1f48b099e36282c51da98 (commit)
via 48054d5a0cbb379af956c32330f919467b9d6b23 (commit)
via b058641f53581df0131c18e0e521d74d71790a23 (commit)
via 08fbb8a87decb5d7b9cd1568e515eec22e17e5e3 (commit)
via af2ca1df688ca14a0f58bb7151b19ee7afb88776 (commit)
via 5b1c236c65180291b33f01b8a0f7f1bc46a4d1a3 (commit)
via db06972d3798e67c5d581234c673cb4b9cdffb7c (commit)
via 47b58321cd15762b9cdd0f09e66e9f5b29e67eec (commit)
via e1c60036dfd2d4e1fcd372807e4a3f0f201028cb (commit)
via 9bd788945d172e66939dc6d8fdb3016803696cee (commit)
via 2118111e02bb90ffc92e14d37681acf3c5f52b98 (commit)
via 7f6ff763142c2a503f06410eaf4f8fc8e9259f02 (commit)
via df1c5d5232a2ab551cd98b77ae388ad568a683ad (commit)
via d66a48434bce9de43f371bb37811c820634f9937 (commit)
via c178521c5c7133093866a8403b3d1d8af2cb6684 (commit)
via 60635a58548ae680058d67837e6ce1696c3b7faa (commit)
via 660990cbc4df062e7814f86040f7cf87662190c2 (commit)
via 8c4c6e96fb6289bbacc2e4d15b8a7b131c8fadd8 (commit)
via 27e9cf518daa0a9f668b744b56b57add5c4e10d5 (commit)
via 191f569b711f30366f584045e4cefd9211929ad8 (commit)
via f687f77020bc9c4619915e7bb1d7f5a7c6c90ccc (commit)
via 6e0725b08ad12bafcc939622daa5a2f7b734ecbe (commit)
via 8377d0dcf3c758cc50111a98a7a9dafdc3237c44 (commit)
via 5e9811640dc3e514ee012b16287522f7993ade46 (commit)
via d007842f66f874ad8ceacf664f8c0645d2ec4226 (commit)
via d25f0e1e01a6cc3991ae63f0957c61ec171395ce (commit)
via dc2960e9845fe08a27d49dcf9d6474ad8b825fb1 (commit)
via dfce83e671afebb518b21733bc67e3627320e568 (commit)
via 7a8781d9f723a1783f9e027909f4e117ce532a83 (commit)
via be9f19012418391c0652a7f76e3bd2eddf72a351 (commit)
via d1d37e198212833b5ca1e5c943f3f8aeef805a17 (commit)
via ad8e7ea08e814913ff6fadbf593a3fba44d4bbbc (commit)
via 26a9ee9fd3b8d2cf0c9643ac5441bcedfa3c2ab3 (commit)
via ba046cd24c29b6b91cb49492c4dafa72af9b4a18 (commit)
via 02fb3337a339f2043183ae66062a774ae6375e83 (commit)
via ab624f466e084edecb4a6299a56f8d33282a1ba0 (commit)
via 0be5bed016c3b12abf5e7049ab7b63d2d474efc0 (commit)
via 2e6d5f8d602a632d8a770c13d7ccbc09a25825ad (commit)
via 7642e09748501d041309b84de9d46b3aeab74a63 (commit)
via 9dd3b10a7ce79fb1b1289136987f82c2bf9c2310 (commit)
via 739735c08bb30957be78506bc4cf0d6c5f01745d (commit)
via 0eb1e5059c26a083708f6d765035aba8aa4c6d2d (commit)
via abf4296a7becc39021bc31594b29fa540a86efff (commit)
via 0840c03b4269e5fb478905cb98f0bb04a5555bb8 (commit)
via d3752093f7cca1ade17038ec16e52e9a86f31115 (commit)
via 3afd0a061c6c24433c858142c91746d2702f0d04 (commit)
via 4813e06cf4e0a9d9f453890557b639715e081eca (commit)
via 01e052f372b1878b9bc40bfcea80d37e408a58ca (commit)
via e85c7ea5983687a583f57964694acce71a4a72c9 (commit)
via 25b99148984082fd818624cd74cde2d86d428c2b (commit)
via 97a7be5ddb8f5a89206912eae174bacf3d230a8e (commit)
via f37600d5ae721b626b9e9323dbff17a404908e46 (commit)
via 6fc763dca0e5c617de20cf49353364219987db63 (commit)
via bbed8c3d28fb5a8cecd3c6d2682a8fa4b01c6566 (commit)
via 55fe56a1ce1c5bb0c359942f37d912a18041a2ba (commit)
via 23eca85ebf0a8a6f749984504cba553e2c4d38e8 (commit)
via 09ed404f9b47807c74e0c6e59077e95a7b28b953 (commit)
via 46cfb5cccadfaaea4568d8478c3ba349c9b877dd (commit)
via b6bd57985bbd40a4cd76e0b37c3e5ae7d3563051 (commit)
via 8ee676bcecbdc959c24fce66ae32ddf94fe999af (commit)
via 0318fc199d2d653198294eed8bbfd4ee69b340de (commit)
via b47eafefecf38d32013bc7377aa4eebf531c49f6 (commit)
via 0239399581611def5ec0029ff48babb818cc7eb8 (commit)
via 416bae7d607e43d00b3661a201c3162bfe7e8270 (commit)
via e974876675bbddc9e2f75999f7902d0d0c8e10c8 (commit)
via 36e6a75b004f79c320c8d6cc128c9f40dcd1099e (commit)
via d6d04321180adf453ecb64c28e03b2c43591e521 (commit)
via be67ff2306c8aa98efd9ae3b017a2926dd3fd46e (commit)
via cb5eb7ddeb879f1658e446d06e9556aa075d4e3a (commit)
via d69fb646ed39dafe4277334a28b52d948e6348ae (commit)
via 655179cfb251cd07a03469524e91c4ed81b57803 (commit)
via aca3456d32de52b95c0ec0986c041cc9ab63e132 (commit)
via 55a49c67cdff76b0dca55d425ae977fb3feda9c2 (commit)
via 85aef712019c4b0293e1726c705b6bfc749ad537 (commit)
via fcf8b9df5fb8511e9bd78f42bd797f2a8718c6e6 (commit)
via 7fe200c92eb411c554a82fedb9b4126686961f0c (commit)
via 0ef3b7adcd45bbd3127fc3aef69cf6798e69c807 (commit)
via 936b3ffaaaca125fe6a87ea3ad3910aa2d45c3d5 (commit)
via 695a3119423907a059435465f98669976a969f28 (commit)
via 4d94e850cc95e491ce268c9782c0eae335e7f7ea (commit)
via 2cfdda6be3038b9318c9f72da1b7c20488f2ae99 (commit)
via 01b2a2fa9d86fbc5796626461b282581e46d2979 (commit)
via 4a6f54f71484ebc8531371da819d4f98f59418e9 (commit)
via 14023798bdf7385a96500cf8bb7d93d485def18a (commit)
via 8e75dec19bbdb3ae1e01558ed5bb8cd89ad45582 (commit)
via e642b05a1f39d86f19bcca83ab1da8bbb8acad80 (commit)
via 84bf517629617cd0e5569ab36fae83c4df9c82bb (commit)
via f8dc48154ee1ac174b8d9cfddf302e579057e6e5 (commit)
via b9d570e44c4ee74c55e73371f0b63e89f2392ad7 (commit)
via 2caf9ff64737b348fdf6da3ac6d3183c8b66e74a (commit)
via d124a5b6f1fb3fb55918e3b75343c6bb70023980 (commit)
via bb5479f56b9bdab1eff81e8ecca1d8e703a9ef50 (commit)
via 50b124aea4fc6c367ee64acb81041384c169906e (commit)
via c8b7d82c25958cdae8bb02e3a93b2b8d6dd078d1 (commit)
via e8e45c21b3a6a6c8169259ea5d66d8d475bfbfa6 (commit)
via 16e8be506f32de668699e6954f5de60ca9d14ddf (commit)
via 3c243b229dd01343e387ee9a729bce54711c5a50 (commit)
via 2fb93c012e0c3f8a5e8e0fff208abe5685b7d4ce (commit)
via 27ae48b404de59c031c7f97f64edd4056c5e62dc (commit)
via 4de51f893dc92b863a9d3baa747153c2a011923c (commit)
via a1b3cfd353802b33b1ed51b51c9d98f6fe56d2bc (commit)
via 75445a1c6e208d27ab8d85166ebd079be947f98b (commit)
via 643e32a402aa9dcfbf9d7df47495c77474eeffe8 (commit)
via a2ea0fb9c996d174936122809275cc464cd2e17c (commit)
via 72922ff97062fbb154821827932d8a8952c371fc (commit)
via aa95c10d7631603afd110b43cc6f12da22fda7f0 (commit)
via 4e379f979dede89254759e40974d9c04d90ed451 (commit)
via fb7adf34e96efc9c2e0986ab3f567c6d38500a89 (commit)
via 2bc9bc74ea4450a1581ffcaf5f1598a4c03d3721 (commit)
via 0c8f4051f57e4e1bcc29436bf50a1f7d4ff9e79d (commit)
via 0fccc0c84fb7706ff71039896564c5496f0cb7ed (commit)
via d7647f9c62f62e2115947ec0f1aaea57cc266b0b (commit)
via 111e2c83f6584bc452e5658f17779b30d2959c99 (commit)
via 3c1fc036f79481a10628e3f841666c35a0b7b823 (commit)
via 5e66a7a9ecddb8e00f5ed44fa22a94d02a177e8d (commit)
via bc9125aec50bb75fcf601eb2e0c9bcb5dd3a1a1b (commit)
via 9869caead03573be43747c30c4f38929c975b409 (commit)
via 95218da8e5bfd78889639e7cac68b4afcd44d264 (commit)
via c32bfa79b248fb9351dafc853ecc0a18ca2e3a66 (commit)
via 926dd635c3bca2100bd6a9975e2368183623c8ac (commit)
via 863be53af8c369067f33c4b5688b44ebb380f789 (commit)
via 86f31f3156e151a9446396aa04105c8dbfb6b592 (commit)
via f909b685377ab7c3d16f5903ccc54d0465721046 (commit)
via 9926f9b8f9f9ed697975752c62a46e34bf45e3ca (commit)
via 947d85d0e851a6fee3c34c734e3bd21065b57ca4 (commit)
via 61068e227821fa32305d4edd142b2dcc6ca74eb4 (commit)
via 2f41159cd685809c1dea5f079e99c5ecb90e1893 (commit)
via 8a91e6ec6c257a9e630e0408ac1a5f464d769a55 (commit)
via 240e5333012a0a60e9b42f8741c44450eae22ea2 (commit)
via 22362bbf67afbd7851f731f6bbd337333267a939 (commit)
via fdfe820d25fc0ac6b8258a448fd9188d4e1f7090 (commit)
via 6067533e5c4ebded8260ef4b97f2709868bb2799 (commit)
via 8d178815af6a1eda06930288f3bcb4b261e25bbd (commit)
via c2d22d81551f2309538404b52615d04394a3b9c3 (commit)
via a3077227a49f8cd0ae4e7c1f20cc87c4844ef0fe (commit)
via 685c9b375d73134e0c59ba8c7623f74487f69348 (commit)
via 4cfe351f170077546b2187f9d33d41b4148aa6a0 (commit)
via 042bfd80328dd3c5888ac70630f1545aba82f125 (commit)
via d46357aadcf46d1be0be68fd510d9065ecba2a57 (commit)
via 77249210b9ea779e45b35271a915a50250e00a00 (commit)
via 72b8ac61724514b5b8b8807aac6d9e2c9da7268b (commit)
via a45069dffcd932ef9ed38935db9f1cd54188a093 (commit)
via be41be890f1349ae4c870a887f7acd99ba1eaac5 (commit)
via 282af2bfa3dacd3b8145460dfed2cd9ac294983e (commit)
via 1c004d95a8b715500af448683e4a07e9b66ea926 (commit)
via fb2e8b3a65c9e36b745c81766b8c3973c8bd5737 (commit)
via 9f0f84c089904c5e544a21bfb4f4def60c9eb0fe (commit)
via a756e890b5e9dee98694bb88d3714613c0af686e (commit)
via 17c0037b8ab08e7aaa1a9eee9e1a0753308b2ce2 (commit)
via 4b74e2550ba3030f666d03bf57f65f1d6d1a30f7 (commit)
via e42a6c969cda1e3676c9884e9697349e18db92b7 (commit)
via f4486e178f04944792e169c1a7a0c5098dd2c223 (commit)
via 857b8cbf4ad057e50232eb152bd1ec4903da807f (commit)
via e4cf4a7222b006c5534edaecb23614fb224585ff (commit)
via b11b84e52dd4670259d7c0a59d2e7cee4550b16a (commit)
via 1090b92f9a37d021c445b046b9245861df6ca36f (commit)
via 38faa03d1c37053d1c4ffe5e689716d42191a59e (commit)
via d27e68b1e1a5f124be2c02e0e6976afaabf0520b (commit)
via e58c2fd32cb85d37cd96ff8bb6a7cdf0f653a5a1 (commit)
via a043d8ecda6aff57922fe98a33c7c3f6155d5d64 (commit)
via 4700bdc96332953deebe6b6c4204177a994e4930 (commit)
via 157e22bdf89b15d1a6c20e6e87673fee503d8b44 (commit)
via e9799d37bb8a0d145d0080c8ac93781c08f150e6 (commit)
via 725dea7f0e4a4d455355153575307e2803d78ab1 (commit)
via 8bab20ff2358dfcc7d7f09520688b9a07bbc1ea3 (commit)
via b96a30b26a045cfaa8ad579b0a8bf84f5ed4e73f (commit)
via 4c3b2b24d863782742d49ed2bd60a38dba93d4bd (commit)
via 167b544c41361a7cf316b042baf057afb9002d3b (commit)
via 00d53a32412c50c20c573f40d895e8ad7f74ad4f (commit)
via 8674224a8d108e92075436de2656e8ba912f7df6 (commit)
via 3de4ecaf232d8a75e9da6e54384da948807a0659 (commit)
via f43b44eea132aa6ed092a787d0933d5b119a4025 (commit)
via 7c41946c022b31c44c9e8b76b4c511c195c6743a (commit)
via 85a61f11135e898164d5e41e949bb6ba6929531b (commit)
via 0f0c175daf770bf863f32a4798657625704a0c54 (commit)
via bf86e0c9ea67c5ddd6e70ddec63a1f46d8c62630 (commit)
via 0860ae366d73314446d4886a093f4e86e94863d4 (commit)
via 25201656d56854345b11d4a3d553655da2a43bfa (commit)
via ca1da8aa5de24358d7d4e7e9a4625347457118cf (commit)
via 274ab7a8bd50c7d04214b97193c7461b99c2382e (commit)
via b48b9c7e0d0b51d399f248cb3387dbae770d157e (commit)
via 0a55e2f325a76a08c3feec9911c146bbf9e017a1 (commit)
via a2108be0aee261fe1046f96a94543c25937b55a4 (commit)
via 859e0b3e82d4cc5270d8fb557f0120dc35edd1a3 (commit)
via 6ae0d625f0571efa6482618636629e8e210a7718 (commit)
via 835c041058405743d746a6cc02746018774540e4 (commit)
via e8757a6861d8328cf7f8f2bd815af3a6461cfdc9 (commit)
via dd41cb13ce276c055af55e486404773dfbf31154 (commit)
via f4d68d5f8042f235514afd7ce27ff7cf43785726 (commit)
via f8a24304737fef27da9f2f4f8a02c1a558d6a34e (commit)
via 6c4f943e65aad695443076065082d40cfddbfe83 (commit)
via 99fbbc37fe2c3e465af2e1e5990b89602b02e8d4 (commit)
via ee109e14bb0c0409c689ca73fb9ddd5f2435e29b (commit)
via 8ab5a4d30ab27be1f14082a2f40e2d7f53b5e512 (commit)
via 83d9d50018d7a2cf6b1850cfa732323e69c1d411 (commit)
via fc64e5bbe8b9f06f5073e461590e0013c184889c (commit)
via 6312ca7fc3a9cd0ed0a7b15583161a814162a7c4 (commit)
via 5d3ce61a456ae34baf4d16ed5eab7eb2b22251b3 (commit)
via efc0544bdf19f781e928c4cc6f764439ebc5e414 (commit)
via de8d385ff22a2e60e26667a2138dce54677b59e4 (commit)
via 83d8c2ef2fa7b9e4b9a71f9f7ad54c1fa945696d (commit)
via c1233a28b0dfd3f287897a17542a3874f5401ddb (commit)
via 1adbd6a03cb9827f87be0496d71718fb1c9b6cb2 (commit)
via a04f885fad995fb689bd064cf024ee60a71625de (commit)
via f23495620fafe83545866d6cdac59e3660365253 (commit)
via 28ebeea1c461508fbca3995e34509b2c63a102a2 (commit)
via e6750157521cadc40bbe57db8d1cdda8328effa9 (commit)
via f31569d8589dc7dbbcd46a423e67ba3ba9132e87 (commit)
via 5ace1ac933c66fea6c727d062c402677584c1f5b (commit)
via 20f4fc2b067a6c4969cfded667e6a0d7ad90e1dc (commit)
via e2e93cd2354580d7707c07f8f73e07e0e27f18c0 (commit)
via 60aa5f582f596951f0e14e73164f53edc4f8b8e7 (commit)
via 7c623395e4217f10a57a7c312c2e4066ac7c77bd (commit)
via 90251f717001cf297ea3e203777918b8614b2f0d (commit)
via e07f341d540ce584b3edc424a8cf9a8f1b9a542c (commit)
via c533897b58690ab30d12dd9837de04e08d840ee3 (commit)
via 4ea6a9c51663de36095fa119698ded5f42d70690 (commit)
via 4246e4ed383f3178287eeac97807b52563592f2f (commit)
via 9925af3b3f4daa47ba8c2eb66f556b01ed6f0502 (commit)
via a09e9bd49e0dfa6f4eb9b50da0e1d8b7e76affa2 (commit)
via a713b09fa81eb688de94ca720a304138ed48f96b (commit)
via 3511c6e6512c0004d9332ea85d1d3d4c03a414e0 (commit)
via cfc683caf3aa9c700a7b10ab689d949ffae2d207 (commit)
via 4162a63acb076e595b004e862b4f117a184156b1 (commit)
via e3fec3af0e4ce88e51f52d9dd3388a63e78e0b90 (commit)
via 4317f74e10dbaa6a03a2bba9993fa2c79aa241bb (commit)
via b312e9ab66e413ee0708fc2db23d27e7f4b7b251 (commit)
via b63f18150df8c3449b7b6b52b3228863e6104474 (commit)
via fb3a565eeded13fcb8f746229c6a55aa7f1d000c (commit)
via 27dc1e7a87eb35dfa6a67f44b8ddf2d66d26abab (commit)
via f75fcf0b19faa026baba075f8e76dc5aef4d31da (commit)
via 38293a1f2ca44708f117d870ec1c6e50600fc2c4 (commit)
via ed259e05b0cef2364c20515f60bf464e5396b3d7 (commit)
via d3d28bdcd3444d16e1ccca67e5127fa732ad3b81 (commit)
via 70cc3db9edd5e1b30d8efb8107e57de021fdaac3 (commit)
via 54cbca7f405db2241d18cd93c63624d573eefb4a (commit)
via 5836dc544cdbe3180f8ce34788e2187824263ea8 (commit)
via 21dae0aa029e8d549c8ba4d5bffeb10ef341f17d (commit)
via 83966d1e3ede61d6e71a26d179e2d2d30b4655df (commit)
via 1e0c42db90fa4f02586da442501cf1669a93db04 (commit)
via e589380dcc531fb2c3d8bf87e0e2fed8ba09270f (commit)
via 28e4d9aaa305d597c533f43c8eaaae37567d6896 (commit)
via 6cbb6ed662f7ee7b959865eb741c6c0029d0afd8 (commit)
via 7f486337fadd80b4529e927bed9ed809f8658cdf (commit)
via cdb634f118607f9730c7218cc15773fb35a7309a (commit)
via 44fab7f71b9e197762f3ce95bd79dc70cf4f7a47 (commit)
via 5d221a4027cdd09be11a45cd30bea2ef5b67bbed (commit)
via d5b8d3d8ddea4161a63f0653d1a56dffe2297bdc (commit)
via 0da895c79de752795279c4babfb7578d409c6d72 (commit)
via cd98bdae788fd3871f568b99d9bda61b76879f7f (commit)
via 845ffd35d10646345dc1f534ad725cada568fa5c (commit)
via ce19b150c67834a91d7742f362a175904172a7d7 (commit)
via 01d2ac16e2edab2b81294cd8974938226dd76953 (commit)
via 07738f9d7122d58dd7e42b7fc414f46c3f5ede35 (commit)
via 80b1a680ca8b5ac8eda1b0b6ccb665e243e34182 (commit)
via 77af0818a39b800189928ae950839a43f3d8f2d9 (commit)
via e86811d1938f1f16fc7ecf44d09b08ad9058a756 (commit)
via 1e653cb517d99f96a7eed74a8ca457fe2332a15e (commit)
via 2b9c6a45464d5c064012984665bd3141c4aded27 (commit)
via 77520e9dda0fc9cac5bbd8bdf7225ecc52bd8ed5 (commit)
via 0abfd1c9e17568e36411cfc30e5e084fa84c92df (commit)
via 788685826e01b05cdb12c78bf367192d0139745c (commit)
via 221bb9df74bd6b827ea7dbc13cf07abb3ba939c9 (commit)
via ada6840f0a21dfb6368a22d59e2a24c815eaf997 (commit)
via e363285144493f03d8ba898cf762fc8bccf2f4a9 (commit)
via e1725e2609322cd2c87b0d90bf85b6c5b647dd66 (commit)
via 2133c6ae39aef257ad8c46bbffd0765de83bdce8 (commit)
via f597ce3956c82c48cd5c3623c18bfd01b506047c (commit)
via b6d51351a7935aeddcf3520e33dc5bf5e58a84a7 (commit)
via 544b01dd9fe18dd5ddb914239e5435854042b0d5 (commit)
via 10c4a13810da974778a046128414e392d9395d02 (commit)
via 97d4ccea5736cdc872913ba633154323c17a0e49 (commit)
via 21e0ea894b81e5b6ac02c369b89456b8d8bae9cb (commit)
via 2fb30723fa35fa5a9ffae03b0715650a76a5f20a (commit)
via b58a4e5e12ee86e6ece24e90bc2f1e854cd92527 (commit)
via 86ac11a23c5c1c50b3d8daf1932103476674c72c (commit)
via b377f3a03ff6d2ba7b1fc562ae75707ddff310c7 (commit)
via 6085d54e697681307b4a5f60d169c9e4110c8286 (commit)
via 0cbbce437ec709e5dde8056c0462d7bd501f65c9 (commit)
via e0c71421f186a8cf77f1bf7aba7aff072a9caff7 (commit)
via 532c36304e8b5caf9faf9e98a66967c1073f5353 (commit)
via 27b34684dc5476292dc1f35c430c4509847fd31e (commit)
This update added new revisions after undoing existing revisions. That is
to say, the old revision is not a strict subset of the new revision. This
situation occurs when you --force push a change and generate a repository
containing something like this:
* -- * -- B -- O -- O -- O (c9906b3b0b5944052658fd0623c5bc681a6cccfb)
\
N -- N -- N (46b7d9d9e329ea3ed9a338924e0cec4e3c83ee04)
When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.
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 46b7d9d9e329ea3ed9a338924e0cec4e3c83ee04
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Wed Sep 19 17:46:06 2012 +0900
[2300] document in the man page about statistics socket counters introduced in Xfrin
open
openfail
close
connfail
conn
senderr
recverr
commit 5b63f9693efd47c600139b8522c37ea2f01666a0
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Sep 28 14:35:39 2012 +0900
[2300] check statistics socket counters for Xfrin in lettuce test
commit 499e6f2e3f5bd141459ba46f90359d9dbc72c125
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Thu Aug 29 17:31:07 2013 +0900
[2300] set self.tsig_key_name in XfrinConnection.__init__()
Because this is used for outputting an error message when failing to connect to
master. (This is not a related change.)
commit dc10e21d3ace93577e654885981ace4cae92ee09
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Tue Aug 27 19:39:52 2013 +0900
[2300] add unit tests for statistics socket counters introduced in Xfrin
commit 90b12d797d33c23ec96cc877ecf4c84a42f6214a
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Wed Sep 19 16:59:47 2012 +0900
[2300] override some methods of asyncore.dispatcher in XfrinConnection
This is for implementation of statistics socket counters for Xfrin
commit f7a34944f319ebffaced2e1ea8a7dd1c420a4c99
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Sep 28 15:58:58 2012 +0900
[2300] use _master_addrinfo[0] for address family in _get_ipver_str()
Because _get_ipver_str() is referring to an attribute of NoneType when it is
invoked earlier than init_socket() and when the socket object is None.
commit d9ee65e374924d0f1cde75f4aa9c242218d1ad85
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Wed Sep 19 16:58:55 2012 +0900
[2300] define new statistics counters in Xfrin spec
open
openfail
close
connfail
conn
senderr
recverr
-----------------------------------------------------------------------
Summary of changes:
.gitignore | 4 +
AUTHORS | 1 +
ChangeLog | 650 +++++-
Makefile.am | 16 +-
README | 6 +
configure.ac | 160 +-
doc/Doxyfile | 41 +-
doc/Doxyfile-xml | 7 +
doc/Makefile.am | 4 +-
doc/design/Makefile.am | 1 +
doc/design/cc-protocol.txt | 377 ++--
doc/design/datasrc/.gitignore | 3 +
doc/design/datasrc/Makefile.am | 30 +
doc/design/datasrc/auth-local.txt | 142 ++
doc/design/datasrc/auth-mapped.txt | 99 +
doc/design/datasrc/data-source-classes.txt | 366 ++++
doc/design/datasrc/memmgr-mapped-init.txt | 137 ++
doc/design/datasrc/memmgr-mapped-reload.txt | 94 +
doc/design/datasrc/overview.txt | 68 +
doc/design/ipc-high.txt | 382 ++++
doc/design/resolver/01-scaling-across-cores | 347 ++++
.../resolver/02-mixed-recursive-authority-setup | 150 ++
doc/design/resolver/03-cache-algorithm | 22 +
doc/design/resolver/03-cache-algorithm.txt | 256 +++
doc/design/resolver/README | 5 +
doc/devel/01-dns.dox | 2 +-
doc/devel/02-dhcp.dox | 2 +-
doc/devel/mainpage.dox | 59 +-
doc/guide/.gitignore | 1 +
doc/guide/Makefile.am | 4 +-
doc/guide/bind10-guide.xml | 823 ++++++--
examples/README | 2 +-
examples/host/host.cc | 2 +-
ext/Makefile.am | 8 +
ext/asio/Makefile.am | 6 +
ext/asio/asio/Makefile.am | 310 +++
m4macros/Makefile.am | 2 +
m4macros/ax_boost_for_bind10.m4 | 84 +-
m4macros/ax_sqlite3_for_bind10.m4 | 25 +
src/bin/Makefile.am | 15 +-
src/bin/auth/auth_messages.mes | 66 +-
src/bin/auth/auth_srv.cc | 146 +-
src/bin/auth/auth_srv.h | 17 +-
src/bin/auth/b10-auth.xml.pre | 27 +-
src/bin/auth/datasrc_clients_mgr.h | 305 ++-
src/bin/auth/gen-statisticsitems.py.pre.in | 2 +-
src/bin/auth/main.cc | 9 +-
src/bin/auth/query.cc | 8 +-
src/bin/auth/query.h | 4 +-
src/bin/auth/statistics.cc.pre | 15 +-
src/bin/auth/statistics.h | 18 +
src/bin/auth/statistics_msg_items.def | 1 +
src/bin/auth/tests/auth_srv_unittest.cc | 345 ++--
src/bin/auth/tests/config_unittest.cc | 2 +-
.../auth/tests/datasrc_clients_builder_unittest.cc | 298 ++-
src/bin/auth/tests/datasrc_clients_mgr_unittest.cc | 87 +-
src/bin/auth/tests/query_unittest.cc | 113 +-
src/bin/auth/tests/statistics_unittest.cc.pre | 58 +
src/bin/auth/tests/test_datasrc_clients_mgr.cc | 15 +-
src/bin/auth/tests/test_datasrc_clients_mgr.h | 30 +-
src/bin/bind10/TODO | 2 +-
src/bin/bind10/init.py.in | 104 +-
src/bin/bind10/init_messages.mes | 6 +-
src/bin/bind10/run_bind10.sh.in | 4 +-
src/bin/bind10/tests/Makefile.am | 2 +-
src/bin/bind10/tests/init_test.py.in | 67 +-
src/bin/bindctl/README | 2 +-
src/bin/bindctl/bindcmd.py | 65 +-
src/bin/bindctl/run_bindctl.sh.in | 2 +-
src/bin/bindctl/tests/Makefile.am | 2 +-
src/bin/bindctl/tests/bindctl_test.py | 60 +-
src/bin/cfgmgr/plugins/datasrc.spec.pre.in | 17 +-
src/bin/cfgmgr/plugins/tests/Makefile.am | 5 +-
src/bin/cfgmgr/plugins/tests/datasrc_test.py | 106 +-
src/bin/cfgmgr/plugins/tests/tsig_keys_test.py | 4 +-
src/bin/cfgmgr/tests/Makefile.am | 2 +-
src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in | 2 +-
src/bin/cmdctl/Makefile.am | 19 +-
src/bin/cmdctl/b10-certgen.cc | 6 +-
src/bin/cmdctl/b10-cmdctl.xml | 2 -
src/bin/cmdctl/cmdctl.py.in | 111 +-
src/bin/cmdctl/cmdctl.spec.pre.in | 5 -
src/bin/cmdctl/run_b10-cmdctl.sh.in | 2 +-
src/bin/cmdctl/tests/Makefile.am | 4 +-
src/bin/cmdctl/tests/b10-certgen_test.py | 7 +-
src/bin/cmdctl/tests/cmdctl_test.py | 293 ++-
src/bin/d2/.gitignore | 6 +
src/bin/d2/Makefile.am | 79 +
src/bin/d2/b10-dhcp-ddns.xml | 121 ++
src/bin/d2/d2_cfg_mgr.cc | 214 ++
src/bin/d2/d2_cfg_mgr.h | 237 +++
src/bin/d2/d2_config.cc | 633 ++++++
src/bin/d2/d2_config.h | 952 +++++++++
src/bin/d2/d2_controller.cc | 68 +
src/bin/d2/d2_controller.h | 72 +
src/bin/d2/d2_log.cc | 27 +
src/bin/d2/d2_log.h | 32 +
src/bin/d2/d2_messages.mes | 254 +++
src/bin/d2/d2_process.cc | 394 ++++
src/bin/d2/d2_process.h | 333 +++
src/bin/d2/d2_queue_mgr.cc | 260 +++
src/bin/d2/d2_queue_mgr.h | 355 ++++
src/bin/d2/d2_update_message.cc | 221 ++
src/bin/d2/d2_update_message.h | 341 ++++
src/bin/d2/d2_update_mgr.cc | 230 +++
src/bin/d2/d2_update_mgr.h | 294 +++
src/bin/d2/d2_zone.cc | 36 +
src/bin/d2/d2_zone.h | 117 ++
src/bin/d2/d_cfg_mgr.cc | 240 +++
src/bin/d2/d_cfg_mgr.h | 331 +++
src/bin/d2/d_controller.cc | 423 ++++
src/bin/d2/d_controller.h | 557 +++++
src/bin/d2/d_process.h | 219 ++
src/bin/d2/dhcp-ddns.spec | 212 ++
src/bin/d2/dns_client.cc | 247 +++
src/bin/d2/dns_client.h | 192 ++
src/bin/d2/main.cc | 49 +
src/bin/d2/spec_config.h.pre.in | 15 +
src/bin/d2/tests/.gitignore | 2 +
src/bin/d2/tests/Makefile.am | 98 +
src/bin/d2/tests/d2_cfg_mgr_unittests.cc | 1260 ++++++++++++
src/bin/d2/tests/d2_controller_unittests.cc | 225 ++
src/bin/d2/tests/d2_process_unittests.cc | 605 ++++++
src/bin/d2/tests/d2_queue_mgr_unittests.cc | 440 ++++
src/bin/d2/tests/d2_test.py | 168 ++
src/bin/d2/tests/d2_unittests.cc | 31 +
src/bin/d2/tests/d2_update_message_unittests.cc | 591 ++++++
src/bin/d2/tests/d2_update_mgr_unittests.cc | 443 ++++
src/bin/d2/tests/d2_zone_unittests.cc | 75 +
src/bin/d2/tests/d_cfg_mgr_unittests.cc | 386 ++++
src/bin/d2/tests/d_controller_unittests.cc | 364 ++++
src/bin/d2/tests/d_test_stubs.cc | 319 +++
src/bin/d2/tests/d_test_stubs.h | 667 ++++++
src/bin/d2/tests/dns_client_unittests.cc | 408 ++++
src/bin/d2/tests/test_data_files_config.h.in | 17 +
src/bin/dbutil/b10-dbutil.xml | 2 +-
src/bin/dbutil/dbutil.py.in | 2 +-
src/bin/dbutil/run_dbutil.sh.in | 2 +-
src/bin/dbutil/tests/Makefile.am | 6 +
src/bin/dbutil/tests/dbutil_test.sh.in | 61 +-
src/bin/ddns/ddns.py.in | 8 +-
src/bin/ddns/ddns_messages.mes | 2 +-
src/bin/ddns/tests/Makefile.am | 2 +-
src/bin/ddns/tests/ddns_test.py | 8 +-
src/bin/dhcp4/Makefile.am | 1 +
src/bin/dhcp4/config_parser.cc | 1784 +++-------------
src/bin/dhcp4/config_parser.h | 24 +-
src/bin/dhcp4/ctrl_dhcp4_srv.cc | 59 +-
src/bin/dhcp4/ctrl_dhcp4_srv.h | 7 +-
src/bin/dhcp4/dhcp4.dox | 6 +-
src/bin/dhcp4/dhcp4.spec | 27 +-
src/bin/dhcp4/dhcp4_hooks.dox | 216 ++
src/bin/dhcp4/dhcp4_log.h | 3 +
src/bin/dhcp4/dhcp4_messages.mes | 70 +-
src/bin/dhcp4/dhcp4_srv.cc | 653 ++++--
src/bin/dhcp4/dhcp4_srv.h | 98 +-
src/bin/dhcp4/tests/Makefile.am | 16 +-
src/bin/dhcp4/tests/callout_library_1.cc | 22 +
src/bin/dhcp4/tests/callout_library_2.cc | 22 +
src/bin/dhcp4/tests/callout_library_common.h | 82 +
src/bin/dhcp4/tests/config_parser_unittest.cc | 374 +++-
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc | 92 +-
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc | 2138 +++++++++++++++++---
src/bin/dhcp4/tests/marker_file.cc | 66 +
src/bin/dhcp4/tests/marker_file.h.in | 69 +
src/bin/dhcp4/tests/test_libraries.h.in | 51 +
src/bin/dhcp6/Makefile.am | 2 +
src/bin/dhcp6/config_parser.cc | 1860 +++--------------
src/bin/dhcp6/config_parser.h | 13 +-
src/bin/dhcp6/ctrl_dhcp6_srv.cc | 52 +-
src/bin/dhcp6/ctrl_dhcp6_srv.h | 4 +-
src/bin/dhcp6/dhcp6.dox | 83 +-
src/bin/dhcp6/dhcp6.spec | 32 +-
src/bin/dhcp6/dhcp6_hooks.dox | 239 +++
src/bin/dhcp6/dhcp6_log.h | 3 +
src/bin/dhcp6/dhcp6_messages.mes | 120 +-
src/bin/dhcp6/dhcp6_srv.cc | 1002 +++++++--
src/bin/dhcp6/dhcp6_srv.h | 180 +-
src/bin/dhcp6/tests/Makefile.am | 25 +-
src/bin/dhcp6/tests/callout_library_1.cc | 22 +
src/bin/dhcp6/tests/callout_library_2.cc | 22 +
src/bin/dhcp6/tests/callout_library_common.h | 82 +
src/bin/dhcp6/tests/config_parser_unittest.cc | 491 ++++-
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc | 98 +-
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 1015 ++++++++--
src/bin/dhcp6/tests/dhcp6_test_utils.h | 407 ++++
src/bin/dhcp6/tests/hooks_unittest.cc | 1453 +++++++++++++
src/bin/dhcp6/tests/marker_file.cc | 66 +
src/bin/dhcp6/tests/marker_file.h.in | 69 +
src/bin/dhcp6/tests/test_libraries.h.in | 51 +
src/bin/loadzone/.gitignore | 1 -
src/bin/loadzone/b10-loadzone.xml | 33 +
src/bin/loadzone/loadzone.py.in | 64 +-
src/bin/loadzone/loadzone_messages.mes | 5 +
src/bin/loadzone/run_loadzone.sh.in | 2 +-
src/bin/loadzone/tests/Makefile.am | 2 +-
src/bin/loadzone/tests/correct/Makefile.am | 2 +-
src/bin/loadzone/tests/correct/correct_test.sh.in | 2 +-
src/bin/loadzone/tests/correct/ttlext.db | 2 +-
src/bin/loadzone/tests/loadzone_test.py | 39 +-
src/bin/memmgr/.gitignore | 5 +
src/bin/memmgr/Makefile.am | 62 +
src/bin/memmgr/b10-memmgr.xml | 109 +
src/bin/memmgr/memmgr.py.in | 214 ++
src/bin/memmgr/memmgr.spec.pre.in | 25 +
src/bin/memmgr/memmgr_messages.mes | 51 +
src/bin/memmgr/tests/Makefile.am | 30 +
src/bin/memmgr/tests/memmgr_test.py | 235 +++
src/bin/msgq/Makefile.am | 1 +
src/bin/msgq/msgq.py.in | 308 ++-
src/bin/msgq/msgq.spec | 14 +-
src/bin/msgq/msgq_messages.mes | 16 +-
src/bin/msgq/run_msgq.sh.in | 2 +-
src/bin/msgq/tests/Makefile.am | 8 +-
src/bin/msgq/tests/msgq_run_test.py | 334 +++
src/bin/msgq/tests/msgq_test.py | 261 ++-
src/bin/resolver/Makefile.am | 3 +-
src/bin/resolver/bench/.gitignore | 1 +
src/bin/resolver/bench/Makefile.am | 24 +
src/bin/resolver/bench/dummy_work.cc | 28 +
src/bin/resolver/bench/dummy_work.h | 36 +
src/bin/resolver/bench/fake_resolution.cc | 172 ++
src/bin/resolver/bench/fake_resolution.h | 228 +++
src/bin/resolver/bench/main.cc | 27 +
src/bin/resolver/bench/naive_resolver.cc | 66 +
src/bin/resolver/bench/naive_resolver.h | 44 +
src/bin/resolver/main.cc | 3 +-
src/bin/resolver/resolver.cc | 21 +-
src/bin/resolver/resolver.h | 4 -
src/bin/resolver/response_scrubber.h | 4 +-
src/bin/sockcreator/README | 2 +-
src/bin/sockcreator/sockcreator.h | 2 +-
src/bin/sockcreator/tests/sockcreator_tests.cc | 2 +-
src/bin/stats/stats.py.in | 86 +-
src/bin/stats/stats_httpd.py.in | 11 +-
src/bin/stats/stats_httpd_messages.mes | 4 +-
src/bin/stats/stats_messages.mes | 11 +
src/bin/stats/tests/Makefile.am | 4 +-
src/bin/stats/tests/b10-stats-httpd_test.py | 1082 ----------
src/bin/stats/tests/b10-stats_test.py | 1379 -------------
src/bin/stats/tests/stats-httpd_test.py | 1128 +++++++++++
src/bin/stats/tests/stats_test.py | 1668 +++++++++++++++
src/bin/stats/tests/test_utils.py | 384 ++--
src/bin/tests/Makefile.am | 2 +-
src/bin/tests/process_rename_test.py.in | 5 +
src/bin/usermgr/Makefile.am | 9 +-
src/bin/usermgr/b10-cmdctl-usermgr.py.in | 299 ++-
src/bin/usermgr/b10-cmdctl-usermgr.xml | 43 +-
src/bin/usermgr/run_b10-cmdctl-usermgr.sh.in | 3 +
src/bin/usermgr/tests/Makefile.am | 22 +
src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py | 511 +++++
src/bin/xfrin/b10-xfrin.xml | 194 +-
src/bin/xfrin/tests/Makefile.am | 2 +-
src/bin/xfrin/tests/xfrin_test.py | 1017 +++++-----
src/bin/xfrin/xfrin.py.in | 774 +++----
src/bin/xfrin/xfrin.spec | 284 +--
src/bin/xfrin/xfrin_messages.mes | 85 +-
src/bin/xfrout/b10-xfrout.xml | 70 +-
src/bin/xfrout/tests/Makefile.am | 2 +-
src/bin/xfrout/tests/xfrout_test.py.in | 108 +-
src/bin/xfrout/xfrout.py.in | 66 +-
src/bin/xfrout/xfrout.spec.pre.in | 98 +-
src/bin/xfrout/xfrout_messages.mes | 2 +-
src/bin/zonemgr/tests/Makefile.am | 2 +-
src/bin/zonemgr/tests/zonemgr_test.py | 208 +-
src/bin/zonemgr/zonemgr.py.in | 310 ++-
src/bin/zonemgr/zonemgr_messages.mes | 29 +-
src/lib/Makefile.am | 11 +-
src/lib/acl/loader.h | 4 +-
src/lib/asiodns/README | 5 +-
src/lib/asiodns/asiodns_messages.mes | 95 +
src/lib/asiodns/dns_answer.h | 2 +-
src/lib/asiodns/dns_server.h | 2 +-
src/lib/asiodns/dns_service.cc | 34 +-
src/lib/asiodns/dns_service.h | 6 +-
src/lib/asiodns/io_fetch.cc | 3 +-
src/lib/asiodns/io_fetch.h | 2 +-
src/lib/asiodns/sync_udp_server.cc | 115 +-
src/lib/asiodns/sync_udp_server.h | 80 +-
src/lib/asiodns/tcp_server.cc | 184 +-
src/lib/asiodns/tcp_server.h | 3 -
src/lib/asiodns/tests/dns_server_unittest.cc | 226 ++-
src/lib/asiodns/tests/dns_service_unittest.cc | 2 +-
src/lib/asiodns/udp_server.cc | 64 +-
src/lib/asiodns/udp_server.h | 2 -
src/lib/asiolink/Makefile.am | 7 +-
src/lib/asiolink/io_asio_socket.h | 2 +-
src/lib/asiolink/io_service.cc | 28 +-
src/lib/asiolink/io_service.h | 15 +-
src/lib/asiolink/local_socket.cc | 102 +
src/lib/asiolink/local_socket.h | 132 ++
src/lib/asiolink/tcp_endpoint.h | 2 +-
src/lib/asiolink/tcp_socket.h | 1 -
src/lib/asiolink/tests/Makefile.am | 2 +
src/lib/asiolink/tests/io_service_unittest.cc | 48 +
src/lib/asiolink/tests/local_socket_unittest.cc | 253 +++
src/lib/asiolink/tests/tcp_socket_unittest.cc | 2 +-
src/lib/asiolink/tests/udp_socket_unittest.cc | 2 +-
src/lib/asiolink/udp_endpoint.h | 2 +-
src/lib/asiolink/udp_socket.h | 1 -
src/lib/bench/benchmark.h | 4 +-
.../tests/testdata/message_nxdomain_large_ttl.wire | 2 +-
src/lib/cc/cc_messages.mes | 2 +-
src/lib/cc/data.cc | 45 +-
src/lib/cc/data.h | 43 +-
src/lib/cc/proto_defs.cc | 15 +-
src/lib/cc/session.cc | 52 +-
src/lib/cc/tests/data_unittests.cc | 66 +-
src/lib/cc/tests/session_unittests.cc | 4 +-
src/lib/config/ccsession.cc | 89 +-
src/lib/config/ccsession.h | 182 +-
src/lib/config/config_data.h | 2 +-
src/lib/config/config_messages.mes | 4 +
src/lib/config/documentation.txt | 2 +-
src/lib/config/tests/ccsession_unittests.cc | 131 +-
src/lib/config/tests/fake_session.cc | 13 +-
src/lib/config/tests/fake_session.h | 3 +-
src/lib/cryptolink/cryptolink.h | 2 +-
src/lib/datasrc/.gitignore | 2 +
src/lib/datasrc/Makefile.am | 22 +-
src/lib/datasrc/cache_config.cc | 198 ++
src/lib/datasrc/cache_config.h | 219 ++
src/lib/datasrc/client.h | 30 +-
src/lib/datasrc/client_list.cc | 437 ++--
src/lib/datasrc/client_list.h | 272 ++-
src/lib/datasrc/data_source.h | 68 -
src/lib/datasrc/database.cc | 26 +-
src/lib/datasrc/database.h | 53 +-
src/lib/datasrc/datasrc_messages.mes | 200 +-
src/lib/datasrc/exceptions.h | 47 +
src/lib/datasrc/factory.cc | 6 +-
src/lib/datasrc/factory.h | 17 +-
src/lib/datasrc/master_loader_callbacks.h | 2 +-
src/lib/datasrc/memory/Makefile.am | 9 +-
src/lib/datasrc/memory/domaintree.h | 275 ++-
src/lib/datasrc/memory/memory_client.cc | 133 +-
src/lib/datasrc/memory/memory_client.h | 79 +-
src/lib/datasrc/memory/memory_messages.mes | 158 +-
src/lib/datasrc/memory/rdata_serialization.h | 4 +-
src/lib/datasrc/memory/rdataset.cc | 10 +-
src/lib/datasrc/memory/rdataset.h | 18 +-
src/lib/datasrc/memory/segment_object_holder.cc | 41 +
src/lib/datasrc/memory/segment_object_holder.h | 82 +-
src/lib/datasrc/memory/zone_data.cc | 19 +-
src/lib/datasrc/memory/zone_data.h | 63 +-
src/lib/datasrc/memory/zone_data_loader.cc | 118 +-
src/lib/datasrc/memory/zone_data_loader.h | 2 +-
src/lib/datasrc/memory/zone_data_updater.cc | 118 +-
src/lib/datasrc/memory/zone_data_updater.h | 43 +-
src/lib/datasrc/memory/zone_finder.cc | 4 +-
src/lib/datasrc/memory/zone_table.cc | 87 +-
src/lib/datasrc/memory/zone_table.h | 116 +-
src/lib/datasrc/memory/zone_table_segment.cc | 28 +-
src/lib/datasrc/memory/zone_table_segment.h | 295 ++-
src/lib/datasrc/memory/zone_table_segment_local.cc | 32 +-
src/lib/datasrc/memory/zone_table_segment_local.h | 65 +-
.../datasrc/memory/zone_table_segment_mapped.cc | 392 ++++
src/lib/datasrc/memory/zone_table_segment_mapped.h | 144 ++
src/lib/datasrc/memory/zone_writer.cc | 175 ++
src/lib/datasrc/memory/zone_writer.h | 91 +-
src/lib/datasrc/memory/zone_writer_local.cc | 93 -
src/lib/datasrc/memory/zone_writer_local.h | 95 -
src/lib/datasrc/result.h | 25 +-
src/lib/datasrc/sqlite3_accessor.cc | 25 +-
src/lib/datasrc/sqlite3_accessor.h | 6 +-
src/lib/datasrc/sqlite3_accessor_link.cc | 5 +
src/lib/datasrc/sqlite3_datasrc_messages.mes | 142 ++
src/lib/datasrc/static.zone.pre | 12 +-
src/lib/datasrc/static_datasrc.h | 50 -
src/lib/datasrc/static_datasrc_link.cc | 68 -
src/lib/datasrc/tests/Makefile.am | 7 +-
src/lib/datasrc/tests/cache_config_unittest.cc | 312 +++
src/lib/datasrc/tests/client_list_unittest.cc | 993 ++++++---
src/lib/datasrc/tests/database_unittest.cc | 21 +-
src/lib/datasrc/tests/factory_unittest.cc | 56 +-
src/lib/datasrc/tests/memory/Makefile.am | 19 +-
.../datasrc/tests/memory/domaintree_unittest.cc | 114 +-
.../datasrc/tests/memory/memory_client_unittest.cc | 376 ++--
src/lib/datasrc/tests/memory/memory_segment_mock.h | 62 +
src/lib/datasrc/tests/memory/memory_segment_test.h | 62 -
.../tests/memory/rdata_serialization_unittest.cc | 2 +-
src/lib/datasrc/tests/memory/rdataset_unittest.cc | 74 +-
.../tests/memory/rrset_collection_unittest.cc | 24 +-
.../tests/memory/segment_object_holder_unittest.cc | 78 +-
src/lib/datasrc/tests/memory/testdata/Makefile.am | 1 +
.../testdata/example.org-duplicate-type-bad.zone | 5 +-
.../datasrc/tests/memory/testdata/template.zone | 4 +
.../tests/memory/zone_data_loader_unittest.cc | 48 +-
src/lib/datasrc/tests/memory/zone_data_unittest.cc | 17 +-
.../tests/memory/zone_data_updater_unittest.cc | 217 +-
.../datasrc/tests/memory/zone_finder_unittest.cc | 85 +-
src/lib/datasrc/tests/memory/zone_loader_util.cc | 89 +
src/lib/datasrc/tests/memory/zone_loader_util.h | 62 +
.../memory/zone_table_segment_mapped_unittest.cc | 566 ++++++
.../datasrc/tests/memory/zone_table_segment_mock.h | 94 +
.../datasrc/tests/memory/zone_table_segment_test.h | 116 --
.../tests/memory/zone_table_segment_unittest.cc | 48 +-
.../datasrc/tests/memory/zone_table_unittest.cc | 132 +-
.../datasrc/tests/memory/zone_writer_unittest.cc | 229 ++-
src/lib/datasrc/tests/mock_client.cc | 197 ++
src/lib/datasrc/tests/mock_client.h | 83 +
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc | 16 +-
src/lib/datasrc/tests/testdata/rrset_toWire2 | 2 +-
.../datasrc/tests/zone_finder_context_unittest.cc | 26 +-
src/lib/datasrc/tests/zone_loader_unittest.cc | 65 +-
.../datasrc/tests/zone_table_accessor_unittest.cc | 112 +
src/lib/datasrc/zone_finder.cc | 3 +-
src/lib/datasrc/zone_iterator.h | 2 +-
src/lib/datasrc/zone_loader.cc | 2 +-
src/lib/datasrc/zone_loader.h | 2 +-
src/lib/datasrc/zone_table_accessor.h | 215 ++
src/lib/datasrc/zone_table_accessor_cache.cc | 60 +
src/lib/datasrc/zone_table_accessor_cache.h | 76 +
src/lib/dhcp/Makefile.am | 41 +
src/lib/dhcp/dhcp6.h | 4 +-
src/lib/dhcp/duid.cc | 21 +-
src/lib/dhcp/duid.h | 16 +-
src/lib/dhcp/hwaddr.cc | 11 +-
src/lib/dhcp/hwaddr.h | 8 +-
src/lib/dhcp/iface_mgr.cc | 381 ++--
src/lib/dhcp/iface_mgr.h | 600 +++---
src/lib/dhcp/iface_mgr_bsd.cc | 11 +-
src/lib/dhcp/iface_mgr_linux.cc | 73 +-
src/lib/dhcp/iface_mgr_sun.cc | 10 +-
src/lib/dhcp/libdhcp++.cc | 19 +-
src/lib/dhcp/libdhcp++.dox | 47 +
src/lib/dhcp/libdhcp++.h | 23 +-
src/lib/dhcp/option.cc | 13 +-
src/lib/dhcp/option.h | 15 +-
src/lib/dhcp/option4_client_fqdn.cc | 525 +++++
src/lib/dhcp/option4_client_fqdn.h | 370 ++++
src/lib/dhcp/option6_client_fqdn.cc | 455 +++++
src/lib/dhcp/option6_client_fqdn.h | 271 +++
src/lib/dhcp/option6_ia.h | 9 +-
src/lib/dhcp/option6_iaaddr.h | 6 +
src/lib/dhcp/option_custom.cc | 49 +-
src/lib/dhcp/option_custom.h | 14 +-
src/lib/dhcp/option_data_types.cc | 7 +-
src/lib/dhcp/option_data_types.h | 7 +-
src/lib/dhcp/option_definition.cc | 147 +-
src/lib/dhcp/option_definition.h | 60 +-
src/lib/dhcp/option_int.h | 2 +-
src/lib/dhcp/option_int_array.h | 2 +-
src/lib/dhcp/option_string.cc | 86 +
src/lib/dhcp/option_string.h | 114 ++
src/lib/dhcp/pkt4.cc | 155 +-
src/lib/dhcp/pkt4.h | 172 +-
src/lib/dhcp/pkt6.cc | 339 +++-
src/lib/dhcp/pkt6.h | 196 +-
src/lib/dhcp/pkt_filter.h | 111 +
src/lib/dhcp/pkt_filter_inet.cc | 252 +++
src/lib/dhcp/pkt_filter_inet.h | 90 +
src/lib/dhcp/pkt_filter_lpf.cc | 268 +++
src/lib/dhcp/pkt_filter_lpf.h | 85 +
src/lib/dhcp/protocol_util.cc | 243 +++
src/lib/dhcp/protocol_util.h | 153 ++
src/lib/dhcp/std_option_defs.h | 5 +-
src/lib/dhcp/tests/Makefile.am | 10 +
src/lib/dhcp/tests/duid_unittest.cc | 81 +-
src/lib/dhcp/tests/hwaddr_unittest.cc | 11 +-
src/lib/dhcp/tests/iface_mgr_unittest.cc | 591 ++++--
src/lib/dhcp/tests/libdhcp++_unittest.cc | 96 +-
src/lib/dhcp/tests/option4_addrlst_unittest.cc | 90 +-
src/lib/dhcp/tests/option4_client_fqdn_unittest.cc | 959 +++++++++
src/lib/dhcp/tests/option6_addrlst_unittest.cc | 65 +-
src/lib/dhcp/tests/option6_client_fqdn_unittest.cc | 809 ++++++++
src/lib/dhcp/tests/option6_ia_unittest.cc | 61 +-
src/lib/dhcp/tests/option6_iaaddr_unittest.cc | 23 +-
src/lib/dhcp/tests/option_custom_unittest.cc | 28 +-
src/lib/dhcp/tests/option_definition_unittest.cc | 29 +-
src/lib/dhcp/tests/option_int_unittest.cc | 12 +-
src/lib/dhcp/tests/option_string_unittest.cc | 160 ++
src/lib/dhcp/tests/option_unittest.cc | 264 +--
src/lib/dhcp/tests/pkt4_unittest.cc | 232 ++-
src/lib/dhcp/tests/pkt6_unittest.cc | 439 +++-
src/lib/dhcp/tests/pkt_filter_inet_unittest.cc | 283 +++
src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc | 307 +++
src/lib/dhcp/tests/protocol_util_unittest.cc | 392 ++++
src/lib/dhcp_ddns/.gitignore | 2 +
src/lib/dhcp_ddns/Makefile.am | 56 +
src/lib/dhcp_ddns/dhcp_ddns_log.cc | 27 +
src/lib/dhcp_ddns/dhcp_ddns_log.h | 31 +
src/lib/dhcp_ddns/dhcp_ddns_messages.mes | 76 +
src/lib/dhcp_ddns/libdhcp_ddns.dox | 47 +
src/lib/dhcp_ddns/ncr_io.cc | 292 +++
src/lib/dhcp_ddns/ncr_io.h | 671 ++++++
src/lib/dhcp_ddns/ncr_msg.cc | 540 +++++
src/lib/dhcp_ddns/ncr_msg.h | 525 +++++
src/lib/dhcp_ddns/ncr_udp.cc | 328 +++
src/lib/dhcp_ddns/ncr_udp.h | 562 +++++
src/lib/dhcp_ddns/tests/.gitignore | 1 +
src/lib/dhcp_ddns/tests/Makefile.am | 58 +
src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc | 518 +++++
src/lib/dhcp_ddns/tests/ncr_unittests.cc | 516 +++++
src/lib/{dhcp => dhcp_ddns}/tests/run_unittests.cc | 0
src/lib/dhcpsrv/Makefile.am | 17 +-
src/lib/dhcpsrv/alloc_engine.cc | 354 +++-
src/lib/dhcpsrv/alloc_engine.h | 175 +-
src/lib/dhcpsrv/callout_handle_store.h | 91 +
src/lib/dhcpsrv/cfgmgr.cc | 85 +-
src/lib/dhcpsrv/cfgmgr.h | 97 +-
src/lib/dhcpsrv/database_backends.dox | 45 +-
src/lib/dhcpsrv/dbaccess_parser.h | 4 +-
src/lib/dhcpsrv/dhcp_config_parser.h | 42 +-
src/lib/dhcpsrv/dhcp_parsers.cc | 1107 ++++++++++
src/lib/dhcpsrv/dhcp_parsers.h | 879 ++++++++
src/lib/dhcpsrv/dhcpsrv_log.h | 3 +
src/lib/dhcpsrv/dhcpsrv_messages.mes | 43 +-
src/lib/dhcpsrv/key_from_key.h | 82 +
src/lib/dhcpsrv/lease_mgr.cc | 46 +
src/lib/dhcpsrv/lease_mgr.h | 24 +-
src/lib/dhcpsrv/memfile_lease_mgr.cc | 93 +-
src/lib/dhcpsrv/memfile_lease_mgr.h | 148 +-
src/lib/dhcpsrv/mysql_lease_mgr.cc | 136 +-
src/lib/dhcpsrv/mysql_lease_mgr.h | 57 +-
src/lib/dhcpsrv/pool.cc | 11 +-
src/lib/dhcpsrv/pool.h | 5 +-
src/lib/dhcpsrv/subnet.cc | 65 +-
src/lib/dhcpsrv/subnet.h | 95 +-
src/lib/dhcpsrv/tests/Makefile.am | 21 +-
src/lib/dhcpsrv/tests/alloc_engine_unittest.cc | 778 ++++++-
.../dhcpsrv/tests/callout_handle_store_unittest.cc | 122 ++
src/lib/dhcpsrv/tests/callout_library.cc | 31 +
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc | 267 +++
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc | 1000 +++++++++
.../dhcpsrv/tests/lease_mgr_factory_unittest.cc | 2 +-
src/lib/dhcpsrv/tests/lease_mgr_unittest.cc | 104 +-
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc | 191 +-
src/lib/dhcpsrv/tests/run_unittests.cc | 1 +
src/lib/dhcpsrv/tests/schema_copy.h | 8 +
src/lib/dhcpsrv/tests/subnet_unittest.cc | 18 +-
src/lib/dhcpsrv/tests/test_get_callout_handle.cc | 31 +
src/lib/dhcpsrv/tests/test_get_callout_handle.h | 46 +
src/lib/dhcpsrv/tests/test_libraries.h.in | 51 +
src/lib/dhcpsrv/tests/test_utils.cc | 24 +-
src/lib/dns/gen-rdatacode.py.in | 48 +-
src/lib/dns/master_lexer.h | 4 +-
src/lib/dns/master_loader.cc | 2 +-
src/lib/dns/message.h | 8 +-
src/lib/dns/messagerenderer.cc | 2 +-
src/lib/dns/nsec3hash.cc | 2 +-
src/lib/dns/python/pydnspp_common.cc | 3 +-
src/lib/dns/python/pydnspp_common.h | 2 +-
src/lib/dns/python/rrset_python.cc | 10 +-
src/lib/dns/python/tests/Makefile.am | 2 +-
src/lib/dns/python/tests/message_python_test.py | 2 +-
src/lib/dns/python/tests/nsec3hash_python_test.py | 2 +-
.../python/tests/rrset_collection_python_test.py | 2 +-
src/lib/dns/python/tests/rrset_python_test.py | 7 +-
src/lib/dns/rdata.cc | 3 +
src/lib/dns/rdata/any_255/tsig_250.cc | 255 ++-
src/lib/dns/rdata/any_255/tsig_250.h | 14 +-
src/lib/dns/rdata/ch_3/a_1.cc | 8 +-
src/lib/dns/rdata/generic/afsdb_18.cc | 67 +-
src/lib/dns/rdata/generic/afsdb_18.h | 4 +-
src/lib/dns/rdata/generic/detail/ds_like.h | 12 +-
.../dns/rdata/generic/detail/nsec3param_common.cc | 36 +-
.../dns/rdata/generic/detail/nsec3param_common.h | 25 +-
src/lib/dns/rdata/generic/detail/nsec_bitmap.cc | 66 +-
src/lib/dns/rdata/generic/detail/nsec_bitmap.h | 32 +-
src/lib/dns/rdata/generic/dlv_32769.cc | 2 +-
src/lib/dns/rdata/generic/dnskey_48.cc | 189 +-
src/lib/dns/rdata/generic/dnskey_48.h | 9 +
src/lib/dns/rdata/generic/ds_43.cc | 2 +-
src/lib/dns/rdata/generic/minfo_14.cc | 73 +-
src/lib/dns/rdata/generic/minfo_14.h | 6 +-
src/lib/dns/rdata/generic/mx_15.cc | 17 +-
src/lib/dns/rdata/generic/mx_15.h | 3 +
src/lib/dns/rdata/generic/nsec3_50.cc | 106 +-
src/lib/dns/rdata/generic/nsec3_50.h | 3 +
src/lib/dns/rdata/generic/nsec3param_51.cc | 83 +-
src/lib/dns/rdata/generic/nsec3param_51.h | 4 +
src/lib/dns/rdata/generic/nsec_47.cc | 8 +-
src/lib/dns/rdata/generic/opt_41.cc | 19 +-
src/lib/dns/rdata/generic/rp_17.cc | 86 +-
src/lib/dns/rdata/generic/rp_17.h | 12 +-
src/lib/dns/rdata/generic/rrsig_46.cc | 144 +-
src/lib/dns/rdata/generic/rrsig_46.h | 12 +-
src/lib/dns/rdata/generic/spf_99.cc | 2 +-
src/lib/dns/rdata/generic/sshfp_44.cc | 239 ++-
src/lib/dns/rdata/generic/sshfp_44.h | 17 +-
src/lib/dns/rdata/generic/txt_16.cc | 2 +-
src/lib/dns/rdata/hs_4/a_1.cc | 8 +-
src/lib/dns/rdata/in_1/dhcid_49.cc | 90 +-
src/lib/dns/rdata/in_1/dhcid_49.h | 5 +-
src/lib/dns/rdata/in_1/srv_33.cc | 2 +-
src/lib/dns/rrparamregistry-placeholder.cc | 44 +-
src/lib/dns/rrparamregistry.h | 17 +-
src/lib/dns/rrttl.cc | 6 +-
src/lib/dns/serial.h | 5 +-
src/lib/dns/tests/labelsequence_unittest.cc | 2 +-
src/lib/dns/tests/master_loader_unittest.cc | 16 +-
src/lib/dns/tests/masterload_unittest.cc | 24 +-
src/lib/dns/tests/message_unittest.cc | 4 +-
src/lib/dns/tests/rdata_afsdb_unittest.cc | 37 +-
src/lib/dns/tests/rdata_dhcid_unittest.cc | 93 +-
src/lib/dns/tests/rdata_dnskey_unittest.cc | 166 +-
src/lib/dns/tests/rdata_ds_like_unittest.cc | 53 +-
src/lib/dns/tests/rdata_minfo_unittest.cc | 113 +-
src/lib/dns/tests/rdata_nsec3_unittest.cc | 140 +-
src/lib/dns/tests/rdata_nsec3param_unittest.cc | 129 +-
src/lib/dns/tests/rdata_opt_unittest.cc | 2 +-
src/lib/dns/tests/rdata_rp_unittest.cc | 72 +-
src/lib/dns/tests/rdata_rrsig_unittest.cc | 233 ++-
src/lib/dns/tests/rdata_sshfp_unittest.cc | 97 +-
src/lib/dns/tests/rdata_tsig_unittest.cc | 198 +-
src/lib/dns/tests/rrcollator_unittest.cc | 6 +-
src/lib/dns/tests/rrparamregistry_unittest.cc | 7 +-
src/lib/dns/tests/rrset_unittest.cc | 4 +-
src/lib/dns/tests/testdata/.gitignore | 2 +
src/lib/dns/tests/testdata/Makefile.am | 4 +-
src/lib/dns/tests/testdata/example.org | 2 +-
.../rdata_dnskey_empty_keydata_fromWire.spec | 7 +
src/lib/dns/tests/testdata/rdata_dnskey_fromWire | 24 -
.../dns/tests/testdata/rdata_dnskey_fromWire.spec | 7 +
src/lib/dns/tests/testdata/rrset_toWire2 | 2 +-
src/lib/dns/tsigkey.cc | 2 +-
src/lib/dns/tsigrecord.cc | 7 +-
src/lib/hooks/.gitignore | 2 +
src/lib/hooks/Makefile.am | 64 +
src/lib/hooks/callout_handle.cc | 162 ++
src/lib/hooks/callout_handle.h | 383 ++++
src/lib/hooks/callout_manager.cc | 260 +++
src/lib/hooks/callout_manager.h | 377 ++++
src/lib/hooks/hooks.h | 38 +
src/lib/hooks/hooks_component_developer.dox | 483 +++++
src/lib/hooks/hooks_log.cc | 26 +
src/lib/hooks/hooks_log.h | 50 +
src/lib/hooks/hooks_maintenance.dox | 274 +++
src/lib/hooks/hooks_manager.cc | 200 ++
src/lib/hooks/hooks_manager.h | 316 +++
src/lib/hooks/hooks_messages.mes | 162 ++
src/lib/hooks/hooks_user.dox | 1031 ++++++++++
src/lib/hooks/images/DataScopeArgument.dia | Bin 0 -> 1887 bytes
src/lib/hooks/images/DataScopeArgument.png | Bin 0 -> 11672 bytes
src/lib/hooks/images/DataScopeContext.dia | Bin 0 -> 2161 bytes
src/lib/hooks/images/DataScopeContext.png | Bin 0 -> 14180 bytes
src/lib/hooks/images/HooksUml.dia | Bin 0 -> 2354 bytes
src/lib/hooks/images/HooksUml.png | Bin 0 -> 13841 bytes
src/lib/hooks/library_handle.cc | 76 +
src/lib/hooks/library_handle.h | 149 ++
src/lib/hooks/library_manager.cc | 353 ++++
src/lib/hooks/library_manager.h | 231 +++
src/lib/hooks/library_manager_collection.cc | 129 ++
src/lib/hooks/library_manager_collection.h | 170 ++
src/lib/hooks/pointer_converter.h | 121 ++
src/lib/hooks/server_hooks.cc | 159 ++
src/lib/hooks/server_hooks.h | 183 ++
src/lib/hooks/tests/.gitignore | 4 +
src/lib/hooks/tests/Makefile.am | 104 +
src/lib/hooks/tests/basic_callout_library.cc | 115 ++
src/lib/hooks/tests/callout_handle_unittest.cc | 329 +++
src/lib/hooks/tests/callout_manager_unittest.cc | 882 ++++++++
src/lib/hooks/tests/common_test_class.h | 142 ++
src/lib/hooks/tests/framework_exception_library.cc | 47 +
src/lib/hooks/tests/full_callout_library.cc | 137 ++
src/lib/hooks/tests/handles_unittest.cc | 974 +++++++++
src/lib/hooks/tests/hooks_manager_unittest.cc | 524 +++++
src/lib/hooks/tests/incorrect_version_library.cc | 33 +
.../tests/library_manager_collection_unittest.cc | 251 +++
src/lib/hooks/tests/library_manager_unittest.cc | 569 ++++++
src/lib/hooks/tests/load_callout_library.cc | 119 ++
src/lib/hooks/tests/load_error_callout_library.cc | 49 +
src/lib/hooks/tests/marker_file.h.in | 27 +
src/lib/hooks/tests/no_version_library.cc | 30 +
src/lib/hooks/tests/run_unittests.cc | 25 +
src/lib/hooks/tests/server_hooks_unittest.cc | 178 ++
src/lib/hooks/tests/test_libraries.h.in | 79 +
src/lib/hooks/tests/unload_callout_library.cc | 52 +
src/lib/log/Makefile.am | 18 +-
src/lib/log/dummylog.cc | 37 -
src/lib/log/dummylog.h | 61 -
src/lib/log/interprocess/Makefile.am | 21 +
src/lib/log/interprocess/README | 13 +
src/lib/log/interprocess/interprocess_sync.h | 151 ++
src/lib/log/interprocess/interprocess_sync_file.cc | 134 ++
src/lib/log/interprocess/interprocess_sync_file.h | 93 +
src/lib/log/interprocess/interprocess_sync_null.cc | 44 +
src/lib/log/interprocess/interprocess_sync_null.h | 66 +
.../log/interprocess}/tests/.gitignore | 0
src/lib/log/interprocess/tests/Makefile.am | 37 +
.../tests/interprocess_sync_file_unittest.cc | 151 ++
.../tests/interprocess_sync_null_unittest.cc | 75 +
src/lib/log/interprocess/tests/run_unittests.cc | 25 +
src/lib/log/log_dbglevels.h | 2 +-
src/lib/log/logger.cc | 2 +-
src/lib/log/logger.h | 14 +-
src/lib/log/logger_impl.cc | 16 +-
src/lib/log/logger_impl.h | 10 +-
src/lib/log/logger_manager.cc | 27 +-
src/lib/log/logger_manager.h | 13 +-
src/lib/log/logger_manager_impl.cc | 23 +-
src/lib/log/logger_manager_impl.h | 2 +-
src/lib/log/logger_unittest_support.cc | 2 +-
src/lib/log/message_exception.h | 4 +-
src/lib/log/message_initializer.cc | 25 +-
src/lib/log/message_initializer.h | 16 +-
src/lib/log/message_reader.cc | 2 +-
src/lib/log/tests/Makefile.am | 21 +-
src/lib/log/tests/buffer_logger_test.cc | 5 +-
src/lib/log/tests/logger_example.cc | 12 +-
src/lib/log/tests/logger_lock_test.cc | 17 +-
src/lib/log/tests/logger_lock_test.sh.in | 1 +
src/lib/log/tests/logger_manager_unittest.cc | 6 +-
src/lib/log/tests/logger_unittest.cc | 5 +-
src/lib/log/tests/message_dictionary_unittest.cc | 5 +-
.../log/tests/message_initializer_1_unittest.cc | 34 +
src/lib/nsas/TODO | 2 +-
src/lib/nsas/address_request_callback.h | 2 +-
src/lib/nsas/nameserver_entry.cc | 4 +-
src/lib/nsas/nameserver_entry.h | 6 +-
src/lib/nsas/tests/hash_unittest.cc | 2 +-
src/lib/nsas/tests/nameserver_entry_unittest.cc | 6 +-
src/lib/nsas/tests/zone_entry_unittest.cc | 6 +-
src/lib/nsas/zone_entry.cc | 8 +-
src/lib/nsas/zone_entry.h | 4 +-
src/lib/python/bind10_config.py.in | 7 +-
src/lib/python/isc/Makefile.am | 4 +
src/lib/python/isc/acl/tests/Makefile.am | 2 +-
src/lib/python/isc/bind10/component.py | 2 +-
src/lib/python/isc/bind10/sockcreator.py | 4 +-
src/lib/python/isc/bind10/tests/Makefile.am | 2 +-
src/lib/python/isc/bind10/tests/component_test.py | 2 +-
.../python/isc/bind10/tests/sockcreator_test.py | 2 +-
.../python/isc/bind10/tests/socket_cache_test.py | 6 +-
src/lib/python/isc/cc/Makefile.am | 1 +
src/lib/python/isc/cc/cc_generated/Makefile.am | 1 +
src/lib/python/isc/cc/data.py | 4 +-
src/lib/python/isc/cc/session.py | 47 +-
src/lib/python/isc/cc/tests/Makefile.am | 2 +-
src/lib/python/isc/cc/tests/session_test.py | 6 +-
src/lib/python/isc/config/ccsession.py | 157 +-
src/lib/python/isc/config/cfgmgr.py | 9 +-
src/lib/python/isc/config/tests/Makefile.am | 2 +-
src/lib/python/isc/config/tests/ccsession_test.py | 101 +-
src/lib/python/isc/config/tests/cfgmgr_test.py | 6 +-
src/lib/python/isc/datasrc/Makefile.am | 6 +
src/lib/python/isc/datasrc/client_inc.cc | 2 +-
src/lib/python/isc/datasrc/client_python.cc | 4 +-
.../isc/datasrc/configurableclientlist_inc.cc | 134 ++
.../isc/datasrc/configurableclientlist_python.cc | 295 ++-
src/lib/python/isc/datasrc/datasrc.cc | 59 +
src/lib/python/isc/datasrc/finder_python.cc | 6 +-
src/lib/python/isc/datasrc/iterator_python.cc | 2 +-
src/lib/python/isc/datasrc/tests/Makefile.am | 20 +-
.../python/isc/datasrc/tests/clientlist_test.py | 330 ++-
src/lib/python/isc/datasrc/tests/datasrc_test.py | 27 +-
.../python/isc/datasrc/tests/testdata/Makefile.am | 12 +
.../datasrc/tests/testdata/example.com-broken.zone | 6 +
.../python/isc/datasrc/tests/zone_loader_test.py | 26 +-
src/lib/python/isc/datasrc/updater_python.cc | 2 +-
.../isc/datasrc/zonetable_accessor_python.cc | 182 ++
.../python/isc/datasrc/zonetable_accessor_python.h | 43 +
.../isc/datasrc/zonetable_iterator_python.cc | 197 ++
.../python/isc/datasrc/zonetable_iterator_python.h | 44 +
src/lib/python/isc/datasrc/zonewriter_inc.cc | 103 +
src/lib/python/isc/datasrc/zonewriter_python.cc | 253 +++
src/lib/python/isc/datasrc/zonewriter_python.h | 50 +
src/lib/python/isc/ddns/libddns_messages.mes | 10 +
src/lib/python/isc/ddns/session.py | 25 +-
src/lib/python/isc/ddns/tests/Makefile.am | 2 +-
src/lib/python/isc/ddns/tests/session_tests.py | 12 +-
src/lib/python/isc/log/tests/Makefile.am | 2 +-
src/lib/python/isc/log_messages/Makefile.am | 4 +
.../python/isc/log_messages/libmemmgr_messages.py | 1 +
src/lib/python/isc/log_messages/memmgr_messages.py | 1 +
src/lib/python/isc/memmgr/Makefile.am | 25 +
src/lib/python/isc/{bind10 => memmgr}/__init__.py | 0
src/lib/python/isc/memmgr/builder.py | 185 ++
src/lib/python/isc/memmgr/datasrc_info.py | 413 ++++
src/lib/python/isc/memmgr/libmemmgr_messages.mes | 35 +
src/lib/python/isc/memmgr/logger.py | 20 +
src/lib/python/isc/memmgr/tests/Makefile.am | 36 +
src/lib/python/isc/memmgr/tests/builder_tests.py | 240 +++
.../python/isc/memmgr/tests/datasrc_info_tests.py | 469 +++++
.../python/isc/memmgr/tests/testdata/Makefile.am | 2 +
.../tests/testdata/example.com.zone} | 0
src/lib/python/isc/net/tests/Makefile.am | 2 +-
src/lib/python/isc/notify/notify_out.py | 122 +-
src/lib/python/isc/notify/notify_out_messages.mes | 27 +-
src/lib/python/isc/notify/tests/Makefile.am | 4 +-
src/lib/python/isc/notify/tests/notify_out_test.py | 165 +-
.../isc/notify/tests/testdata/test_spec1.spec | 57 +
src/lib/python/isc/server_common/.gitignore | 1 +
src/lib/python/isc/server_common/Makefile.am | 3 +
.../python/isc/server_common/bind10_server.py.in | 275 +++
.../isc/server_common/datasrc_clients_mgr.py | 173 ++
src/lib/python/isc/server_common/dns_tcp.py | 6 +-
.../isc/server_common/server_common_messages.mes | 20 +
src/lib/python/isc/server_common/tests/.gitignore | 2 +
src/lib/python/isc/server_common/tests/Makefile.am | 15 +-
.../isc/server_common/tests/bind10_server_test.py | 294 +++
.../tests/datasrc_clients_mgr_test.py | 142 ++
.../isc/server_common/tests/tsig_keyring_test.py | 2 +-
src/lib/python/isc/statistics/Makefile.am | 2 +-
src/lib/python/isc/statistics/counters.py | 276 +--
src/lib/python/isc/statistics/dns.py | 166 ++
src/lib/python/isc/statistics/tests/Makefile.am | 4 +-
.../python/isc/statistics/tests/counters_test.py | 262 +--
src/lib/python/isc/statistics/tests/dns_test.py | 224 ++
.../isc/statistics/tests/testdata/test_spec2.spec | 96 +-
.../isc/statistics/tests/testdata/test_spec3.spec | 204 +-
src/lib/python/isc/sysinfo/sysinfo.py | 2 +-
src/lib/python/isc/sysinfo/tests/Makefile.am | 2 +-
src/lib/python/isc/util/Makefile.am | 3 +-
src/lib/python/isc/util/address_formatter.py | 82 +
src/lib/python/isc/util/cio/tests/Makefile.am | 2 +-
src/lib/python/isc/util/socketserver_mixin.py | 2 +-
src/lib/python/isc/util/tests/Makefile.am | 5 +-
.../isc/util/tests/address_formatter_test.py | 68 +
src/lib/python/isc/xfrin/diff.py | 22 +-
src/lib/python/isc/xfrin/tests/Makefile.am | 2 +-
src/lib/python/isc/xfrin/tests/diff_tests.py | 2 +-
src/lib/resolve/Makefile.am | 1 -
src/lib/resolve/recursive_query.cc | 7 +-
src/lib/resolve/tests/recursive_query_unittest.cc | 12 +-
.../resolve/tests/recursive_query_unittest_2.cc | 2 +-
.../resolve/tests/recursive_query_unittest_3.cc | 2 +-
src/lib/server_common/client.cc | 10 +-
src/lib/server_common/client.h | 2 +-
src/lib/server_common/portconfig.cc | 13 +-
src/lib/server_common/server_common_messages.mes | 2 +-
src/lib/server_common/tests/client_unittest.cc | 4 +-
.../server_common/tests/socket_requestor_test.cc | 2 +-
src/lib/statistics/counter.h | 8 +-
src/lib/statistics/tests/counter_unittest.cc | 5 +
src/lib/testutils/mockups.h | 19 +-
src/lib/util/Makefile.am | 21 +-
src/lib/util/buffer.h | 18 +-
src/lib/util/encode/base16_from_binary.h | 2 +-
src/lib/util/encode/base32hex_from_binary.h | 2 +-
src/lib/util/encode/base_n.cc | 108 +-
src/lib/util/encode/binary_from_base16.h | 2 +-
src/lib/util/encode/binary_from_base32hex.h | 2 +-
src/lib/util/filename.h | 4 +-
src/lib/util/interprocess_sync.h | 149 --
src/lib/util/interprocess_sync_file.cc | 132 --
src/lib/util/interprocess_sync_file.h | 91 -
src/lib/util/interprocess_sync_null.cc | 42 -
src/lib/util/interprocess_sync_null.h | 64 -
src/lib/util/lru_list.h | 2 +-
src/lib/util/memory_segment.h | 290 ++-
src/lib/util/memory_segment_local.cc | 24 +-
src/lib/util/memory_segment_local.h | 32 +
src/lib/util/memory_segment_mapped.cc | 466 +++++
src/lib/util/memory_segment_mapped.h | 268 +++
src/lib/util/python/.gitignore | 1 +
src/lib/util/python/Makefile.am | 2 +-
src/lib/util/python/doxygen2pydoc.py.in | 680 +++++++
src/lib/util/python/gen_wiredata.py.in | 51 +-
src/lib/util/random/qid_gen.h | 2 +
src/lib/util/random/random_number_generator.h | 1 +
src/lib/util/tests/Makefile.am | 7 +-
src/lib/util/tests/buffer_unittest.cc | 5 +
src/lib/util/tests/fd_share_tests.cc | 2 +-
.../util/tests/interprocess_sync_file_unittest.cc | 183 --
.../util/tests/interprocess_sync_null_unittest.cc | 76 -
src/lib/util/tests/io_utilities_unittest.cc | 2 +-
src/lib/util/tests/lru_list_unittest.cc | 2 +-
.../util/tests/memory_segment_common_unittest.cc | 106 +
.../util/tests/memory_segment_common_unittest.h | 36 +
.../util/tests/memory_segment_local_unittest.cc | 9 +-
.../util/tests/memory_segment_mapped_unittest.cc | 650 ++++++
.../util/tests/random_number_generator_unittest.cc | 16 +-
src/lib/util/tests/run_unittests.cc | 1 -
src/lib/util/threads/Makefile.am | 3 +-
src/lib/util/threads/sync.cc | 20 +
src/lib/util/threads/sync.h | 45 +-
src/lib/util/threads/tests/lock_unittest.cc | 38 +
src/lib/util/unittests/Makefile.am | 1 +
src/lib/util/unittests/interprocess_util.cc | 48 +
src/lib/util/unittests/interprocess_util.h | 31 +
src/lib/util/unittests/mock_socketsession.h | 7 +-
src/lib/util/unittests/resolver.h | 2 +-
src/lib/util/unittests/run_all.cc | 2 +-
src/lib/xfr/tests/client_test.cc | 2 +-
tests/Makefile.am | 2 +-
tests/lettuce/Makefile.am | 1 +
tests/lettuce/README | 12 +-
.../configurations/example.org.inmem.config | 6 +
tests/lettuce/configurations/glue.config | 34 +
.../configurations/ixfr-out/testset1-config.db | 1 -
tests/lettuce/configurations/xfrin/.gitignore | 2 +
.../xfrin/retransfer_master.conf.orig | 3 -
.../xfrin/retransfer_master_diffs.conf | 47 +
.../xfrin/retransfer_master_v4.conf.orig | 45 +
.../xfrin/retransfer_slave.conf.orig | 1 -
.../xfrin/retransfer_slave_diffs.conf | 41 +
.../xfrin/retransfer_slave_notify.conf | 52 -
.../xfrin/retransfer_slave_notify.conf.orig | 49 +
.../xfrin/retransfer_slave_notify_v4.conf | 49 +
tests/lettuce/configurations/xfrout_master.conf | 41 +
tests/lettuce/data/.gitignore | 1 +
tests/lettuce/data/glue.sqlite3 | Bin 0 -> 17408 bytes
tests/lettuce/data/xfrin-before-diffs.sqlite3.orig | Bin 0 -> 15360 bytes
tests/lettuce/data/xfrin-diffs.sqlite3 | Bin 0 -> 407552 bytes
tests/lettuce/features/.gitignore | 1 +
tests/lettuce/features/auth_badzone.feature | 16 +-
tests/lettuce/features/bindctl_commands.feature | 15 +
tests/lettuce/features/ddns_system.feature | 36 +
tests/lettuce/features/example.feature | 10 +-
tests/lettuce/features/nsec3_auth.feature | 30 +-
tests/lettuce/features/queries.feature | 89 +-
tests/lettuce/features/resolver_basic.feature | 36 -
.../features/resolver_basic.feature.disabled | 36 +
tests/lettuce/features/terrain/bind10_control.py | 140 +-
tests/lettuce/features/terrain/loadzone.py | 106 +
tests/lettuce/features/terrain/nsupdate.py | 2 +-
tests/lettuce/features/terrain/querying.py | 14 +-
tests/lettuce/features/terrain/steps.py | 12 +-
tests/lettuce/features/terrain/terrain.py | 89 +-
tests/lettuce/features/terrain/transfer.py | 71 +-
tests/lettuce/features/xfrin_bind10.feature | 74 +-
.../lettuce/features/xfrin_notify_handling.feature | 729 ++++---
tests/lettuce/features/xfrout_bind10.feature | 39 +
tests/lettuce/run_python-tool.sh | 23 +
tests/lettuce/setup_intree_bind10.sh.in | 9 +-
tests/lettuce/tools/xfr-client.py | 103 +
tests/system/.gitignore | 2 -
tests/system/Makefile.am | 16 -
tests/system/README | 64 -
tests/system/bindctl/clean.sh | 20 -
tests/system/bindctl/nsx1/.gitignore | 3 -
.../system/bindctl/nsx1/b10-config.db.template.in | 29 -
tests/system/bindctl/nsx1/example-normalized.db | 3 -
tests/system/bindctl/nsx1/root.db | 25 -
tests/system/bindctl/setup.sh | 26 -
tests/system/bindctl/tests.sh | 238 ---
tests/system/cleanall.sh | 36 -
tests/system/common/default_user.csv | 1 -
tests/system/common/rndc.conf | 25 -
tests/system/common/rndc.key | 22 -
tests/system/conf.sh.in | 73 -
tests/system/glue/.gitignore | 1 -
tests/system/glue/auth.good | 15 -
tests/system/glue/clean.sh | 23 -
tests/system/glue/example.good | 22 -
tests/system/glue/noglue.good | 14 -
tests/system/glue/nsx1/.gitignore | 3 -
tests/system/glue/nsx1/b10-config.db.in | 36 -
tests/system/glue/nsx1/com.db | 31 -
tests/system/glue/nsx1/net.db | 32 -
tests/system/glue/nsx1/root-servers.nil.db | 26 -
tests/system/glue/nsx1/root.db | 53 -
tests/system/glue/setup.sh.in | 25 -
tests/system/glue/test.good | 19 -
tests/system/glue/tests.sh | 68 -
tests/system/ifconfig.sh | 226 ---
tests/system/ixfr/.gitignore | 8 -
tests/system/ixfr/README | 86 -
tests/system/ixfr/b10-config.db.in | 51 -
tests/system/ixfr/clean_ns.sh | 28 -
tests/system/ixfr/common_tests.sh.in | 78 -
tests/system/ixfr/db.example.common | 1556 --------------
tests/system/ixfr/db.example.n0.in | 29 -
tests/system/ixfr/db.example.n2.in | 28 -
tests/system/ixfr/db.example.n2.refresh.in | 28 -
tests/system/ixfr/db.example.n4.in | 31 -
tests/system/ixfr/db.example.n6.in | 29 -
tests/system/ixfr/in-1/.gitignore | 1 -
tests/system/ixfr/in-1/clean.sh | 1 -
tests/system/ixfr/in-1/ns1/README | 3 -
tests/system/ixfr/in-1/nsx2/README | 3 -
tests/system/ixfr/in-1/setup.sh.in | 30 -
tests/system/ixfr/in-1/tests.sh | 37 -
tests/system/ixfr/in-2/.gitignore | 1 -
tests/system/ixfr/in-2/clean.sh | 1 -
tests/system/ixfr/in-2/ns1/.gitignore | 1 -
tests/system/ixfr/in-2/ns1/README | 3 -
tests/system/ixfr/in-2/nsx2/.gitignore | 1 -
tests/system/ixfr/in-2/nsx2/README | 3 -
tests/system/ixfr/in-2/setup.sh.in | 29 -
tests/system/ixfr/in-2/tests.sh | 81 -
tests/system/ixfr/in-3/.gitignore | 1 -
tests/system/ixfr/in-3/clean.sh | 1 -
tests/system/ixfr/in-3/ns1/README | 3 -
tests/system/ixfr/in-3/nsx2/README | 3 -
tests/system/ixfr/in-3/setup.sh.in | 29 -
tests/system/ixfr/in-3/tests.sh | 66 -
tests/system/ixfr/in-4/.gitignore | 1 -
tests/system/ixfr/in-4/clean.sh | 1 -
tests/system/ixfr/in-4/ns1/README | 3 -
tests/system/ixfr/in-4/nsx2/README | 3 -
tests/system/ixfr/in-4/setup.sh.in | 30 -
tests/system/ixfr/in-4/tests.sh | 53 -
tests/system/ixfr/ixfr_init.sh.in | 330 ---
tests/system/ixfr/named_noixfr.conf | 42 -
tests/system/ixfr/named_nonotify.conf | 40 -
tests/system/ixfr/named_notify.conf | 41 -
tests/system/run.sh.in | 125 --
tests/system/runall.sh | 44 -
tests/system/start.pl | 229 ---
tests/system/stop.pl | 188 --
tests/tools/badpacket/option_info.h | 4 +-
tests/tools/dhcp-ubench/benchmark.h | 2 +-
tests/tools/dhcp-ubench/dhcp-perf-guide.xml | 10 +-
tests/tools/perfdhcp/command_options.cc | 4 +-
tests/tools/perfdhcp/command_options.h | 2 +-
tests/tools/perfdhcp/perf_pkt4.cc | 2 +-
tests/tools/perfdhcp/pkt_transform.h | 2 +-
tests/tools/perfdhcp/stats_mgr.h | 40 +-
tests/tools/perfdhcp/test_control.cc | 38 +-
tests/tools/perfdhcp/test_control.h | 18 +-
.../tools/perfdhcp/tests/command_options_helper.h | 2 +-
.../perfdhcp/tests/command_options_unittest.cc | 2 +-
tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc | 10 +-
tests/tools/perfdhcp/tests/stats_mgr_unittest.cc | 2 +-
.../tools/perfdhcp/tests/test_control_unittest.cc | 29 +-
tools/query_cmp/src/lib/compare_rrset.py | 2 +-
tools/reorder_message_file.py | 2 +-
tools/system_messages.py | 2 +-
1011 files changed, 88543 insertions(+), 22538 deletions(-)
create mode 100644 doc/Doxyfile-xml
create mode 100644 doc/design/Makefile.am
create mode 100644 doc/design/datasrc/.gitignore
create mode 100644 doc/design/datasrc/Makefile.am
create mode 100644 doc/design/datasrc/auth-local.txt
create mode 100644 doc/design/datasrc/auth-mapped.txt
create mode 100644 doc/design/datasrc/data-source-classes.txt
create mode 100644 doc/design/datasrc/memmgr-mapped-init.txt
create mode 100644 doc/design/datasrc/memmgr-mapped-reload.txt
create mode 100644 doc/design/datasrc/overview.txt
create mode 100644 doc/design/ipc-high.txt
create mode 100644 doc/design/resolver/01-scaling-across-cores
create mode 100644 doc/design/resolver/02-mixed-recursive-authority-setup
create mode 100644 doc/design/resolver/03-cache-algorithm
create mode 100644 doc/design/resolver/03-cache-algorithm.txt
create mode 100644 doc/design/resolver/README
create mode 100644 ext/Makefile.am
create mode 100644 ext/asio/Makefile.am
create mode 100644 ext/asio/asio/Makefile.am
create mode 100644 m4macros/Makefile.am
create mode 100644 m4macros/ax_sqlite3_for_bind10.m4
create mode 100644 src/bin/d2/.gitignore
create mode 100644 src/bin/d2/Makefile.am
create mode 100644 src/bin/d2/b10-dhcp-ddns.xml
create mode 100644 src/bin/d2/d2_cfg_mgr.cc
create mode 100644 src/bin/d2/d2_cfg_mgr.h
create mode 100644 src/bin/d2/d2_config.cc
create mode 100644 src/bin/d2/d2_config.h
create mode 100644 src/bin/d2/d2_controller.cc
create mode 100644 src/bin/d2/d2_controller.h
create mode 100644 src/bin/d2/d2_log.cc
create mode 100644 src/bin/d2/d2_log.h
create mode 100644 src/bin/d2/d2_messages.mes
create mode 100644 src/bin/d2/d2_process.cc
create mode 100644 src/bin/d2/d2_process.h
create mode 100644 src/bin/d2/d2_queue_mgr.cc
create mode 100644 src/bin/d2/d2_queue_mgr.h
create mode 100644 src/bin/d2/d2_update_message.cc
create mode 100644 src/bin/d2/d2_update_message.h
create mode 100644 src/bin/d2/d2_update_mgr.cc
create mode 100644 src/bin/d2/d2_update_mgr.h
create mode 100644 src/bin/d2/d2_zone.cc
create mode 100644 src/bin/d2/d2_zone.h
create mode 100644 src/bin/d2/d_cfg_mgr.cc
create mode 100644 src/bin/d2/d_cfg_mgr.h
create mode 100644 src/bin/d2/d_controller.cc
create mode 100644 src/bin/d2/d_controller.h
create mode 100644 src/bin/d2/d_process.h
create mode 100644 src/bin/d2/dhcp-ddns.spec
create mode 100644 src/bin/d2/dns_client.cc
create mode 100644 src/bin/d2/dns_client.h
create mode 100644 src/bin/d2/main.cc
create mode 100644 src/bin/d2/spec_config.h.pre.in
create mode 100644 src/bin/d2/tests/.gitignore
create mode 100644 src/bin/d2/tests/Makefile.am
create mode 100644 src/bin/d2/tests/d2_cfg_mgr_unittests.cc
create mode 100644 src/bin/d2/tests/d2_controller_unittests.cc
create mode 100644 src/bin/d2/tests/d2_process_unittests.cc
create mode 100644 src/bin/d2/tests/d2_queue_mgr_unittests.cc
create mode 100644 src/bin/d2/tests/d2_test.py
create mode 100644 src/bin/d2/tests/d2_unittests.cc
create mode 100644 src/bin/d2/tests/d2_update_message_unittests.cc
create mode 100644 src/bin/d2/tests/d2_update_mgr_unittests.cc
create mode 100644 src/bin/d2/tests/d2_zone_unittests.cc
create mode 100644 src/bin/d2/tests/d_cfg_mgr_unittests.cc
create mode 100644 src/bin/d2/tests/d_controller_unittests.cc
create mode 100644 src/bin/d2/tests/d_test_stubs.cc
create mode 100644 src/bin/d2/tests/d_test_stubs.h
create mode 100644 src/bin/d2/tests/dns_client_unittests.cc
create mode 100644 src/bin/d2/tests/test_data_files_config.h.in
create mode 100644 src/bin/dhcp4/dhcp4_hooks.dox
create mode 100644 src/bin/dhcp4/tests/callout_library_1.cc
create mode 100644 src/bin/dhcp4/tests/callout_library_2.cc
create mode 100644 src/bin/dhcp4/tests/callout_library_common.h
create mode 100644 src/bin/dhcp4/tests/marker_file.cc
create mode 100644 src/bin/dhcp4/tests/marker_file.h.in
create mode 100644 src/bin/dhcp4/tests/test_libraries.h.in
create mode 100644 src/bin/dhcp6/dhcp6_hooks.dox
create mode 100644 src/bin/dhcp6/tests/callout_library_1.cc
create mode 100644 src/bin/dhcp6/tests/callout_library_2.cc
create mode 100644 src/bin/dhcp6/tests/callout_library_common.h
create mode 100644 src/bin/dhcp6/tests/dhcp6_test_utils.h
create mode 100644 src/bin/dhcp6/tests/hooks_unittest.cc
create mode 100644 src/bin/dhcp6/tests/marker_file.cc
create mode 100644 src/bin/dhcp6/tests/marker_file.h.in
create mode 100644 src/bin/dhcp6/tests/test_libraries.h.in
create mode 100644 src/bin/memmgr/.gitignore
create mode 100644 src/bin/memmgr/Makefile.am
create mode 100644 src/bin/memmgr/b10-memmgr.xml
create mode 100755 src/bin/memmgr/memmgr.py.in
create mode 100644 src/bin/memmgr/memmgr.spec.pre.in
create mode 100644 src/bin/memmgr/memmgr_messages.mes
create mode 100644 src/bin/memmgr/tests/Makefile.am
create mode 100755 src/bin/memmgr/tests/memmgr_test.py
create mode 100644 src/bin/msgq/tests/msgq_run_test.py
create mode 100644 src/bin/resolver/bench/.gitignore
create mode 100644 src/bin/resolver/bench/Makefile.am
create mode 100644 src/bin/resolver/bench/dummy_work.cc
create mode 100644 src/bin/resolver/bench/dummy_work.h
create mode 100644 src/bin/resolver/bench/fake_resolution.cc
create mode 100644 src/bin/resolver/bench/fake_resolution.h
create mode 100644 src/bin/resolver/bench/main.cc
create mode 100644 src/bin/resolver/bench/naive_resolver.cc
create mode 100644 src/bin/resolver/bench/naive_resolver.h
delete mode 100644 src/bin/stats/tests/b10-stats-httpd_test.py
delete mode 100644 src/bin/stats/tests/b10-stats_test.py
create mode 100644 src/bin/stats/tests/stats-httpd_test.py
create mode 100644 src/bin/stats/tests/stats_test.py
mode change 100644 => 100755 src/bin/usermgr/b10-cmdctl-usermgr.py.in
mode change 100644 => 100755 src/bin/usermgr/run_b10-cmdctl-usermgr.sh.in
create mode 100644 src/bin/usermgr/tests/Makefile.am
create mode 100644 src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py
create mode 100644 src/lib/asiolink/local_socket.cc
create mode 100644 src/lib/asiolink/local_socket.h
create mode 100644 src/lib/asiolink/tests/io_service_unittest.cc
create mode 100644 src/lib/asiolink/tests/local_socket_unittest.cc
create mode 100644 src/lib/datasrc/cache_config.cc
create mode 100644 src/lib/datasrc/cache_config.h
delete mode 100644 src/lib/datasrc/data_source.h
create mode 100644 src/lib/datasrc/memory/segment_object_holder.cc
create mode 100644 src/lib/datasrc/memory/zone_table_segment_mapped.cc
create mode 100644 src/lib/datasrc/memory/zone_table_segment_mapped.h
create mode 100644 src/lib/datasrc/memory/zone_writer.cc
delete mode 100644 src/lib/datasrc/memory/zone_writer_local.cc
delete mode 100644 src/lib/datasrc/memory/zone_writer_local.h
create mode 100644 src/lib/datasrc/sqlite3_datasrc_messages.mes
delete mode 100644 src/lib/datasrc/static_datasrc.h
delete mode 100644 src/lib/datasrc/static_datasrc_link.cc
create mode 100644 src/lib/datasrc/tests/cache_config_unittest.cc
create mode 100644 src/lib/datasrc/tests/memory/memory_segment_mock.h
delete mode 100644 src/lib/datasrc/tests/memory/memory_segment_test.h
create mode 100644 src/lib/datasrc/tests/memory/testdata/template.zone
create mode 100644 src/lib/datasrc/tests/memory/zone_loader_util.cc
create mode 100644 src/lib/datasrc/tests/memory/zone_loader_util.h
create mode 100644 src/lib/datasrc/tests/memory/zone_table_segment_mapped_unittest.cc
create mode 100644 src/lib/datasrc/tests/memory/zone_table_segment_mock.h
delete mode 100644 src/lib/datasrc/tests/memory/zone_table_segment_test.h
create mode 100644 src/lib/datasrc/tests/mock_client.cc
create mode 100644 src/lib/datasrc/tests/mock_client.h
create mode 100644 src/lib/datasrc/tests/zone_table_accessor_unittest.cc
create mode 100644 src/lib/datasrc/zone_table_accessor.h
create mode 100644 src/lib/datasrc/zone_table_accessor_cache.cc
create mode 100644 src/lib/datasrc/zone_table_accessor_cache.h
create mode 100644 src/lib/dhcp/option4_client_fqdn.cc
create mode 100644 src/lib/dhcp/option4_client_fqdn.h
create mode 100644 src/lib/dhcp/option6_client_fqdn.cc
create mode 100644 src/lib/dhcp/option6_client_fqdn.h
create mode 100644 src/lib/dhcp/option_string.cc
create mode 100644 src/lib/dhcp/option_string.h
create mode 100644 src/lib/dhcp/pkt_filter.h
create mode 100644 src/lib/dhcp/pkt_filter_inet.cc
create mode 100644 src/lib/dhcp/pkt_filter_inet.h
create mode 100644 src/lib/dhcp/pkt_filter_lpf.cc
create mode 100644 src/lib/dhcp/pkt_filter_lpf.h
create mode 100644 src/lib/dhcp/protocol_util.cc
create mode 100644 src/lib/dhcp/protocol_util.h
create mode 100644 src/lib/dhcp/tests/option4_client_fqdn_unittest.cc
create mode 100644 src/lib/dhcp/tests/option6_client_fqdn_unittest.cc
create mode 100644 src/lib/dhcp/tests/option_string_unittest.cc
create mode 100644 src/lib/dhcp/tests/pkt_filter_inet_unittest.cc
create mode 100644 src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc
create mode 100644 src/lib/dhcp/tests/protocol_util_unittest.cc
create mode 100644 src/lib/dhcp_ddns/.gitignore
create mode 100644 src/lib/dhcp_ddns/Makefile.am
create mode 100644 src/lib/dhcp_ddns/dhcp_ddns_log.cc
create mode 100644 src/lib/dhcp_ddns/dhcp_ddns_log.h
create mode 100644 src/lib/dhcp_ddns/dhcp_ddns_messages.mes
create mode 100644 src/lib/dhcp_ddns/libdhcp_ddns.dox
create mode 100644 src/lib/dhcp_ddns/ncr_io.cc
create mode 100644 src/lib/dhcp_ddns/ncr_io.h
create mode 100644 src/lib/dhcp_ddns/ncr_msg.cc
create mode 100644 src/lib/dhcp_ddns/ncr_msg.h
create mode 100644 src/lib/dhcp_ddns/ncr_udp.cc
create mode 100644 src/lib/dhcp_ddns/ncr_udp.h
create mode 100644 src/lib/dhcp_ddns/tests/.gitignore
create mode 100644 src/lib/dhcp_ddns/tests/Makefile.am
create mode 100644 src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
create mode 100644 src/lib/dhcp_ddns/tests/ncr_unittests.cc
copy src/lib/{dhcp => dhcp_ddns}/tests/run_unittests.cc (100%)
create mode 100644 src/lib/dhcpsrv/callout_handle_store.h
create mode 100644 src/lib/dhcpsrv/dhcp_parsers.cc
create mode 100644 src/lib/dhcpsrv/dhcp_parsers.h
create mode 100644 src/lib/dhcpsrv/key_from_key.h
create mode 100644 src/lib/dhcpsrv/tests/callout_handle_store_unittest.cc
create mode 100644 src/lib/dhcpsrv/tests/callout_library.cc
create mode 100644 src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
create mode 100644 src/lib/dhcpsrv/tests/test_get_callout_handle.cc
create mode 100644 src/lib/dhcpsrv/tests/test_get_callout_handle.h
create mode 100644 src/lib/dhcpsrv/tests/test_libraries.h.in
create mode 100644 src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.spec
delete mode 100644 src/lib/dns/tests/testdata/rdata_dnskey_fromWire
create mode 100644 src/lib/dns/tests/testdata/rdata_dnskey_fromWire.spec
create mode 100644 src/lib/hooks/.gitignore
create mode 100644 src/lib/hooks/Makefile.am
create mode 100644 src/lib/hooks/callout_handle.cc
create mode 100644 src/lib/hooks/callout_handle.h
create mode 100644 src/lib/hooks/callout_manager.cc
create mode 100644 src/lib/hooks/callout_manager.h
create mode 100644 src/lib/hooks/hooks.h
create mode 100644 src/lib/hooks/hooks_component_developer.dox
create mode 100644 src/lib/hooks/hooks_log.cc
create mode 100644 src/lib/hooks/hooks_log.h
create mode 100644 src/lib/hooks/hooks_maintenance.dox
create mode 100644 src/lib/hooks/hooks_manager.cc
create mode 100644 src/lib/hooks/hooks_manager.h
create mode 100644 src/lib/hooks/hooks_messages.mes
create mode 100644 src/lib/hooks/hooks_user.dox
create mode 100644 src/lib/hooks/images/DataScopeArgument.dia
create mode 100644 src/lib/hooks/images/DataScopeArgument.png
create mode 100644 src/lib/hooks/images/DataScopeContext.dia
create mode 100644 src/lib/hooks/images/DataScopeContext.png
create mode 100644 src/lib/hooks/images/HooksUml.dia
create mode 100644 src/lib/hooks/images/HooksUml.png
create mode 100644 src/lib/hooks/library_handle.cc
create mode 100644 src/lib/hooks/library_handle.h
create mode 100644 src/lib/hooks/library_manager.cc
create mode 100644 src/lib/hooks/library_manager.h
create mode 100644 src/lib/hooks/library_manager_collection.cc
create mode 100644 src/lib/hooks/library_manager_collection.h
create mode 100644 src/lib/hooks/pointer_converter.h
create mode 100644 src/lib/hooks/server_hooks.cc
create mode 100644 src/lib/hooks/server_hooks.h
create mode 100644 src/lib/hooks/tests/.gitignore
create mode 100644 src/lib/hooks/tests/Makefile.am
create mode 100644 src/lib/hooks/tests/basic_callout_library.cc
create mode 100644 src/lib/hooks/tests/callout_handle_unittest.cc
create mode 100644 src/lib/hooks/tests/callout_manager_unittest.cc
create mode 100644 src/lib/hooks/tests/common_test_class.h
create mode 100644 src/lib/hooks/tests/framework_exception_library.cc
create mode 100644 src/lib/hooks/tests/full_callout_library.cc
create mode 100644 src/lib/hooks/tests/handles_unittest.cc
create mode 100644 src/lib/hooks/tests/hooks_manager_unittest.cc
create mode 100644 src/lib/hooks/tests/incorrect_version_library.cc
create mode 100644 src/lib/hooks/tests/library_manager_collection_unittest.cc
create mode 100644 src/lib/hooks/tests/library_manager_unittest.cc
create mode 100644 src/lib/hooks/tests/load_callout_library.cc
create mode 100644 src/lib/hooks/tests/load_error_callout_library.cc
create mode 100644 src/lib/hooks/tests/marker_file.h.in
create mode 100644 src/lib/hooks/tests/no_version_library.cc
create mode 100644 src/lib/hooks/tests/run_unittests.cc
create mode 100644 src/lib/hooks/tests/server_hooks_unittest.cc
create mode 100644 src/lib/hooks/tests/test_libraries.h.in
create mode 100644 src/lib/hooks/tests/unload_callout_library.cc
delete mode 100644 src/lib/log/dummylog.cc
delete mode 100644 src/lib/log/dummylog.h
create mode 100644 src/lib/log/interprocess/Makefile.am
create mode 100644 src/lib/log/interprocess/README
create mode 100644 src/lib/log/interprocess/interprocess_sync.h
create mode 100644 src/lib/log/interprocess/interprocess_sync_file.cc
create mode 100644 src/lib/log/interprocess/interprocess_sync_file.h
create mode 100644 src/lib/log/interprocess/interprocess_sync_null.cc
create mode 100644 src/lib/log/interprocess/interprocess_sync_null.h
copy src/{bin/resolver => lib/log/interprocess}/tests/.gitignore (100%)
create mode 100644 src/lib/log/interprocess/tests/Makefile.am
create mode 100644 src/lib/log/interprocess/tests/interprocess_sync_file_unittest.cc
create mode 100644 src/lib/log/interprocess/tests/interprocess_sync_null_unittest.cc
create mode 100644 src/lib/log/interprocess/tests/run_unittests.cc
create mode 100644 src/lib/python/isc/datasrc/configurableclientlist_inc.cc
create mode 100644 src/lib/python/isc/datasrc/tests/testdata/Makefile.am
create mode 100644 src/lib/python/isc/datasrc/tests/testdata/example.com-broken.zone
create mode 100644 src/lib/python/isc/datasrc/zonetable_accessor_python.cc
create mode 100644 src/lib/python/isc/datasrc/zonetable_accessor_python.h
create mode 100644 src/lib/python/isc/datasrc/zonetable_iterator_python.cc
create mode 100644 src/lib/python/isc/datasrc/zonetable_iterator_python.h
create mode 100644 src/lib/python/isc/datasrc/zonewriter_inc.cc
create mode 100644 src/lib/python/isc/datasrc/zonewriter_python.cc
create mode 100644 src/lib/python/isc/datasrc/zonewriter_python.h
create mode 100644 src/lib/python/isc/log_messages/libmemmgr_messages.py
create mode 100644 src/lib/python/isc/log_messages/memmgr_messages.py
create mode 100644 src/lib/python/isc/memmgr/Makefile.am
copy src/lib/python/isc/{bind10 => memmgr}/__init__.py (100%)
create mode 100644 src/lib/python/isc/memmgr/builder.py
create mode 100644 src/lib/python/isc/memmgr/datasrc_info.py
create mode 100644 src/lib/python/isc/memmgr/libmemmgr_messages.mes
create mode 100644 src/lib/python/isc/memmgr/logger.py
create mode 100644 src/lib/python/isc/memmgr/tests/Makefile.am
create mode 100644 src/lib/python/isc/memmgr/tests/builder_tests.py
create mode 100644 src/lib/python/isc/memmgr/tests/datasrc_info_tests.py
create mode 100644 src/lib/python/isc/memmgr/tests/testdata/Makefile.am
copy src/lib/python/isc/{datasrc/tests/testdata/example.com => memmgr/tests/testdata/example.com.zone} (100%)
create mode 100644 src/lib/python/isc/notify/tests/testdata/test_spec1.spec
create mode 100644 src/lib/python/isc/server_common/.gitignore
create mode 100644 src/lib/python/isc/server_common/bind10_server.py.in
create mode 100644 src/lib/python/isc/server_common/datasrc_clients_mgr.py
create mode 100644 src/lib/python/isc/server_common/tests/.gitignore
create mode 100755 src/lib/python/isc/server_common/tests/bind10_server_test.py
create mode 100644 src/lib/python/isc/server_common/tests/datasrc_clients_mgr_test.py
create mode 100644 src/lib/python/isc/statistics/dns.py
create mode 100644 src/lib/python/isc/statistics/tests/dns_test.py
create mode 100644 src/lib/python/isc/util/address_formatter.py
create mode 100644 src/lib/python/isc/util/tests/address_formatter_test.py
delete mode 100644 src/lib/util/interprocess_sync.h
delete mode 100644 src/lib/util/interprocess_sync_file.cc
delete mode 100644 src/lib/util/interprocess_sync_file.h
delete mode 100644 src/lib/util/interprocess_sync_null.cc
delete mode 100644 src/lib/util/interprocess_sync_null.h
create mode 100644 src/lib/util/memory_segment_mapped.cc
create mode 100644 src/lib/util/memory_segment_mapped.h
create mode 100755 src/lib/util/python/doxygen2pydoc.py.in
delete mode 100644 src/lib/util/tests/interprocess_sync_file_unittest.cc
delete mode 100644 src/lib/util/tests/interprocess_sync_null_unittest.cc
create mode 100644 src/lib/util/tests/memory_segment_common_unittest.cc
create mode 100644 src/lib/util/tests/memory_segment_common_unittest.h
create mode 100644 src/lib/util/tests/memory_segment_mapped_unittest.cc
create mode 100644 src/lib/util/unittests/interprocess_util.cc
create mode 100644 src/lib/util/unittests/interprocess_util.h
create mode 100644 tests/lettuce/Makefile.am
create mode 100644 tests/lettuce/configurations/glue.config
create mode 100644 tests/lettuce/configurations/xfrin/retransfer_master_diffs.conf
create mode 100644 tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig
create mode 100644 tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf
delete mode 100644 tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf
create mode 100644 tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
create mode 100644 tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf
create mode 100644 tests/lettuce/configurations/xfrout_master.conf
create mode 100644 tests/lettuce/data/glue.sqlite3
create mode 100644 tests/lettuce/data/xfrin-before-diffs.sqlite3.orig
create mode 100644 tests/lettuce/data/xfrin-diffs.sqlite3
create mode 100644 tests/lettuce/features/.gitignore
delete mode 100644 tests/lettuce/features/resolver_basic.feature
create mode 100644 tests/lettuce/features/resolver_basic.feature.disabled
create mode 100644 tests/lettuce/features/terrain/loadzone.py
create mode 100644 tests/lettuce/features/xfrout_bind10.feature
create mode 100755 tests/lettuce/run_python-tool.sh
mode change 100644 => 100755 tests/lettuce/setup_intree_bind10.sh.in
create mode 100755 tests/lettuce/tools/xfr-client.py
delete mode 100644 tests/system/.gitignore
delete mode 100644 tests/system/Makefile.am
delete mode 100644 tests/system/README
delete mode 100755 tests/system/bindctl/clean.sh
delete mode 100644 tests/system/bindctl/nsx1/.gitignore
delete mode 100644 tests/system/bindctl/nsx1/b10-config.db.template.in
delete mode 100644 tests/system/bindctl/nsx1/example-normalized.db
delete mode 100644 tests/system/bindctl/nsx1/root.db
delete mode 100755 tests/system/bindctl/setup.sh
delete mode 100755 tests/system/bindctl/tests.sh
delete mode 100755 tests/system/cleanall.sh
delete mode 100644 tests/system/common/default_user.csv
delete mode 100644 tests/system/common/rndc.conf
delete mode 100644 tests/system/common/rndc.key
delete mode 100755 tests/system/conf.sh.in
delete mode 100644 tests/system/glue/.gitignore
delete mode 100644 tests/system/glue/auth.good
delete mode 100755 tests/system/glue/clean.sh
delete mode 100644 tests/system/glue/example.good
delete mode 100644 tests/system/glue/noglue.good
delete mode 100644 tests/system/glue/nsx1/.gitignore
delete mode 100644 tests/system/glue/nsx1/b10-config.db.in
delete mode 100644 tests/system/glue/nsx1/com.db
delete mode 100644 tests/system/glue/nsx1/net.db
delete mode 100644 tests/system/glue/nsx1/root-servers.nil.db
delete mode 100644 tests/system/glue/nsx1/root.db
delete mode 100755 tests/system/glue/setup.sh.in
delete mode 100644 tests/system/glue/test.good
delete mode 100755 tests/system/glue/tests.sh
delete mode 100755 tests/system/ifconfig.sh
delete mode 100644 tests/system/ixfr/.gitignore
delete mode 100644 tests/system/ixfr/README
delete mode 100644 tests/system/ixfr/b10-config.db.in
delete mode 100644 tests/system/ixfr/clean_ns.sh
delete mode 100644 tests/system/ixfr/common_tests.sh.in
delete mode 100644 tests/system/ixfr/db.example.common
delete mode 100644 tests/system/ixfr/db.example.n0.in
delete mode 100644 tests/system/ixfr/db.example.n2.in
delete mode 100644 tests/system/ixfr/db.example.n2.refresh.in
delete mode 100644 tests/system/ixfr/db.example.n4.in
delete mode 100644 tests/system/ixfr/db.example.n6.in
delete mode 100644 tests/system/ixfr/in-1/.gitignore
delete mode 120000 tests/system/ixfr/in-1/clean.sh
delete mode 100644 tests/system/ixfr/in-1/ns1/README
delete mode 100644 tests/system/ixfr/in-1/nsx2/README
delete mode 100644 tests/system/ixfr/in-1/setup.sh.in
delete mode 100644 tests/system/ixfr/in-1/tests.sh
delete mode 100644 tests/system/ixfr/in-2/.gitignore
delete mode 120000 tests/system/ixfr/in-2/clean.sh
delete mode 100644 tests/system/ixfr/in-2/ns1/.gitignore
delete mode 100644 tests/system/ixfr/in-2/ns1/README
delete mode 100644 tests/system/ixfr/in-2/nsx2/.gitignore
delete mode 100644 tests/system/ixfr/in-2/nsx2/README
delete mode 100644 tests/system/ixfr/in-2/setup.sh.in
delete mode 100644 tests/system/ixfr/in-2/tests.sh
delete mode 100644 tests/system/ixfr/in-3/.gitignore
delete mode 120000 tests/system/ixfr/in-3/clean.sh
delete mode 100644 tests/system/ixfr/in-3/ns1/README
delete mode 100644 tests/system/ixfr/in-3/nsx2/README
delete mode 100644 tests/system/ixfr/in-3/setup.sh.in
delete mode 100644 tests/system/ixfr/in-3/tests.sh
delete mode 100644 tests/system/ixfr/in-4/.gitignore
delete mode 120000 tests/system/ixfr/in-4/clean.sh
delete mode 100644 tests/system/ixfr/in-4/ns1/README
delete mode 100644 tests/system/ixfr/in-4/nsx2/README
delete mode 100644 tests/system/ixfr/in-4/setup.sh.in
delete mode 100644 tests/system/ixfr/in-4/tests.sh
delete mode 100644 tests/system/ixfr/ixfr_init.sh.in
delete mode 100644 tests/system/ixfr/named_noixfr.conf
delete mode 100644 tests/system/ixfr/named_nonotify.conf
delete mode 100644 tests/system/ixfr/named_notify.conf
delete mode 100755 tests/system/run.sh.in
delete mode 100755 tests/system/runall.sh
delete mode 100755 tests/system/start.pl
delete mode 100755 tests/system/stop.pl
-----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index 7bc41b4..45f9424 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,9 @@ Makefile
Makefile.in
TAGS
+*.log
+*.trs
+
/aclocal.m4
/autom4te.cache/
/config.guess
@@ -30,6 +33,7 @@ TAGS
/missing
/py-compile
/stamp-h1
+/test-driver
/all.info
/coverage-cpp-html
diff --git a/AUTHORS b/AUTHORS
index 67cb090..b915e25 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -14,6 +14,7 @@ Michael Graff
Michal Vaner
Mukund Sivaraman
Naoki Kambe
+Paul Selkirk
Shane Kerr
Shen Tingting
Stephen Morris
diff --git a/ChangeLog b/ChangeLog
index d7af227..cfee686 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,644 @@
+669. [func] tmark
+ Added main process event loop to D2Process which is the primary
+ application object in b10-dhcp-ddns. This allows DHCP-DDNS
+ to queue requests received from clients for processing while
+ listening for command control events.
+ (Trac #3075 git e2f9d2e4c1b36f01eb5bfa2c4f8d55cf139c7e02)
+
+668. [func] marcin
+ libdhcpsrv: Implemented changes to lease allocation engine to
+ propagate information about client's FQDN.
+ (Trac #3083, git 37af28303d1cd61f675faea969cd1159df65bf9d)
+
+667. [func] tomek
+ Additional hooks (buffer4_receive, lease4_renew,
+ lease4_release, buffer4_send) added to the DHCPv4 server.
+ (Trac #2983, git fd47f18f898695b98623a63a0a1c68d2e4b37568)
+
+666. [func] vorner
+ The CmdCtl's command "print_settings" was removed. It served no real
+ purpose and was just experimental leftover from early development.
+ (Trac #3028, git 0d22246092ad4822d48f5a52af5f644f5ae2f5e2)
+
+665. [doc] stephen
+ Added the "Hook's Maintenance Guide" to the BIND 10 developer
+ documentation.
+ (Trac #3063, git 5d1ee7b7470fc644b798ac47db1811c829f5ac24)
+
+664. [bug] tmark
+ Corrects a bug in Hooks processing that was improperly
+ creating a new callout handle on every call, rather
+ than maintaining it throughout the context of the
+ packet being processed.
+ (Trac #3062, git 28684bcfe5e54ad0421d75d4445a04b75358ce77)
+
+663. [func] marcin
+ b10-dhcp6: Server processes the DHCPv6 Client FQDN Option
+ sent by a client and generates the response. The DHCPv6 Client
+ FQDN Option is represented by the new class in the libdhcp++.
+ As a result of FQDN Option processing, the server generates
+ NameChangeRequests which represent changes to DNS mappings for
+ a particular lease (addition or removal of DNS mappings).
+ Currently all generated NameChangeRequests are dropped. Sending
+ them to b10-dhcp-ddns will be implemented with the future tickets.
+ (Trac #3036, git 209f3964b9f12afbf36f3fa6b62964e03049ec6e)
+
+662. [func] marcin
+ libdhcp++: Implemented an Option4ClientFqdn class which represents
+ DHCPv4 Client FQDN Option (code 81) defined in RFC4702. This class
+ supports the domain name encoding in canonical FQDN format as well
+ as in deprecated ASCII format.
+ (Trac# 3082, git 1b434debfbf4a43070eb480fa0975a6eff6429d4)
+
+661. [func] stephen
+ Copy additional header files to the BIND 10 installation directory
+ to allow the building of DHCP hooks libraries against an installed
+ version of BIND 10.
+ (Trac #3092, git e9beef0b435ba108af9e5979476bd2928808b342)
+
+660. [func] fujiwara
+ src/lib/cc: Integer size of C++ CC library is changed to int64_t.
+ b10-auth: The size of statistics counters is changed to uint64_t.
+ b10-auth sends lower 63 bit of counter values to b10-stats.
+ (Trac #3015, git e5b3471d579937f19e446f8a380464e0fc059567
+ and Trac #3016, git ffbcf9833ebd2f1952664cc0498608b988628d53)
+
+659. [func] stephen
+ Added capability to configure the hooks libraries for the
+ b10-dhcp4 and b10-dhcp6 servers through the BIND 10
+ configuration mechanism.
+ (Trac #2981, git aff6b06b2490fe4fa6568e7575a9a9105cfd7fae)
+
+658. [func]* vorner
+ The resolver, being experimental, is no longer installed by default.
+ If you really want to use it, even when it is known to be buggy, use
+ the ./configure --enable-experimental-resolver option.
+ (Trac #3064, git f5f07c976d2d42bdf80fea4433202ecf1f260648)
+
+657. [bug] vorner
+ Due to various problems with older versions of boost and
+ shared memory, the server rejects to compile with combination
+ of boost < 1.48 and shared memory enabled. Most users don't
+ need shared memory, admins of large servers are asked to
+ upgrade boost.
+ (Trac #3025, git 598e458c7af7d5bb81131112396e4c5845060ecd)
+
+656. [func] tomek
+ Additional hooks (buffer6_receive, lease6_renew,
+ lease6_release, buffer6_send) added to the DHCPv6 server.
+ (Trac #2984, git 540dd0449121094a56f294c500c2ed811f6016b6)
+
+655. [func] tmark
+ Added D2UpdateMgr class to b10-dhcp-ddns. This class is
+ the b10-dhcp-ddns task master, instantiating and supervising
+ transactions that carry out the DNS updates needed to
+ fulfill the requests (NameChangeRequests) received from
+ b10-dhcp-ddns clients (e.g. DHCP servers).
+ (Trac #3059 git d72675617d6b60e3eb6160305738771f015849ba)
+
+654. [bug] stephen
+ Always clear "skip" flag before calling any callouts on a hook.
+ (Trac# 3050, git ff0b9b45869b1d9a4b99e785fbce421e184c2e93)
+
+653. [func] tmark
+ Added initial implementation of D2QueueMgr to
+ b10-dhcp-ddns. This class manages the receipt and
+ queueing of requests received by b10-dhcp-ddns from
+ its clients (e.g. DHCP servers)
+ (Trac# 3052, git a970f6c5255e000c053a2dc47926cea7cec2761c)
+
+652. [doc] stephen
+ Added the "Hook Developer's Guide" to the BIND 10 developer
+ documentation.
+ (Trac# 2982, git 26a805c7e49a9ec85ee825f179cda41a2358f4c6)
+
+651. [bug] muks
+ A race condition when creating cmdctl certificates caused corruption
+ of these certificates in rare cases. This has now been fixed.
+ (Trac# 2962, git 09f557d871faef090ed444ebeee7f13e142184a0)
+
+650. [func] muks
+ The DomainTree rebalancing code has been updated to be more
+ understandable. This ChangeLog entry is made just to make a note
+ of this change. The change should not cause any observable
+ difference whatsoever.
+ (Trac# 2811, git 7c0bad1643af13dedf9356e9fb3a51264b7481de)
+
+649. [func] muks
+ The default b10-xfrout also_notify port has been changed from
+ 0 to 53.
+ (Trac# 2925, git 8acbf043daf590a9f2ad003e715cd4ffb0b3f979)
+
+648. [func] tmark
+ Moved classes pertaining to sending and receiving
+ NameChangeRequests from src/bin/d2 into their own library,
+ libdhcp_ddns, in src/lib/dhcp_ddns. This allows the
+ classes to be shared between DHDCP-DDNS and its clients,
+ such as the DHCP servers.
+ (Trac# 3065, git 3d39bccaf3f0565152ef73ec3e2cd03e77572c56)
+
+647. [func] tmark
+ Added initial implementation of classes for sending
+ and receiving NameChangeRequests between DHCP-DDNS
+ and its clients such as DHCP. This includes both
+ abstract classes and a derivation which traffics
+ requests across UDP sockets.
+ (Trac #3008, git b54530b4539cec4476986442e72c047dddba7b48)
+
+646. [func] stephen
+ Extended the hooks framework to add a "validate libraries" function.
+ This will be used to check libraries specified during BIND 10
+ configuration.
+ (Trac #3054, git 0f845ed94f462dee85b67f056656b2a197878b04)
+
+645. [func] tomek
+ Added initial set of hooks (pkt4_receive, subnet4_select,
+ lease4_select, pkt4_send) to the DHCPv6 server.
+ (Trac #2994, git be65cfba939a6a7abd3c93931ce35c33d3e8247b)
+
+644. [func] marcin
+ b10-dhcp4, b10-dhcp6: Implemented selection of the interfaces
+ that server listens on, using Configuration Manager. It is
+ possible to specify interface names explicitly or use asterisk
+ to specify that server should listen on all available interfaces.
+ Sockets are reopened according to the new configuration as
+ soon as it is committed.
+ (Trac #1555, git f48a3bff3fbbd15584d788a264d5966154394f04)
+
+643. [bug] muks
+ When running some unittests as root that depended on insufficient
+ file permissions, the tests used to fail because the root user
+ could still access such files. Such tests are now skipped when
+ they are run as the root user.
+ (Trac #3056, git 92ebabdbcf6168666b03d7f7fbb31f899be39322)
+
+642. [func] tomek
+ Added initial set of hooks (pkt6_receive, subnet6_select,
+ lease6_select, pkt6_send) to the DHCPv6 server.
+ (Trac #2995, git d6de376f97313ba40fef989e4a437d184fdf70cc)
+
+641. [func] stephen
+ Added the hooks framework. This allows shared libraries of
+ user-written functions to be loaded at run-time and the
+ functions called during packet processing.
+ (Trac #2980, git 82c997a72890a12af135ace5b9ee100e41c5534e)
+
+640. [func] marcin
+ b10-dhcp-ddns: Implemented DNSClient class which implements
+ asynchronous DNS updates using UDP. The TCP and TSIG support
+ will be implemented at later time. Nevertheless, class API
+ accommodates the use of TCP and TSIG.
+ (Trac #2977, git 5a67a8982baa1fd6b796c063eeb13850c633702c)
+
+639. [bug] muks
+ Added workaround for build failure on Fedora 19 between GCC 4.8.x
+ and boost versions less than 1.54. Fedora 19 currently ships
+ boost-1.53.
+ (Trac #3039, git 4ef6830ed357ceb859ebb3e5e821a064bd8797bb)
+
+638. [bug]* naokikambe
+ Per-zone statistics counters are distinguished by zone class,
+ e.g. IN, CH, and HS. A class name is added onto a zone name in
+ structure of per-zone statistics.
+ (Trac #2884, git c0153581c3533ef045a92e68e0464aab00947cbb)
+
+637. [func] tmark
+ Added initial implementation of NameChangeRequest,
+ which embodies DNS update requests sent to DHCP-DDNS
+ by its clients.
+ (trac3007 git f33bdd59c6a8c8ea883f11578b463277d01c2b70)
+
+636. [func] tmark
+ Added the initial implementation of configuration parsing for
+ DHCP-DDNS.
+ (Trac #2957, git c04fb71fa44c2a458aac57ae54eeb1711c017a49)
+
+635. [func] marcin
+ b10-dhcp-ddns: Implemented DNS Update message construction.
+ (Trac #2796, git eac5e751473e238dee1ebf16491634a1fbea25e2)
+
+634. [bug] muks
+ When processing DDNS updates, we now check the zone more
+ thoroughly with the received zone data updates to check if it is
+ valid. If the zone fails validation, we reply with SERVFAIL
+ rcode. So, while previously we may have allowed more zone data
+ cases without checking which resulted in invalid zones, such
+ update requests are now rejected.
+ (Trac #2759, git d8991bf8ed720a316f7506c1dd9db7de5c57ad4d)
+
+633. [func] jinmei
+ b10-memmgr: a new BIND 10 module that manages shared memory
+ segments for DNS zone data. At this point it's runnable but does
+ nothing really meaningful for end users; it was added to the
+ master branch for further development.
+ (Trac #2854, git d05d7aa36d0f8f87b94dba114134b50ca37eabff)
+
+632. [bug] marcin
+ perfdhcp: Fixed a bug in whereby the application was sporadically
+ crashing when timed out packets were garbage collected.
+ (Trac #2979, git 6d42b333f446eccc9d0204bcc04df38fed0c31db)
+
+631. [bug] muks
+ Applied a patch by Tomas Hozza to fix a couple of compile errors
+ on Fedora 19 development release.
+ (Trac #3001, git 6e42b90971b377261c72d51c38bf4a8dc336664a)
+
+630. [bug] muks
+ If there is a problem loading the backend module for a type of
+ data source, b10-auth would not serve any zones. This behaviour
+ has been changed now so that it serves zones from all other usable
+ data sources that were configured.
+ (Trac #2947, git 9a3ddf1e2bfa2546bfcc7df6d9b11bfbdb5cf35f)
+
+629. [func] stephen
+ Added first part of the hooks framework.
+ (Trac #2794, git d2b107586db7c2deaecba212c891d231d7e54a07)
+
+628. [func] y-aharen
+ b10-auth: A new statistics item 'qryrecursion' has been introduced.
+ The counter is for the number of queries (OpCode=Query) with Recursion
+ Desired (RD) bit on.
+ (Trac #2796, git 3d291f42cdb186682983aa833a1a67cb9e6a8434)
+
+627. [func] tmark
+ Logger name for DHCP-DDNS has been changed from "d2_logger" to
+ "dhcpddns". In addition, its log messages now use two suffixes,
+ DCTL_ for logs the emanate from the underlying base classes, and
+ DHCP_DDNS_ for logs which emanate from DHCP-DDNS specific code
+ (Trac #2978, git 5aec5fb20b0486574226f89bd877267cb9116921)
+
+626. [func] tmark
+ Created the initial implementation of DHCP-DDNS service
+ controller class, D2Controller, and the abstract class from
+ which it derives,DControllerBase. D2Controller manages the
+ lifecycle and BIND10 integration of the DHCP-DDNS application
+ process, D2Process. Also note, module name is now
+ b10-dhcp-ddns.
+ (Trac #2956, git a41cac582e46213c120b19928e4162535ba5fe76)
+
+625. [bug]* jinmei
+ b10-xfrin/b10-loadzone: b10-xfrin now refers to the unified
+ "data_sources" module configuration instead of almost-deprecated
+ the Auth/database_file configuration (Note: zonemgr still uses the
+ latter, so a secondary server would still need it for the moment).
+ Due to this change, b10-xfrin does not auto-generate an initial
+ zone for the very first transfer anymore; b10-loadzone has been
+ extended with a new -e option for the initial setup.
+ (Trac #2946, git 8191aec04c5279c199909f00f0a0b2b8f7bede94)
+
+624. [bug] jinmei
+ logging: prevented multiple BIND 10 processes from generating
+ multiple small log files when they dumped logs to files and try
+ to roll over them simultaneously. This fix relies on a feature of
+ underling logging library (log4cplus) version 1.1.0 or higher,
+ so the problem can still happen if BIND 10 is built with an older
+ version of log4cplus. (But this is expected to happen rarely in
+ any case unless a verbose debug level is specified).
+ (Trac #1622, git 5da8f8131b1224c99603852e1574b2a1adace236)
+
+623. [func] tmark
+ Created the initial, bare-bones implementation of DHCP-DDNS
+ service process class, D2Process, and the abstract class
+ from which it derives, DProcessBase. D2Process will provide
+ the DHCP-DDNS specific event loop and business logic.
+ (Trac #2955, git dbe4772246039a1257b6492936fda2a8600cd245)
+
+622. [func]* jinmei
+ b10-xfrin now has tighter control on the choice of IXFR or AXFR
+ through zones/request_ixfr configuration item. It includes
+ the new "IXFR only" behavior for some special cases. b10-xfrin
+ now also uses AXFR whenever necessary, so it is now safe to try
+ IXFR by default and it's made the default. The previous
+ use_ixfr configuration item was deprecated and triggers startup
+ failure if specified; configuration using use_ixfr should be
+ updated.
+ (Trac #2911, git 8118f8e4e9c0ad3e7b690bbce265a163e4f8767a)
+
+621. [func] team
+ libdns++: All Rdata classes now use the generic lexer in
+ constructors from text. This means that the name fields in such
+ RRs in a zone file can now be non-absolute (the origin name in that
+ context will be used), e.g., when loaded by b10-loadzone. Note
+ that the existing string constructors for these Rdata classes also
+ use the generic lexer, and they now expect an absolute name (with
+ the trailing '.') in the name fields.
+ (Trac #2522, git ea97070cf6b41299351fc29af66fa39c6465d56a)
+ (Trac #2521, git c6603decaadcd33ccf9aee4a7b22447acec4b7f6)
+ (See also ChangeLog 594, 564, 545)
+
+620. [bug] jinmei
+ b10-auth now returns SERVFAIL to queries for a zone that is
+ configured to be loaded in-memory but isn't due to load time
+ errors (missing zone file or errors in the zone file, etc).
+ Such zones were previously treated as non existent and would
+ result in REFUSED or unintentional match against less specific
+ zones. The revised behavior is also compatible with BIND 9.
+ (Trac #2905, git 56ee9810fdfb5f86bd6948e6bf26545ac714edd8)
+
+619. [bug] jinmei
+ b10-xfrout now uses blocking send for xfr response messages
+ to prevent abrupt termination of the stream due to a slower
+ client or narrower network bandwidth.
+ (Trac #2934, git bde0e94518469557c8b455ccbecc079a38382afd)
+
+618. [func]* marcin
+ b10-dhcp4: Added the ability for the server to respond to a
+ directly connected client which does not yet have an IP address.
+ On Linux, the server will unicast the response to the client's
+ hardware address and the 'yiaddr' (the client's new IP
+ address). Sending a response to the unicast address prevents other
+ (not interested) hosts from receiving the server response. This
+ capability is not yet implemented on non-Linux Operating Systems
+ where, in all cases, the server responds to the broadcast
+ address. The logic conforms to section 4.1 of RFC 2131.
+ (Trac #2902, git c2d40e3d425f1e51647be6a717c4a97d7ca3c29c)
+
+617. [bug] marcin
+ b10-dhcp4: Fixed a bug whereby the domain-name option was encoded
+ as FQDN (using technique described in RFC1035) instead of a string.
+ Also, created new class which represents an option carrying a single
+ string value. This class is now used for all standard options of
+ this kind.
+ (Trac #2786, git 96b1a7eb31b16bf9b270ad3d82873c0bd86a3530)
+
+616. [doc] stephen
+ Added description to the DHCP "Database Back-Ends" section of the
+ BIND 10 Developer's Guide about how to set up a MySQL database for
+ testing the DHCP MySQL backend.
+ (Trac #2653, git da3579feea036aa2b7d094b1c260a80a69d2f9aa)
+
+615. [bug] jinmei
+ b10-auth: Avoid referencing to a freed object when authoritative
+ server addresses are reconfigured. It caused a crash on a busy
+ server during initial startup time, and the same crash could also
+ happen if listen_on parameters are reconfigured at run time.
+ (Trac #2946, git d5f2a0d0954acd8bc33aabb220fab31652394fcd)
+
+614. [func] tmark
+ b10-d2: Initial DHCP-DDNS (a.k.a. D2) module implemented.
+ Currently it does nothing useful, except for providing the
+ skeleton implementation to be expanded in the future.
+ (Trac #2954, git 392c5ec5d15cd8c809bc9c6096b9f2bfe7b8c66a)
+
+613. [func] jinmei
+ datasrc: Error handling in loading zones into memory is now more
+ consistent and convenient: data source configuration does not fail
+ due to zones configured to be loaded into memory but not available
+ in the data source, just like the case of missing zone file for
+ the MasterFiles type of data source. Also, zones that aren't
+ loaded into memory due to errors can now be reloaded for b10-auth
+ using the bindctl Auth loadzone command after fixing the error,
+ without reconfiguring the entire data source.
+ (Trac #2851, git a3d4fe8a32003534150ed076ea0bbf80e1fcc43c)
+
+612. [func] tomek
+ b10-dhcp6: Support for relayed DHCPv6 traffic has been added.
+ (Trac #2898, git c3f6b67fa16a07f7f7ede24dd85feaa7c157e1cb)
+
+611. [func] naokikambe
+ Added Xfrin statistics items such as the number of successful
+ transfers. These are per-zone type counters. Their values can be
+ obtained with zone names by invoking "Stats show Xfrin" via bindctl
+ while Xfrin is running.
+ (Trac #2252, git e1a0ea8ef5c51b9b25afa111fbfe9347afbe5413)
+
+bind10-1.1.0beta2 released on May 10, 2013
+
+610. [bug] muks
+ When the sqlite3 program is not available on the system (in
+ PATH), we no longer attempt to run some tests which depend
+ on it.
+ (Trac #1909, git f85b274b85b57a094d33ca06dfbe12ae67bb47df)
+
+609. [bug] jinmei
+ Handled some rare error cases in DNS server classes correctly.
+ This fix specifically solves occasional crash of b10-auth due to
+ errors caused by TCP DNS clients. Also, as a result of cleanups
+ with the fix, b10-auth should now be a little bit faster in
+ handling UDP queries: in some local experiments it ran about 5%
+ faster.
+ (Trac #2903, git 6d3e0f4b36a754248f8a03a29e2c36aef644cdcc)
+
+608. [bug] jinmei
+ b10-cmdctl: fixed a hangup problem on receiving the shutdown
+ command from bindctl. Note, however, that cmdctl is defined as
+ a "needed" module by default, so shutting down cmdctl would cause
+ shutdown of the entire BIND 10 system anyway, and is therefore
+ still not very useful in practice.
+ (Trac #2712, git fa392e8eb391a17d30550d4b290c975710651d98)
+
+607. [bug] jinmei
+ Worked around some unit test regressions on FreeBSD 9.1 due to
+ a binary compatibility issue between standard and system
+ libraries (http://www.freebsd.org/cgi/query-pr.cgi?pr=175453).
+ While not all tests still pass, main BIND 10 programs should
+ generally work correctly. Still, there can be odd run time
+ behavior such as abrupt crash instead of graceful shutdown
+ when some fatal event happens, so it's generally discouraged to
+ use BIND 10 on FreeBSD 9.1 RELEASE. According to the above
+ bug report for FreeBSD, it seems upgrading or downgrading the
+ FreeBSD version will solve this problem.
+ (Trac #2887, git 69dfb4544d9ded3c10cffbbfd573ae05fdeb771f)
+
+606. [bug] jinmei
+ b10-xfrout now correctly stops sending notify requests once it
+ receives a valid response. It previously handled it as if the
+ requests are timed out and resent it a few times in a short
+ period.
+ (Trac #2879, git 4c45f29f28ae766a9f7dc3142859f1d0000284e1)
+
+605. [bug] tmark
+ Modified perfdhcp to calculate the times displayed for packet sent
+ and received as time elapsed since perfdhcp process start time.
+ Previously these were times since the start of the epoch.
+ However the large numbers involved caused loss of precision
+ in the calculation of the test statistics.
+ (Trac #2785, git e9556924dcd1cf285dc358c47d65ed7c413e02cf)
+
+604. [func] marcin
+ libdhcp++: abstracted methods which open sockets and send/receive
+ DHCP4 packets to a separate class. Other classes will be derived
+ from it to implement OS-specific methods of DHCPv4 packets filtering.
+ The primary purpose for this change is to add support for Direct
+ DHCPv4 response to a client which doesn't have an address yet on
+ different OSes.
+ (Trac #991, git 33ffc9a750cd3fb34158ef676aab6b05df0302e2)
+
+603. [func] tmark
+ The directory in which the b10-dhcp4 and b10-dhcp6 server id files has
+ been changed from the local state directory (set by the "configure"
+ --localstatedir switch) to the "bind10" subdirectory of it. After an
+ upgrade, server id files in the former location will be orphaned and
+ should be manually removed.
+ (Trac #2770, git a622140d411b3f07a68a1451e19df36118a80650)
+
+602. [bug] tmark
+ Perfdhcp will now exit gracefully if the command line argument for
+ IP version (-4 or -6) does not match the command line argument
+ given for the server. Prior to this perfdhcp would core when given
+ an IP version of -6 but a valid IPv4 address for server.
+ (Trac #2784, git 96b66c0c79dccf9a0206a45916b9b23fe9b94f74)
+
+601. [bug]* jinmei, vorner
+ The "delete record" interface of the database based data source
+ was extended so that the parameter includes reversed name in
+ addition to the actual name. This may help the underlying
+ accessor implementation if reversed names are more convenient
+ for the delete operation. This was the case for the SQLite3
+ accessor implementation, and it now performs delete operations
+ much faster. At a higher level, this means IXFR and DDNS Updates
+ to the sqlite3 database are no longer so slow on large zones as
+ they were before.
+ (Trac #2877, git 33bd949ac7288c61ed0a664b7329b50b36d180e5)
+
+600. [bug] tmark
+ Changed mysql_lease_mgr to set the SQL mode option to STRICT. This
+ causes mysql it to treat invalid input data as an error. Rather than
+ "successfully" inserting a too large value by truncating it, the
+ insert will fail, and the lease manager will throw an exception.
+ Also, attempts to create a HWAddr (hardware address) object with
+ too long an array of data now throw an exception.
+ (Trac #2387, git cac02e9290600407bd6f3071c6654c1216278616)
+
+599. [func] tomek
+ libdhcp++: Pkt6 class is now able to parse and build relayed DHCPv6
+ messages.
+ (Trac #2827, git 29c3f7f4e82d7e85f0f5fb692345fd55092796b4)
+
+bind10-1.1.0beta1 released on April 4, 2013
+
+598. [func]* jinmei
+ The separate "static" data source is now deprecated as it can be
+ served in the more generic "MasterFiles" type of data source.
+ This means existing configuration may not work after an update.
+ If "config show data_sources/classes/CH[0]" on bindctl contains a
+ "static" type of data source, you'll need to update it as follows:
+ > config set data_sources/classes/CH[0]/type MasterFiles
+ > config set data_sources/classes/CH[0]/params {"BIND": =>
+ "<the value of current data_sources/classes/CH[0]/params>"}
+ > config set data_sources/classes/CH[0]/cache-enable true
+ > config commit
+ (Same for CH[1], CH[2], IN[0], etc, if applicable, although it
+ should be very unlikely in practice. Also note: '=>' above
+ indicates the next line is actually part of the command. Do
+ not type in this "arrow").
+ (Part of Trac #2833, git 0363b4187fe3c1a148ad424af39e12846610d2d7)
+
+597. [func] tmark
+ b10-dhcp6: Added unit tests for handling requests when no
+ IPv6 subnets are configured/defined. Testing these conditions
+ was overlooked during implementation of Trac #2719.
+ (Trac #2721, git ce7f53b2de60e2411483b4aa31c714763a36da64)
+
+596. [bug] jinmei
+ Added special handling for the case where b10-auth receives a
+ NOTIFY message, but zonemgr isn't running. Previously this was
+ logged as a communications problem at the ERROR level, resulting
+ in increasing noise when zonemgr is intentionally stopped. Other
+ than the log level there is no change in externally visible
+ behavior.
+ (Trac #2562, git 119eed9938b17cbad3a74c823aa9eddb7cd337c2)
+
+595. [bug] tomek
+ All DHCP components now gracefully refuse to handle too short
+ DUIDs and client-id.
+ (Trac #2723, git a043d8ecda6aff57922fe98a33c7c3f6155d5d64)
+
+594. [func] muks, pselkirk
+ libdns++: the NSEC, DS, DLV, and AFSDB Rdata classes now use the
+ generic lexer in constructors from text. This means that the name
+ fields in such RRs in a zone file can now be non-absolute (the
+ origin name in that context will be used), e.g., when loaded by
+ b10-loadzone.
+ (Trac #2386, git dc0f34afb1eccc574421a802557198e6cd2363fa)
+ (Trac #2391, git 1450d8d486cba3bee8be46e8001d66898edd370c)
+
+593. [func] jelte
+ Address + port output and logs is now consistent according to our
+ coding guidelines, e.g. <address>:<port> in the case of IPv4, and
+ [<address>]:<port> in the case of IPv6, instead of <address>#<port>
+ (Trac #1086, git bcefe1e95cdd61ee4a09b20522c3c56b315a1acc)
+
+592. [bug] jinmei
+ b10-auth and zonemgr now handle some uncommon NOTIFY messages more
+ gracefully: auth immediately returns a NOTAUTH response if the
+ server does not have authority for the zone (the behavior
+ compatible with BIND 9) without bothering zonemgr; zonemgr now
+ simply skips retransfer if the specified zone is not in its
+ secondary zone list, instead of producing noisy error logs.
+ (Trac #1938, git 89d7de8e2f809aef2184b450e7dee1bfec98ad14)
+
+591. [func] vorner
+ Ported the remaining tests from the old shell/perl based system to
+ lettuce. Make target `systest' is now gone. Currently, the lettuce
+ tests are in git only, not part of the release tarball.
+ (Trac #2624, git df1c5d5232a2ab551cd98b77ae388ad568a683ad)
+
+590. [bug] tmark
+ Modified "include" statements in DHCP MySQL lease manager code to
+ fix build problems if MySQL is installed in a non-standard location.
+ (Trac #2825, git 4813e06cf4e0a9d9f453890557b639715e081eca)
+
+589. [bug] jelte
+ b10-cmdctl now automatically re-reads the user accounts file when
+ it is updated.
+ (Trac #2710, git 16e8be506f32de668699e6954f5de60ca9d14ddf)
+
+588. [bug]* jreed
+ b10-xfrout: Log message id XFROUT_QUERY_QUOTA_EXCCEEDED
+ changed to XFROUT_QUERY_QUOTA_EXCEEDED.
+ (git be41be890f1349ae4c870a887f7acd99ba1eaac5)
+
+587. [bug] jelte
+ When used from python, the dynamic datasource factory now
+ explicitly loads the logging messages dictionary, so that correct
+ logging messages does not depend on incidental earlier import
+ statements. Also, the sqlite3-specific log messages have been moved
+ from the general datasource library to the sqlite3 datasource
+ (which also explicitly loads its messages).
+ (Trac #2746, git 1c004d95a8b715500af448683e4a07e9b66ea926)
+
+586. [func] marcin
+ libdhcp++: Removed unnecessary calls to the function which
+ validates option definitions used to create instances of options
+ being decoded in the received packets. Eliminating these calls
+ lowered the CPU utilization by the server by approximately 10%.
+ Also, added the composite search indexes on the container used to
+ store DHCP leases by Memfile backend. This resulted in the
+ significant performance rise when using this backend to store
+ leases.
+ (Trac #2701, git b96a30b26a045cfaa8ad579b0a8bf84f5ed4e73f)
+
+585. [func] jinmei, muks
+ The zone data loader now accepts RRs in any order during load.
+ Before it used to reject adding non-consecutive RRsets. It
+ expected records for a single owner name and its type to be
+ grouped together. These restrictions are now removed. It now also
+ suppresses any duplicate RRs in the zone file when loading them
+ into memory.
+ (Trac #2440, git 232307060189c47285121f696d4efb206f632432)
+ (Trac #2441, git 0860ae366d73314446d4886a093f4e86e94863d4)
+
+584. [bug] jinmei
+ Fixed build failure with Boost 1.53 (and probably higher) in the
+ internal utility library. Note that with -Werror it may still
+ fail, but it's due to a Boost bug that is reportedly fixed in their
+ development trunk. See https://svn.boost.org/trac/boost/ticket/8080
+ Until the fix is available in a released Boost version you may need
+ to specify the --without-werror configure option to build BIND 10.
+ (Trac #2764, git ca1da8aa5de24358d7d4e7e9a4625347457118cf)
+
+583. [func]* jelte
+ b10-cmdctl-usermgr has been updated and its options and arguments
+ have changed; it now defaults to the same accounts file as
+ b10-cmdctl defaults to. It can now be used to remove users from the
+ accounts file as well, and it now accepts command-line arguments to
+ specify the username and password to add or remove, in which case
+ it will not prompt for them.
+ Note that using a password on the command line is not recommended,
+ as this can be viewed by other users.
+ (Trac #2713, git 9925af3b3f4daa47ba8c2eb66f556b01ed6f0502)
+
582. [func] naokikambe
New statistics items related unixdomain sockets added into Xfrout :
open, openfail, close, bindfail, acceptfail, accept, senderr, and
@@ -378,7 +1019,7 @@ bind10-1.0.0-beta released on December 20, 2012
531. [func] tomek
b10-dhcp6: Added support for expired leases. Leases for IPv6
addresses that are past their valid lifetime may be recycled, i.e.
- rellocated to other clients if needed.
+ relocated to other clients if needed.
(Trac #2327, git 62a23854f619349d319d02c3a385d9bc55442d5e)
530. [func]* team
@@ -2800,7 +3441,7 @@ bind10-devel-20110322 released on March 22, 2011
183. [bug] jerry
src/bin/xfrout: Enable parallel sessions between xfrout server and
- muti-Auth. The session needs to be created only on the first time
+ multi-Auth. The session needs to be created only on the first time
or if an error occur.
(Trac #419, git 1d60afb59e9606f312caef352ecb2fe488c4e751)
@@ -3849,9 +4490,10 @@ bind10-devel-20100421 released on April 21, 2010
bind10-devel-20100319 released on March 19, 2010
-For complete code revision history, see http://bind10.isc.org/browser
+For complete code revision history, see
+ http://git.bind10.isc.org/cgi-bin/cgit.cgi/bind10
Specific git changesets can be accessed at:
- http://bind10.isc.org/changeset/?reponame=&old=rrrr^&new=rrrr
+ http://git.bind10.isc.org/cgi-bin/cgit.cgi/bind10/commit/?id=rrr
or after cloning the original git repository by executing:
% git diff rrrr^ rrrr
Subversion changesets are not accessible any more. The subversion
diff --git a/Makefile.am b/Makefile.am
index fe995a7..2b7d149 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2,7 +2,7 @@ ACLOCAL_AMFLAGS = -I m4macros -I examples/m4 ${ACLOCAL_FLAGS}
# ^^^^^^^^ This has to be the first line and cannot come later in this
# Makefile.am due to some bork in some versions of autotools.
-SUBDIRS = compatcheck doc . src tests
+SUBDIRS = compatcheck doc . src tests m4macros ext
USE_LCOV=@USE_LCOV@
LCOV=@LCOV@
GENHTML=@GENHTML@
@@ -46,7 +46,7 @@ endif
clean-cpp-coverage:
@if [ $(USE_LCOV) = yes ] ; then \
$(LCOV) --directory . --zerocounters; \
- rm -rf coverage/; \
+ rm -rf $(abs_top_srcdir)/coverage-cpp-html/; \
else \
echo "C++ code coverage not enabled at configuration time." ; \
echo "Use: ./configure --with-lcov" ; \
@@ -110,16 +110,12 @@ report-coverage: report-cpp-coverage report-python-coverage
# for static C++ check using cppcheck (when available)
cppcheck:
- cppcheck --enable=all --suppressions src/cppcheck-suppress.lst --inline-suppr \
+ cppcheck -I./src/lib -I./src/bin --enable=all --suppressions \
+ src/cppcheck-suppress.lst --inline-suppr \
--quiet --error-exitcode=1 \
--template '{file}:{line}: check_fail: {message} ({severity},{id})' \
src
-# system tests
-systest:
- cd tests/system; \
- sh $(abs_srcdir)/tests/system/runall.sh
-
### include tool to generate documentation from log message specifications
### in the distributed tarball:
EXTRA_DIST = tools/system_messages.py
@@ -439,6 +435,10 @@ pkgconfig_DATA = dns++.pc
CLEANFILES = $(abs_top_builddir)/logger_lockfile
+# config.h may be included by headers supplied for building user-written
+# hooks libraries, so we need to include it in the distribution.
+pkginclude_HEADERS = config.h
+
if HAVE_GTEST_SOURCE
noinst_LIBRARIES = libgtest.a
libgtest_a_CXXFLAGS = $(GTEST_INCLUDES) $(AM_CXXFLAGS)
diff --git a/README b/README
index 70c6bee..f9b24c5 100644
--- a/README
+++ b/README
@@ -25,6 +25,12 @@ the DHCPv4 and DHCPv6 servers must be considered experimental.
Limitations and known issues with this DHCP release can be found
at http://bind10.isc.org/wiki/KeaKnownIssues
+NOTE: The API/ABI provided by libraries in BIND 10 may change in future
+point releases. So please do not assume currently that any code that you
+compile for a particular version of a BIND 10 library will work in
+future versions of the library. We aim to stabilize the public API/ABI
+interface of BIND 10 libraries in future releases.
+
Documentation is included with the source. See doc/guide/bind10-guide.txt
(or bind10-guide.html) for installation instructions. The
documentation is also available via the BIND 10 website at
diff --git a/configure.ac b/configure.ac
index f2bdf0b..e01f5cf 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, 20130221, bind10-dev at isc.org)
+AC_INIT(bind10, 20130529, bind10-dev at isc.org)
AC_CONFIG_SRCDIR(README)
# serial-tests is not available in automake version before 1.13. In
# automake 1.13 and higher, AM_PROG_INSTALL is undefined, so we'll check
@@ -129,7 +129,7 @@ AC_SUBST(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
# gcc specific settings:
if test "X$GXX" = "Xyes"; then
-B10_CXXFLAGS="$B10_CXXFLAGS -Wall -Wextra -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare"
+B10_CXXFLAGS="$B10_CXXFLAGS -Wall -Wextra -Wnon-virtual-dtor -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare"
case "$host" in
*-solaris*)
MULTITHREADING_FLAG=-pthreads
@@ -323,9 +323,9 @@ if test -x ${PYTHON}-config; then
# so we only go through the flag if it's contained; also, protecting
# the output with [] seems necessary for environment to avoid getting
# an empty output accidentally.
- python_config_ldflags=[`${PYTHON}-config --ldflags | sed -ne 's/\([ \t]*-L\)[ ]*\([^ \t]*[ \t]*\)/\1\2/pg'`]
+ python_config_ldflags=[`${PYTHON}-config --ldflags | ${SED} -ne 's/\([ \t]*-L\)[ ]*\([^ \t]*[ \t]*\)/\1\2/gp'`]
for flag in $python_config_ldflags; do
- flag=`echo $flag | sed -ne 's/^\(\-L.*\)$/\1/p'`
+ flag=`echo $flag | ${SED} -ne 's/^\(\-L.*\)$/\1/p'`
if test "X${flag}" != X; then
PYTHON_LDFLAGS="$PYTHON_LDFLAGS ${flag}"
fi
@@ -351,7 +351,7 @@ fi
if test "x$ISC_RPATH_FLAG" != "x"; then
python_rpath=
for flag in ${PYTHON_LDFLAGS}; do
- python_rpath="${python_rpath} `echo $flag | sed -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`"
+ python_rpath="${python_rpath} `echo $flag | ${SED} -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`"
done
PYTHON_LDFLAGS="${PYTHON_LDFLAGS} ${python_rpath}"
fi
@@ -388,8 +388,6 @@ In this case we will continue, but naming of python processes will not work.])
fi
fi
-# TODO: check for _sqlite3.py module
-
# (g++ only check)
# Python 3.2 has an unused parameter in one of its headers. This
# has been reported, but not fixed as of yet, so we check if we need
@@ -432,6 +430,7 @@ AC_SUBST(B10_CXXFLAGS)
AC_SEARCH_LIBS(inet_pton, [nsl])
AC_SEARCH_LIBS(recvfrom, [socket])
AC_SEARCH_LIBS(nanosleep, [rt])
+AC_SEARCH_LIBS(dlsym, [dl])
# Checks for header files.
@@ -471,6 +470,17 @@ AM_COND_IF([OS_BSD], [AC_DEFINE([OS_BSD], [1], [Running on BSD?])])
AM_CONDITIONAL(OS_SOLARIS, test $OS_TYPE = Solaris)
AM_COND_IF([OS_SOLARIS], [AC_DEFINE([OS_SOLARIS], [1], [Running on Solaris?])])
+# Deal with variants
+AM_CONDITIONAL(OS_FREEBSD, test $system = FreeBSD)
+AM_COND_IF([OS_FREEBSD], [AC_DEFINE([OS_FREEBSD], [1], [Running on FreeBSD?])])
+AM_CONDITIONAL(OS_NETBSD, test $system = NetBSD)
+AM_COND_IF([OS_NETBSD], [AC_DEFINE([OS_NETBSD], [1], [Running on NetBSD?])])
+AM_CONDITIONAL(OS_OPENBSD, test $system = OpenBSD)
+AM_COND_IF([OS_OPENBSD], [AC_DEFINE([OS_OPENBSD], [1], [Running on OpenBSD?])])
+AM_CONDITIONAL(OS_OSX, test $system = Darwin)
+AM_COND_IF([OS_OSX], [AC_DEFINE([OS_OSX], [1], [Running on OSX?])])
+
+
AC_MSG_CHECKING(for sa_len in struct sockaddr)
AC_TRY_COMPILE([
#include <sys/types.h>
@@ -536,7 +546,7 @@ if test "$lcov" != "no"; then
AC_MSG_ERROR([Cannot find lcov.])
fi
# is genhtml always in the same directory?
- GENHTML=`echo "$LCOV" | sed s/lcov$/genhtml/`
+ GENHTML=`echo "$LCOV" | ${SED} s/lcov$/genhtml/`
if test ! -x $GENHTML; then
AC_MSG_ERROR([genhtml not found, needed for lcov])
fi
@@ -712,15 +722,15 @@ fi
BOTAN_LDFLAGS=
BOTAN_NEWLIBS=
for flag in ${BOTAN_LIBS}; do
- BOTAN_LDFLAGS="${BOTAN_LDFLAGS} `echo $flag | sed -ne '/^\(\-L\)/p'`"
- BOTAN_LIBS="${BOTAN_LIBS} `echo $flag | sed -ne '/^\(\-l\)/p'`"
+ BOTAN_LDFLAGS="${BOTAN_LDFLAGS} `echo $flag | ${SED} -ne '/^\(\-L\)/p'`"
+ BOTAN_LIBS="${BOTAN_LIBS} `echo $flag | ${SED} -ne '/^\(\-l\)/p'`"
done
# See python_rpath for some info on why we do this
if test "x$ISC_RPATH_FLAG" != "x"; then
BOTAN_RPATH=
for flag in ${BOTAN_LIBS}; do
- BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | sed -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`"
+ BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | ${SED} -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`"
done
AC_SUBST(BOTAN_RPATH)
@@ -873,10 +883,14 @@ LIBS=$LIBS_SAVED
AX_BOOST_FOR_BIND10
# Boost offset_ptr is required in one library and not optional right now, so
# we unconditionally fail here if it doesn't work.
-if test "$BOOST_OFFSET_PTR_FAILURE" = "yes"; then
+if test "$BOOST_OFFSET_PTR_WOULDFAIL" = "yes" -a "$werror_ok" = 1; then
AC_MSG_ERROR([Failed to compile a required header file. Try upgrading Boost to 1.44 or higher (when using clang++) or specifying --without-werror. See the ChangeLog entry for Trac no. 2147 for more details.])
fi
+if test "$BOOST_STATIC_ASSERT_WOULDFAIL" = "yes" -a X"$werror_ok" = X1; then
+ AC_MSG_ERROR([Failed to use Boost static assertions. Try upgrading Boost to 1.54 or higher (when using GCC 4.8) or specifying --without-werror. See trac ticket no. 3039 for more details.])
+fi
+
# There's a known bug in FreeBSD ports for Boost that would trigger a false
# warning in build with g++ and -Werror (we exclude clang++ explicitly to
# avoid unexpected false positives).
@@ -884,6 +898,44 @@ if test "$BOOST_NUMERIC_CAST_WOULDFAIL" = "yes" -a X"$werror_ok" = X1 -a $CLANGP
AC_MSG_ERROR([Failed to compile a required header file. If you are using FreeBSD and Boost installed via ports, retry with specifying --without-werror. See the ChangeLog entry for Trac no. 1991 for more details.])
fi
+build_experimental_resolver=no
+AC_ARG_ENABLE(experimental-resolver,
+ [AC_HELP_STRING([--enable-experimental-resolver],
+ [enable building of the experimental resolver [default=no]])],
+ [build_experimental_resolver=$enableval])
+AM_CONDITIONAL([BUILD_EXPERIMENTAL_RESOLVER], [test "$build_experimental_resolver" = "yes"])
+if test "$build_experimental_resolver" = "yes"; then
+ BUILD_EXPERIMENTAL_RESOLVER=yes
+else
+ BUILD_EXPERIMENTAL_RESOLVER=no
+fi
+AC_SUBST(BUILD_EXPERIMENTAL_RESOLVER)
+
+use_shared_memory=yes
+AC_ARG_WITH(shared-memory,
+ AC_HELP_STRING([--with-shared-memory],
+ [Build with Boost shared memory support; for large scale authoritative DNS servers]),
+ [use_shared_memory=$withval])
+if test X$use_shared_memory = Xyes -a "$BOOST_MAPPED_FILE_WOULDFAIL" = "yes"; then
+ AC_MSG_ERROR([Boost shared memory does not compile on this system. If you don't need it (most normal users won't) build without it by rerunning this script with --without-shared-memory; using a different compiler or a different version of Boost may also help.])
+fi
+AM_CONDITIONAL([USE_SHARED_MEMORY], [test x$use_shared_memory = xyes])
+if test "x$use_shared_memory" = "xyes"; then
+ AC_DEFINE(USE_SHARED_MEMORY, 1, [Define to 1 if shared memory support is enabled])
+fi
+AC_SUBST(BOOST_MAPPED_FILE_CXXFLAG)
+
+if test "$BOOST_OFFSET_PTR_OLD" = "yes" -a "$use_shared_memory" = "yes" ; then
+ AC_MSG_ERROR([You're trying to compile against boost older than 1.48 with
+shared memory. Older versions of boost have a bug which causes segfaults in
+offset_ptr implementation when compiled by GCC with optimisations enabled.
+See ticket no. 3025 for details.
+
+Either update boost to newer version or use --without-shared-memory.
+Note that most users likely don't need shared memory support.
+])
+fi
+
# Add some default CPP flags needed for Boost, identified by the AX macro.
CPPFLAGS="$CPPFLAGS $CPPFLAGS_BOOST_THREADCONF"
@@ -1032,12 +1084,16 @@ AC_SUBST(GTEST_LDFLAGS)
AC_SUBST(GTEST_LDADD)
AC_SUBST(GTEST_SOURCE)
-dnl check for pkg-config itself so we don't try the m4 macro without pkg-config
+dnl check for pkg-config itself
AC_CHECK_PROG(HAVE_PKG_CONFIG, pkg-config, yes, no)
if test "x$HAVE_PKG_CONFIG" = "xno" ; then
AC_MSG_ERROR(Please install pkg-config)
fi
-PKG_CHECK_MODULES(SQLITE, sqlite3 >= 3.3.9, enable_features="$enable_features SQLite3")
+
+AX_SQLITE3_FOR_BIND10
+if test "x$have_sqlite" = "xyes" ; then
+ enable_features="$enable_features SQLite3"
+fi
#
# ASIO: we extensively use it as the C++ event management module.
@@ -1100,6 +1156,11 @@ if test "x$enable_generate_docs" != xno ; then
fi
AC_MSG_RESULT(yes)
fi
+
+ AC_PATH_PROG([ELINKS], [elinks])
+ if test -z "$ELINKS"; then
+ AC_MSG_ERROR("elinks not found; it is required for --enable-generate-docs")
+ fi
fi
@@ -1116,6 +1177,14 @@ AC_ARG_ENABLE(logger-checks, [AC_HELP_STRING([--enable-logger-checks],
AM_CONDITIONAL(ENABLE_LOGGER_CHECKS, test x$enable_logger_checks != xno)
AM_COND_IF([ENABLE_LOGGER_CHECKS], [AC_DEFINE([ENABLE_LOGGER_CHECKS], [1], [Check logger messages?])])
+# Check for asciidoc
+AC_PATH_PROG(ASCIIDOC, asciidoc, no)
+AM_CONDITIONAL(HAVE_ASCIIDOC, test "x$ASCIIDOC" != "xno")
+
+# Check for plantuml
+AC_PATH_PROG(PLANTUML, plantuml, no)
+AM_CONDITIONAL(HAVE_PLANTUML, test "x$PLANTUML" != "xno")
+
# Check for valgrind
AC_PATH_PROG(VALGRIND, valgrind, no)
AM_CONDITIONAL(HAVE_VALGRIND, test "x$VALGRIND" != "xno")
@@ -1155,6 +1224,11 @@ AM_COND_IF([HAVE_OPTRESET], [AC_DEFINE([HAVE_OPTRESET], [1], [Check for optreset
AC_CONFIG_FILES([Makefile
doc/Makefile
doc/guide/Makefile
+ doc/design/Makefile
+ doc/design/datasrc/Makefile
+ ext/Makefile
+ ext/asio/Makefile
+ ext/asio/asio/Makefile
compatcheck/Makefile
src/Makefile
src/bin/Makefile
@@ -1176,6 +1250,8 @@ AC_CONFIG_FILES([Makefile
src/bin/loadzone/Makefile
src/bin/loadzone/tests/Makefile
src/bin/loadzone/tests/correct/Makefile
+ src/bin/memmgr/Makefile
+ src/bin/memmgr/tests/Makefile
src/bin/msgq/Makefile
src/bin/msgq/tests/Makefile
src/bin/auth/Makefile
@@ -1188,8 +1264,11 @@ AC_CONFIG_FILES([Makefile
src/bin/dhcp6/tests/Makefile
src/bin/dhcp4/Makefile
src/bin/dhcp4/tests/Makefile
+ src/bin/d2/Makefile
+ src/bin/d2/tests/Makefile
src/bin/resolver/Makefile
src/bin/resolver/tests/Makefile
+ src/bin/resolver/bench/Makefile
src/bin/sysinfo/Makefile
src/bin/sockcreator/Makefile
src/bin/sockcreator/tests/Makefile
@@ -1204,6 +1283,7 @@ AC_CONFIG_FILES([Makefile
src/bin/stats/tests/Makefile
src/bin/stats/tests/testdata/Makefile
src/bin/usermgr/Makefile
+ src/bin/usermgr/tests/Makefile
src/bin/tests/Makefile
src/lib/Makefile
src/lib/asiolink/Makefile
@@ -1225,6 +1305,7 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/util/cio/tests/Makefile
src/lib/python/isc/datasrc/Makefile
src/lib/python/isc/datasrc/tests/Makefile
+ src/lib/python/isc/datasrc/tests/testdata/Makefile
src/lib/python/isc/dns/Makefile
src/lib/python/isc/cc/Makefile
src/lib/python/isc/cc/cc_generated/Makefile
@@ -1244,6 +1325,9 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/bind10/tests/Makefile
src/lib/python/isc/ddns/Makefile
src/lib/python/isc/ddns/tests/Makefile
+ src/lib/python/isc/memmgr/Makefile
+ src/lib/python/isc/memmgr/tests/Makefile
+ src/lib/python/isc/memmgr/tests/testdata/Makefile
src/lib/python/isc/xfrin/Makefile
src/lib/python/isc/xfrin/tests/Makefile
src/lib/python/isc/server_common/Makefile
@@ -1265,6 +1349,8 @@ AC_CONFIG_FILES([Makefile
src/lib/dns/benchmarks/Makefile
src/lib/dhcp/Makefile
src/lib/dhcp/tests/Makefile
+ src/lib/dhcp_ddns/Makefile
+ src/lib/dhcp_ddns/tests/Makefile
src/lib/dhcpsrv/Makefile
src/lib/dhcpsrv/tests/Makefile
src/lib/exceptions/Makefile
@@ -1278,7 +1364,11 @@ AC_CONFIG_FILES([Makefile
src/lib/datasrc/tests/memory/testdata/Makefile
src/lib/xfr/Makefile
src/lib/xfr/tests/Makefile
+ src/lib/hooks/Makefile
+ src/lib/hooks/tests/Makefile
src/lib/log/Makefile
+ src/lib/log/interprocess/Makefile
+ src/lib/log/interprocess/tests/Makefile
src/lib/log/compiler/Makefile
src/lib/log/tests/Makefile
src/lib/resolve/Makefile
@@ -1304,13 +1394,14 @@ AC_CONFIG_FILES([Makefile
src/lib/statistics/Makefile
src/lib/statistics/tests/Makefile
tests/Makefile
- tests/system/Makefile
tests/tools/Makefile
tests/tools/badpacket/Makefile
tests/tools/badpacket/tests/Makefile
tests/tools/perfdhcp/Makefile
tests/tools/perfdhcp/tests/Makefile
tests/tools/perfdhcp/tests/testdata/Makefile
+ tests/lettuce/Makefile
+ m4macros/Makefile
dns++.pc
])
AC_OUTPUT([doc/version.ent
@@ -1325,6 +1416,10 @@ AC_OUTPUT([doc/version.ent
src/bin/dbutil/run_dbutil.sh
src/bin/dbutil/tests/dbutil_test.sh
src/bin/ddns/ddns.py
+ src/bin/dhcp4/tests/marker_file.h
+ src/bin/dhcp4/tests/test_libraries.h
+ src/bin/dhcp6/tests/marker_file.h
+ src/bin/dhcp6/tests/test_libraries.h
src/bin/xfrin/tests/xfrin_test
src/bin/xfrin/xfrin.py
src/bin/xfrin/run_b10-xfrin.sh
@@ -1354,6 +1449,8 @@ AC_OUTPUT([doc/version.ent
src/bin/loadzone/loadzone.py
src/bin/usermgr/run_b10-cmdctl-usermgr.sh
src/bin/usermgr/b10-cmdctl-usermgr.py
+ src/bin/memmgr/memmgr.py
+ src/bin/memmgr/memmgr.spec.pre
src/bin/msgq/msgq.py
src/bin/msgq/run_msgq.sh
src/bin/auth/auth.spec.pre
@@ -1364,18 +1461,24 @@ AC_OUTPUT([doc/version.ent
src/bin/auth/gen-statisticsitems.py.pre
src/bin/dhcp4/spec_config.h.pre
src/bin/dhcp6/spec_config.h.pre
+ src/bin/d2/spec_config.h.pre
+ src/bin/d2/tests/test_data_files_config.h
src/bin/tests/process_rename_test.py
src/lib/config/tests/data_def_unittests_config.h
+ src/lib/dhcpsrv/tests/test_libraries.h
src/lib/python/isc/config/tests/config_test
src/lib/python/isc/cc/tests/cc_test
src/lib/python/isc/notify/tests/notify_out_test
src/lib/python/isc/log/tests/log_console.py
src/lib/python/isc/log_messages/work/__init__.py
+ src/lib/python/isc/server_common/bind10_server.py
src/lib/dns/gen-rdatacode.py
src/lib/python/bind10_config.py
src/lib/cc/session_config.h.pre
src/lib/cc/tests/session_unittests_config.h
src/lib/datasrc/datasrc_config.h.pre
+ src/lib/hooks/tests/marker_file.h
+ src/lib/hooks/tests/test_libraries.h
src/lib/log/tests/console_test.sh
src/lib/log/tests/destination_test.sh
src/lib/log/tests/init_logger_test.sh
@@ -1384,27 +1487,11 @@ AC_OUTPUT([doc/version.ent
src/lib/log/tests/logger_lock_test.sh
src/lib/log/tests/severity_test.sh
src/lib/log/tests/tempdir.h
+ src/lib/util/python/doxygen2pydoc.py
src/lib/util/python/mkpywrapper.py
src/lib/util/python/gen_wiredata.py
src/lib/server_common/tests/data_path.h
tests/lettuce/setup_intree_bind10.sh
- tests/system/conf.sh
- tests/system/run.sh
- tests/system/glue/setup.sh
- tests/system/glue/nsx1/b10-config.db
- tests/system/bindctl/nsx1/b10-config.db.template
- tests/system/ixfr/db.example.n0
- tests/system/ixfr/db.example.n2
- tests/system/ixfr/db.example.n2.refresh
- tests/system/ixfr/db.example.n4
- tests/system/ixfr/db.example.n6
- tests/system/ixfr/ixfr_init.sh
- tests/system/ixfr/b10-config.db
- tests/system/ixfr/common_tests.sh
- tests/system/ixfr/in-1/setup.sh
- tests/system/ixfr/in-2/setup.sh
- tests/system/ixfr/in-3/setup.sh
- tests/system/ixfr/in-4/setup.sh
], [
chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
chmod +x src/bin/xfrin/run_b10-xfrin.sh
@@ -1432,17 +1519,10 @@ AC_OUTPUT([doc/version.ent
chmod +x src/lib/log/tests/local_file_test.sh
chmod +x src/lib/log/tests/logger_lock_test.sh
chmod +x src/lib/log/tests/severity_test.sh
+ chmod +x src/lib/util/python/doxygen2pydoc.py
chmod +x src/lib/util/python/mkpywrapper.py
chmod +x src/lib/util/python/gen_wiredata.py
chmod +x src/lib/python/isc/log/tests/log_console.py
- chmod +x tests/system/conf.sh
- chmod +x tests/system/run.sh
- chmod +x tests/system/ixfr/ixfr_init.sh
- chmod +x tests/system/ixfr/common_tests.sh
- chmod +x tests/system/ixfr/in-1/setup.sh
- chmod +x tests/system/ixfr/in-2/setup.sh
- chmod +x tests/system/ixfr/in-3/setup.sh
- chmod +x tests/system/ixfr/in-4/setup.sh
])
AC_OUTPUT
diff --git a/doc/Doxyfile b/doc/Doxyfile
index 5c071c1..9967df2 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -661,34 +661,37 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
-INPUT = ../src/lib/exceptions \
+INPUT = ../src/bin/auth \
+ ../src/bin/d2 \
+ ../src/bin/dhcp4 \
+ ../src/bin/dhcp6 \
+ ../src/bin/resolver \
+ ../src/bin/sockcreator \
+ ../src/lib/acl \
+ ../src/lib/asiolink \
+ ../src/lib/bench \
+ ../src/lib/cache \
../src/lib/cc \
../src/lib/config \
../src/lib/cryptolink \
- ../src/lib/dns \
../src/lib/datasrc \
../src/lib/datasrc/memory \
- ../src/bin/auth \
- ../src/bin/resolver \
- ../src/lib/bench \
+ ../src/lib/dhcp \
+ ../src/lib/dhcp_ddns \
+ ../src/lib/dhcpsrv \
+ ../src/lib/dns \
+ ../src/lib/exceptions \
+ ../src/lib/hooks \
../src/lib/log \
../src/lib/log/compiler \
- ../src/lib/asiolink/ \
../src/lib/nsas \
- ../src/lib/testutils \
- ../src/lib/cache \
- ../src/lib/server_common/ \
- ../src/bin/sockcreator/ \
- ../src/lib/util/ \
- ../src/lib/util/io/ \
- ../src/lib/util/threads/ \
../src/lib/resolve \
- ../src/lib/acl \
+ ../src/lib/server_common \
../src/lib/statistics \
- ../src/bin/dhcp6 \
- ../src/lib/dhcp \
- ../src/lib/dhcpsrv \
- ../src/bin/dhcp4 \
+ ../src/lib/testutils \
+ ../src/lib/util \
+ ../src/lib/util/io \
+ ../src/lib/util/threads \
../tests/tools/perfdhcp \
devel
@@ -775,7 +778,7 @@ EXAMPLE_RECURSIVE = NO
# directories that contain image that are included in the documentation (see
# the \image command).
-IMAGE_PATH = ../doc/images
+IMAGE_PATH = ../doc/images ../src/lib/hooks/images
# The INPUT_FILTER tag can be used to specify a program that doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program
diff --git a/doc/Doxyfile-xml b/doc/Doxyfile-xml
new file mode 100644
index 0000000..ae5be8a
--- /dev/null
+++ b/doc/Doxyfile-xml
@@ -0,0 +1,7 @@
+# This is a doxygen configuration for generating XML output as well as HTML.
+#
+# Inherit everything from our default Doxyfile except GENERATE_XML, which
+# will be reset to YES
+
+ at INCLUDE = Doxyfile
+GENERATE_XML = YES
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 3120280..4af8188 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -1,6 +1,6 @@
-SUBDIRS = guide
+SUBDIRS = guide design
-EXTRA_DIST = version.ent.in differences.txt
+EXTRA_DIST = version.ent.in differences.txt Doxyfile Doxyfile-xml
devel:
mkdir -p html
diff --git a/doc/design/Makefile.am b/doc/design/Makefile.am
new file mode 100644
index 0000000..e0c888e
--- /dev/null
+++ b/doc/design/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = datasrc
diff --git a/doc/design/cc-protocol.txt b/doc/design/cc-protocol.txt
index 0129530..15db9c1 100644
--- a/doc/design/cc-protocol.txt
+++ b/doc/design/cc-protocol.txt
@@ -1,296 +1,185 @@
-protocol version 0x536b616e
+The CC protocol
+===============
-DATA 0x01
-HASH 0x02
-LIST 0x03
-NULL 0x04
-TYPE_MASK 0x0f
+We use our home-grown protocol for IPC between modules. There's a
+central daemon routing the messages.
-LENGTH_32 0x00
-LENGTH_16 0x10
-LENGTH_8 0x20
-LENGTH_MASK 0xf0
-
-
-MESSAGE ENCODING
-----------------
-
-When decoding, the entire message length must be known. If this is
-transmitted over a raw stream such as TCP, this is usually encoded
-with a 4-byte length followed by the message itself. If some other
-wrapping is used (say as part of a different message structure) the
-length of the message must be preserved and included for decoding.
-
-The first 4 bytes of the message is the protocol version encoded
-directly as a 4-byte value. Immediately following this is a HASH
-element. The length of the hash element is the remainder of the
-message after subtracting 4 bytes for the protocol version.
-
-This initial HASH is intended to be used by the message routing system
-if one is in use.
-
-
-ITEM TYPES
+Addressing
----------
-There are four basic types encoded in this protocol. A simple data
-blob (DATA), a tag-value series (HASH), an ordered list (LIST), and
-a NULL type (which is used internally to encode DATA types which are
-empty and can be used to indicate existance without data in a hash.)
-
-Each item can be of any type, so a hash of hashes and hashes of lists
-are typical.
-
-All multi-byte integers which are encoded in binary are in network
-byte order.
-
-
-ITEM ENCODING
--------------
-
-Each item is preceeded by a single byte which describes that item.
-This byte contains the item type and item length encoding:
-
- Thing Length Description
- ---------------- -------- ------------------------------------
- TyLen 1 byte Item type and length encoding
- Length variable Item data blob length
- Item Data variable Item data blob
-
-The TyLen field includes both the item data type and the item's
-length. The length bytes are encoded depending on the length of data
-portion, and the smallest data encoding type supported should be
-used. Note that this length compression is used just for data
-compactness. It is wasteful to encode the most common length (8-bit
-length) as 4 bytes, so this method allows one byte to be used rather
-than 4, three of which are nearly always zero.
-
-
-HASH
-----
-
-This is a tag/value pair where each tag is an opaque unique blob and
-the data elements are of any type. Hashes are not encoded in any
-specific tag or item order.
-
-The length of the HASH's data area is processed for tag/value pairs
-until the entire area is consumed. Running out of data prematurely
-indicates an incorrectly encoded message.
-
-The data area consists of repeated items:
-
- Thing Length Description
- ---------------- -------- ------------------------------------
- Tag Length 1 byte The length of the tag.
- Tag Variable The tag name
- Item Variable Encoded item
-
-The Tag Length field is always one byte, which limits the tag name to
-255 bytes maximum. A tag length of zero is invalid.
-
-
-LIST
-----
-
-A LIST is a list of items encoded and decoded in a specific order.
-The order is chosen entirely by the source curing encoding.
-
-The length of the LIST's data is consumed by the ITEMs it contains.
-Running out of room prematurely indicates an incorrectly encoded
-message.
-
-The data area consists of repeated items:
+Each connected client gets an unique address, called ``l-name''. A
+message can be sent directly to such l-name, if it is known to the
+sender.
- Thing Length Description
- -------------- ------ ----------------------------------------
- Item Variable Encoded item
+A client may subscribe to a group of communication. A message can be
+broadcasted to a whole group instead of a single client. There's also
+an instance parameter to addressing, but we didn't find any actual use
+for it and it is not used for anything. It is left in the default `*`
+for most of our code and should be done so in any new code. It wasn't
+priority to remove it yet.
+Wire format
+-----------
-DATA
-----
+Each message on the wire looks like this:
-A DATA item is a simple blob of data. No further processing of this
-data is performed by this protocol on these elements.
+ <message length><header length><header><body>
-The data blob is the entire data area. The data area can be 0 or more
-bytes long.
+The message length is 4-byte unsigned integer in network byte order,
+specifying the number of bytes of the rest of the message (eg. header
+length, header and body put together).
-It is typical to encode integers as strings rather than binary
-integers. However, so long as both sender and recipient agree on the
-format of the data blob itself, any blob encoding may be used.
+The header length is 2-byte unsigned integer in network byte order,
+specifying the length of the header.
+The header is a string representation of single JSON object. It
+specifies the type of message and routing information.
-NULL
-----
+The body is the payload of the message. It takes the whole rest of
+size of the message (so its length is message length - 2 - header
+length). The content is not examined by the routing daemon, but the
+clients expect it to be valid JSON object.
-This data element indicates no data is actually present. This can be
-used to indicate that a tag is present in a HASH but no data is
-actually at that location, or in a LIST to indicate empty item
-positions.
+The body may be empty in case the message is not to be routed to
+client, but it is instruction for the routing daemon. See message
+types below.
-There is no data portion of this type, and the encoded length is
-ignored and is always zero.
+The message is sent in this format to the routing daemon, the daemon
+optionally modifies the headers and delivers it in the same format to
+the recipient(s).
-Note that this is different than a DATA element with a zero length.
+The headers
+-----------
+The header object can contain following information:
-EXAMPLE
--------
-
-This is Ruby syntax, but should be clear enough for anyone to read.
-
-Example data encoding:
-
-{
- "from" => "sender at host",
- "to" => "recipient at host",
- "seq" => 1234,
- "data" => {
- "list" => [ 1, 2, nil, "this" ],
- "description" => "Fun for all",
- },
-}
-
-
-Wire-format:
-
-In this format, strings are not shown in hex, but are included "like
-this." Descriptions are written (like this.)
-
-Message Length: 0x64 (100 bytes)
-Protocol Version: 0x53 0x6b 0x61 0x6e
-(remaining length: 96 bytes)
-
-0x04 "from" 0x21 0x0b "sender at host"
-0x02 "to" 0x21 0x0e "recipient at host"
-0x03 "seq" 0x21 0x04 "1234"
-0x04 "data" 0x22
- 0x04 "list" 0x23
- 0x21 0x01 "1"
- 0x21 0x01 "2"
- 0x04
- 0x21 0x04 "this"
- 0x0b "description" 0x0b "Fun for all"
-
-
-MESSAGE ROUTING
----------------
-
-The message routing daemon uses the top-level hash to contain routing
-instructions and additional control data. Not all of these are
-required for various control message types; see the individual
-descriptions for more information.
-
- Tag Description
- ------- ----------------------------------------
- msg Sender-supplied data
- from sender's identity
- group Group name this message is being sent to
- instance Instance in this group
- repl if present, this message is a reply.
- seq sequence number, used in replies
- to recipient or "*" for no specific receiver
- type "send" for a channel message
-
-
-"type" is a DATA element, which indicates to the message routing
-system what the purpose of this message is.
+|====================================================================================================
+|Name |type |Description
+|====================================================================================================
+|from |string|Sender's l-name
+|type |string|Type of the message. The routed message is "send".
+|group |string|The group to deliver to.
+|instance |string|Instance in the group. Purpose lost in history. Defaults to "*".
+|to |string|Override recipient (group/instance ignored).
+|seq |int |Tracking number of the message.
+|reply |int |If present, contains a seq number of message this is a reply to.
+|want_answer|bool |If present and true, the daemon generates error if there's no matching recipient.
+|====================================================================================================
+Types of messages
+-----------------
Get Local Name (type "getlname")
---------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Upon connection, this is the first message to be sent to the control
-daemon. It will return the local name of this client. Each
-connection gets its own unique local name, and local names are never
-repeated. They should be considered opaque strings, in a format
-useful only to the message routing system. They are used in replies
-or to send to a specific destination.
+Upon connection, this is the first message to be sent to the daemon.
+It will return the local name of this client. Each connection gets
+its own unique local name, and local names are never repeated. They
+should be considered opaque strings, in a format useful only to the
+message routing system. They are used in replies or to send to a
+specific destination.
To request the local name, the only element included is the
- "type" => "getlname"
+ {"type": "getlname"}
tuple. The response is also a simple, single tuple:
- "lname" => "UTF-8 encoded local name blob"
+ {"lname" => "Opaque utf-8 string"}
Until this message is sent, no other types of messages may be sent on
this connection.
-
Regular Group Messages (type "send")
-------------------------------------
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-When sending a message:
+Message routed to other client. This one expects the body to be
+non-empty.
-"msg" is the sender supplied data. It is encoded as per its type.
-It is a required field, but may be the NULL type if not needed.
-In OpenReg, this was another wire format message, stored as an
-ITEM_DATA. This was done to make it easy to decode the routing
-information without having to decode arbitrary application-supplied
-data, but rather treat this application data as an opaque blob.
+Expected headers are:
-"from" is a DATA element, and its value is a UTF-8 encoded sender
-identity. It MUST be the "local name" supplied by the message
-routing system upon connection. The message routing system will
-enforce this, but will not add it. It is a required field.
+* from
+* group
+* instance (set to "*" if no specific instance desired)
+* seq (should be unique for the sender)
+* to (set to "*" if not directed to specific client)
+* reply (optional, only if it is reply)
+* want_answer (optional, only when not a reply)
-"group" is a DATA element, and its value is the UTF-8 encoded group
-name this message is being transmitted to. It is a required field for
-all messages of type "send".
+A client does not see its own transmissions.
-"instance" is a DATA element, and its value is the UTF-8 encoded
-instance name, with "*" meaning all instances.
+Group Subscriptions (type "subscribe")
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-"repl" is the sequence number being replied to, if this is a reply.
+Indicates the sender wants to be included in the given group.
-"seq" is a unique identity per client. That is, the <lname, seq>
-tuple must be unique over the lifetime of the connection, or at least
-over the lifetime of the expected reply duration.
+Expected headers are:
-"to" is a DATA element, and its value is a UTF-8 encoded recipient
-identity. This must be a specific recipient name or "*" to indicate
-"all listeners on this channel." It is a required field.
+* group
+* instance (leave at "*" for default)
-When a message of type "send" is received by the client, all the data
-is used as above. This indicates a message of the given type was
-received.
+There is no response to this message and the client is subscribed to
+the given group and instance.
-A client does not see its own transmissions. (XXXMLG Need to check this)
+The group can be any utf-8 string and the group doesn't have to exist
+before (it is created when at least one client is in it). A client may
+be subscribed in multiple groups.
+Group Unsubscribe (type "unsubscribe")
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Group Subscriptions (type "subscribe")
---------------------------------------
+The headers to be included are "group" and "instance" and have the same
+meaning as a "subscribe" message. Only, the client is removed from the
+group.
-A subscription requires the "group", "instance", and a flag to
-indicate the subscription type ("sybtype"). If instance is "*" the
-instance name will be ignored when decising to forward a message to
-this client or not.
+Transmitted messages
+--------------------
-"subtype" is a DATA element, and contains "normal" for normal channel
-subscriptions, "meonly" for only those messages on a channel with the
-recipient specified exactly as the local name, or "promisc" to receive
-all channel messages regardless of other filters. As its name
-implies, "normal" is for typical subscriptions, and "promisc" is
-intended for channel message debugging.
+These are the messages generally transmitted in the body of the
+message.
-There is no response to this message.
+Command
+~~~~~~~
+It is a command from one process to another, to do something or send
+some information. It is identified by a name and can optionally have
+parameters. It'd look like this:
-Group Unsubscribe (type "unsubscribe")
--------------------------------
+ {"command": ["name", <parameters>]}
+
+The parameters may be omitted (then the array is 1 element long). If
+present, it may be any JSON element. However, the most usual is an
+object with named parameter values.
+
+It is usually transmitted with the `want_answer` header turned on to
+cope with the situation the remote end doesn't exist, and sent to a
+group (eg. `to` with value of `*`).
+
+Success reply
+~~~~~~~~~~~~~
+
+When the command is successful, the other side answers by a reply of
+the following format:
+
+ {"result": [0, <result>]}
+
+The result is the return value of the command. It may be any JSON
+element and it may be omitted (for the case of ``void'' function).
-The fields to be included are "group" and "instance" and have the same
-meaning as a "subscribe" message.
+This is transmitted with the `reply` header set to the `seq` number of
+the original command. It is sent with the `to` header set.
-There is no response to this message.
+Error reply
+~~~~~~~~~~~
+In case something goes wrong, an error reply is sent. This is similar
+as throwing an exception from local function. The format is similar:
-Statistics (type "stats")
--------------------------
+ {"result": [ecode, "Error description"]}
-Request statistics from the message router. No other fields are
-inclued in the request.
+The `ecode` is non-zero error code. Most of the current code uses `1`
+for all errors. The string after that is mandatory and must contain a
+human-readable description of the error.
-The response contains a single element "stats" which is an opaque
-element. This is used mostly for debugging, and its format is
-specific to the message router. In general, some method to simply
-dump raw messages would produce something useful during debugging.
+The negative error codes are reserved for errors from the daemon.
+Currently, only `-1` is used and it is generated when a message with
+`reply` not included is sent, it has the `want_answer` header set to
+`true` and there's no recipient to deliver the message to. This
+usually means a command was sent to a non-existent recipient.
diff --git a/doc/design/datasrc/.gitignore b/doc/design/datasrc/.gitignore
new file mode 100644
index 0000000..065b83e
--- /dev/null
+++ b/doc/design/datasrc/.gitignore
@@ -0,0 +1,3 @@
+/*.html
+/*.png
+/*.xml
diff --git a/doc/design/datasrc/Makefile.am b/doc/design/datasrc/Makefile.am
new file mode 100644
index 0000000..d339cd7
--- /dev/null
+++ b/doc/design/datasrc/Makefile.am
@@ -0,0 +1,30 @@
+UML_FILES = \
+ overview.txt \
+ auth-local.txt \
+ auth-mapped.txt \
+ memmgr-mapped-init.txt \
+ memmgr-mapped-reload.txt
+
+TEXT_FILES = \
+ data-source-classes.txt
+
+devel: $(patsubst %.txt, %.png, $(UML_FILES)) $(patsubst %.txt, %.html, $(TEXT_FILES))
+
+.txt.html:
+if HAVE_ASCIIDOC
+ $(AM_V_GEN) $(ASCIIDOC) -n $<
+else
+ @echo "*** asciidoc is required to regenerate $(@) ***"; exit 1;
+endif
+
+.txt.png:
+if HAVE_PLANTUML
+ $(AM_V_GEN) $(PLANTUML) $<
+else
+ @echo "*** plantuml is required to regenerate $(@) ***"; exit 1;
+endif
+
+CLEANFILES = \
+ $(patsubst %.txt, %.png, $(UML_FILES)) \
+ $(patsubst %.txt, %.html, $(TEXT_FILES)) \
+ $(patsubst %.txt, %.xml, $(TEXT_FILES))
diff --git a/doc/design/datasrc/auth-local.txt b/doc/design/datasrc/auth-local.txt
new file mode 100644
index 0000000..afe8ad8
--- /dev/null
+++ b/doc/design/datasrc/auth-local.txt
@@ -0,0 +1,142 @@
+ at startuml
+
+participant auth as ":b10-auth"
+[-> auth: new/initial config\n(datasrc cfg)
+activate auth
+
+participant list as ":Configurable\nClientList"
+create list
+auth -> list: <<construct>>
+
+auth -> list: configure(cfg)
+activate list
+
+participant cache_config as ":CacheConfig"
+create cache_config
+list -> cache_config: <<construct>> (cfg)
+
+participant zt_segment as ":ZoneTable\nSegment\n(Local)"
+create zt_segment
+list -> zt_segment: <<construct>>
+activate zt_segment
+
+participant zone_table as ":ZoneTable"
+create zone_table
+zt_segment -> zone_table: <<construct>>
+
+deactivate zt_segment
+
+list -> zt_segment: isWritable()
+activate zt_segment
+note over zt_segment: Local segments are\nalways writable
+zt_segment --> list: true
+deactivate zt_segment
+
+loop for each zone in cache_config
+list -> cache_config: getLoadAction()
+activate cache_config
+
+participant la1 as "la1:LoadAction"
+create la1
+cache_config -> la1: <<construct>>
+
+participant la2 as "la2:LoadAction"
+
+cache_config --> list : la1
+
+deactivate cache_config
+
+participant w1 as "w1:ZoneWriter"
+create w1
+list -> w1: <<construct>> (la1)
+
+participant w2 as "w2:ZoneWriter"
+
+list -> w1: load()
+activate w1
+w1 -> la1: (funcall)
+activate la1
+
+participant zd1 as "zd1:ZoneData"
+create zd1
+la1 -> zd1: <<construct>> via helpers
+
+participant zd2 as "zd2:ZoneData"
+
+la1 --> w1: zd1
+deactivate la1
+deactivate w1
+
+list -> w1: install()
+activate w1
+
+w1 -> zone_table: addZone(zd1)
+activate zone_table
+zone_table --> w1: NULL (no old data)
+deactivate zone_table
+
+deactivate w1
+
+end
+
+deactivate list
+deactivate auth
+
+...
+
+[-> auth: reload\n(zonename)
+activate auth
+
+auth -> list: getCachedw1\n(zone_name)
+activate list
+
+list -> cache_config: getLoadAction()
+activate cache_config
+
+create la2
+cache_config -> la2: <<construct>>
+
+cache_config --> list : la2
+
+deactivate cache_config
+
+create w2
+list -> w2: <<construct>> (la2)
+
+list --> auth: w2
+
+deactivate list
+
+
+auth -> w2: load()
+activate w2
+w2 -> la2: (funcall)
+activate la2
+
+create zd2
+la2 -> zd2: <<construct>> via helpers
+
+la2 --> w2: zd2
+deactivate la2
+deactivate w2
+
+auth -> w2: install()
+activate w2
+
+w2 -> zone_table: addZone(zd2)
+activate zone_table
+zone_table --> w2: zd1 (old data)
+deactivate zone_table
+
+deactivate w2
+
+auth -> w2: cleanup()
+activate w2
+
+w2 -> zd1: <<destroy>>
+destroy zd1
+deactivate w2
+
+deactivate auth
+
+ at enduml
diff --git a/doc/design/datasrc/auth-mapped.txt b/doc/design/datasrc/auth-mapped.txt
new file mode 100644
index 0000000..b5b1a39
--- /dev/null
+++ b/doc/design/datasrc/auth-mapped.txt
@@ -0,0 +1,99 @@
+ at startuml
+
+participant auth as ":b10-auth"
+[-> auth: new/initial config\n(datasrc cfg)
+activate auth
+
+participant list as ":Configurable\nClientList"
+create list
+auth -> list: <<construct>>
+
+auth -> list: configure(cfg)
+activate list
+
+participant CacheConfig as ":CacheConfig"
+create CacheConfig
+list -> CacheConfig: <<construct>> (cfg)
+
+participant zt_segment as ":ZoneTable\nSegment\n(Mapped)"
+create zt_segment
+list -> zt_segment: <<construct>>
+
+list -> zt_segment: isWritable()
+activate zt_segment
+note over zt_segment: Segment not writable\nwhen not reset
+zt_segment --> list: false
+deactivate zt_segment
+
+deactivate list
+
+auth -> list: getStatus()
+activate list
+list --> auth: DataSourceStatus[]
+deactivate list
+
+[<- auth: subscribe to\nmemmgr group
+
+deactivate auth
+
+...
+
+[-> auth: command from\nmemmgr\n(datasrc_name,\nsegmentparam)
+activate auth
+
+auth -> list: resetMemorySegment\n(datasrc_name,\nREAD_ONLY,\nsegmentparam)
+activate list
+
+list -> zt_segment: reset\n(READ_ONLY,\nsegmentparam)
+activate zt_segment
+
+participant segment as "seg1:Memory\nSegment\n(Mapped)"
+create segment
+zt_segment -> segment: <<construct>>
+
+deactivate zt_segment
+deactivate list
+deactivate auth
+
+...
+
+[-> auth: command from\nmemmgr\n(datasrc_name,\nsegmentparam)
+activate auth
+
+auth -> list: resetMemorySegment\n(datasrc_name,\nREAD_ONLY,\nsegmentparam)
+activate list
+
+list -> zt_segment: reset\n(READ_ONLY,\nsegmentparam)
+activate zt_segment
+
+zt_segment -> segment: <<destroy>>
+destroy segment
+
+participant segment2 as "seg2:Memory\nSegment\n(Mapped)"
+create segment2
+zt_segment -> segment2: <<construct>>
+
+deactivate zt_segment
+deactivate list
+deactivate auth
+
+...
+
+[-> auth: reload\n(zonename)
+activate auth
+
+auth -> list: getCachedZoneWriter\n(zone_name)
+activate list
+
+list -> zt_segment: isWritable()
+activate zt_segment
+note over zt_segment: Segment not writable\nas it is READ_ONLY
+zt_segment --> list: false
+deactivate zt_segment
+
+list --> auth: CACHE_NOT_WRITABLE
+deactivate list
+
+deactivate auth
+
+ at enduml
diff --git a/doc/design/datasrc/data-source-classes.txt b/doc/design/datasrc/data-source-classes.txt
new file mode 100644
index 0000000..0f2dcbb
--- /dev/null
+++ b/doc/design/datasrc/data-source-classes.txt
@@ -0,0 +1,366 @@
+Data Source Library Classes
+===========================
+
+About this document
+-------------------
+
+This memo describes major classes used in the data source library,
+mainly focusing on handling in-memory cache with consideration of the
+shared memory support. It will give an overview of the entire design
+architecture and some specific details of how these classes are expected
+to be used.
+
+Before reading, the higher level inter-module protocol should be understood:
+http://bind10.isc.org/wiki/SharedMemoryIPC
+
+Overall relationships between classes
+-------------------------------------
+
+The following diagram shows major classes in the data source library
+related to in-memory caches and their relationship.
+
+image::overview.png[Class diagram showing overview of relationships]
+
+Major design decisions of this architecture are:
+
+* Keep each class as concise as possible, each focusing on one or
+ small set of responsibilities. Smaller classes are generally easier
+ to understand (at the cost of understanding how they work in the
+ "big picture" of course) and easier to test.
+
+* On a related point, minimize dependency to any single class. A
+ monolithic class on which many others are dependent is generally
+ difficult to maintain because you'll need to ensure a change to the
+ monolithic class doesn't break anything on any other classes.
+
+* Use polymorphism for any "fluid" behavior, and hide specific details
+ under abstract interfaces so implementation details won't be
+ directly referenced from any other part of the library.
+ Specifically, the underlying memory segment type (local, mapped, and
+ possibly others) and the source of in-memory data (master file or
+ other data source) are hidden via a kind of polymorphism.
+
+* Separate classes directly used by applications from classes that
+ implement details. Make the former classes as generic as possible,
+ agnostic about implementation specific details such as the memory
+ segment type (or, ideally and where possible, whether it's for
+ in-memory cache or the underlying data source).
+
+The following give a summarized description of these classes.
+
+* `ConfigurableClientList`: The front end to application classes. An
+ application that uses the data source library generally maintains
+ one or more `ConfigurableClientList` object (usually one per RR
+ class, or when we support views, probably one per view). This class
+ is a container of sets of data source related classes, providing
+ accessor to these classes and also acting as a factory of other
+ related class objects. Note: Due to internal implementation
+ reasons, there is a base class for `ConfigurableClientList` named
+ `ClientList` in the C++ version, and applications are expected to
+ use the latter. But conceptually `ConfigurableClientList` is an
+ independent value class; the inheritance is not for polymorphism.
+ Note also that the Python version doesn't have the base class.
+
+* `DataSourceInfo`: this is a straightforward tuple of set of class
+ objects corresponding to a single data source, including
+ `DataSourceClient`, `CacheConfig`, and `ZoneTableSegment`.
+ `ConfigurableClientList` maintains a list of `DataSourceInfo`, one
+ for each data source specified in its configuration.
+
+* `DataSourceClient`: The front end class to applications for a single
+ data source. Applications will get a specific `DataSourceClient`
+ object by `ConfigurableClientList::find()`.
+ `DataSourceClient` itself is a set of factories for various
+ operations on the data source such as lookup or update.
+
+* `CacheConfig`: library internal representation of in-memory cache
+ configuration for a data source. It knows which zones are to be
+ cached and where the zone data (RRs) should come from, either from a
+ master file or other data source. With this knowledge it will
+ create an appropriate `LoadAction` object. Note that `CacheConfig`
+ isn't aware of the underlying memory segment type for the in-memory
+ data. It's intentionally separated from this class (see the
+ conciseness and minimal-dependency design decisions above).
+
+* `ZoneTableSegment`: when in-memory cache is enabled, it provides
+ memory-segment-type independent interface to the in-memory data.
+ This is an abstract base class (see polymorphism in the design
+ decisions) and inherited by segment-type specific subclasses:
+ `ZoneTableSegmentLocal` and `ZoneTableSegmentMapped` (and possibly
+ others). Any subclass of `ZoneTableSegment` is expected to maintain
+ the specific type of `MemorySegment` object.
+
+* `ZoneWriter`: a frontend utility class for applications to update
+ in-memory zone data (currently it can only load a whole zone and
+ replace any existing zone content with a new one, but this should be
+ extended so it can handle partial updates).
+ Applications will get a specific `ZoneWriter`
+ object by `ConfigurableClientList::getCachedZoneWriter()`.
+ `ZoneWriter` is constructed with `ZoneableSegment` and `LoadAction`.
+ Since these are abstract classes, `ZoneWriter` doesn't have to be
+ aware of "fluid" details. It's only responsible for "somehow" preparing
+ `ZoneData` for a new version of a specified zone using `LoadAction`,
+ and installing it in the `ZoneTable` (which can be accessed via
+ `ZoneTableSegment`).
+
+* `DataSourceStatus`: created by `ConfigurableClientList::getStatus()`,
+ a straightforward tuple that represents some status information of a
+ specific data source managed in the `ConfigurableClientList`.
+ `getStatus()` generates `DataSourceStatus` for all data sources
+ managed in it, and returns them as a vector.
+
+* `ZoneTableAccessor`, `ZoneTableIterator`: frontend classes to get
+ access to the conceptual "zone table" (a set of zones) stored in a
+ specific data source. In particular, `ZoneTableIterator` allows
+ applications to iterate over all zones (by name) stored in the
+ specific data source.
+ Applications will get a specific `ZoneTableAccessor`
+ object by `ConfigurableClientList::getZoneTableAccessor()`,
+ and get an iterator object by calling `getIterator` on the accessor.
+ These are abstract classes and provide unified interfaces
+ independent from whether it's for in-memory cached zones or "real"
+ underlying data source. But the initial implementation only
+ provides the in-memory cache version of subclass (see the next
+ item).
+
+* `ZoneTableAccessorCache`, `ZoneTableIteratorCache`: implementation
+ classes of `ZoneTableAccessor` and `ZoneTableIterator` for in-memory
+ cache. They refer to `CacheConfig` to get a list of zones to be
+ cached.
+
+* `ZoneTableHeader`, `ZoneTable`: top-level interface to actual
+ in-memory data. These were separated based on a prior version of
+ the design (http://bind10.isc.org/wiki/ScalableZoneLoadDesign) where
+ `ZoneTableHeader` may contain multiple `ZoneTable`s. It's
+ one-to-one relationship in the latest version (of implementation),
+ so we could probably unify them as a cleanup.
+
+* `ZoneData`: representing the in-memory content of a single zone.
+ `ZoneTable` contains (zero, one or) multiple `ZoneData` objects.
+
+* `RdataSet`: representing the in-memory content of (data of) a single
+ RRset.
+ `ZoneData` contains `RdataSet`s corresponding to the RRsets stored
+ in the zone.
+
+* `LoadAction`: a "polymorphic" functor that implements loading zone
+ data into memory. It hides from its user (i.e., `ZoneWriter`)
+ details about the source of the data: master file or other data
+ source (and perhaps some others). The "polymorphism" is actually
+ realized as different implementations of the functor interface, not
+ class inheritance (but conceptually the effect and goal is the
+ same). Note: there's a proposal to replace `LoadAction` with
+ a revised `ZoneDataLoader`, although the overall concept doesn't
+ change. See Trac ticket #2912.
+
+* `ZoneDataLoader` and `ZoneDataUpdater`: helper classes for the
+ `LoadAction` functor(s). These work independently from the source
+ of data, taking a sequence of RRsets objects, converting them
+ into the in-memory data structures (`RdataSet`), and installing them
+ into a newly created `ZoneData` object.
+
+Sequence for auth module using local memory segment
+---------------------------------------------------
+
+In the remaining sections, we explain how the classes shown in the
+previous section work together through their methods for commonly
+intended operations.
+
+The following sequence diagram shows the case for the authoritative
+DNS server module to maintain "local" in-memory data. Note that
+"auth" is a conceptual "class" (not actually implemented as a C++
+class) to represent the server application behavior. For the purpose
+of this document that should be sufficient. The same note applies to
+all examples below.
+
+image::auth-local.png[Sequence diagram for auth server using local memory segment]
+
+1. On startup, the auth module creates a `ConfigurableClientList`
+ for each RR class specified in the configuration for "data_sources"
+ module. It then calls `ConfigurableClientList::configure()`
+ for the given configuration of that RR class.
+
+2. For each data source, `ConfigurableClientList` creates a
+ `CacheConfig` object with the corresponding cache related
+ configuration.
+
+3. If in-memory cache is enabled for the data source,
+ `ZoneTableSegment` is also created. In this scenario the cache
+ type is specified as "local" in the configuration, so a functor
+ creates `ZoneTableSegmentLocal` as the actual instance.
+ In this case its `ZoneTable` is immediately created, too.
+
+4. `ConfigurableClientList` checks if the created `ZoneTableSegment` is
+ writable. It is always so for "local" type of segments. So
+ `ConfigurableClientList` immediately loads zones to be cached into
+ memory. For each such zone, it first gets the appropriate
+ `LoadAction` through `CacheConfig`, then creates `ZoneWriter` with
+ the `LoadAction`, and loads the data using the writer.
+
+5. If the auth module receives a "reload" command for a cached zone
+ from other module (xfrin, an end user, etc), it calls
+ `ConfigurableClientList::getCachedZoneWriter` to load and install
+ the new version of the zone. The same loading sequence takes place
+ except that the user of the writer is the auth module.
+ Also, the old version of the zone data is destroyed at the end of
+ the process.
+
+Sequence for auth module using mapped memory segment
+----------------------------------------------------
+
+This is an example for the authoritative server module that uses
+mapped type memory segment for in-memory data.
+
+image::auth-mapped.png[Sequence diagram for auth server using mapped memory segment]
+
+1. The sequence is the same to the point of creating `CacheConfig`.
+
+2. But in this case a `ZoneTableSegmentMapped` object is created based
+ on the configuration of the cache type. This type of
+ `ZoneTableSegment` is initially empty and isn't even associated
+ with a `MemorySegment` (and therefore considered non-writable).
+
+3. `ConfigurableClientList` checks if the zone table segment is
+ writable to know whether to load zones into memory by itself,
+ but as `ZoneTableSegment::isWritable()` returns false, it skips
+ the loading.
+
+4. The auth module gets the status of each data source, and notices
+ there's a `WAITING` state of segment. So it subscribes to the
+ "Memmgr" group on a command session and waits for an update
+ from the memory manager (memmgr) module. (See also the note at the
+ end of the section)
+
+5. When the auth module receives an update command from memmgr, it
+ calls `ConfigurableClientList::resetMemorySegment()` with the command
+ argument and the segment mode of `READ_ONLY`.
+ Note that the auth module handles the command argument as mostly
+ opaque data; it's not expected to deal with details of segment
+ type-specific behavior. If the reset fails, auth aborts (as there's
+ no clear way to handle the failure).
+
+6. `ConfigurableClientList::resetMemorySegment()` subsequently calls
+ `reset()` method on the corresponding `ZoneTableSegment` with the
+ given parameters.
+ In the case of `ZoneTableSegmentMapped`, it creates a new
+ `MemorySegment` object for the mapped type, which internally maps
+ the specific file into memory.
+ memmgr is expected to have prepared all necessary data in the file,
+ so all the data are immediately ready for use (i.e., there
+ shouldn't be any explicit load operation).
+
+7. When a change is made in the mapped data, memmgr will send another
+ update command with parameters for new mapping. The auth module
+ calls `ConfigurableClientList::resetMemorySegment()`, and the
+ underlying memory segment is swapped with a new one. The old
+ memory segment object is destroyed. Note that
+ this "destroy" just means unmapping the memory region; the data
+ stored in the file are intact. Again, if mapping fails, auth
+ aborts.
+
+8. If the auth module happens to receive a reload command from other
+ module, it could call
+ `ConfigurableClientList::getCachedZoneWriter()`
+ to reload the data by itself, just like in the previous section.
+ In this case, however, the writability check of
+ `getCachedZoneWriter()` fails (the segment was created as
+ `READ_ONLY` and is non-writable), so loading won't happen.
+
+NOTE: While less likely in practice, it's possible that the same auth
+module uses both "local" and "mapped" (and even others) type of
+segments for different data sources. In such cases the sequence is
+either the one in this or previous section depending on the specified
+segment type in the configuration. The auth module itself isn't aware
+of per segment-type details, but changes the behavior depending on the
+segment state of each data source at step 4 above: if it's `WAITING`,
+it means the auth module needs help from memmgr (that's all the auth
+module should know; it shouldn't be bothered with further details such
+as mapped file names); if it's something else, the auth module doesn't
+have to do anything further.
+
+Sequence for memmgr module initialization using mapped memory segment
+---------------------------------------------------------------------
+
+This sequence shows the common initialization sequence for the
+memory manager (memmgr) module using a mapped type memory segment.
+This is a mixture of the sequences shown in Sections 2 and 3.
+
+image::memmgr-mapped-init.png[]
+
+1. Initial sequence is the same until the application module (memmgr)
+ calls `ConfigurableClientList::getStatus()` as that for the
+ previous section.
+
+2. The memmgr module identifies the data sources whose in-memory cache
+ type is "mapped". (Unlike other application modules, the memmgr
+ should know what such types means due to its exact responsibility).
+ For each such data source, it calls
+ `ConfigurableClientList::resetMemorySegment` with the READ_WRITE
+ mode and other mapped-type specific parameters. memmgr should be
+ able to generate the parameters from its own configuration and
+ other data source specific information (such as the RR class and
+ data source name).
+
+3. The `ConfigurableClientList` class calls
+ `ZoneTableSegment::reset()` on the corresponding zone table
+ segment with the given parameters. In this case, since the mode is
+ READ_WRITE, a new `ZoneTable` will be created (assuming this is a
+ very first time initialization; if there's already a zone table
+ in the segment, it will be used).
+
+4. The memmgr module then calls
+ `ConfigurableClientList::getZoneTableAccessor()`, and calls the
+ `getItertor()` method on it to get a list of zones for which
+ zone data are to be loaded into the memory segment.
+
+5. The memmgr module loads the zone data for each such zone. This
+ sequence is the same as shown in Section 2.
+
+6. On loading all zone data, the memmgr module sends an update command
+ to all interested modules (such as auth) in the segment, and waits
+ for acknowledgment from all of them.
+
+7. Then it calls `ConfigurableClientList::resetMemorySegment()` for
+ this data source with almost the same parameter as step 2 above,
+ but with a different mapped file name. This will make a swap of
+ the underlying memory segment with a new mapping. The old
+ `MemorySegment` object will be destroyed, but as explained in the
+ previous section, it simply means unmapping the file.
+
+8. The memmgr loads the zone data into the newly mapped memory region
+ by repeating the sequence shown in step 5.
+
+9. The memmgr repeats all this sequence for data sources that use
+ "mapped" segment for in-memory cache. Note: it could handle
+ multiple data sources in parallel, e.g., while waiting for
+ acknowledgment from other modules.
+
+Sequence for memmgr module to reload a zone using mapped memory segment
+-----------------------------------------------------------------------
+
+This example is a continuation of the previous section, describing how
+the memory manager reloads a zone in mapped memory segment.
+
+image::memmgr-mapped-reload.png[]
+
+1. When the memmgr module receives a reload command from other module,
+ it calls `ConfigurableClientList::getCachedZoneWriter()` for the
+ specified zone name. This method checks the writability of
+ the segment, and since it's writable (as memmgr created it in the
+ READ_WRITE mode), `getCachedZoneWriter()` succeeds and returns
+ a `ZoneWriter`.
+
+2. The memmgr module uses the writer to load the new version of zone
+ data. There is nothing specific to mapped-type segment here.
+
+3. The memmgr module then sends an update command to other modules
+ that would share this version, and waits for acknowledgment from
+ all of them.
+
+4. On getting acknowledgments, the memmgr module calls
+ `ConfigurableClientList::resetMemorySegment()` with the parameter
+ specifying the other mapped file. This will swap the underlying
+ `MemorySegment` with a newly created one, mapping the other file.
+
+5. The memmgr updates this segment, too, so the two files will contain
+ the same version of data.
diff --git a/doc/design/datasrc/memmgr-mapped-init.txt b/doc/design/datasrc/memmgr-mapped-init.txt
new file mode 100644
index 0000000..9daea13
--- /dev/null
+++ b/doc/design/datasrc/memmgr-mapped-init.txt
@@ -0,0 +1,137 @@
+ at startuml
+
+participant memmgr as ":b10-memmgr"
+[-> memmgr: new/initial config\n(datasrc cfg)
+activate memmgr
+
+participant list as ":Configurable\nClientList"
+create list
+memmgr -> list: <<construct>>
+
+memmgr -> list: configure(cfg)
+activate list
+
+participant CacheConfig as ":CacheConfig"
+create CacheConfig
+list -> CacheConfig: <<construct>> (cfg)
+
+participant zt_segment as ":ZoneTable\nSegment\n(Mapped)"
+create zt_segment
+list -> zt_segment: <<construct>>
+
+list -> zt_segment: isWritable()
+activate zt_segment
+note over zt_segment: Segment not writable\nwhen not reset
+zt_segment --> list: false
+deactivate zt_segment
+
+deactivate list
+
+memmgr -> list: getStatus()
+activate list
+list --> memmgr: DataSourceStatus[]
+deactivate list
+
+loop for each datasrc with mapped segment
+
+memmgr -> list: resetMemorySegment\n(datasrc_name,\nREAD_WRITE,\nsegmentparam)
+activate list
+
+list -> zt_segment: reset\n(READ_WRITE,\nsegmentparam)
+activate zt_segment
+
+participant segment as "seg1:Memory\nSegment\n(Mapped)"
+create segment
+zt_segment -> segment: <<construct>>
+
+participant segment.2 as "seg2:Memory\nSegment\n(Mapped)"
+
+participant ZoneTable as ":ZoneTable"
+create ZoneTable
+zt_segment -> ZoneTable: <<construct>>
+
+deactivate zt_segment
+deactivate list
+
+memmgr -> list: getZoneTableAccessor\n(datasrc_name,\ncache=true)
+activate list
+list -> memmgr: ZoneTableAccessor
+deactivate list
+
+
+loop for each zone given by ZoneTableIterator
+
+memmgr -> list: getCachedZoneWriter\n(zone_name)
+activate list
+
+list -> CacheConfig: getLoadAction()
+activate CacheConfig
+
+participant LoadAction as "la:LoadAction"
+create LoadAction
+CacheConfig -> LoadAction: <<construct>>
+
+CacheConfig --> list : la
+
+deactivate CacheConfig
+
+participant ZoneWriter as "zw:ZoneWriter"
+create ZoneWriter
+list -> ZoneWriter: <<construct>> (la)
+
+list --> memmgr: zw
+
+deactivate list
+
+
+memmgr -> ZoneWriter: load()
+activate ZoneWriter
+ZoneWriter -> LoadAction: (funcall)
+activate LoadAction
+
+participant ZoneData as "zd:ZoneData"
+create ZoneData
+LoadAction -> ZoneData: <<construct>> via helpers
+
+LoadAction --> ZoneWriter: zd
+deactivate LoadAction
+deactivate ZoneWriter
+
+memmgr -> ZoneWriter: install()
+activate ZoneWriter
+
+ZoneWriter -> ZoneTable: addZone(zd)
+activate ZoneTable
+ZoneTable --> ZoneWriter: NULL (no old data)
+deactivate ZoneTable
+
+deactivate ZoneWriter
+
+end
+
+[<- memmgr: command to\nmodules\n(datasrc_name,\nsegmentparam)
+[--> memmgr: ack from all\nmodules
+
+memmgr -> list: resetMemorySegment\n(datasrc_name,\nREAD_WRITE,\nsegmentparam)
+activate list
+
+list -> zt_segment: reset\n(READ_WRITE,\nsegmentparam)
+activate zt_segment
+
+zt_segment -> segment: <<destroy>>
+destroy segment
+create segment.2
+zt_segment -> segment.2: <<construct>>
+
+deactivate zt_segment
+deactivate list
+
+note left of memmgr: load zone\nfor each zone\ngiven by\nZoneTableIterator
+
+end
+
+[<-- memmgr
+
+deactivate memmgr
+
+ at enduml
diff --git a/doc/design/datasrc/memmgr-mapped-reload.txt b/doc/design/datasrc/memmgr-mapped-reload.txt
new file mode 100644
index 0000000..676e961
--- /dev/null
+++ b/doc/design/datasrc/memmgr-mapped-reload.txt
@@ -0,0 +1,94 @@
+ at startuml
+
+participant memmgr as ":b10-memmgr"
+[-> memmgr: reload\n(zonename)
+activate memmgr
+
+participant list as ":Configurable\nClientList"
+memmgr -> list: getCachedZoneWriter\n(zone_name)
+activate list
+
+participant CacheConfig as ":CacheConfig"
+
+participant zt_segment as ":ZoneTable\nSegment\n(Mapped)"
+participant segment as "existing:Memory\nSegment\n(Mapped)"
+participant segment2 as "new:Memory\nSegment\n(Mapped)"
+
+list -> zt_segment: isWritable()
+activate zt_segment
+zt_segment --> list: true
+deactivate zt_segment
+
+list -> CacheConfig: getLoadAction()
+activate CacheConfig
+
+participant ZoneTable as ":ZoneTable"
+participant ZoneWriter as "zw:ZoneWriter"
+
+participant LoadAction as "la:LoadAction"
+create LoadAction
+CacheConfig -> LoadAction: <<construct>>
+CacheConfig --> list: la
+deactivate CacheConfig
+
+create ZoneWriter
+list -> ZoneWriter: <<construct>> (la)
+list --> memmgr: zw
+deactivate list
+
+memmgr -> ZoneWriter: load()
+activate ZoneWriter
+ZoneWriter -> LoadAction: (funcall)
+activate LoadAction
+
+participant ZoneData as "zd_existing\n:ZoneData"
+participant ZoneData2 as "zd_new\n:ZoneData"
+
+create ZoneData2
+LoadAction -> ZoneData2: <<construct>> via helpers
+
+LoadAction --> ZoneWriter: zd_new
+deactivate LoadAction
+deactivate ZoneWriter
+
+memmgr -> ZoneWriter: install()
+activate ZoneWriter
+
+ZoneWriter -> ZoneTable: addZone(zd_new)
+activate ZoneTable
+ZoneTable --> ZoneWriter: zd_existing (old data)
+deactivate ZoneTable
+
+deactivate ZoneWriter
+
+memmgr -> ZoneWriter: cleanup()
+activate ZoneWriter
+
+ZoneWriter -> ZoneData: <<destroy>>
+destroy ZoneData
+deactivate ZoneWriter
+
+[<- memmgr: command to\nmodules\n(datasrc_name,\nsegmentparam)
+[--> memmgr: ack from all\nmodules
+
+memmgr -> list: resetMemorySegment\n(datasrc_name,\nREAD_WRITE,\nsegmentparam)
+activate list
+
+list -> zt_segment: reset\n(READ_WRITE,\nsegmentparam)
+activate zt_segment
+
+zt_segment -> segment: <<destroy>>
+destroy segment
+create segment2
+zt_segment -> segment2: <<construct>>
+
+deactivate zt_segment
+deactivate list
+
+note left of memmgr: (repeat the\nsame sequence\nfor loading to the\nother segment)
+
+memmgr -> list: getCachedZoneWriter\n(zone_name)
+
+...
+
+ at enduml
diff --git a/doc/design/datasrc/overview.txt b/doc/design/datasrc/overview.txt
new file mode 100644
index 0000000..4ee971c
--- /dev/null
+++ b/doc/design/datasrc/overview.txt
@@ -0,0 +1,68 @@
+ at startuml
+
+hide members
+
+note "Automatic placement of classes\ndoesn't look good. This diagram\nhas to be improved." as n1
+
+Auth "1" *--> "*" ConfigurableClientList
+Auth --> DataSourceClient
+Auth --> ZoneWriter
+Auth --> ZoneTableAccessor
+Auth --> DataSourceStatus
+Auth --> ZoneTableIterator
+
+ConfigurableClientList "1" *--> "*" DataSourceInfo
+ConfigurableClientList ..> ZoneTableSegment : <<reset>>
+ConfigurableClientList ..> DataSourceStatus : <<create>>
+ConfigurableClientList ..> ZoneWriter : <<create>>
+ConfigurableClientList ..> ZoneTableAccessor : <<create>>
+
+DataSourceInfo "1" *--> "*" DataSourceClient
+DataSourceInfo "1" *--> "*" CacheConfig
+DataSourceInfo "1" *--> "*" ZoneTableSegment
+
+ZoneTableAccessor ..> ZoneTableIterator : <<create>>
+
+ZoneTableAccessorCache --> CacheConfig
+ZoneTableAccessorCache ..> ZoneTableIteratorCache : <<create>>
+ZoneTableAccessorCache --o ZoneTableAccessor
+
+ZoneTableIteratorCache --o ZoneTableIterator
+ZoneTableIteratorCache --> CacheConfig
+
+ZoneWriter --> ZoneTableSegment
+ZoneWriter ..> ZoneData : add/replace
+
+ZoneTableSegment "1" *--> "1" ZoneTableHeader
+ZoneTableSegment "1" *--> "1" MemorySegment
+
+CacheConfig ..> LoadAction
+
+LoadAction ..> ZoneData : create
+LoadAction *--> ZoneDataLoader
+
+ZoneDataLoader --> ZoneData
+ZoneDataLoader *--> ZoneDataUpdater
+ZoneDataLoader --> MemorySegment
+
+ZoneDataUpdater --> ZoneData
+ZoneDataUpdater ..> RdataSet : create
+ZoneDataUpdater ..> RdataSet : add
+
+ZoneTableHeader "1" *--> "1" ZoneTable
+ZoneTable "1" *--> "1" ZoneData
+ZoneData "1" *--> "1" RdataSet
+
+LoadFromFile --o LoadAction
+IteratorLoader --o LoadAction
+
+MemorySegmentMapped --o MemorySegment
+MemorySegmentLocal --o MemorySegment
+
+ZoneTableSegmentMapped --o ZoneTableSegment
+ZoneTableSegmentLocal --o ZoneTableSegment
+
+ZoneTableSegmentMapped *--> MemorySegmentMapped
+ZoneTableSegmentLocal *--> MemorySegmentLocal
+
+ at enduml
diff --git a/doc/design/ipc-high.txt b/doc/design/ipc-high.txt
new file mode 100644
index 0000000..5addb88
--- /dev/null
+++ b/doc/design/ipc-high.txt
@@ -0,0 +1,382 @@
+The IPC protocol
+================
+
+While the cc-protocol.txt describes the low-level primitives, here we
+describe how the whole IPC should work and how to use it.
+
+Definitions
+-----------
+
+system::
+ The system that moves data between the users and does bookkeeping.
+ In our current implementation, it is implemented as the MsgQ daemon,
+ which the users connect to and it routes the data.
+user::
+ Usually a process; generally an entity that wants to communicate
+ with the other users.
+session::
+ Session is the interface by which the user communicates with the
+ system. Single user may have multiple sessions, a session belongs to
+ single user.
+message::
+ A data blob sent by one user. The recipient might be the system
+ itself, other session or set of sessions (called group, see below,
+ it is possibly empty). Message is either a response or an original
+ message (TODO: Better name?).
+group::
+ A named set of sessions. Conceptually, all the possible groups
+ exist, there's no explicit creation and deletion of groups.
+session id::
+ Unique identifier of a session. It is not reused for the whole
+ lifetime of the system. Historically called `lname` in the code.
+undelivery signal::
+ While sending an original message, a client may request an
+ undelivery signal. If the recipient specification yields no
+ sessions to deliver the message to, the system informs user about
+ the situation.
+sequence number::
+ Each message sent through the system carries a sequence number. The
+ number should be unique per sender. It can be used to pair a
+ response to the original message, since the response specifies which
+ sequence number had the message it response to. Even responses and
+ messages not expecting answer have their sequence number, but it is
+ generally unused.
+non-blocking operation::
+ Operation that will complete without waiting for anything.
+fast operation::
+ Operation that may wait for other process, but only for a very short
+ time. Generally, this includes communication between the user and
+ system, but not between two clients. It can be expected to be fast
+ enough to use this inside an interactive session, but may be too
+ heavy in the middle of query processing, for example. Every
+ non-blocking operation is considered fast.
+
+The session
+-----------
+
+The session interface allows for several operations interacting with
+the system. In the code, it is represented by a class.
+
+Possible operations include:
+
+Opening a session::
+ The session is created and connects to the system. This operation is
+ fast. The session receives session id from the system.
+
+Group management::
+ A user may subscribe (become member) of a group, or unsubscribe from
+ a group. These are fast operations.
+
+Send::
+ A user may send a message, addressed to the system, or other
+ session(s). This operation is expected to be non-blocking
+ (current implementation is based on assumption of how OS handles the
+ sends, which may need to be revisited if it turns out to be false).
+
+Receive synchronously::
+ User may wait for an incoming message in blocking mode. It is
+ possible to specify the kind of message to wait for, either original
+ message or response to a message. This interface has a timeout.
+
+Receive asynchronously::
+ Similar to previous, but non-blocking. It terminates immediately.
+ The user provides a callback that is invoked when the requested
+ message arrives.
+
+Terminate::
+ A session may be terminated. No more messages are sent or received
+ over it, the session is automatically unsubscribed from all the
+ groups. This operation is non-blocking. A session is terminated
+ automatically if the user exits.
+
+Assumptions
+-----------
+
+We assume reliability and order of delivery. Messages sent from user A
+to B are all delivered unchanged in original order as long as B
+exists.
+
+All above operations are expected to always succeed. If there's an
+error reported, it should be considered fatal and user should
+exit. In case a user still wants to continue, the session must be
+considered terminated and a new one must be created. Care must be
+taken not to use any information obtained from the previous session,
+since the state in other users and the system may have changed during
+the reconnect.
+
+Addressing
+----------
+
+Addressing happens in three ways:
+
+By group name::
+ The message is routed to all the sessions subscribed to this group.
+ It is legal to address an empty group; such message is then
+ delivered to no sessions.
+By session ID::
+ The message is sent to the single session, if it is still alive.
+By an alias::
+ A session may have any number of aliases - well known names. Only
+ single session may hold given alias (but it is not yet enforced by
+ the system). The message is delivered to the one session owning the
+ alias, if any. Internally, the aliases are implemented as groups
+ with single subscribed session, so it is the same as the first
+ option on the protocol level, but semantically it is different.
+
+The system
+----------
+
+The system performs these goals:
+
+ * Maintains the open sessions and allows creating new ones.
+ * Keeps information about groups and which sessions are subscribed to
+ which group.
+ * Routes the messages between users.
+
+Also, the system itself is a user of the system. It can be reached by
+the alias `Msgq` and provides following high-level services (see
+below):
+
+Notifications about sessions::
+ When a session is opened to the system or when a session is
+ terminated, a notification is sent to interested users. The
+ notification contains the session ID of the session in question.
+ The termination notification is probably more useful (if a user
+ communicated with a given session before, it might be interested it
+ is no longer available), the opening notification is provided mostly
+ for completeness.
+Notifications about group subscriptions::
+ When a session subscribes to a group or unsubscribes from a group, a
+ notification is sent to interested users. The notification contains
+ both the session ID of the session subscribing/unsubscribing and
+ name of the group. This includes notifications about aliases (since
+ aliases are groups internally).
+Commands to list sessions::
+ There's a command to list session IDs of all currently opened sessions
+ and a command to list session IDs of all sessions subscribed to a
+ given group. Note that using these lists might need some care, as
+ the information might be outdated at the time it is delivered to the
+ user.
+
+User shows interest in notifications about sessions and group
+subscriptions by subscribing to a group with well-known name (as with
+any notification).
+
+Note that due to implementation details, the `Msgq` alias is not yet
+available during early stage of the bootstrap of bind10 system. This
+means some very core services can't rely on the above services of the
+system. The alias is guaranteed to be working before the first
+non-core module is started.
+
+Higher-level services
+---------------------
+
+While the system is able to send any kind of data, the payload sent by
+users in bind10 is structured data encoded as JSON. The messages sent
+are of three general types:
+
+Command::
+ A message sent to single destination, with the undeliverable
+ signal turned on and expecting an answer. This is a request
+ to perform some operation on the recipient (it can have side effects
+ or not). The command is identified by a name and it can have
+ parameters. A command with the same name may behave differently (or
+ have different parameters) on different receiving users.
+Reply::
+ An answer to the `Command`. It is sent directly to the session where
+ the command originated from, does not expect further answer and the
+ undeliverable notification is not set. It either confirms the
+ command was run successfully and contains an optional result, or
+ notifies the sender of failure to run the command. Success and
+ failure differ only in the payload sent through the system, not in
+ the way it is sent. The undeliverable signal is failure
+ reply sent by the system on behalf of the missing recipient.
+Notification::
+ A message sent to any number of destinations (eg. sent to a group),
+ not expecting an answer. It notifies other users about an event or
+ change of state.
+
+Details of the higher-level
+---------------------------
+
+While there are libraries implementing the communication in convenient
+way, it is useful to know what happens inside.
+
+The notifications are probably the simplest. Users interested in
+receiving notifications of some family subscribe to corresponding
+group. Then, a client sends a message to the group. For example, if
+clients `receiver-A` and `receiver-B` want to receive notifications
+about changes to zone data, they'd subscribe to the
+`Notifications/ZoneUpdates` group. Then, other client (let's say
+`XfrIn`, with session ID `s12345`) would send something like:
+
+ s12345 -> notifications/ZoneUpdates
+ {"notification": ["zone-update", {
+ "class": "IN",
+ "origin": "example.org.",
+ "serial": 123456
+ }]}
+
+Both receivers would receive the message and know that the
+`example.org` zone is now at version 123456. Note that multiple users
+may produce the same kind of notification. Also, single group may be
+used to send multiple notification names (but they should be related;
+in our example, the `notifications/ZoneUpdates` could be used for
+`zone-update`, `zone-available` and `zone-unavailable` notifications
+for change in zone data, configuration of new zone in the system and
+removal of a zone from configuration).
+
+Sending a command to single recipient is slightly more complex. The
+sending user sends a message to the receiving one, addressed either by
+session ID or by an alias (group to which at most one session may be
+subscribed). The message contains the name of the command and
+parameters. It is sent with the undeliverable signals turned on.
+The user also starts a timer (with reasonably long timeout). The
+sender also subscribes to notifications about terminated sessions or
+unsubscription from the alias group.
+
+The receiving user gets the message, runs the command and sends a
+response back, with the result. The response has the undeliverable
+signal turned off and it is marked as response to the message
+containing the command. The sending user receives the answer and pairs
+it with the command.
+
+There are several things that may go wrong.
+
+* There might be an error on the receiving user (bad parameters, the
+ operation failed, the recipient doesn't know command of that name).
+ The receiving side sends the response as previous, the only
+ difference is the content of the payload. The sending user is
+ notified about it, without delays.
+* The recipient user doesn't exist (either the session ID is wrong or
+ terminated already, or the alias is empty). The system sends a
+ failure response and the sending user knows immediately the command
+ failed.
+* The recipient disconnects while processing the command (possibly
+ crashes). The sender gets a notification about disconnection or
+ unsubscription from the alias group and knows the answer won't come.
+* The recipient ``blackholes'' the command. It receives it, but never
+ answers. The timeout in sender times out. As this is a serious
+ programmer error in the recipient and should be rare, the sender
+ should at least log an error to notify about the case.
+
+One example would be asking the question of life, universe and
+everything (all the examples assume the sending user is already
+subscribed to the notifications):
+
+ s12345 -> DeepThought
+ {"command": ["question", {
+ "what": ["Life", "Universe", "*"]
+ }]}
+ s23456 -> s12345
+ {"reply": [0, 42]}
+
+The deep thought had an alias. But the answer is sent from its session
+ID. The `0` in the reply means ``success''.
+
+Another example might be asking for some data at a bureau and getting
+an error:
+
+ s12345 -> Burreau
+ {"command": ["provide-information", {
+ "about": "me",
+ "topic": "taxes"
+ }]}
+ s23456 -> s12345
+ {"reply": [1, "You need to fill in other form"]}
+
+And, in this example, the sender is trying to reach an non-existent
+session. The `msgq` here is not the alias `Msgq`, but a special
+``phantom'' session ID that is not listed anywhere.
+
+ s12345 -> s0
+ {"command": ["ping"]}
+ msgq -> s12345
+ {"reply": [-1, "No such recipient"]}
+
+Last, an example when the other user disconnects while processing the
+command.
+
+ s12345 -> s23456
+ {"command": ["shutdown"]}
+ msgq -> s12345
+ {"notification": ["disconnected", {
+ "lname": "s23456"
+ }]}
+
+The system does not support sending a command to multiple users
+directly. It can be accomplished as this:
+
+* The sending user calls a command on the system to get list of
+ sessions in given group. This is command to alias, so it can be done
+ by the previous way.
+* After receiving the list of session IDs, multiple copies of the
+ command are sent by the sending user, one to each of the session
+ IDs.
+* Successes and failures are handled the same as above, since these
+ are just single-recipient commands.
+
+So, this would be an example with unhelpful war council.
+
+ s12345 -> Msgq
+ {"command": ["get-subscriptions", {
+ "group": "WarCouncil"
+ }]}
+ msgq -> s12345
+ {"reply": [0, ["s1", "s2", "s3"]]}
+ s12345 -> s1
+ {"command": ["advice", {
+ "topic": "Should we attack?"
+ }]}
+ s12345 -> s2
+ {"command": ["advice", {
+ "topic": "Should we attack?"
+ }]}
+ s12345 -> s3
+ {"command": ["advice", {
+ "topic": "Should we attack?"
+ }]}
+ s1 -> s12345
+ {"reply": [0, true]}
+ s2 -> s12345
+ {"reply": [0, false]}
+ s3 -> s12345
+ {"reply": [1, "Advice feature not implemented"]}
+
+Users
+-----
+
+While there's a lot of flexibility for the behaviour of a user, it
+usually comes to something like this (during the lifetime of the
+user):
+
+* The user starts up.
+* Then it creates one or more sessions (there may be technical reasons
+ to have more than one session, such as threads, but it is not
+ required by the system).
+* It subscribes to some groups to receive notifications in future.
+* It binds to some aliases if it wants to be reachable by others by a
+ nice name.
+* It invokes some start-up commands (to get the configuration, for
+ example).
+* During the lifetime, it listens for notifications and answers
+ commands. It also invokes remote commands and sends notifications
+ about things that are happening.
+* Eventually, the user terminates, closing all the sessions it had
+ opened.
+
+Known limitations
+-----------------
+
+It is meant mostly as signalling protocol. Sending millions of
+messages or messages of several tens of megabytes is probably a bad
+idea. While there's no architectural limitation with regards of the
+number of transferred messages and the maximum size of message is 4GB,
+the code is not optimised and it would probably be very slow.
+
+We currently expect the system not to be at heavy load. Therefore, we
+expect the system to keep up with users sending messages. The
+libraries write in blocking mode, which is no problem if the
+expectation is true, as the write buffers will generally be empty and
+the write wouldn't block, but if it turns out it is not the case, we
+might need to reconsider.
diff --git a/doc/design/resolver/01-scaling-across-cores b/doc/design/resolver/01-scaling-across-cores
new file mode 100644
index 0000000..dbd962f
--- /dev/null
+++ b/doc/design/resolver/01-scaling-across-cores
@@ -0,0 +1,347 @@
+Scaling across (many) cores
+===========================
+
+Problem statement
+-----------------
+
+The general issue is how to insure that the resolver scales.
+
+Currently resolvers are CPU bound, and it seems likely that both
+instructions-per-cycle and CPU frequency will not increase radically,
+scaling will need to be across multiple cores.
+
+How can we best scale a recursive resolver across multiple cores?
+
+Image of how resolution looks like
+----------------------------------
+
+ Receive the query. @# <------------------------\
+ | |
+ | |
+ v |
+ Parse it, etc. $ |
+ | |
+ | |
+ v |
+ Look into the cache. $# |
+ Cry <---- No <---------- Is it there? -----------> Yes ---------\ |
+ | ^ | |
+ Prepare upstream query $ | | |
+ | | | |
+ v | | |
+ Send an upstream query (#) | | |
+ | | | |
+ | | | |
+ v | | |
+ Wait for answer @(#) | | |
+ | | | |
+ v | | |
+ Parse $ | | |
+ | | | |
+ v | | |
+ Is it enough? $ ----> No ---------/ | |
+ | | |
+ Yes | |
+ | | |
+ \-----------------------> Build answer $ <----------------------/ |
+ | |
+ | |
+ v |
+ Send answer # -----------------------------/
+
+This is simplified version, however. There may be other tasks (validation, for
+example), which are not drawn mostly for simplicity, as they don't produce more
+problems. The validation would be done as part of some computational task and
+they could do more lookups in the cache or upstream queries.
+
+Also, multiple queries may generate the same upstream query, so they should be
+aggregated together somehow.
+
+Legend
+~~~~~~
+ * $ - CPU intensive
+ * @ - Waiting for external event
+ * # - Possible interaction with other tasks
+
+Goals
+-----
+ * Run the CPU intensive tasks in multiple threads to allow concurrency.
+ * Minimise waiting for locks.
+ * Don't require too much memory.
+ * Minimise the number of upstream queries (both because they are slow and
+ expensive and also because we don't want to eat too much bandwidth and spam
+ the authoritative servers).
+ * Design simple enough so it can be implemented.
+
+Naïve version
+-------------
+
+Let's look at possible approaches and list their pros and cons. Many of the
+simple versions would not really work, but let's have a look at them anyway,
+because thinking about them might bring some solutions for the real versions.
+
+We take one query, handle it fully, with blocking waits for the answers. After
+this is done, we take another. The cache is private for each one process.
+
+Advantages:
+
+ * Very simple.
+ * No locks.
+
+Disadvantages:
+
+ * To scale across cores, we need to run *a lot* of processes, since they'd be
+ waiting for something most of their time. That means a lot of memory eaten,
+ because each one has its own cache. Also, running so many processes may be
+ problematic, processes are not very cheap.
+ * Many things would be asked multiple times, because the caches are not
+ shared.
+
+Threads
+~~~~~~~
+
+Some of the problems could be solved by using threads, but they'd not improve
+it much, since threads are not really cheap either (starting several hundred
+threads might not be a good idea either).
+
+Also, threads bring other problems. When we still assume separate caches (for
+caches, see below), we need to ensure safe access to logging, configuration,
+network, etc. These could be a bottleneck (eg. if we lock every time we read a
+packet from network, when there are many threads, they'll just fight over the
+lock).
+
+Supercache
+~~~~~~~~~~
+
+The problem with cache could be solved by placing a ``supercache'' between the
+resolvers and the Internet. That one would do almost no processing, it would
+just take the query, looked up in the cache and either answered from the cache
+or forwarded the query to the external world. It would store the answer and
+forward it back.
+
+The cache, if single-threaded, could be a bottle-neck. To solve it, there could
+be several approaches:
+
+Layered cache::
+ Each process has it's own small cache, which catches many queries. Then, a
+ group of processes shares another level of bigger cache, which catches most
+ of the queries that get past the private caches. We further group them and
+ each level handles less queries from each process, so they can keep up.
+ However, with each level, we add some overhead to do another lookup.
+Segmented cache::
+ We have several caches of the same level, in parallel. When we would ask a
+ cache, we hash the query and decide which cache to ask by the hash. Only that
+ cache would have that answer if any and each could run in a separate process.
+ The only problem is, could there be a pattern of queries that would skew to
+ use only one cache while the rest would be idle?
+Shared cache access::
+ A cache would be accessed by multiple processes/threads. See below for
+ details, but there's a risk of lock contention on the cache (it depends on
+ the data structure).
+
+Upstream queries
+~~~~~~~~~~~~~~~~
+
+Before doing an upstream query, we look into the cache to ensure we don't have
+the information yet. When we get the answer, we want to update the cache.
+
+This suggests the upstream queries are tightly coupled with the cache. Now,
+when we have several cache processes/threads, each can have some set of opened
+sockets which are not shared with other caches to do the lookups. This way we
+can avoid locking the upstream network communication.
+
+Also, we can have three conceptual states for data in cache, and act
+differently when it is requested.
+
+Present::
+ If it is available, in positive or negative version, we just provide the
+ answer right away.
+Not present::
+ The continuation of processing is queued somehow (blocked/callback is
+ stored/whatever). An upstream query is sent and we get to the next state.
+Waiting for answer::
+ If another query for the same thing arrives, we just queue it the same way
+ and keep waiting. When the answer comes, all the queued tasks are resumed.
+ If the TTL > 0, we store the answer and set it to ``present''.
+
+We want to do aggregation of upstream queries anyway, using cache for it saves
+some more processing and possibly locks.
+
+Multiple parallel queries
+-------------------------
+
+It seems obvious we can't afford to have a thread or process for each
+outstanding query. We need to handle multiple queries in each one at any given
+time.
+
+Coroutines
+~~~~~~~~~~
+
+The OS-level threads might be too expensive, but coroutines might be cheap
+enough. In that way, we could still write a code that would be easy to read,
+but limit the number of OS threads to reasonable number.
+
+In this model, when a query comes, a new coroutine/user-level thread is created
+for it. We use special reads and writes whenever there's an operation that
+could block. These reads and writes would internally schedule the operation
+and switch to another coroutine (if there's any ready to be executed).
+
+Each thread/process maintains its own set of coroutines and they do not
+migrate. This way, the queue of coroutines is kept lock-less, as well as any
+private caches. Only the shared caches are protected by a lock.
+
+[NOTE]
+The `coro` unit we have in the current code is *not* considered a coroutine
+library here. We would need a coroutine library where we have real stack for
+each coroutine and we switch the stacks on coroutine switch. That is possible
+with reasonable amount of dark magic (see `ucontext.h`, for example, but there
+are surely some higher-level libraries for that).
+
+There are some trouble with multiple coroutines waiting on the same event, like
+the same upstream query (possibly even coroutines from different threads), but
+it should be possible to solve.
+
+Event-based
+~~~~~~~~~~~
+
+We use events (`asio` and stuff) for writing it. Each outstanding query is an
+object with some callbacks on it. When we would do a possibly blocking
+operation, we schedule a callback to happen once the operation finishes.
+
+This is more lightweight than the coroutines (the query objects will be smaller
+than the stacks for coroutines), but it is harder to write and read for.
+
+[NOTE]
+Do not consider cross-breeding the models. That leads to space-time distortions
+and brain damage. Implementing one on top of other is OK, but mixing it in the
+same bit of code is a way do madhouse.
+
+Landlords and peasants
+~~~~~~~~~~~~~~~~~~~~~~
+
+In both the coroutines and event-based models, the cache and other shared
+things are easier to imagine as objects the working threads fight over to hold
+for a short while. In this model, it is easier to imagine each such shared
+object as something owned by a landlord that doesn't let anyone else on it,
+but you can send requests to him.
+
+A query is an object once again, with some kind of state machine.
+
+Then there are two kinds of threads. The peasants are just to do the heavy
+work. There's a global work-queue for peasants. Once a peasant is idle, it
+comes to the queue and picks up a handful of queries from there. It does as
+much on each the query as possible without requiring any shared resource.
+
+The other kind, the landlords, have a resource to watch over each. So we would
+have a cache (or several parts of cache), the sockets for accepting queries and
+answering them, possibly more. Each of these would have a separate landlord
+thread and a queue of tasks to do on the resource (look up something, send an
+answer...).
+
+Similarly, the landlord would take a handful of tasks from its queue and start
+handling them. It would possibly produce some more tasks for the peasants.
+
+The point here is, all the synchronisation is done on the queues, not on the
+shared resources themselves. And, we would append to a queues once the whole
+batch was completed. By tweaking the size of the batch, we could balance the
+lock contention, throughput and RTT. The append/remove would be a quick
+operation, and the cost of locks would amortize in the larger amount of queries
+handled per one lock operation.
+
+The possible downside is, a query needs to travel across several threads during
+its lifetime. It might turn out it is faster to move the query between cores
+than accessing the cache from several threads, since it is smaller, but it
+might be slower as well.
+
+It would be critical to make some kind of queue that is fast to append to and
+fast to take out first n items. Also, the tasks in the queues can be just
+abstract `boost::function<void (Worker&)>` functors, and each worker would just
+iterate through the queue, calling each functor. The parameter would be to
+allow easy generation of more tasks for other queues (they would be stored
+privately first, and appended to remote queues at the end of batch).
+
+Also, if we wanted to generate multiple parallel upstream queries from a single
+query, we would need to be careful. A query object would not have a lock on
+itself and the upstream queries could end up in a different caches/threads. To
+protect the original query, we would add another landlord that would aggregate
+answers together and let the query continue processing once it got enough
+answers. That way, the answers would be pushed all to the same threads and they
+could not fight over the query.
+
+[NOTE]
+This model would work only with threads, not processes.
+
+Shared caches
+-------------
+
+While it seems it is good to have some sort of L1 cache with pre-rendered
+answers (according to measurements in the #2777 ticket), we probably need some
+kind of larger shared cache.
+
+If we had just a single shared cache protected by lock, there'd be a lot of
+lock contention on the lock.
+
+Partitioning the cache
+~~~~~~~~~~~~~~~~~~~~~~
+
+We split the cache into parts, either by the layers or by parallel bits we
+switch between by a hash. If we take it to the extreme, a lock on each hash
+bucket would be this kind, though that might be wasting resources (how
+expensive is it to create a lock?).
+
+Landlords
+~~~~~~~~~
+
+The landlords do synchronizations themselves. Still, the cache would need to be
+partitioned.
+
+RCU
+~~~
+
+The RCU is a lock-less synchronization mechanism. An item is accessed through a
+pointer. An updater creates a copy of the structure (in our case, it would be
+content of single hash bucket) and then atomically replaces the pointer. The
+readers from before have the old version, the new ones get the new version.
+When all the old readers die out, the old copy is reclaimed. Also, the
+reclamation can AFAIK be postponed for later times when we are slightly more
+idle or to a different thread.
+
+We could use it for cache â in the fast track, we would just read the cache. In
+the slow one, we would have to wait in queue to do the update, in a single
+updater thread (because we don't really want to be updating the same cell twice
+at the same time).
+
+Proposals
+---------
+
+In either case, we would have some kind of L1 cache with pre-rendered answers.
+For these proposals (except the third), we wouldn't care if we split the cache
+into parallel chunks or layers.
+
+Hybrid RCU/Landlord
+~~~~~~~~~~~~~~~~~~~
+
+The landlord approach, just read only accesses to the cache are done directly
+by the peasants. Only if they don't find what they want, they'd append the
+queue to the task of the landlord. The landlord would be doing the RCU updates.
+It could happen that by the time the landlord gets to the task the answer is
+already there, but that would not matter much.
+
+Accessing network would be from landlords.
+
+Coroutines+RCU
+~~~~~~~~~~~~~~
+
+We would do the coroutines, and the reads from shared cache would go without
+locking. When doing write, we would have to lock.
+
+To avoid locking, each worker thread would have its own set of upstream sockets
+and we would dup the sockets from users so we don't have to lock that.
+
+Multiple processes with coroutines and RCU
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This would need the layered cache. The upper caches would be mapped to local
+memory for read-only access. Each cache would be a separate process. The
+process would do the updates â if the answer was not there, the process would
+be asked by some kind of IPC to pull it from upstream cache or network.
diff --git a/doc/design/resolver/02-mixed-recursive-authority-setup b/doc/design/resolver/02-mixed-recursive-authority-setup
new file mode 100644
index 0000000..a1cc5f6
--- /dev/null
+++ b/doc/design/resolver/02-mixed-recursive-authority-setup
@@ -0,0 +1,150 @@
+Mixed recursive & authoritative setup
+=====================================
+
+Ideally we will run the authoritative server independently of the
+recursive resolver.
+
+We need a way to run both an authoritative and a recursive resolver on
+the same machine and listening on the same IP/port. But we need a way to
+run only one of them as well.
+
+This is mostly the same problem as we have with DDNS packets and xfr-out
+requests, but they aren't that performance sensitive as auth & resolver.
+
+There are a number of possible approaches to this:
+
+One fat module
+--------------
+
+With some build system or dynamic linker tricks, we create three modules:
+
+ * Stand-alone auth
+ * Stand-alone resolver
+ * Compound module containing both
+
+The user then chooses either one stand-alone module, or the compound one,
+depending on the requirements.
+
+Advantages
+~~~~~~~~~~
+
+ * It is easier to switch between processing and ask authoritative questions
+ from within the resolver processing.
+
+Disadvantages
+~~~~~~~~~~~~~
+
+ * The code is not separated (one bugs takes down both, admin can't see which
+ one takes how much CPU).
+ * BIND 9 does this and its code is a jungle. Maybe it's not just a
+ coincidence.
+ * Limits flexibility -- for example, we can't then decide to make the resolver
+ threaded (or we would have to make sure the auth processing doesn't break
+ with threads, which will be hard).
+
+There's also the idea of putting the auth into a loadable library and the
+resolver could load and use it somehow. But the advantages and disadvantages
+are probably the same.
+
+Auth first
+----------
+
+We do the same as with xfrout and ddns. When a query comes, it is examined and
+if the `RD` bit is set, it is forwarded to the resolver.
+
+Advantages
+~~~~~~~~~~
+
+ * Separate auth and resolver modules
+ * Minimal changes to auth
+ * No slowdown on the auth side
+
+Disadvantages
+~~~~~~~~~~~~~
+
+ * Counter-intuitive asymmetric design
+ * Possible slowdown on the resolver side
+ * Resolver needs to know both modes (for running stand-alone too)
+
+There's also the possibility of the reverse -- resolver first. It may make
+more sense for performance (the more usual scenario would probably be a
+high-load resolver with just few low-volume authoritative zones). On the other
+hand, auth already has some forwarding tricks.
+
+Auth with cache
+---------------
+
+This is mostly the same as ``Auth first'', however, the cache is in the auth
+server. If it is in the cache, it is answered right away. If not, it is then
+forwarded to the resolver. The resolver then updates the cache too.
+
+Advantages
+~~~~~~~~~~
+
+ * Probably good performance
+
+Disadvantages
+~~~~~~~~~~~~~
+
+ * Cache duplication (several auth modules, it doesn't feel like it would work
+ with shared memory without locking).
+ * Cache is probably very different from authoritative zones, it would
+ complicate auth processing.
+ * The resolver needs own copy of cache (to be able to get partial results),
+ probably a different one than the auth server.
+
+Receptionist
+------------
+
+One module does only the listening. It doesn't process the queries itself, it
+only looks into them and forwards them to the processing modules.
+
+Advantages
+~~~~~~~~~~
+
+ * Clean design with separated modules
+ * Easy to run modules stand-alone
+ * Allows for solving the xfrout & ddns forwarding without auth running
+ * Allows for views (different auths with different configurations)
+ * Allows balancing/clustering across multiple machines
+ * Easy to create new modules for different kinds of DNS handling and share
+ port with them too
+
+Disadvantages
+~~~~~~~~~~~~~
+
+ * Need to set up another module (not a problem if we have inter-module
+ dependencies in b10-init)
+ * Possible performance impact. However, experiments show this is not an issue,
+ and the receptionist can actually increase the throughput with some tuning
+ and the increase in RTT is not big.
+
+Implementation ideas
+~~~~~~~~~~~~~~~~~~~~
+
+ * Let's have a new TCP transport, where we send not only the DNS messages,
+ but also the source and destination ports and addresses (two reasons --
+ ACLs in target module and not keeping state in the receptionist). It would
+ allow for transfer of a batch of messages at once, to save some calls to
+ kernel (like a length of block of messages, it is read at once, then they
+ are all parsed one by one, the whole block of answers is sent back).
+ * A module creates a listening socket (UNIX by default) on startup and
+ contacts all the receptionists. It sends what kind of packets to send
+ to the module and the address of the UNIX socket. All the receptionists
+ connect to the module. This allows for auto-configuring the receptionist.
+ * The queries are sent from the receptionist in batches, the answers are sent
+ back to the receptionist in batches too.
+ * It is possible to fine-tune and use OS-specific tricks (like epoll or
+ sending multiple UDP messages by single call to sendmmsg()).
+
+Proposal
+--------
+
+Implement the receptionist in a way we can still work without it (not throwing
+the current UDPServer and TCPServer in asiodns away).
+
+The way we handle xfrout and DDNS needs some changes, since we can't forward
+sockets for the query. We would implement the receptionist protocol on them,
+which would allow the receptionist to forward messages to them. We would then
+modify auth to be able to forward the queries over the receptionist protocol,
+so ordinary users don't need to start the receptionist.
diff --git a/doc/design/resolver/03-cache-algorithm b/doc/design/resolver/03-cache-algorithm
new file mode 100644
index 0000000..42bfa09
--- /dev/null
+++ b/doc/design/resolver/03-cache-algorithm
@@ -0,0 +1,22 @@
+03-cache-algorithm
+
+Introduction
+------------
+Cache performance may be important for the resolver. It might not be
+critical. We need to research this.
+
+One key question is: given a specific cache hit rate, how much of an
+impact does cache performance have?
+
+For example, if we have 90% cache hit rate, will we still be spending
+most of our time in system calls or in looking things up in our cache?
+
+There are several ways we can consider figuring this out, including
+measuring this in existing resolvers (BIND 9, Unbound) or modeling
+with specific values.
+
+Once we know how critical the cache performance is, we can consider
+which algorithm is best for that. If it is very critical, then a
+custom algorithm designed for DNS caching makes sense. If it is not,
+then we can consider using an STL-based data structure.
+
diff --git a/doc/design/resolver/03-cache-algorithm.txt b/doc/design/resolver/03-cache-algorithm.txt
new file mode 100644
index 0000000..4aacc4d
--- /dev/null
+++ b/doc/design/resolver/03-cache-algorithm.txt
@@ -0,0 +1,256 @@
+03-cache-algorithm
+
+Introduction
+------------
+Cache performance may be important for the resolver. It might not be
+critical. We need to research this.
+
+One key question is: given a specific cache hit rate, how much of an
+impact does cache performance have?
+
+For example, if we have 90% cache hit rate, will we still be spending
+most of our time in system calls or in looking things up in our cache?
+
+There are several ways we can consider figuring this out, including
+measuring this in existing resolvers (BIND 9, Unbound) or modeling
+with specific values.
+
+Once we know how critical the cache performance is, we can consider
+which algorithm is best for that. If it is very critical, then a
+custom algorithm designed for DNS caching makes sense. If it is not,
+then we can consider using an STL-based data structure.
+
+Effectiveness of Cache
+----------------------
+
+First, I'll try to answer the introductory questions.
+
+In some simplified model, we can express the amount of running time
+for answering queries directly from the cache in the total running
+time including that used for recursive resolution due to cache miss as
+follows:
+
+A = r*Q2*/(r*Q2+ Q1*(1-r))
+where
+A: amount of time for answering queries from the cache per unit time
+ (such as sec, 0<=A<=1)
+r: cache hit rate (0<=r<=1)
+Q1: max qps of the server with 100% cache hit
+Q2: max qps of the server with 0% cache hit
+
+Q1 can be measured easily for given data set; measuring Q2 is tricky
+in general (it requires many external queries with unreliable
+results), but we can still have some not-so-unrealistic numbers
+through controlled simulation.
+
+As a data point for these values, see a previous experimental results
+of mine:
+https://lists.isc.org/pipermail/bind10-dev/2012-July/003628.html
+
+Looking at the "ideal" server implementation (no protocol overhead)
+with the set up 90% and 85% cache hit rate with 1 recursion on cache
+miss, and with the possible maximum total throughput, we can deduce
+Q1 and Q2, which are: 170591qps and 60138qps respectively.
+
+This means, with 90% cache hit rate (r = 0.9), the server would spend
+76% of its run time for receiving queries and answering responses
+directly from the cache: 0.9*60138/(0.9*60138 + 0.1*170591) = 0.76.
+
+I also ran more realistic experiments: using BIND 9.9.2 and unbound
+1.4.19 in the "forward only" mode with crafted query data and the
+forwarded server to emulate the situation of 100% and 0% cache hit
+rates. I then measured the max response throughput using a
+queryperf-like tool. In both cases Q2 is about 28% of Q1 (I'm not
+showing specific numbers to avoid unnecessary discussion about
+specific performance of existing servers; it's out of scope of this
+memo). Using Q2 = 0.28*Q1, above equation with 90% cache hit rate
+will be: A = 0.9 * 0.28 / (0.9*0.28 + 0.1) = 0.716. So the server will
+spend about 72% of its running time to answer queries directly from
+the cache.
+
+Of course, these experimental results are too simplified. First, in
+these experiments we assumed only one external query is needed on
+cache miss. In general it can be more; however, it may not actually
+be too optimistic either: in my another research result:
+http://bind10.isc.org/wiki/ResolverPerformanceResearch
+In the more detailed analysis using real query sample and tracing what
+an actual resolver would do, it looked we'd need about 1.44 to 1.63
+external queries per cache miss in average.
+
+Still, of course, the real world cases are not that simple: in reality
+we'd need to deal with timeouts, slower remote servers, unexpected
+intermediate results, etc. DNSSEC validating resolvers will clearly
+need to do more work.
+
+So, in the real world deployment Q2 should be much smaller than Q1.
+Here are some specific cases of the relationship between Q1 and Q2 for
+given A (assuming r = 0.9):
+
+70%: Q2 = 0.26 * Q1
+60%: Q2 = 0.17 * Q1
+50%: Q2 = 0.11 * Q1
+
+So, even if "recursive resolution is 10 times heavier" than the cache
+only case, we can assume the server spends a half of its run time for
+answering queries directly from the cache at the cache hit rate of
+90%. I think this is a reasonably safe assumption.
+
+Now, assuming the number of 50% or more, does this suggest we should
+highly optimize the cache? Opinions may vary on this point, but I
+personally think the answer is yes. I've written an experimental
+cache only implementation that employs the idea of fully-rendered
+cached data. On one test machine (2.20GHz AMD64, using a single
+core), queryperf-like benchmark shows it can handle over 180Kqps,
+while BIND 9.9.2 can just handle 41K qps. The experimental
+implementation skips some necessary features for a production server,
+and cache management itself is always inevitable bottleneck, so the
+production version wouldn't be that fast, but it still suggests it may
+not be very difficult to reach over 100Kqps in production environment
+including recursive resolution overhead.
+
+Cache Types
+-----------
+
+1. Record cache
+
+Conceptually, any recursive resolver (with cache) implementation would
+have cache for RRs (or RRsets in the modern version of protocol) given
+in responses to its external queries. In BIND 9, it's called the
+"cached DB", using an in-memory rbt-like tree. unbound calls it
+"rrset cache", which is implemented as a hash table.
+
+2. Delegation cache
+
+Recursive server implementations would also have cache to determine
+the deepest zone cut for a given query name in the recursion process.
+Neither BIND 9 nor unbound has a separate cache for this purpose;
+basically they try to find an NR RRset from the "record cache" whose
+owner name best matches the given query name.
+
+3. Remote server cache
+
+In addition, a recursive server implementation may maintain a cache
+for information of remote authoritative servers. Both BIND 9 and
+unbound conceptually have this type of cache, although there are some
+non-negligible differences in details. BIND 9's implementation of
+this cache is called ADB. Its a hash table whose key is domain name,
+and each entry stores corresponding IPv6/v4 addresses; another data
+structure for each address stores averaged RTT for the address,
+lameness information, EDNS availability, etc. unbound's
+implementation is called "infrastructure cache". It's a hash table
+keyed with IP addresses whose entries store similar information as
+that in BIND 9's per address ADB entry. In unbound a remote server's
+address must be determined by looking up the record cache (rrset cache
+in unbound terminology); unlike BIND 9's ADB, there's no direct
+shortcut from a server's domain name to IP addresses.
+
+4. Full response cache
+
+unbound has an additional cache layer, called the "message cache".
+It's a hash table whose hash key is query parameter (essentially qname
+and type) and entry is a sequence to record (rrset) cache entries.
+This sequence constructs a complete response to the corresponding
+query, so it would help optimize building a response message skipping
+the record cache for each section (answer/authority/additional) of the
+response message. PowerDNS recursor has (seemingly) the same concept
+called "packet cache" (but I don't know its implementation details
+very much).
+
+BIND 9 doesn't have this type of cache; it always looks into the
+record cache to build a complete response to a given query.
+
+Miscellaneous General Requirements
+----------------------------------
+
+- Minimize contention between threads (if threaded)
+- Cache purge policy: normally only a very small part of cached DNS
+ information will be reused, and those reused are very heavily
+ reused. So LRU-like algorithm should generally work well, but we'll
+ also need to honor DNS TTL.
+
+Random Ideas for BIND 10
+------------------------
+
+Below are specific random ideas for BIND 10. Some are based on
+experimental results with reasonably realistic data; some others are
+mostly a guess.
+
+1. Fully rendered response cache
+
+Some real world query samples show that a very small portion of entire
+queries are very popular and queried very often and many times; the
+rest is rarely reused, if any. Two different data sets show top
+10,000 queries would cover around 80% of total queries, regardless
+of the size of the total queries. This suggests an idea of having a
+small, highly optimized full response cache.
+
+I tried this idea in the jinmei-l1cache branch. It's a hash table
+keyed with a tuple of query name and type whose entry stores fully
+rendered, wire-format response image (answer section only, assuming
+the "minimal-responses" option). It also maintains offsets to each
+RR, so it can easily update TTLs when necessary or rotate RRs if
+optionally requested. If neither TTL adjustment nor RR rotation is
+required, query handling is just to lookup the hash table and copy the
+pre-rendered data. Experimental benchmark showed it ran vary fast;
+more than 4 times faster than BIND 9, and even much faster than other
+implementations that have full response cache (although, as usual, the
+comparison is not entirely fair).
+
+Also, the cache size is quite small; the run time memory footprint of
+this server process was just about 5MB. So, I think it's reasonable
+to have each process/thread have their own copy of this cache to
+completely eliminate contention. Also, if we can keep the cache size
+this small, it would be easier to dump it to a file on shutdown and
+reuse it on restart. This will be quite effective (if the downtime is
+reasonably short) because the cached data are expected to be highly
+popular.
+
+2. Record cache
+
+For the normal record cache, I don't have a particular idea beyond
+something obvious, like a hash table to map from query parameters to
+corresponding RRset (or negative information). But I guess this cache
+should be shared by multiple threads. That will help reconstruct the
+full response cache data on TTL expiration more efficiently. And, if
+shared, the data structure should be chosen so that contention
+overhead can be minimized. In general, I guess something like hash
+tables is more suitable than tree-like structure in that sense.
+
+There's other points to discuss for this cache related to other types
+of cache (see below).
+
+3. Separate delegation cache
+
+One thing I'm guessing is that it may make sense if we have a separate
+cache structure for delegation data. It's conceptually a set of NS
+RRs so we can identify the best (longest) matching one for a given
+query name.
+
+Analysis of some sets of query data showed the vast majority of
+end client's queries are for A and AAAA (not surprisingly). So, even
+if we separate this cache from the record cache, the additional
+overhead (both for memory and fetch) will probably (hopefully) be
+marginal. Separating caches will also help reduce contention between
+threads. It *might* also help improve lookup performance because this
+can be optimized for longest match search.
+
+4. Remote server cache without involving the record cache
+
+Likewise, it may make sense to maintain the remote server cache
+separately from the record cache. I guess these AAAA and A records
+are rarely the queried by end clients, so, like the case of delegation
+cache it's possible that the data sets are mostly disjoint. Also, for
+this purpose the RRsets don't have to have higher trust rank (per
+RFC2181 5.4.1): glue or additional are okay, and, by separating these
+from the record cache, we can avoid accidental promotion of these data
+to trustworthy answers and returning them to clients (BIND 9 had this
+type of bugs before).
+
+Custom vs Existing Library (STL etc)
+------------------------------------
+
+It may have to be discussed, but I guess in many cases we end up
+introducing custom implementation because these caches should be
+highly performance sensitive, directly related our core business, and
+also have to be memory efficient. But in some sub components we may
+be able to benefit from existing generic libraries.
diff --git a/doc/design/resolver/README b/doc/design/resolver/README
new file mode 100644
index 0000000..b6e9285
--- /dev/null
+++ b/doc/design/resolver/README
@@ -0,0 +1,5 @@
+This directory contains research and design documents for the BIND 10
+resolver reimplementation.
+
+Each file contains a specific issue and discussion surrounding that
+issue.
diff --git a/doc/devel/01-dns.dox b/doc/devel/01-dns.dox
index e72dbbd..ed76dea 100644
--- a/doc/devel/01-dns.dox
+++ b/doc/devel/01-dns.dox
@@ -8,7 +8,7 @@
*
* @section b10-cfgmgr b10-cfgmgr Overview
*
- * @todo: Descibe b10-cfgmgr here.
+ * @todo: Describe b10-cfgmgr here.
*
*
*/
diff --git a/doc/devel/02-dhcp.dox b/doc/devel/02-dhcp.dox
index b98920f..ddc5e23 100644
--- a/doc/devel/02-dhcp.dox
+++ b/doc/devel/02-dhcp.dox
@@ -21,7 +21,7 @@
*
* DHCPv4 server component is now integrated with the BIND10 message queue.
* The integration is performed by establishSession() and disconnectSession()
- * functions in isc::dhcp::ControlledDhcpv4Srv class. main() method deifined
+ * functions in isc::dhcp::ControlledDhcpv4Srv class. main() method defined
* in the src/bin/dhcp4/main.cc file instantiates isc::dhcp::ControlledDhcpv4Srv
* class that establishes connection with msgq and install necessary handlers
* for receiving commands and configuration updates. It is derived from
diff --git a/doc/devel/mainpage.dox b/doc/devel/mainpage.dox
index 295fd03..2b1ea8d 100644
--- a/doc/devel/mainpage.dox
+++ b/doc/devel/mainpage.dox
@@ -1,35 +1,68 @@
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
/**
- *
* @mainpage BIND10 Developer's Guide
*
* Welcome to BIND10 Developer's Guide. This documentation is addressed
- * at existing and prospecting developers and programmers, who would like
- * to gain insight into internal workings of BIND 10. It could also be useful
- * for existing and prospective contributors.
+ * at existing and prospecting developers and programmers and provides
+ * information needed to both extend and maintain BIND 10.
+ *
+ * If you wish to write "hook" code - code that is loaded by BIND 10 at
+ * run-time and modifies its behavior you should read the section
+ * @ref hooksdgDevelopersGuide.
+ *
+ * BIND 10 maintanenace information is divided into a number of sections
+ * depending on focus. DNS-specific issues are covered in the
+ * @ref dnsMaintenanceGuide while information on DHCP-specific topics can
+ * be found in the @ref dhcpMaintenanceGuide. General BIND 10 topics, not
+ * specific to any protocol, are discussed in @ref miscellaneousTopics.
*
* If you are a user or system administrator, rather than software engineer,
- * you should read <a href="http://bind10.isc.org/docs/bind10-guide.html">BIND10
+ * you should read the
+ * <a href="http://bind10.isc.org/docs/bind10-guide.html">BIND10
* Guide (Administrator Reference for BIND10)</a> instead.
*
- * Regardless of your field of expertise, you are encouraged to visit
+ * Regardless of your field of expertise, you are encouraged to visit the
* <a href="http://bind10.isc.org/">BIND10 webpage (http://bind10.isc.org)</a>
+ * @section hooksFramework Hooks Framework
+ * - @subpage hooksdgDevelopersGuide
+ * - @subpage dhcpv4Hooks
+ * - @subpage dhcpv6Hooks
+ * - @subpage hooksComponentDeveloperGuide
+ * - @subpage hooksmgMaintenanceGuide
*
- * @section DNS
+ * @section dnsMaintenanceGuide DNS Maintenance Guide
* - Authoritative DNS (todo)
* - Recursive resolver (todo)
* - @subpage DataScrubbing
*
- * @section DHCP
+ * @section dhcpMaintenanceGuide DHCP Maintenance Guide
* - @subpage dhcp4
* - @subpage dhcpv4Session
* - @subpage dhcpv4ConfigParser
* - @subpage dhcpv4ConfigInherit
+ * - @subpage dhcpv4Other
* - @subpage dhcp6
* - @subpage dhcpv6Session
* - @subpage dhcpv6ConfigParser
* - @subpage dhcpv6ConfigInherit
+ * - @subpage dhcpv6DDNSIntegration
+ * - @subpage dhcpv6Other
* - @subpage libdhcp
* - @subpage libdhcpIntro
+ * - @subpage libdhcpRelay
* - @subpage libdhcpIfaceMgr
* - @subpage libdhcpsrv
* - @subpage leasemgr
@@ -37,8 +70,9 @@
* - @subpage allocengine
* - @subpage dhcpDatabaseBackends
* - @subpage perfdhcpInternals
+ * - @subpage libdhcp_ddns
*
- * @section misc Miscellaneous topics
+ * @section miscellaneousTopics Miscellaneous Topics
* - @subpage LoggingApi
* - @subpage LoggingApiOverview
* - @subpage LoggingApiLoggerNames
@@ -46,7 +80,10 @@
* - @subpage SocketSessionUtility
* - <a href="./doxygen-error.log">Documentation warnings and errors</a>
*
- * @todo: Move this logo to the right (and possibly up). Not sure what
- * is the best way to do it in Doxygen, without using CSS hacks.
* @image html isc-logo.png
*/
+/*
+ * @todo: Move the logo to the right (and possibly up). Not sure what
+ * is the best way to do it in Doxygen, without using CSS hacks.
+ */
+
diff --git a/doc/guide/.gitignore b/doc/guide/.gitignore
index 168d4ed..fc2510e 100644
--- a/doc/guide/.gitignore
+++ b/doc/guide/.gitignore
@@ -1,3 +1,4 @@
/bind10-guide.html
/bind10-guide.txt
/bind10-messages.html
+/bind10-messages.xml
diff --git a/doc/guide/Makefile.am b/doc/guide/Makefile.am
index 1d63c04..8f3aaaf 100644
--- a/doc/guide/Makefile.am
+++ b/doc/guide/Makefile.am
@@ -21,10 +21,8 @@ bind10-guide.html: bind10-guide.xml
http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl \
$(srcdir)/bind10-guide.xml
-HTML2TXT = elinks -dump -no-numbering -no-references
-
bind10-guide.txt: bind10-guide.html
- $(HTML2TXT) bind10-guide.html > $@
+ @ELINKS@ -dump -no-numbering -no-references bind10-guide.html > $@
bind10-messages.html: bind10-messages.xml
@XSLTPROC@ --novalid --xinclude --nonet \
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 583ee60..6c538cd 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -427,7 +427,7 @@ var/
<listitem>
<para>Go into the source and run configure:
<screen>$ <userinput>cd bind10-<replaceable>VERSION</replaceable></userinput>
- $ <userinput>./configure</userinput></screen>
+$ <userinput>./configure</userinput></screen>
</para>
</listitem>
@@ -438,21 +438,29 @@ var/
</listitem>
<listitem>
- <para>Install it as root (to default /usr/local):
+ <para>Install it as root (by default to prefix
+ <filename>/usr/local/</filename>):
<screen>$ <userinput>make install</userinput></screen>
</para>
</listitem>
<listitem>
+ <para>Change directory to the install prefix (by default
+ <filename>/usr/local/</filename>):
+ <screen>$ <userinput>cd /usr/local/</userinput></screen>
+ </para>
+ </listitem>
+
+ <listitem>
<para>Create a user for yourself:
- <screen>$ <userinput>cd /usr/local/etc/bind10/</userinput></screen>
- <screen>$ <userinput>/usr/local/sbin/b10-cmdctl-usermgr</userinput></screen>
+ <screen>$ <userinput>sbin/b10-cmdctl-usermgr add root</userinput></screen>
+ and enter a newly chosen password when prompted.
</para>
</listitem>
<listitem>
<para>Start the server (as root):
- <screen>$ <userinput>/usr/local/sbin/bind10</userinput></screen>
+ <screen>$ <userinput>sbin/bind10</userinput></screen>
</para>
</listitem>
@@ -461,7 +469,7 @@ var/
configuration. In another console, enable the authoritative
DNS service (by using the <command>bindctl</command> utility
to configure the <command>b10-auth</command> component to
- run): <screen>$ <userinput>bindctl</userinput></screen>
+ run): <screen>$ <userinput>bin/bindctl</userinput></screen>
(Login with the username and password you used above to create a user.)
<screen>
> <userinput>config add Init/components b10-auth</userinput>
@@ -481,7 +489,7 @@ var/
<listitem>
<para>Load desired zone file(s), for example:
- <screen>$ <userinput>b10-loadzone <replaceable>-c '{"database_file": "/usr/local/var/bind10/zone.sqlite3"}'</replaceable> <replaceable>your.zone.example.org</replaceable> <replaceable>your.zone.file</replaceable></userinput></screen>
+ <screen>$ <userinput>bin/b10-loadzone <replaceable>-c '{"database_file": "/usr/local/var/bind10/zone.sqlite3"}'</replaceable> <replaceable>your.zone.example.org</replaceable> <replaceable>your.zone.file</replaceable></userinput></screen>
</para>
(If you use the sqlite3 data source with the default DB
file, you can omit the -c option).
@@ -772,6 +780,16 @@ as a dependency earlier -->
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>--without-werror</term>
+ <listitem>
+ <simpara>Disable the default use of the
+ <option>-Werror</option> compiler flag so that
+ compiler warnings aren't build failures.
+ </simpara>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<note>
<para>
@@ -2487,8 +2505,8 @@ can use various data source backends.
<para>
The configuration is located in data_sources/classes. Each item there
represents one RR class and a list used to answer queries for that
- class. The default contains two classes. The CH class contains a static
- data source — one that serves things like
+ class. The default contains two classes. The CH class contains a
+ built-in data source — one that serves things like
<quote>AUTHORS.BIND.</quote>. The IN class contains single SQLite3
data source with database file located at
<filename>/usr/local/var/bind10/zone.sqlite3</filename>.
@@ -2555,7 +2573,7 @@ can use various data source backends.
</para>
<para>
- First, let's disable the static data source
+ First, let's disable the built-in data source
(<quote>VERSION.BIND</quote> and friends). As it is the only
data source in the CH class, we can remove the whole class.
@@ -2591,21 +2609,26 @@ can use various data source backends.
> <userinput>config set data_sources/classes/IN[1]/params { "example.org": "/path/to/example.org", "example.com": "/path/to/example.com" }</userinput>
> <userinput>config commit</userinput></screen>
+ Unfortunately, due to current technical limitations, the
+ params must be set as one JSON blob. To reload a zone, use the
+ same <command>Auth loadzone</command> command as above.
+ </para>
+
+ <para>
Initially, a map value has to be set, but this value may be an
- empty map. After that, key/value pairs can be added with 'config
- add' and keys can be removed with 'config remove'. The initial
- value may be an empty map, but it has to be set before zones are
- added or removed.
+ empty map. After that, key/value pairs can be added with
+ <command>config add</command> and keys can be removed with
+ <command>config remove</command>. The initial value may be an
+ empty map, but it has to be set before zones are added or
+ removed.
<screen>
> <userinput>config set data_sources/classes/IN[1]/params {}</userinput>
> <userinput>config add data_sources/classes/IN[1]/params another.example.org /path/to/another.example.org</userinput>
> <userinput>config add data_sources/classes/IN[1]/params another.example.com /path/to/another.example.com</userinput>
> <userinput>config remove data_sources/classes/IN[1]/params another.example.org</userinput>
- </screen>
+> <userinput>config commit</userinput></screen>
- <command>bindctl</command>. To reload a zone, you the same command
- as above.
</para>
</section>
@@ -2614,7 +2637,7 @@ can use various data source backends.
There's also <varname>Auth/database_file</varname> configuration
variable, pointing to a SQLite3 database file. This is no longer
used by <command>b10-auth</command>, but it is left in place for
- now, since other modules use it. Once <command>b10-xfrin</command>,
+ now, since other modules use it. Once <command>b10-zonemgr</command>,
<command>b10-xfrout</command> and <command>b10-ddns</command>
are ported to the new configuration, this will disappear. But for
now, make sure that if you use any of these modules, the new
@@ -2624,6 +2647,50 @@ can use various data source backends.
</para>
</note>
+ <section id='datasrc-static'>
+ <title>Adding a static data source</title>
+
+ <para>
+ BIND 10 includes a zone file named
+ <filename>static.zone</filename> in the CH (Chaos) class for
+ providing information about the server via the AUTHORS.BIND
+ and VERSION.BIND TXT records. By default, this BIND zone is
+ configured and its records are served.
+ </para>
+
+ <para>
+ If you have removed this zone from the configuration (e.g., by
+ using the commands in the previous section to disable the
+ "built-in data source"), here is how you can add it back to
+ serve the zones in the <filename>static.zone</filename> file.
+ </para>
+
+ <para>First, add the CH class if it doesn't exist:
+
+ <screen>> <userinput>config add data_sources/classes CH</userinput>
+> <userinput>config commit</userinput></screen>
+
+ Then, add a data source of type <emphasis>MasterFiles</emphasis>
+ in the CH class to serve the zones in
+ <filename>static.zone</filename>:
+
+ <screen>> <userinput>config add data_sources/classes/CH</userinput>
+> <userinput>config set data_sources/classes/CH[0]/type MasterFiles</userinput>
+> <userinput>config set data_sources/classes/CH[0]/cache-enable true</userinput>
+> <userinput>config set data_sources/classes/CH[0]/params {"BIND": "/usr/local/bind10/share/bind10/static.zone"}</userinput>
+> <userinput>config commit</userinput></screen>
+
+ Then, lookup the static data from
+ <filename>static.zone</filename> to test it (assuming your
+ authoritative server is running on <command>localhost</command>):
+
+ <screen>> <userinput>dig @localhost -c CH -t TXT version.bind</userinput>
+> <userinput>dig @localhost -c CH -t TXT authors.bind</userinput></screen>
+
+ </para>
+
+ </section>
+
</section>
<section>
@@ -2715,19 +2782,29 @@ TODO
<para>
The <command>b10-xfrin</command> process supports both AXFR and
- IXFR. Due to some implementation limitations of the current
- development release, however, it only tries AXFR by default,
- and care should be taken to enable IXFR.
+ IXFR.
</para>
-<!-- TODO: http://bind10.isc.org/ticket/1279 -->
<section>
<title>Configuration for Incoming Zone Transfers</title>
<para>
- In practice, you need to specify a list of secondary zones to
- enable incoming zone transfers for these zones (you can still
- trigger a zone transfer manually, without a prior configuration
- (see below)).
+ In order to enable incoming zone transfers for a secondary
+ zone, you will first need to make the zone "exist" in some
+ data source.
+ One easy way to do this is to create an empty zone using the
+ <command>b10-loadzone</command> utility.
+ For example, this makes an empty zone (or empties any existing
+ content of the zone) "example.com" in the default data source
+ for <command>b10-loadzone</command> (which is SQLite3-based
+ data source):
+ <screen>$ <userinput>b10-loadzone <replaceable>-e</replaceable> <replaceable>example.com</replaceable></userinput></screen>
+ </para>
+
+ <para>
+ Next, you need to specify a list of secondary zones to
+ enable incoming zone transfers for these zones in most
+ practical cases (you can still trigger a zone transfer
+ manually, without a prior configuration (see below)).
</para>
<para>
@@ -2742,6 +2819,17 @@ TODO
(We assume there has been no zone configuration before).
</para>
+
+ <note>
+ <simpara>
+ There is a plan to revise overall zone management
+ configuration (which are primary and secondary zones, which
+ data source they are stored, etc) so it can be configured
+ more consistently and in a unified way among various BIND 10 modules.
+ When it's done, part or all of the initial configuration
+ setup described in this section may be deprecated.
+ </simpara>
+ </note>
</section>
<section>
@@ -2753,34 +2841,82 @@ TODO
> <userinput>config set Xfrin/zones[0]/tsig_key "<option>example.key</option>"</userinput>
</section>
- <section>
- <title>Enabling IXFR</title>
- <para>
- As noted above, <command>b10-xfrin</command> uses AXFR for
- zone transfers by default. To enable IXFR for zone transfers
- for a particular zone, set the <varname>use_ixfr</varname>
- configuration parameter to <quote>true</quote>.
- In the above example of configuration sequence, you'll need
- to add the following before performing <userinput>commit</userinput>:
- <screen>> <userinput>config set Xfrin/zones[0]/use_ixfr true</userinput></screen>
- </para>
+ <section id="request_ixfr">
+ <title>Control the use of IXFR</title>
+ <para>
+ By default, <command>b10-xfrin</command> uses IXFR for
+ transferring zones specified in
+ the <varname>Xfrin/zones</varname> list of the configuration,
+ unless it doesn't know the current SOA serial of the zone
+ (including the case where the zone has never transferred or
+ locally loaded), in which case it automatically uses AXFR.
+ If the attempt of IXFR fails, <command>b10-xfrin</command>
+ automatically retries the transfer using AXFR.
+ In general, this works for any master server implementations
+ including those that don't support IXFR and in any local state
+ of the zone. So there should normally be no need to configure
+ on whether to use IXFR.
+ </para>
+
+ <para>
+ In some cases, however, it may be desirable to specify how and
+ whether to use IXFR and AXFR.
+ The <varname>request_ixfr</varname> configuration item under
+ <varname>Xfrin/zones</varname> can be used to control such
+ policies.
+ It can take the following values.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>yes</term>
+ <listitem>
+ <simpara>
+ This is the default behavior as described above.
+ </simpara>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>no</term>
+ <listitem>
+ <simpara>
+ Only use AXFR. Note that this value normally shouldn't
+ be needed thanks to the automatic fallback from IXFR to IXFR.
+ A possible case where this value needs to be used is
+ that the master server has a bug and crashes if it
+ receives an IXFR request.
+ </simpara>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>only</term>
+ <listitem>
+ <simpara>
+ Only use IXFR except when the current SOA serial is not
+ known.
+ This value has a severe drawback, that is, if the master
+ server does not support IXFR zone transfers never
+ succeed (except for the very first one, which will use AXFR),
+ and the zone will eventually expire.
+ Therefore it should not be used in general.
+ Still, in some special cases the use of this value may
+ make sense. For example, if the operator is sure that
+ the master server supports IXFR and the zone is very
+ large, they may want to avoid falling back to AXFR as
+ it can be more expensive.
+ </simpara>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <note>
+ <simpara>
+ There used to be a boolean configuration item named
+ <varname>use_ixfr</varname>.
+ It was deprecated for the finer control described above.
+ The <varname>request_ixfr</varname> item should be used instead.
+ </simpara>
+ </note>
-<!-- TODO: http://bind10.isc.org/ticket/1279 -->
- <note><simpara>
- One reason why IXFR is disabled by default in the current
- release is because it does not support automatic fallback from IXFR to
- AXFR when it encounters a primary server that doesn't support
- outbound IXFR (and, not many existing implementations support
- it). Another, related reason is that it does not use AXFR even
- if it has no knowledge about the zone (like at the very first
- time the secondary server is set up). IXFR requires the
- "current version" of the zone, so obviously it doesn't work
- in this situation and AXFR is the only workable choice.
- The current release of <command>b10-xfrin</command> does not
- make this selection automatically.
- These features will be implemented in a near future
- version, at which point we will enable IXFR by default.
- </simpara></note>
</section>
<!-- TODO:
@@ -2844,6 +2980,23 @@ what if a NOTIFY is sent?
<screen>> <userinput>Xfrin retransfer zone_name="<option>foo.example.org</option>" master=<option>192.0.2.99</option></userinput></screen>
</para>
+
+ <para>
+ The <command>retransfer</command> command always uses AXFR.
+ To use IXFR for a zone that has already been transferred once,
+ use the <command>refresh</command> command.
+ It honors the <varname>Xfrin/zones/request_ixfr</varname>
+ configuration item (see <xref linkend="request_ixfr"/>.), and
+ if it's configured to use IXFR, it will be used.
+ </para>
+
+ <para>
+ Both the <command>retransfer</command>
+ and <command>refresh</command> commands can be used for
+ an initial transfer before setting up secondary
+ configurations.
+ In this case AXFR will be used for the obvious reason.
+ </para>
</section>
<section>
@@ -2869,7 +3022,6 @@ http://bind10.isc.org/wiki/ScalableZoneLoadDesign#a7.2UpdatingaZone
</para>
</section>
-<!-- TODO: can that retransfer be used to identify a new zone? -->
<!-- TODO: what if doesn't exist at that master IP? -->
</chapter>
@@ -3504,7 +3656,7 @@ $</screen>
will be available. It will look similar to this:
<screen>
> <userinput>config show Dhcp4</userinput>
-Dhcp4/interface/ list (default)
+Dhcp4/interfaces/ list (default)
Dhcp4/renew-timer 1000 integer (default)
Dhcp4/rebind-timer 2000 integer (default)
Dhcp4/valid-lifetime 4000 integer (default)
@@ -3591,6 +3743,60 @@ Dhcp4/subnet4 [] list (default)
</note>
</section>
+ <section id="dhcp4-interface-selection">
+ <title>Interface selection</title>
+ <para>
+ When DHCPv4 server starts up, by default it will listen to the DHCP
+ traffic and respond to it on all interfaces detected during startup.
+ However, in many cases it is desired to configure the server to listen and
+ respond on selected interfaces only. The sample commands in this section
+ show how to make interface selection using bindctl.
+ </para>
+ <para>
+ The default configuration can be presented with the following command:
+ <screen>
+> <userinput>config show Dhcp4/interfaces</userinput>
+<userinput>Dhcp4/interfaces[0] "*" string</userinput></screen>
+ An asterisk sign plays a role of the wildcard and means "listen on all interfaces".
+ </para>
+ <para>
+ In order to override the default configuration, the existing entry can be replaced
+ with the actual interface name:
+ <screen>
+> <userinput>config set Dhcp4/interfaces[0] eth1</userinput>
+> <userinput>config commit</userinput></screen>
+ Other interface names can be added on one-by-one basis:
+ <screen>
+> <userinput>config add Dhcp4/interfaces eth2</userinput>
+> <userinput>config commit</userinput></screen>
+ Configuration will now contain two interfaces which can be presented as follows:
+ <screen>
+> <userinput>config show Dhcp4/interfaces</userinput>
+<userinput>Dhcp4/interfaces[0] "eth1" string</userinput>
+<userinput>Dhcp4/interfaces[1] "eth2" string</userinput></screen>
+ When configuration gets committed, the server will start to listen on
+ eth1 and eth2 interfaces only.
+ </para>
+ <para>
+ It is possible to use wildcard interface name (asterisk) concurrently with explicit
+ interface names:
+ <screen>
+> <userinput>config add Dhcp4/interfaces *</userinput>
+> <userinput>config commit</userinput></screen>
+ This will result in the following configuration:
+ <screen>
+> <userinput>config show Dhcp4/interfaces</userinput>
+<userinput>Dhcp4/interfaces[0] "eth1" string</userinput>
+<userinput>Dhcp4/interfaces[1] "eth2" string</userinput>
+<userinput>Dhcp4/interfaces[2] "*" string</userinput></screen>
+ The presence of the wildcard name implies that server will listen on all interfaces.
+ In order to fall back to the previous configuration when server listens on eth1 and eth2:
+ <screen>
+> <userinput>config remove Dhcp4/interfaces[2]</userinput>
+> <userinput>config commit</userinput></screen>
+ </para>
+ </section>
+
<section id="dhcp4-address-config">
<title>Configuration of Address Pools</title>
<para>
@@ -3735,7 +3941,10 @@ Dhcp4/subnet4 [] list (default)
</note>
<para>
- Below is a list of currently supported standard DHCPv4 options. The "Name" and "Code"
+ The currently supported standard DHCPv4 options are
+ listed in <xref linkend="dhcp4-std-options-list"/>
+ and <xref linkend="dhcp4-std-options-list-part2"/>.
+ The "Name" and "Code"
are the values that should be used as a name in the option-data
structures. "Type" designates the format of the data: the meanings of
the various types is given in <xref linkend="dhcp-types"/>.
@@ -3749,115 +3958,155 @@ Dhcp4/subnet4 [] list (default)
<!-- @todo: describe record types -->
<para>
- <table border="1" cellpadding="5%" id="dhcp4-std-options-list">
- <caption>List of standard DHCPv4 options</caption>
+ <table frame="all" id="dhcp4-std-options-list">
+ <title>List of standard DHCPv4 options</title>
+ <tgroup cols='4'>
+ <colspec colname='name'/>
+ <colspec colname='code'/>
+ <colspec colname='type'/>
+ <colspec colname='array'/>
<thead>
- <tr><th>Name</th><th>Code</th><th>Type</th><th>Array?</th></tr>
+ <row>
+ <entry>Name</entry>
+ <entry>Code</entry>
+ <entry>Type</entry>
+ <entry>Array?</entry>
+ </row>
</thead>
<tbody>
-<tr><td>subnet-mask</td><td>1</td><td>ipv4-address</td><td>false</td></tr>
-<tr><td>time-offset</td><td>2</td><td>uint32</td><td>false</td></tr>
-<tr><td>routers</td><td>3</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>time-servers</td><td>4</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>name-servers</td><td>5</td><td>ipv4-address</td><td>false</td></tr>
-<tr><td>domain-name-servers</td><td>6</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>log-servers</td><td>7</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>cookie-servers</td><td>8</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>lpr-servers</td><td>9</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>impress-servers</td><td>10</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>resource-location-servers</td><td>11</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>host-name</td><td>12</td><td>string</td><td>false</td></tr>
-<tr><td>boot-size</td><td>13</td><td>uint16</td><td>false</td></tr>
-<tr><td>merit-dump</td><td>14</td><td>string</td><td>false</td></tr>
-<tr><td>domain-name</td><td>15</td><td>fqdn</td><td>false</td></tr>
-<tr><td>swap-server</td><td>16</td><td>ipv4-address</td><td>false</td></tr>
-<tr><td>root-path</td><td>17</td><td>string</td><td>false</td></tr>
-<tr><td>extensions-path</td><td>18</td><td>string</td><td>false</td></tr>
-<tr><td>ip-forwarding</td><td>19</td><td>boolean</td><td>false</td></tr>
-<tr><td>non-local-source-routing</td><td>20</td><td>boolean</td><td>false</td></tr>
-<tr><td>policy-filter</td><td>21</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>max-dgram-reassembly</td><td>22</td><td>uint16</td><td>false</td></tr>
-<tr><td>default-ip-ttl</td><td>23</td><td>uint8</td><td>false</td></tr>
-<tr><td>path-mtu-aging-timeout</td><td>24</td><td>uint32</td><td>false</td></tr>
-<tr><td>path-mtu-plateau-table</td><td>25</td><td>uint16</td><td>true</td></tr>
-<tr><td>interface-mtu</td><td>26</td><td>uint16</td><td>false</td></tr>
-<tr><td>all-subnets-local</td><td>27</td><td>boolean</td><td>false</td></tr>
-<tr><td>broadcast-address</td><td>28</td><td>ipv4-address</td><td>false</td></tr>
-<tr><td>perform-mask-discovery</td><td>29</td><td>boolean</td><td>false</td></tr>
-<tr><td>mask-supplier</td><td>30</td><td>boolean</td><td>false</td></tr>
-<tr><td>router-discovery</td><td>31</td><td>boolean</td><td>false</td></tr>
-<tr><td>router-solicitation-address</td><td>32</td><td>ipv4-address</td><td>false</td></tr>
-<tr><td>static-routes</td><td>33</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>trailer-encapsulation</td><td>34</td><td>boolean</td><td>false</td></tr>
-<tr><td>arp-cache-timeout</td><td>35</td><td>uint32</td><td>false</td></tr>
-<tr><td>ieee802-3-encapsulation</td><td>36</td><td>boolean</td><td>false</td></tr>
-<tr><td>default-tcp-ttl</td><td>37</td><td>uint8</td><td>false</td></tr>
-<tr><td>tcp-keepalive-internal</td><td>38</td><td>uint32</td><td>false</td></tr>
-<tr><td>tcp-keepalive-garbage</td><td>39</td><td>boolean</td><td>false</td></tr>
-<tr><td>nis-domain</td><td>40</td><td>string</td><td>false</td></tr>
-<tr><td>nis-servers</td><td>41</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>ntp-servers</td><td>42</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>vendor-encapsulated-options</td><td>43</td><td>empty</td><td>false</td></tr>
-<tr><td>netbios-name-servers</td><td>44</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>netbios-dd-server</td><td>45</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>netbios-node-type</td><td>46</td><td>uint8</td><td>false</td></tr>
-<tr><td>netbios-scope</td><td>47</td><td>string</td><td>false</td></tr>
-<tr><td>font-servers</td><td>48</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>x-display-manager</td><td>49</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>dhcp-requested-address</td><td>50</td><td>ipv4-address</td><td>false</td></tr>
+<row><entry>subnet-mask</entry><entry>1</entry><entry>ipv4-address</entry><entry>false</entry></row>
+<row><entry>time-offset</entry><entry>2</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>routers</entry><entry>3</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>time-servers</entry><entry>4</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>name-servers</entry><entry>5</entry><entry>ipv4-address</entry><entry>false</entry></row>
+<row><entry>domain-name-servers</entry><entry>6</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>log-servers</entry><entry>7</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>cookie-servers</entry><entry>8</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>lpr-servers</entry><entry>9</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>impress-servers</entry><entry>10</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>resource-location-servers</entry><entry>11</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>host-name</entry><entry>12</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>boot-size</entry><entry>13</entry><entry>uint16</entry><entry>false</entry></row>
+<row><entry>merit-dump</entry><entry>14</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>domain-name</entry><entry>15</entry><entry>fqdn</entry><entry>false</entry></row>
+<row><entry>swap-server</entry><entry>16</entry><entry>ipv4-address</entry><entry>false</entry></row>
+<row><entry>root-path</entry><entry>17</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>extensions-path</entry><entry>18</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>ip-forwarding</entry><entry>19</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>non-local-source-routing</entry><entry>20</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>policy-filter</entry><entry>21</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>max-dgram-reassembly</entry><entry>22</entry><entry>uint16</entry><entry>false</entry></row>
+<row><entry>default-ip-ttl</entry><entry>23</entry><entry>uint8</entry><entry>false</entry></row>
+<row><entry>path-mtu-aging-timeout</entry><entry>24</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>path-mtu-plateau-table</entry><entry>25</entry><entry>uint16</entry><entry>true</entry></row>
+<row><entry>interface-mtu</entry><entry>26</entry><entry>uint16</entry><entry>false</entry></row>
+<row><entry>all-subnets-local</entry><entry>27</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>broadcast-address</entry><entry>28</entry><entry>ipv4-address</entry><entry>false</entry></row>
+<row><entry>perform-mask-discovery</entry><entry>29</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>mask-supplier</entry><entry>30</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>router-discovery</entry><entry>31</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>router-solicitation-address</entry><entry>32</entry><entry>ipv4-address</entry><entry>false</entry></row>
+<row><entry>static-routes</entry><entry>33</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>trailer-encapsulation</entry><entry>34</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>arp-cache-timeout</entry><entry>35</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>ieee802-3-encapsulation</entry><entry>36</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>default-tcp-ttl</entry><entry>37</entry><entry>uint8</entry><entry>false</entry></row>
+<row><entry>tcp-keepalive-internal</entry><entry>38</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>tcp-keepalive-garbage</entry><entry>39</entry><entry>boolean</entry><entry>false</entry></row>
+
+ </tbody>
+ </tgroup>
+ </table>
+ </para>
+
+ <para>
+ <table frame="all" id="dhcp4-std-options-list-part2">
+ <title>List of standard DHCPv4 options (continued)</title>
+ <tgroup cols='4'>
+ <colspec colname='name'/>
+ <colspec colname='code'/>
+ <colspec colname='type'/>
+ <colspec colname='array'/>
+ <thead>
+ <row>
+ <entry>Name</entry>
+ <entry>Code</entry>
+ <entry>Type</entry>
+ <entry>Array?</entry>
+ </row>
+ </thead>
+ <tbody>
+
+<row><entry>nis-domain</entry><entry>40</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>nis-servers</entry><entry>41</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>ntp-servers</entry><entry>42</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>vendor-encapsulated-options</entry><entry>43</entry><entry>empty</entry><entry>false</entry></row>
+<row><entry>netbios-name-servers</entry><entry>44</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>netbios-dd-server</entry><entry>45</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>netbios-node-type</entry><entry>46</entry><entry>uint8</entry><entry>false</entry></row>
+<row><entry>netbios-scope</entry><entry>47</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>font-servers</entry><entry>48</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>x-display-manager</entry><entry>49</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>dhcp-requested-address</entry><entry>50</entry><entry>ipv4-address</entry><entry>false</entry></row>
<!-- Lease time should not be configured by a user.
-<tr><td>dhcp-lease-time</td><td>51</td><td>uint32</td><td>false</td></tr>
+<row><entry>dhcp-lease-time</entry><entry>51</entry><entry>uint32</entry><entry>false</entry></row>
-->
-<tr><td>dhcp-option-overload</td><td>52</td><td>uint8</td><td>false</td></tr>
+<row><entry>dhcp-option-overload</entry><entry>52</entry><entry>uint8</entry><entry>false</entry></row>
<!-- Message Type, Server Identifier and Parameter Request List should not be configured by a user.
-<tr><td>dhcp-message-type</td><td>53</td><td>uint8</td><td>false</td></tr>
-<tr><td>dhcp-server-identifier</td><td>54</td><td>ipv4-address</td><td>false</td></tr>
-<tr><td>dhcp-parameter-request-list</td><td>55</td><td>uint8</td><td>true</td></tr>
+<row><entry>dhcp-message-type</entry><entry>53</entry><entry>uint8</entry><entry>false</entry></row>
+<row><entry>dhcp-server-identifier</entry><entry>54</entry><entry>ipv4-address</entry><entry>false</entry></row>
+<row><entry>dhcp-parameter-request-list</entry><entry>55</entry><entry>uint8</entry><entry>true</entry></row>
-->
-<tr><td>dhcp-message</td><td>56</td><td>string</td><td>false</td></tr>
-<tr><td>dhcp-max-message-size</td><td>57</td><td>uint16</td><td>false</td></tr>
+<row><entry>dhcp-message</entry><entry>56</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>dhcp-max-message-size</entry><entry>57</entry><entry>uint16</entry><entry>false</entry></row>
<!-- Renewal and rebinding time should not be configured by a user.
-<tr><td>dhcp-renewal-time</td><td>58</td><td>uint32</td><td>false</td></tr>
-<tr><td>dhcp-rebinding-time</td><td>59</td><td>uint32</td><td>false</td></tr>
+<row><entry>dhcp-renewal-time</entry><entry>58</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>dhcp-rebinding-time</entry><entry>59</entry><entry>uint32</entry><entry>false</entry></row>
-->
-<tr><td>vendor-class-identifier</td><td>60</td><td>binary</td><td>false</td></tr>
+<row><entry>vendor-class-identifier</entry><entry>60</entry><entry>binary</entry><entry>false</entry></row>
<!-- Client identifier should not be configured by a user.
-<tr><td>dhcp-client-identifier</td><td>61</td><td>binary</td><td>false</td></tr>
+<row><entry>dhcp-client-identifier</entry><entry>61</entry><entry>binary</entry><entry>false</entry></row>
-->
-<tr><td>nwip-domain-name</td><td>62</td><td>string</td><td>false</td></tr>
-<tr><td>nwip-suboptions</td><td>63</td><td>binary</td><td>false</td></tr>
-<tr><td>user-class</td><td>77</td><td>binary</td><td>false</td></tr>
-<tr><td>fqdn</td><td>81</td><td>record</td><td>false</td></tr>
-<tr><td>dhcp-agent-options</td><td>82</td><td>empty</td><td>false</td></tr>
-<tr><td>authenticate</td><td>90</td><td>binary</td><td>false</td></tr>
-<tr><td>client-last-transaction-time</td><td>91</td><td>uint32</td><td>false</td></tr>
-<tr><td>associated-ip</td><td>92</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>subnet-selection</td><td>118</td><td>ipv4-address</td><td>false</td></tr>
-<tr><td>domain-search</td><td>119</td><td>binary</td><td>false</td></tr>
-<tr><td>vivco-suboptions</td><td>124</td><td>binary</td><td>false</td></tr>
-<tr><td>vivso-suboptions</td><td>125</td><td>binary</td><td>false</td></tr>
+<row><entry>nwip-domain-name</entry><entry>62</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>nwip-suboptions</entry><entry>63</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>user-class</entry><entry>77</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>fqdn</entry><entry>81</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>dhcp-agent-options</entry><entry>82</entry><entry>empty</entry><entry>false</entry></row>
+<row><entry>authenticate</entry><entry>90</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>client-last-transaction-time</entry><entry>91</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>associated-ip</entry><entry>92</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>subnet-selection</entry><entry>118</entry><entry>ipv4-address</entry><entry>false</entry></row>
+<row><entry>domain-search</entry><entry>119</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>vivco-suboptions</entry><entry>124</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>vivso-suboptions</entry><entry>125</entry><entry>binary</entry><entry>false</entry></row>
</tbody>
+ </tgroup>
</table>
+
</para>
<para>
- <table border="1" cellpadding="5%" id="dhcp-types">
- <caption>List of standard DHCP option types</caption>
+ <table frame="all" id="dhcp-types">
+ <title>List of standard DHCP option types</title>
+ <tgroup cols='2'>
+ <colspec colname='name'/>
+ <colspec colname='meaning'/>
<thead>
- <tr><th>Name</th><th>Meaning</th></tr>
+ <row><entry>Name</entry><entry>Meaning</entry></row>
</thead>
<tbody>
- <tr><td>binary</td><td>An arbitrary string of bytes, specified as a set of hexadecimal digits.</td></tr>
- <tr><td>boolean</td><td>Boolean value with allowed values true or false</td></tr>
- <tr><td>empty</td><td>No value, data is carried in suboptions</td></tr>
- <tr><td>fqdn</td><td>Fully qualified domain name (e.g. www.example.com)</td></tr>
- <tr><td>ipv4-address</td><td>IPv4 address in the usual dotted-decimal notation (e.g. 192.0.2.1)</td></tr>
- <tr><td>ipv6-address</td><td>IPv6 address in the usual colon notation (e.g. 2001:db8::1)</td></tr>
- <tr><td>record</td><td>Structured data that may comprise any types (except "record" and "empty")</td></tr>
- <tr><td>string</td><td>Any text</td></tr>
- <tr><td>uint8</td><td>8 bit unsigned integer with allowed values 0 to 255</td></tr>
- <tr><td>uint16</td><td>16 bit unsinged integer with allowed values 0 to 65535</td></tr>
- <tr><td>uint32</td><td>32 bit unsigned integer with allowed values 0 to 4294967295</td></tr>
+ <row><entry>binary</entry><entry>An arbitrary string of bytes, specified as a set of hexadecimal digits.</entry></row>
+ <row><entry>boolean</entry><entry>Boolean value with allowed values true or false</entry></row>
+ <row><entry>empty</entry><entry>No value, data is carried in suboptions</entry></row>
+ <row><entry>fqdn</entry><entry>Fully qualified domain name (e.g. www.example.com)</entry></row>
+ <row><entry>ipv4-address</entry><entry>IPv4 address in the usual dotted-decimal notation (e.g. 192.0.2.1)</entry></row>
+ <row><entry>ipv6-address</entry><entry>IPv6 address in the usual colon notation (e.g. 2001:db8::1)</entry></row>
+ <row><entry>record</entry><entry>Structured data that may comprise any types (except "record" and "empty")</entry></row>
+ <row><entry>string</entry><entry>Any text</entry></row>
+ <row><entry>uint8</entry><entry>8 bit unsigned integer with allowed values 0 to 255</entry></row>
+ <row><entry>uint16</entry><entry>16 bit unsinged integer with allowed values 0 to 65535</entry></row>
+ <row><entry>uint32</entry><entry>32 bit unsigned integer with allowed values 0 to 4294967295</entry></row>
</tbody>
+ </tgroup>
</table>
</para>
</section>
@@ -4100,7 +4349,7 @@ Dhcp4/subnet4 [] list (default)
> <userinput>config commit</userinput>
</screen>
Even though the "container" option does not carry any data except
- sub-options, the "data" field must be explictly set to an empty value.
+ sub-options, the "data" field must be explicitly set to an empty value.
This is required because in the current version of BIND 10 DHCP, the
default configuration values are not propagated to the configuration parsers:
if the "data" is not set the parser will assume that this
@@ -4271,7 +4520,7 @@ Dhcp4/renew-timer 1000 integer (default)
will be available. It will look similar to this:
<screen>
> <userinput>config show Dhcp6</userinput>
-Dhcp6/interface/ list (default)
+Dhcp6/interfaces/ list (default)
Dhcp6/renew-timer 1000 integer (default)
Dhcp6/rebind-timer 2000 integer (default)
Dhcp6/preferred-lifetime 3000 integer (default)
@@ -4364,6 +4613,59 @@ Dhcp6/subnet6/ list
</note>
</section>
+ <section id="dhcp6-interface-selection">
+ <title>Interface selection</title>
+ <para>
+ When DHCPv6 server starts up, by default it will listen to the DHCP
+ traffic and respond to it on all interfaces detected during startup.
+ However, in many cases it is desired to configure the server to listen and
+ respond on selected interfaces only. The sample commands in this section
+ show how to make interface selection using bindctl.
+ </para>
+ <para>
+ The default configuration can be presented with the following command:
+ <screen>
+> <userinput>config show Dhcp6/interfaces</userinput>
+<userinput>Dhcp6/interfaces[0] "*" string</userinput></screen>
+ An asterisk sign plays a role of the wildcard and means "listen on all interfaces".
+ </para>
+ <para>
+ In order to override the default configuration, the existing entry can be replaced
+ with the actual interface name:
+ <screen>
+> <userinput>config set Dhcp6/interfaces[0] eth1</userinput>
+> <userinput>config commit</userinput></screen>
+ Other interface names can be added on one-by-one basis:
+ <screen>
+> <userinput>config add Dhcp6/interfaces eth2</userinput>
+> <userinput>config commit</userinput></screen>
+ Configuration will now contain two interfaces which can be presented as follows:
+ <screen>
+> <userinput>config show Dhcp6/interfaces</userinput>
+<userinput>Dhcp6/interfaces[0] "eth1" string</userinput>
+<userinput>Dhcp6/interfaces[1] "eth2" string</userinput></screen>
+ When configuration gets committed, the server will start to listen on
+ eth1 and eth2 interfaces only.
+ </para>
+ <para>
+ It is possible to use wildcard interface name (asterisk) concurrently with explicit
+ interface names:
+ <screen>
+> <userinput>config add Dhcp6/interfaces *</userinput>
+> <userinput>config commit</userinput></screen>
+ This will result in the following configuration:
+ <screen>
+> <userinput>config show Dhcp6/interfaces</userinput>
+<userinput>Dhcp6/interfaces[0] "eth1" string</userinput>
+<userinput>Dhcp6/interfaces[1] "eth2" string</userinput>
+<userinput>Dhcp6/interfaces[2] "*" string</userinput></screen>
+ The presence of the wildcard name implies that server will listen on all interfaces.
+ In order to fall back to the previous configuration when server listens on eth1 and eth2:
+ <screen>
+> <userinput>config remove Dhcp6/interfaces[2]</userinput>
+> <userinput>config commit</userinput></screen>
+ </para>
+ </section>
<section>
<title>Subnet and Address Pool</title>
@@ -4450,7 +4752,7 @@ Dhcp6/subnet6/ list
contains information on all global options that the server is
supposed to configure in all subnets. The second line specifies
option name. For a complete list of currently supported names,
- see <xref linkend="dhcp6-std-options-list"/> below.
+ see <xref linkend="dhcp6-std-options-list"/>.
The third line specifies option code, which must match one of the
values from that
list. Line 4 specifies option space, which must always
@@ -4520,7 +4822,9 @@ Dhcp6/subnet6/ list
<para>
- Below is a list of currently supported standard DHCPv6 options. The "Name" and "Code"
+ The currently supported standard DHCPv6 options are
+ listed in <xref linkend="dhcp6-std-options-list"/>.
+ The "Name" and "Code"
are the values that should be used as a name in the option-data
structures. "Type" designates the format of the data: the meanings of
the various types is given in <xref linkend="dhcp-types"/>.
@@ -4535,63 +4839,68 @@ Dhcp6/subnet6/ list
<!-- @todo: describe record types -->
<para>
- <table border="1" cellpadding="5%" id="dhcp6-std-options-list">
- <caption>List of standard DHCPv6 options</caption>
+ <table frame="all" id="dhcp6-std-options-list">
+ <title>List of standard DHCPv6 options</title>
+ <tgroup cols='4'>
+ <colspec colname='name'/>
+ <colspec colname='code'/>
+ <colspec colname='type'/>
+ <colspec colname='array'/>
<thead>
- <tr><th>Name</th><th>Code</th><th>Type</th><th>Array?</th></tr>
- <tr></tr>
+ <row><entry>Name</entry><entry>Code</entry><entry>Type</entry><entry>Array?</entry></row>
</thead>
<tbody>
<!-- Our engine uses those options on its own, admin must not configure them on his own
-<tr><td>clientid</td><td>1</td><td>binary</td><td>false</td></tr>
-<tr><td>serverid</td><td>2</td><td>binary</td><td>false</td></tr>
-<tr><td>ia-na</td><td>3</td><td>record</td><td>false</td></tr>
-<tr><td>ia-ta</td><td>4</td><td>uint32</td><td>false</td></tr>
-<tr><td>iaaddr</td><td>5</td><td>record</td><td>false</td></tr>
-<tr><td>oro</td><td>6</td><td>uint16</td><td>true</td></tr> -->
-<tr><td>preference</td><td>7</td><td>uint8</td><td>false</td></tr>
+<row><entry>clientid</entry><entry>1</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>serverid</entry><entry>2</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>ia-na</entry><entry>3</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>ia-ta</entry><entry>4</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>iaaddr</entry><entry>5</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>oro</entry><entry>6</entry><entry>uint16</entry><entry>true</entry></row> -->
+<row><entry>preference</entry><entry>7</entry><entry>uint8</entry><entry>false</entry></row>
<!-- Our engine uses those options on its own, admin must not configure them on his own
-<tr><td>elapsed-time</td><td>8</td><td>uint16</td><td>false</td></tr>
-<tr><td>relay-msg</td><td>9</td><td>binary</td><td>false</td></tr>
-<tr><td>auth</td><td>11</td><td>binary</td><td>false</td></tr>
-<tr><td>unicast</td><td>12</td><td>ipv6-address</td><td>false</td></tr>
-<tr><td>status-code</td><td>13</td><td>record</td><td>false</td></tr>
-<tr><td>rapid-commit</td><td>14</td><td>empty</td><td>false</td></tr>
-<tr><td>user-class</td><td>15</td><td>binary</td><td>false</td></tr>
-<tr><td>vendor-class</td><td>16</td><td>record</td><td>false</td></tr>
-<tr><td>vendor-opts</td><td>17</td><td>uint32</td><td>false</td></tr>
-<tr><td>interface-id</td><td>18</td><td>binary</td><td>false</td></tr>
-<tr><td>reconf-msg</td><td>19</td><td>uint8</td><td>false</td></tr>
-<tr><td>reconf-accept</td><td>20</td><td>empty</td><td>false</td></tr> -->
-<tr><td>sip-server-dns</td><td>21</td><td>fqdn</td><td>true</td></tr>
-<tr><td>sip-server-addr</td><td>22</td><td>ipv6-address</td><td>true</td></tr>
-<tr><td>dns-servers</td><td>23</td><td>ipv6-address</td><td>true</td></tr>
-<tr><td>domain-search</td><td>24</td><td>fqdn</td><td>true</td></tr>
-<!-- <tr><td>ia-pd</td><td>25</td><td>record</td><td>false</td></tr> -->
-<!-- <tr><td>iaprefix</td><td>26</td><td>record</td><td>false</td></tr> -->
-<tr><td>nis-servers</td><td>27</td><td>ipv6-address</td><td>true</td></tr>
-<tr><td>nisp-servers</td><td>28</td><td>ipv6-address</td><td>true</td></tr>
-<tr><td>nis-domain-name</td><td>29</td><td>fqdn</td><td>true</td></tr>
-<tr><td>nisp-domain-name</td><td>30</td><td>fqdn</td><td>true</td></tr>
-<tr><td>sntp-servers</td><td>31</td><td>ipv6-address</td><td>true</td></tr>
-<tr><td>information-refresh-time</td><td>32</td><td>uint32</td><td>false</td></tr>
-<tr><td>bcmcs-server-dns</td><td>33</td><td>fqdn</td><td>true</td></tr>
-<tr><td>bcmcs-server-addr</td><td>34</td><td>ipv6-address</td><td>true</td></tr>
-<tr><td>geoconf-civic</td><td>36</td><td>record</td><td>false</td></tr>
-<tr><td>remote-id</td><td>37</td><td>record</td><td>false</td></tr>
-<tr><td>subscriber-id</td><td>38</td><td>binary</td><td>false</td></tr>
-<tr><td>client-fqdn</td><td>39</td><td>record</td><td>false</td></tr>
-<tr><td>pana-agent</td><td>40</td><td>ipv6-address</td><td>true</td></tr>
-<tr><td>new-posix-timezone</td><td>41</td><td>string</td><td>false</td></tr>
-<tr><td>new-tzdb-timezone</td><td>42</td><td>string</td><td>false</td></tr>
-<tr><td>ero</td><td>43</td><td>uint16</td><td>true</td></tr>
-<tr><td>lq-query</td><td>44</td><td>record</td><td>false</td></tr>
-<tr><td>client-data</td><td>45</td><td>empty</td><td>false</td></tr>
-<tr><td>clt-time</td><td>46</td><td>uint32</td><td>false</td></tr>
-<tr><td>lq-relay-data</td><td>47</td><td>record</td><td>false</td></tr>
-<tr><td>lq-client-link</td><td>48</td><td>ipv6-address</td><td>true</td></tr>
+<row><entry>elapsed-time</entry><entry>8</entry><entry>uint16</entry><entry>false</entry></row>
+<row><entry>relay-msg</entry><entry>9</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>auth</entry><entry>11</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>unicast</entry><entry>12</entry><entry>ipv6-address</entry><entry>false</entry></row>
+<row><entry>status-code</entry><entry>13</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>rapid-commit</entry><entry>14</entry><entry>empty</entry><entry>false</entry></row>
+<row><entry>user-class</entry><entry>15</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>vendor-class</entry><entry>16</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>vendor-opts</entry><entry>17</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>interface-id</entry><entry>18</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>reconf-msg</entry><entry>19</entry><entry>uint8</entry><entry>false</entry></row>
+<row><entry>reconf-accept</entry><entry>20</entry><entry>empty</entry><entry>false</entry></row> -->
+<row><entry>sip-server-dns</entry><entry>21</entry><entry>fqdn</entry><entry>true</entry></row>
+<row><entry>sip-server-addr</entry><entry>22</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>dns-servers</entry><entry>23</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>domain-search</entry><entry>24</entry><entry>fqdn</entry><entry>true</entry></row>
+<!-- <row><entry>ia-pd</entry><entry>25</entry><entry>record</entry><entry>false</entry></row> -->
+<!-- <row><entry>iaprefix</entry><entry>26</entry><entry>record</entry><entry>false</entry></row> -->
+<row><entry>nis-servers</entry><entry>27</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>nisp-servers</entry><entry>28</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>nis-domain-name</entry><entry>29</entry><entry>fqdn</entry><entry>true</entry></row>
+<row><entry>nisp-domain-name</entry><entry>30</entry><entry>fqdn</entry><entry>true</entry></row>
+<row><entry>sntp-servers</entry><entry>31</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>information-refresh-time</entry><entry>32</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>bcmcs-server-dns</entry><entry>33</entry><entry>fqdn</entry><entry>true</entry></row>
+<row><entry>bcmcs-server-addr</entry><entry>34</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>geoconf-civic</entry><entry>36</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>remote-id</entry><entry>37</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>subscriber-id</entry><entry>38</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>client-fqdn</entry><entry>39</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>pana-agent</entry><entry>40</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>new-posix-timezone</entry><entry>41</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>new-tzdb-timezone</entry><entry>42</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>ero</entry><entry>43</entry><entry>uint16</entry><entry>true</entry></row>
+<row><entry>lq-query</entry><entry>44</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>client-data</entry><entry>45</entry><entry>empty</entry><entry>false</entry></row>
+<row><entry>clt-time</entry><entry>46</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>lq-relay-data</entry><entry>47</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>lq-client-link</entry><entry>48</entry><entry>ipv6-address</entry><entry>true</entry></row>
</tbody>
+ </tgroup>
</table>
</para>
</section>
@@ -4812,7 +5121,7 @@ should include options from the isc option space:
> <userinput>config commit</userinput>
</screen>
Even though the "container" option does not carry any data except
- sub-options, the "data" field must be explictly set to an empty value.
+ sub-options, the "data" field must be explicitly set to an empty value.
This is required because in the current version of BIND 10 DHCP, the
default configuration values are not propagated to the configuration parsers:
if the "data" is not set the parser will assume that this
@@ -4832,29 +5141,22 @@ should include options from the isc option space:
<section id="dhcp6-config-subnets">
<title>Subnet Selection</title>
<para>
- The DHCPv6 server may receive requests from local (connected
- to the same subnet as the server) and remote (connecting via
- relays) clients.
- <note>
- <para>
- Currently relayed DHCPv6 traffic is not supported. The server will
- only respond to local DHCPv6 requests - see <xref linkend="dhcp6-limit"/>
- </para>
- </note>
- As it may have many subnet configurations defined, it
- must select appropriate subnet for a given request. To do this, the server first
+ The DHCPv6 server may receive requests from local (connected to the
+ same subnet as the server) and remote (connecting via relays) clients.
+ As server may have many subnet configurations defined, it must select
+ appropriate subnet for a given request. To do this, the server first
checks if there is only one subnet defined and source of the packet is
- link-local. If this is the case, the server assumes that the only subnet
- defined is local and client is indeed connected to it. This check
- simplifies small deployments.
+ link-local. If this is the case, the server assumes that the only
+ subnet defined is local and client is indeed connected to it. This
+ check simplifies small deployments.
</para>
<para>
If there are two or more subnets defined, the server can not assume
which of those (if any) subnets are local. Therefore an optional
- "interface" parameter is available within a subnet definition to designate that a given subnet
- is local, i.e. reachable directly over specified interface. For example
- the server that is intended to serve a local subnet over eth0 may be configured
- as follows:
+ "interface" parameter is available within a subnet definition to
+ designate that a given subnet is local, i.e. reachable directly over
+ specified interface. For example the server that is intended to serve
+ a local subnet over eth0 may be configured as follows:
<screen>
> <userinput>config add Dhcp6/subnet6</userinput>
> <userinput>config set Dhcp6/subnet6[1]/subnet "2001:db8:beef::/48"</userinput>
@@ -4865,6 +5167,66 @@ should include options from the isc option space:
</para>
</section>
+ <section id="dhcp6-relays">
+ <title>DHCPv6 Relays</title>
+ <para>
+ A DHCPv6 server with multiple subnets defined must select the
+ appropriate subnet when it receives a request from client. For clients
+ connected via relays, two mechanisms are used:
+ </para>
+ <para>
+ The first uses the linkaddr field in the RELAY_FORW message. The name
+ of this field is somewhat misleading in that it does not contain a link-layer
+ address: instead, it holds an address (typically a global address) that is
+ used to identify a link. The DHCPv6 server checks if the address belongs
+ to a defined subnet and, if it does, that subnet is selected for the client's
+ request.
+ </para>
+ <para>
+ The second mechanism is based on interface-id options. While forwarding a client's
+ message, relays may insert an interface-id option into the message that
+ identifies the interface on the relay that received the message. (Some
+ relays allow configuration of that parameter, but it is sometimes
+ hardcoded and may range from the very simple (e.g. "vlan100") to the very cryptic:
+ one example seen on real hardware was "ISAM144|299|ipv6|nt:vp:1:110"). The
+ server can use this information to select the appropriate subnet.
+ The information is also returned to the relay which then knows the
+ interface to use to transmit the response to the client. In order for
+ this to work successfully, the relay interface IDs must be unique within
+ the network and the server configuration must match those values.
+ </para>
+ <para>
+ When configuring the DHCPv6 server, it should be noted that two
+ similarly-named parameters can be configured for a subnet:
+ <itemizedlist>
+ <listitem><simpara>
+ "interface" defines which local network interface can be used
+ to access a given subnet.
+ </simpara></listitem>
+ <listitem><simpara>
+ "interface-id" specifies the content of the interface-id option
+ used by relays to identify the interface on the relay to which
+ the response packet is sent.
+ </simpara></listitem>
+ </itemizedlist>
+ The two are mutually exclusive: a subnet cannot be both reachable locally
+ (direct traffic) and via relays (remote traffic). Specifying both is a
+ configuration error and the DHCPv6 server will refuse such a configuration.
+ </para>
+
+ <para>
+ To specify interface-id with value "vlan123", the following commands can
+ be used:
+ <screen>
+> <userinput>config add Dhcp6/subnet6</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/subnet "2001:db8:beef::/48"</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:beef::/48" ]</userinput>
+> <userinput>config set Dhcp6/subnet6[0]/interface-id "vland123"</userinput>
+> <userinput>config commit</userinput>
+</screen>
+ </para>
+ </section>
+
</section>
<section id="dhcp6-serverid">
@@ -4906,6 +5268,9 @@ should include options from the isc option space:
<listitem>
<simpara><ulink url="http://tools.ietf.org/html/rfc3646">RFC 3646</ulink>: Supported option is DNS_SERVERS.</simpara>
</listitem>
+ <listitem>
+ <simpara><ulink url="http://tools.ietf.org/html/rfc4704">RFC 4704</ulink>: Supported option is CLIENT_FQDN.</simpara>
+ </listitem>
</itemizedlist>
</section>
@@ -4931,9 +5296,6 @@ Dhcp6/renew-timer 1000 integer (default)
</para>
</listitem>
<listitem>
- <simpara>Relayed traffic is not supported.</simpara>
- </listitem>
- <listitem>
<simpara>Temporary addresses are not supported.</simpara>
</listitem>
<listitem>
@@ -5446,6 +5808,23 @@ TODO; there's a ticket to determine these levels, see #1074
If this is 0, no maximum file size is used.
</para>
+ <note>
+ <simpara>
+ Due to a limitation of the underlying logging library
+ (log4cplus), rolling over the log files (from ".1" to
+ ".2", etc) may show odd results: There can be
+ multiple small files at the timing of roll over. This
+ can happen when multiple BIND 10 processes try to roll
+ over the files simultaneously.
+ Version 1.1.0 of log4cplus solved this problem, so if
+ this or higher version of log4cplus is used to build
+ BIND 10, it shouldn't happen. Even for older versions
+ it is normally expected to happen rarely unless the log
+ messages are produced very frequently by multiple
+ different processes.
+ </simpara>
+ </note>
+
</section>
<section>
diff --git a/examples/README b/examples/README
index aa5f3c9..e8e7953 100644
--- a/examples/README
+++ b/examples/README
@@ -31,7 +31,7 @@ sinclude(m4/ax_boost_include.m4)
sinclude(m4/ax_isc_bind10.m4)
(and same for other m4 files as they are added under m4/)
-On some systems, espeically if you have installed the BIND 10
+On some systems, especially if you have installed the BIND 10
libraries in an uncommon path, programs linked with the BIND 10
library may not work at run time due to the "missing" shared library.
Normally, you should be able to avoid this problem by making sure
diff --git a/examples/host/host.cc b/examples/host/host.cc
index a5c6522..c5dd604 100644
--- a/examples/host/host.cc
+++ b/examples/host/host.cc
@@ -95,7 +95,7 @@ host_lookup(const char* const name, const char* const dns_class,
cout << "Name: " << server << "\n";
// TODO: I guess I have to do a lookup to get that address and aliases
// too
- //cout << "Address: " << address << "\n" ; // "#" << port << "\n";
+ //cout << "Address: " << address << "\n" ;
//cout << "Aliases: " << server << "\n";
}
diff --git a/ext/Makefile.am b/ext/Makefile.am
new file mode 100644
index 0000000..eb16b92
--- /dev/null
+++ b/ext/Makefile.am
@@ -0,0 +1,8 @@
+SUBDIRS = . asio
+
+# As we are copying ASIO headers to the installation directory, copy across
+# the licence file as well.
+asio_datadir = $(pkgincludedir)/asio
+asio_data_DATA = LICENSE_1_0.txt
+
+EXTRA_DIST = LICENSE_1_0.txt
diff --git a/ext/asio/Makefile.am b/ext/asio/Makefile.am
new file mode 100644
index 0000000..9813ccc
--- /dev/null
+++ b/ext/asio/Makefile.am
@@ -0,0 +1,6 @@
+SUBDIRS = . asio
+
+# As we are copying across the ASIO files to the installation directory, copy
+# across the README that tells us where we got them from.
+asio_datadir = $(pkgincludedir)/asio
+asio_data_DATA = README
diff --git a/ext/asio/asio/Makefile.am b/ext/asio/asio/Makefile.am
new file mode 100644
index 0000000..1be0573
--- /dev/null
+++ b/ext/asio/asio/Makefile.am
@@ -0,0 +1,310 @@
+SUBDIRS = .
+
+# Copy across the BIND 10 copy of ASIO to the installation directory, as some
+# header files used by user-libraries may use parts of it.
+asio_includedir = $(pkgincludedir)/asio
+nobase_asio_include_HEADERS = \
+ basic_datagram_socket.hpp \
+ basic_deadline_timer.hpp \
+ basic_io_object.hpp \
+ basic_raw_socket.hpp \
+ basic_serial_port.hpp \
+ basic_socket.hpp \
+ basic_socket_acceptor.hpp \
+ basic_socket_iostream.hpp \
+ basic_socket_streambuf.hpp \
+ basic_stream_socket.hpp \
+ basic_streambuf.hpp \
+ basic_streambuf_fwd.hpp \
+ buffer.hpp \
+ buffered_read_stream.hpp \
+ buffered_read_stream_fwd.hpp \
+ buffered_stream.hpp \
+ buffered_stream_fwd.hpp \
+ buffered_write_stream.hpp \
+ buffered_write_stream_fwd.hpp \
+ buffers_iterator.hpp \
+ completion_condition.hpp \
+ datagram_socket_service.hpp \
+ deadline_timer.hpp \
+ deadline_timer_service.hpp \
+ detail/array_fwd.hpp \
+ detail/base_from_completion_cond.hpp \
+ detail/bind_handler.hpp \
+ detail/buffer_resize_guard.hpp \
+ detail/buffer_sequence_adapter.hpp \
+ detail/buffered_stream_storage.hpp \
+ detail/call_stack.hpp \
+ detail/completion_handler.hpp \
+ detail/config.hpp \
+ detail/consuming_buffers.hpp \
+ detail/deadline_timer_service.hpp \
+ detail/descriptor_ops.hpp \
+ detail/descriptor_read_op.hpp \
+ detail/descriptor_write_op.hpp \
+ detail/dev_poll_reactor.hpp \
+ detail/dev_poll_reactor_fwd.hpp \
+ detail/epoll_reactor.hpp \
+ detail/epoll_reactor_fwd.hpp \
+ detail/event.hpp \
+ detail/eventfd_select_interrupter.hpp \
+ detail/fd_set_adapter.hpp \
+ detail/fenced_block.hpp \
+ detail/gcc_arm_fenced_block.hpp \
+ detail/gcc_fenced_block.hpp \
+ detail/gcc_hppa_fenced_block.hpp \
+ detail/gcc_sync_fenced_block.hpp \
+ detail/gcc_x86_fenced_block.hpp \
+ detail/handler_alloc_helpers.hpp \
+ detail/handler_invoke_helpers.hpp \
+ detail/hash_map.hpp \
+ detail/impl/descriptor_ops.ipp \
+ detail/impl/dev_poll_reactor.hpp \
+ detail/impl/dev_poll_reactor.ipp \
+ detail/impl/epoll_reactor.hpp \
+ detail/impl/epoll_reactor.ipp \
+ detail/impl/eventfd_select_interrupter.ipp \
+ detail/impl/kqueue_reactor.hpp \
+ detail/impl/kqueue_reactor.ipp \
+ detail/impl/pipe_select_interrupter.ipp \
+ detail/impl/posix_event.ipp \
+ detail/impl/posix_mutex.ipp \
+ detail/impl/posix_thread.ipp \
+ detail/impl/posix_tss_ptr.ipp \
+ detail/impl/reactive_descriptor_service.ipp \
+ detail/impl/reactive_serial_port_service.ipp \
+ detail/impl/reactive_socket_service_base.ipp \
+ detail/impl/resolver_service_base.ipp \
+ detail/impl/select_reactor.hpp \
+ detail/impl/select_reactor.ipp \
+ detail/impl/service_registry.hpp \
+ detail/impl/service_registry.ipp \
+ detail/impl/socket_ops.ipp \
+ detail/impl/socket_select_interrupter.ipp \
+ detail/impl/strand_service.hpp \
+ detail/impl/strand_service.ipp \
+ detail/impl/task_io_service.hpp \
+ detail/impl/task_io_service.ipp \
+ detail/impl/throw_error.ipp \
+ detail/impl/timer_queue.ipp \
+ detail/impl/timer_queue_set.ipp \
+ detail/impl/win_event.ipp \
+ detail/impl/win_iocp_handle_service.ipp \
+ detail/impl/win_iocp_io_service.hpp \
+ detail/impl/win_iocp_io_service.ipp \
+ detail/impl/win_iocp_serial_port_service.ipp \
+ detail/impl/win_iocp_socket_service_base.ipp \
+ detail/impl/win_mutex.ipp \
+ detail/impl/win_thread.ipp \
+ detail/impl/win_tss_ptr.ipp \
+ detail/impl/winsock_init.ipp \
+ detail/io_control.hpp \
+ detail/kqueue_reactor.hpp \
+ detail/kqueue_reactor_fwd.hpp \
+ detail/local_free_on_block_exit.hpp \
+ detail/macos_fenced_block.hpp \
+ detail/mutex.hpp \
+ detail/noncopyable.hpp \
+ detail/null_buffers_op.hpp \
+ detail/null_event.hpp \
+ detail/null_fenced_block.hpp \
+ detail/null_mutex.hpp \
+ detail/null_signal_blocker.hpp \
+ detail/null_thread.hpp \
+ detail/null_tss_ptr.hpp \
+ detail/object_pool.hpp \
+ detail/old_win_sdk_compat.hpp \
+ detail/op_queue.hpp \
+ detail/operation.hpp \
+ detail/pipe_select_interrupter.hpp \
+ detail/pop_options.hpp \
+ detail/posix_event.hpp \
+ detail/posix_fd_set_adapter.hpp \
+ detail/posix_mutex.hpp \
+ detail/posix_signal_blocker.hpp \
+ detail/posix_thread.hpp \
+ detail/posix_tss_ptr.hpp \
+ detail/push_options.hpp \
+ detail/reactive_descriptor_service.hpp \
+ detail/reactive_null_buffers_op.hpp \
+ detail/reactive_serial_port_service.hpp \
+ detail/reactive_socket_accept_op.hpp \
+ detail/reactive_socket_connect_op.hpp \
+ detail/reactive_socket_recv_op.hpp \
+ detail/reactive_socket_recvfrom_op.hpp \
+ detail/reactive_socket_send_op.hpp \
+ detail/reactive_socket_sendto_op.hpp \
+ detail/reactive_socket_service.hpp \
+ detail/reactive_socket_service_base.hpp \
+ detail/reactor.hpp \
+ detail/reactor_fwd.hpp \
+ detail/reactor_op.hpp \
+ detail/reactor_op_queue.hpp \
+ detail/regex_fwd.hpp \
+ detail/resolve_endpoint_op.hpp \
+ detail/resolve_op.hpp \
+ detail/resolver_service.hpp \
+ detail/resolver_service_base.hpp \
+ detail/scoped_lock.hpp \
+ detail/select_interrupter.hpp \
+ detail/select_reactor.hpp \
+ detail/select_reactor_fwd.hpp \
+ detail/service_base.hpp \
+ detail/service_id.hpp \
+ detail/service_registry.hpp \
+ detail/service_registry_fwd.hpp \
+ detail/shared_ptr.hpp \
+ detail/signal_blocker.hpp \
+ detail/signal_init.hpp \
+ detail/socket_holder.hpp \
+ detail/socket_ops.hpp \
+ detail/socket_option.hpp \
+ detail/socket_select_interrupter.hpp \
+ detail/socket_types.hpp \
+ detail/solaris_fenced_block.hpp \
+ detail/strand_service.hpp \
+ detail/task_io_service.hpp \
+ detail/task_io_service_fwd.hpp \
+ detail/task_io_service_operation.hpp \
+ detail/thread.hpp \
+ detail/throw_error.hpp \
+ detail/timer_op.hpp \
+ detail/timer_queue.hpp \
+ detail/timer_queue_base.hpp \
+ detail/timer_queue_fwd.hpp \
+ detail/timer_queue_set.hpp \
+ detail/timer_scheduler.hpp \
+ detail/timer_scheduler_fwd.hpp \
+ detail/tss_ptr.hpp \
+ detail/wait_handler.hpp \
+ detail/weak_ptr.hpp \
+ detail/win_event.hpp \
+ detail/win_fd_set_adapter.hpp \
+ detail/win_fenced_block.hpp \
+ detail/win_iocp_handle_read_op.hpp \
+ detail/win_iocp_handle_service.hpp \
+ detail/win_iocp_handle_write_op.hpp \
+ detail/win_iocp_io_service.hpp \
+ detail/win_iocp_io_service_fwd.hpp \
+ detail/win_iocp_null_buffers_op.hpp \
+ detail/win_iocp_operation.hpp \
+ detail/win_iocp_overlapped_op.hpp \
+ detail/win_iocp_overlapped_ptr.hpp \
+ detail/win_iocp_serial_port_service.hpp \
+ detail/win_iocp_socket_accept_op.hpp \
+ detail/win_iocp_socket_recv_op.hpp \
+ detail/win_iocp_socket_recvfrom_op.hpp \
+ detail/win_iocp_socket_send_op.hpp \
+ detail/win_iocp_socket_service.hpp \
+ detail/win_iocp_socket_service_base.hpp \
+ detail/win_mutex.hpp \
+ detail/win_signal_blocker.hpp \
+ detail/win_thread.hpp \
+ detail/win_tss_ptr.hpp \
+ detail/wince_thread.hpp \
+ detail/winsock_init.hpp \
+ detail/wrapped_handler.hpp \
+ error.hpp \
+ error_code.hpp \
+ handler_alloc_hook.hpp \
+ handler_invoke_hook.hpp \
+ impl/error.ipp \
+ impl/error_code.ipp \
+ impl/io_service.hpp \
+ impl/io_service.ipp \
+ impl/read.hpp \
+ impl/read.ipp \
+ impl/read_at.hpp \
+ impl/read_at.ipp \
+ impl/read_until.hpp \
+ impl/read_until.ipp \
+ impl/serial_port_base.hpp \
+ impl/serial_port_base.ipp \
+ impl/src.cpp \
+ impl/src.hpp \
+ impl/write.hpp \
+ impl/write.ipp \
+ impl/write_at.hpp \
+ impl/write_at.ipp \
+ io_service.hpp \
+ ip/address.hpp \
+ ip/address_v4.hpp \
+ ip/address_v6.hpp \
+ ip/basic_endpoint.hpp \
+ ip/basic_resolver.hpp \
+ ip/basic_resolver_entry.hpp \
+ ip/basic_resolver_iterator.hpp \
+ ip/basic_resolver_query.hpp \
+ ip/detail/endpoint.hpp \
+ ip/detail/impl/endpoint.ipp \
+ ip/detail/socket_option.hpp \
+ ip/host_name.hpp \
+ ip/icmp.hpp \
+ ip/impl/address.hpp \
+ ip/impl/address.ipp \
+ ip/impl/address_v4.hpp \
+ ip/impl/address_v4.ipp \
+ ip/impl/address_v6.hpp \
+ ip/impl/address_v6.ipp \
+ ip/impl/basic_endpoint.hpp \
+ ip/impl/host_name.ipp \
+ ip/multicast.hpp \
+ ip/resolver_query_base.hpp \
+ ip/resolver_service.hpp \
+ ip/tcp.hpp \
+ ip/udp.hpp \
+ ip/unicast.hpp \
+ ip/v6_only.hpp \
+ is_read_buffered.hpp \
+ is_write_buffered.hpp \
+ local/basic_endpoint.hpp \
+ local/connect_pair.hpp \
+ local/datagram_protocol.hpp \
+ local/detail/endpoint.hpp \
+ local/detail/impl/endpoint.ipp \
+ local/stream_protocol.hpp \
+ placeholders.hpp \
+ posix/basic_descriptor.hpp \
+ posix/basic_stream_descriptor.hpp \
+ posix/descriptor_base.hpp \
+ posix/stream_descriptor.hpp \
+ posix/stream_descriptor_service.hpp \
+ raw_socket_service.hpp \
+ read.hpp \
+ read_at.hpp \
+ read_until.hpp \
+ serial_port.hpp \
+ serial_port_base.hpp \
+ serial_port_service.hpp \
+ socket_acceptor_service.hpp \
+ socket_base.hpp \
+ ssl.hpp \
+ ssl/basic_context.hpp \
+ ssl/context.hpp \
+ ssl/context_base.hpp \
+ ssl/context_service.hpp \
+ ssl/detail/openssl_context_service.hpp \
+ ssl/detail/openssl_init.hpp \
+ ssl/detail/openssl_operation.hpp \
+ ssl/detail/openssl_stream_service.hpp \
+ ssl/detail/openssl_types.hpp \
+ ssl/stream.hpp \
+ ssl/stream_base.hpp \
+ ssl/stream_service.hpp \
+ strand.hpp \
+ stream_socket_service.hpp \
+ streambuf.hpp \
+ system_error.hpp \
+ thread.hpp \
+ time_traits.hpp \
+ version.hpp \
+ windows/basic_handle.hpp \
+ windows/basic_random_access_handle.hpp \
+ windows/basic_stream_handle.hpp \
+ windows/overlapped_ptr.hpp \
+ windows/random_access_handle.hpp \
+ windows/random_access_handle_service.hpp \
+ windows/stream_handle.hpp \
+ windows/stream_handle_service.hpp \
+ write.hpp \
+ write_at.hpp
diff --git a/m4macros/Makefile.am b/m4macros/Makefile.am
new file mode 100644
index 0000000..eeae7f9
--- /dev/null
+++ b/m4macros/Makefile.am
@@ -0,0 +1,2 @@
+EXTRA_DIST = ax_boost_for_bind10.m4
+EXTRA_DIST += ax_sqlite3_for_bind10.m4
diff --git a/m4macros/ax_boost_for_bind10.m4 b/m4macros/ax_boost_for_bind10.m4
index 1ce367e..3045dfb 100644
--- a/m4macros/ax_boost_for_bind10.m4
+++ b/m4macros/ax_boost_for_bind10.m4
@@ -23,7 +23,19 @@ dnl BOOST_OFFSET_PTR_WOULDFAIL set to "yes" if offset_ptr would cause build
dnl error; otherwise set to "no"
dnl BOOST_NUMERIC_CAST_WOULDFAIL set to "yes" if numeric_cast would cause
dnl build error; otherwise set to "no"
-dnl
+dnl BOOST_MAPPED_FILE_WOULDFAIL set to "yes" if managed_mapped_file would
+dnl cause build failure; otherwise set to "no"
+dnl BOOST_MAPPED_FILE_CXXFLAG set to the compiler flag that would need to
+dnl compile managed_mapped_file (can be empty).
+dnl It is of no use if "WOULDFAIL" is yes.
+dnl BOOST_STATIC_ASSERT_WOULDFAIL set to "yes" if BOOST_STATIC_ASSERT would
+dnl cause build error; otherwise set to "no"
+
+dnl BOOST_OFFSET_PTR_OLD set to "yes" if the version of boost is older than
+dnl 1.48. Older versions of boost have a bug which
+dnl causes segfaults in offset_ptr implementation when
+dnl compiled by GCC with optimisations enabled.
+dnl See ticket no. 3025 for details.
AC_DEFUN([AX_BOOST_FOR_BIND10], [
AC_LANG_SAVE
@@ -74,6 +86,8 @@ AC_TRY_COMPILE([
# Boost offset_ptr is known to not compile on some platforms, depending on
# boost version, its local configuration, and compiler. Detect it.
+CXXFLAGS_SAVED="$CXXFLAGS"
+CXXFLAGS="$CXXFLAGS -Werror"
AC_MSG_CHECKING([Boost offset_ptr compiles])
AC_TRY_COMPILE([
#include <boost/interprocess/offset_ptr.hpp>
@@ -82,6 +96,7 @@ AC_TRY_COMPILE([
BOOST_OFFSET_PTR_WOULDFAIL=no],
[AC_MSG_RESULT(no)
BOOST_OFFSET_PTR_WOULDFAIL=yes])
+CXXFLAGS="$CXXFLAGS_SAVED"
# Detect build failure case known to happen with Boost installed via
# FreeBSD ports
@@ -100,11 +115,74 @@ if test "X$GXX" = "Xyes"; then
BOOST_NUMERIC_CAST_WOULDFAIL=yes])
CXXFLAGS="$CXXFLAGS_SAVED"
+
+ AC_MSG_CHECKING([Boost rbtree is old])
+ AC_TRY_COMPILE([
+ #include <boost/version.hpp>
+ #if BOOST_VERSION < 104800
+ #error Too old
+ #endif
+ ],,[AC_MSG_RESULT(no)
+ BOOST_OFFSET_PTR_OLD=no
+ ],[AC_MSG_RESULT(yes)
+ BOOST_OFFSET_PTR_OLD=yes])
else
- # This doesn't matter for non-g++
- BOOST_NUMERIC_CAST_WOULDFAIL=no
+ # This doesn't matter for non-g++
+ BOOST_NUMERIC_CAST_WOULDFAIL=no
+ BOOST_OFFSET_PTR_OLD=no
+fi
+
+# Boost interprocess::managed_mapped_file is highly system dependent and
+# can cause many portability issues. We are going to check if it could
+# compile at all, possibly with being lenient about compiler warnings.
+BOOST_MAPPED_FILE_WOULDFAIL=yes
+BOOST_MAPPED_FILE_CXXFLAG=
+CXXFLAGS_SAVED="$CXXFLAGS"
+try_flags="no"
+if test "X$GXX" = "Xyes"; then
+ CXXFLAGS="$CXXFLAGS -Wall -Wextra -Werror"
+ try_flags="$try_flags -Wno-error"
+fi
+# clang can cause false positives with -Werror without -Qunused-arguments
+AC_CHECK_DECL([__clang__], [CXXFLAGS="$CXXFLAGS -Qunused-arguments"], [])
+
+AC_MSG_CHECKING([Boost managed_mapped_file compiles])
+CXXFLAGS_SAVED2="$CXXFLAGS"
+for flag in $try_flags; do
+ if test "$flag" != no; then
+ BOOST_MAPPED_FILE_CXXFLAG="$flag"
+ fi
+ CXXFLAGS="$CXXFLAGS $BOOST_MAPPED_FILE_CXXFLAG"
+ AC_TRY_COMPILE([
+ #include <boost/interprocess/managed_mapped_file.hpp>
+ ],[
+ return (boost::interprocess::managed_mapped_file().all_memory_deallocated());
+ ],[AC_MSG_RESULT([yes, with $flag flag])
+ BOOST_MAPPED_FILE_WOULDFAIL=no
+ break
+ ],[])
+
+ CXXFLAGS="$CXXFLAGS_SAVED2"
+done
+
+if test $BOOST_MAPPED_FILE_WOULDFAIL = yes; then
+ AC_MSG_RESULT(no)
fi
+# BOOST_STATIC_ASSERT in versions below Boost 1.54.0 is known to result
+# in warnings with GCC 4.8. Detect it.
+AC_MSG_CHECKING([BOOST_STATIC_ASSERT compiles])
+AC_TRY_COMPILE([
+#include <boost/static_assert.hpp>
+void testfn(void) { BOOST_STATIC_ASSERT(true); }
+],,
+[AC_MSG_RESULT(yes)
+ BOOST_STATIC_ASSERT_WOULDFAIL=no],
+[AC_MSG_RESULT(no)
+ BOOST_STATIC_ASSERT_WOULDFAIL=yes])
+
+CXXFLAGS="$CXXFLAGS_SAVED"
+
AC_SUBST(BOOST_INCLUDES)
CPPFLAGS="$CPPFLAGS_SAVED"
diff --git a/m4macros/ax_sqlite3_for_bind10.m4 b/m4macros/ax_sqlite3_for_bind10.m4
new file mode 100644
index 0000000..4eb7f94
--- /dev/null
+++ b/m4macros/ax_sqlite3_for_bind10.m4
@@ -0,0 +1,25 @@
+dnl @synopsis AX_SQLITE3_FOR_BIND10
+dnl
+dnl Test for the sqlite3 library and program, intended to be used within
+dnl BIND 10, and to test BIND 10.
+dnl
+dnl We use pkg-config to look for the sqlite3 library, so the sqlite3
+dnl development package with the .pc file must be installed.
+dnl
+dnl This macro sets SQLITE_CFLAGS and SQLITE_LIBS. It also sets
+dnl SQLITE3_PROGRAM to the path of the sqlite3 program, if it is found
+dnl in PATH.
+
+AC_DEFUN([AX_SQLITE3_FOR_BIND10], [
+
+PKG_CHECK_MODULES(SQLITE, sqlite3 >= 3.3.9,
+ have_sqlite="yes",
+ have_sqlite="no (sqlite3 not detected)")
+
+# Check for sqlite3 program
+AC_PATH_PROG(SQLITE3_PROGRAM, sqlite3, no)
+AM_CONDITIONAL(HAVE_SQLITE3_PROGRAM, test "x$SQLITE3_PROGRAM" != "xno")
+
+# TODO: check for _sqlite3.py module
+
+])dnl AX_SQLITE3_FOR_BIND10
diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am
index 0b4c1ae..ea2f1b2 100644
--- a/src/bin/Makefile.am
+++ b/src/bin/Makefile.am
@@ -1,5 +1,16 @@
+if BUILD_EXPERIMENTAL_RESOLVER
+# Build resolver only with --enable-experimental-resolver
+experimental_resolver = resolver
+endif
+
SUBDIRS = bind10 bindctl cfgmgr ddns loadzone msgq cmdctl auth xfrin \
- xfrout usermgr zonemgr stats tests resolver sockcreator dhcp4 dhcp6 \
- dbutil sysinfo
+ xfrout usermgr zonemgr stats tests $(experimental_resolver) \
+ sockcreator dhcp4 dhcp6 d2 dbutil sysinfo
+
+if USE_SHARED_MEMORY
+# Build the memory manager only if we have shared memory.
+# It is useless without it.
+SUBDIRS += memmgr
+endif
check-recursive: all-recursive
diff --git a/src/bin/auth/auth_messages.mes b/src/bin/auth/auth_messages.mes
index 77b20b1..e5b655a 100644
--- a/src/bin/auth/auth_messages.mes
+++ b/src/bin/auth/auth_messages.mes
@@ -145,12 +145,40 @@ reconfigure, and has now started this process.
The thread for maintaining data source clients has finished reconfiguring
the data source clients, and is now running with the new configuration.
+% AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_BAD_CLASS invalid RRclass %1 at segment update
+A memory segment update message was sent to the authoritative
+server. But the class contained there is invalid. This means that the
+system is in an inconsistent state and the authoritative server aborts
+to minimize the problem. This is likely caused by a bug in the code.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_ERROR error updating the memory segment: %1
+The authoritative server tried to update the memory segment, but the update
+failed. The authoritative server aborts to avoid system inconsistency. This is
+likely caused by a bug in the code.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_NO_DATASRC there's no data source named %2 in class %1
+The authoritative server was asked to update the memory segment of the
+given data source, but no data source by that name was found. The
+authoritative server aborts because this indicates that the system is in
+an inconsistent state. This is likely caused by a bug in the code.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_UNKNOWN_CLASS unknown class %1 at segment update
+A memory segment update message was sent to the authoritative
+server. The class name for which the update should happen is valid, but
+no client lists are configured for that class. The system is in an
+inconsistent state and the authoritative server aborts. This may be
+caused by a bug in the code.
+
% AUTH_DATASRC_CLIENTS_BUILDER_STARTED data source builder thread started
A separate thread for maintaining data source clients has been started.
% AUTH_DATASRC_CLIENTS_BUILDER_STOPPED data source builder thread stopped
The separate thread for maintaining data source clients has been stopped.
+% AUTH_DATASRC_CLIENTS_BUILDER_WAKE_ERR failed to wake up main thread: %1
+A low-level error happened when trying to send data to the main thread to wake
+it up. Terminating to prevent inconsistent state and possiblu hang ups.
+
% AUTH_DATASRC_CLIENTS_SHUTDOWN_ERROR error on waiting for data source builder thread: %1
This indicates that the separate thread for maintaining data source
clients had been terminated due to an uncaught exception, and the
@@ -266,9 +294,16 @@ bug ticket for this issue.
This is a debug message issued when the authoritative server has received
a command on the command channel.
-% AUTH_RECEIVED_NOTIFY received incoming NOTIFY for zone name %1, zone class %2
+% AUTH_RECEIVED_NOTIFY received incoming NOTIFY for zone %1/%2 from %3
This is a debug message reporting that an incoming NOTIFY was received.
+% AUTH_RECEIVED_NOTIFY_NOTAUTH received bad NOTIFY for zone %1/%2 from %3
+The authoritative server received a NOTIFY message, but the specified zone
+doesn't match any of the zones served by the server. The server doesn't
+process the message further, and returns a response with the Rcode being
+NOTAUTH. Note: RFC 1996 does not specify the server behavior in this case;
+responding with Rcode of NOTAUTH follows BIND 9's behavior.
+
% AUTH_RESPONSE_FAILURE exception while building response to query: %1
This is a debug message, generated by the authoritative server when an
attempt to create a response to a received DNS packet has failed. The
@@ -367,12 +402,33 @@ XFRIN (Transfer-in) process. It is issued during server startup is an
indication that the initialization is proceeding normally.
% AUTH_ZONEMGR_COMMS error communicating with zone manager: %1
-This is a debug message output during the processing of a NOTIFY request.
+This is an internal error during the processing of a NOTIFY request.
An error (listed in the message) has been encountered whilst communicating
with the zone manager. The NOTIFY request will not be honored.
+This may be some temporary failure, but is generally an unexpected
+event and is quite likely a bug. It's probably worth filing a report.
% AUTH_ZONEMGR_ERROR received error response from zone manager: %1
-This is a debug message output during the processing of a NOTIFY
-request. The zone manager component has been informed of the request,
+The zone manager component has been informed of the request,
but has returned an error response (which is included in the message). The
-NOTIFY request will not be honored.
+NOTIFY request will not be honored. As of this writing, this can only
+happen due to a bug inside the Zonemgr implementation. Zonemgr itself
+may log more detailed cause of this, and these are probably worth
+filing a bug report.
+
+% AUTH_ZONEMGR_NOTEXIST received NOTIFY but Zonemgr does not exist
+This is a debug message produced by the authoritative server when it
+receives a NOTIFY message but the Zonemgr component is not running at
+that time. Not running Zonemgr is completely valid for, e.g., primary
+only servers, so this is not necessarily a problem. If this message
+is logged even if Zonemgr is supposed to be running, it's encouraged
+to check other logs to identify why that happens. It may or may not
+be a real problem (for example, if it's immediately after the system
+startup, it's possible that Auth has started up and is running but
+Zonemgr is not yet). Even if this is indeed an unexpected case,
+Zonemgr should normally be restarted by the Init process, so unless
+this repeats too often it may be negligible in practice (still it's
+worth filing a bug report). In any case, the authoritative server
+simply drops the NOTIFY message; if it's a temporary failure or
+delayed startup, subsequently resent messages will eventually reach
+Zonemgr.
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index 30875e5..ce5c02b 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -22,6 +22,7 @@
#include <config/ccsession.h>
#include <cc/data.h>
+#include <cc/proto_defs.h>
#include <exceptions/exceptions.h>
@@ -41,7 +42,7 @@
#include <asiodns/dns_service.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/client_list.h>
#include <xfr/xfrout_client.h>
@@ -305,6 +306,8 @@ public:
MessageAttributes& stats_attrs,
const bool done);
+ /// Are we currently subscribed to the SegmentReader group?
+ bool readers_group_subscribed_;
private:
bool xfrout_connected_;
AbstractXfroutClient& xfrout_client_;
@@ -318,8 +321,10 @@ AuthSrvImpl::AuthSrvImpl(AbstractXfroutClient& xfrout_client,
xfrin_session_(NULL),
counters_(),
keyring_(NULL),
+ datasrc_clients_mgr_(io_service_),
ddns_base_forwarder_(ddns_forwarder),
ddns_forwarder_(NULL),
+ readers_group_subscribed_(false),
xfrout_connected_(false),
xfrout_client_(xfrout_client)
{}
@@ -370,27 +375,11 @@ public:
{}
};
-// This is a derived class of \c SimpleCallback, to serve
-// as a callback in the asiolink module. It checks for queued
-// configuration messages, and executes them if found.
-class ConfigChecker : public SimpleCallback {
-public:
- ConfigChecker(AuthSrv* srv) : server_(srv) {}
- virtual void operator()(const IOMessage&) const {
- ModuleCCSession* cfg_session = server_->getConfigSession();
- if (cfg_session != NULL && cfg_session->hasQueuedMsgs()) {
- cfg_session->checkCommand();
- }
- }
-private:
- AuthSrv* server_;
-};
-
AuthSrv::AuthSrv(isc::xfr::AbstractXfroutClient& xfrout_client,
- isc::util::io::BaseSocketSessionForwarder& ddns_forwarder)
+ isc::util::io::BaseSocketSessionForwarder& ddns_forwarder) :
+ dnss_(NULL)
{
impl_ = new AuthSrvImpl(xfrout_client, ddns_forwarder);
- checkin_ = new ConfigChecker(this);
dns_lookup_ = new MessageLookup(this);
dns_answer_ = new MessageAnswer(this);
}
@@ -402,7 +391,6 @@ AuthSrv::stop() {
AuthSrv::~AuthSrv() {
delete impl_;
- delete checkin_;
delete dns_lookup_;
delete dns_answer_;
}
@@ -520,6 +508,8 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
return;
}
+ stats_attrs.setRequestRD(message.getHeaderFlag(Message::HEADERFLAG_RD));
+
const Opcode& opcode = message.getOpcode();
// Get opcode at this point; for all requests regardless of message body
// sanity check.
@@ -747,6 +737,8 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
std::auto_ptr<TSIGContext> tsig_context,
MessageAttributes& stats_attrs)
{
+ const IOEndpoint& remote_ep = io_message.getRemoteEndpoint(); // for logs
+
// The incoming notify must contain exactly one question for SOA of the
// zone name.
if (message.getRRCount(Message::SECTION_QUESTION) != 1) {
@@ -769,23 +761,34 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
// on, but we don't check these conditions. This behavior is compatible
// with BIND 9.
- // TODO check with the conf-mgr whether current server is the auth of the
- // zone
-
- // In the code that follows, we simply ignore the notify if any internal
- // error happens rather than returning (e.g.) SERVFAIL. RFC 1996 is
- // silent about such cases, but there doesn't seem to be anything we can
- // improve at the primary server side by sending an error anyway.
- if (xfrin_session_ == NULL) {
- LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_NO_XFRIN);
- return (false);
+ // See if we have the specified zone in our data sources; if not return
+ // NOTAUTH, following BIND 9 (this is not specified in RFC 1996).
+ bool is_auth = false;
+ {
+ auth::DataSrcClientsMgr::Holder datasrc_holder(datasrc_clients_mgr_);
+ const shared_ptr<datasrc::ClientList> dsrc_clients =
+ datasrc_holder.findClientList(question->getClass());
+ is_auth = dsrc_clients &&
+ dsrc_clients->find(question->getName(), true, false).exact_match_;
+ }
+ if (!is_auth) {
+ LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RECEIVED_NOTIFY_NOTAUTH)
+ .arg(question->getName()).arg(question->getClass()).arg(remote_ep);
+ makeErrorMessage(renderer_, message, buffer, Rcode::NOTAUTH(),
+ stats_attrs, tsig_context);
+ return (true);
}
LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RECEIVED_NOTIFY)
- .arg(question->getName()).arg(question->getClass());
+ .arg(question->getName()).arg(question->getClass()).arg(remote_ep);
+
+ // xfrin_session_ should have been set and never be replaced except in
+ // tests; otherwise it's an internal bug. assert() may be too strong,
+ // but processMessage() will catch all exceptions, so there's no better
+ // way.
+ assert(xfrin_session_);
- const string remote_ip_address =
- io_message.getRemoteEndpoint().getAddress().toText();
+ const string remote_ip_address = remote_ep.getAddress().toText();
static const string command_template_start =
"{\"command\": [\"notify\", {\"zone_name\" : \"";
static const string command_template_master = "\", \"master\" : \"";
@@ -800,12 +803,24 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
command_template_end);
const unsigned int seq =
xfrin_session_->group_sendmsg(notify_command, "Zonemgr",
- "*", "*");
+ CC_INSTANCE_WILDCARD,
+ CC_INSTANCE_WILDCARD, true);
ConstElementPtr env, answer, parsed_answer;
xfrin_session_->group_recvmsg(env, answer, false, seq);
int rcode;
parsed_answer = parseAnswer(rcode, answer);
- if (rcode != 0) {
+ if (rcode == CC_REPLY_NO_RECPT) {
+ // This can happen when Zonemgr is not running. When we support
+ // notification-based membership framework, we should check if it's
+ // supposed to be running and shouldn't even send the command if
+ // not. Until then, we log this event at the debug level as we
+ // don't know whether it's a real trouble or intentional
+ // configuration. (Also, when it's done, maybe we should simply
+ // propagate the exception and return SERVFAIL to suppress further
+ // NOTIFY).
+ LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_ZONEMGR_NOTEXIST);
+ return (false);
+ } else if (rcode != CC_REPLY_SUCCESS) {
LOG_ERROR(auth_logger, AUTH_ZONEMGR_ERROR)
.arg(parsed_answer->str());
return (false);
@@ -909,3 +924,64 @@ void
AuthSrv::setTCPRecvTimeout(size_t timeout) {
dnss_->setTCPRecvTimeout(timeout);
}
+
+namespace {
+
+bool
+hasMappedSegment(auth::DataSrcClientsMgr& mgr) {
+ auth::DataSrcClientsMgr::Holder holder(mgr);
+ const std::vector<dns::RRClass>& classes(holder.getClasses());
+ BOOST_FOREACH(const dns::RRClass& rrclass, classes) {
+ const boost::shared_ptr<datasrc::ConfigurableClientList>&
+ list(holder.findClientList(rrclass));
+ const std::vector<DataSourceStatus>& states(list->getStatus());
+ BOOST_FOREACH(const datasrc::DataSourceStatus& status, states) {
+ if (status.getSegmentState() != datasrc::SEGMENT_UNUSED &&
+ status.getSegmentType() == "mapped")
+ // We use some segment and it's not a local one, so it
+ // must be remote.
+ return true;
+ }
+ }
+ // No remote segment found in any of the lists
+ return false;
+}
+
+}
+
+void
+AuthSrv::listsReconfigured() {
+ const bool has_remote = hasMappedSegment(impl_->datasrc_clients_mgr_);
+ if (has_remote && !impl_->readers_group_subscribed_) {
+ impl_->config_session_->subscribe("SegmentReader");
+ impl_->config_session_->
+ setUnhandledCallback(boost::bind(&AuthSrv::foreignCommand, this,
+ _1, _2, _3));
+ impl_->readers_group_subscribed_ = true;
+ } else if (!has_remote && impl_->readers_group_subscribed_) {
+ impl_->config_session_->unsubscribe("SegmentReader");
+ impl_->config_session_->
+ setUnhandledCallback(isc::config::ModuleCCSession::
+ UnhandledCallback());
+ impl_->readers_group_subscribed_ = false;
+ }
+}
+
+void
+AuthSrv::reconfigureDone(ConstElementPtr params) {
+ // ACK the segment
+ impl_->config_session_->
+ groupSendMsg(isc::config::createCommand("segment_info_update_ack",
+ params), "MemMgr");
+}
+
+void
+AuthSrv::foreignCommand(const std::string& command, const std::string&,
+ const ConstElementPtr& params)
+{
+ if (command == "segment_info_update") {
+ impl_->datasrc_clients_mgr_.
+ segmentInfoUpdate(params, boost::bind(&AuthSrv::reconfigureDone,
+ this, params));
+ }
+}
diff --git a/src/bin/auth/auth_srv.h b/src/bin/auth/auth_srv.h
index 107e2e6..b8147e0 100644
--- a/src/bin/auth/auth_srv.h
+++ b/src/bin/auth/auth_srv.h
@@ -190,9 +190,6 @@ public:
/// \brief Return pointer to the DNS Answer callback function
isc::asiodns::DNSAnswer* getDNSAnswerProvider() const { return (dns_answer_); }
- /// \brief Return pointer to the Checkin callback function
- isc::asiolink::SimpleCallback* getCheckinProvider() const { return (checkin_); }
-
/// \brief Return data source clients manager.
///
/// \throw None
@@ -228,7 +225,7 @@ public:
* \brief Set and get the addresses we listen on.
*/
void setListenAddresses(const isc::server_common::portconfig::AddressList&
- addreses);
+ addresses);
const isc::server_common::portconfig::AddressList& getListenAddresses()
const;
@@ -273,9 +270,19 @@ public:
/// open forever.
void setTCPRecvTimeout(size_t timeout);
+ /// \brief Notify the authoritative server that the client lists were
+ /// reconfigured.
+ ///
+ /// This is to be called when the work thread finishes reconfiguration
+ /// of the data sources. It involeves some book keeping and asking the
+ /// memory manager for segments, if some are remotely mapped.
+ void listsReconfigured();
+
private:
+ void reconfigureDone(isc::data::ConstElementPtr request);
+ void foreignCommand(const std::string& command, const std::string&,
+ const isc::data::ConstElementPtr& params);
AuthSrvImpl* impl_;
- isc::asiolink::SimpleCallback* checkin_;
isc::asiodns::DNSLookup* dns_lookup_;
isc::asiodns::DNSAnswer* dns_answer_;
isc::asiodns::DNSServiceBase* dnss_;
diff --git a/src/bin/auth/b10-auth.xml.pre b/src/bin/auth/b10-auth.xml.pre
index db5be3e..b8e2946 100644
--- a/src/bin/auth/b10-auth.xml.pre
+++ b/src/bin/auth/b10-auth.xml.pre
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>February 5, 2013</date>
+ <date>July 16, 2013</date>
</refentryinfo>
<refmeta>
@@ -81,8 +81,8 @@
<varlistentry>
<term><option>-v</option></term>
<listitem><para>
- Enable verbose logging mode. This enables logging of
- diagnostic messages at the maximum debug level.
+ Enable verbose logging mode. This enables logging of
+ diagnostic messages at the maximum debug level.
</para></listitem>
</varlistentry>
@@ -248,6 +248,27 @@
but remember that if there's any error related to TSIG, some
of the counted opcode may not be trustworthy.
</para>
+
+ <para>
+ The <quote>qryrecursion</quote> counter is limited to queries
+ (requests of opcode 0) even though the RD bit is not specific
+ to queries. In practice, this bit is generally just ignored for
+ other types of requests, while DNS servers behave differently
+ for queries depending on this bit. It is also known that
+ some authoritative-only servers receive a non negligible
+ number of queries with the RD bit being set, so it would be
+ of particular interest to have a specific counters for such
+ requests.
+ </para>
+
+ <para>
+ There are two request counters related to EDNS:
+ <quote>request.edns0</quote> and <quote>request.badednsver</quote>.
+ The latter is a counter of requests with unsupported EDNS version:
+ other than version 0 in the current implementation. Therefore, total
+ number of requests with EDNS is a sum of <quote>request.edns0</quote>
+ and <quote>request.badednsver</quote>.
+ </para>
</note>
</refsect1>
diff --git a/src/bin/auth/datasrc_clients_mgr.h b/src/bin/auth/datasrc_clients_mgr.h
index 5bbdb99..c6eed31 100644
--- a/src/bin/auth/datasrc_clients_mgr.h
+++ b/src/bin/auth/datasrc_clients_mgr.h
@@ -25,10 +25,13 @@
#include <cc/data.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/client_list.h>
#include <datasrc/memory/zone_writer.h>
+#include <asiolink/io_service.h>
+#include <asiolink/local_socket.h>
+
#include <auth/auth_log.h>
#include <auth/datasrc_config.h>
@@ -36,11 +39,16 @@
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>
+#include <boost/function.hpp>
+#include <boost/foreach.hpp>
#include <exception>
#include <cassert>
+#include <cerrno>
#include <list>
#include <utility>
+#include <sys/types.h>
+#include <sys/socket.h>
namespace isc {
namespace auth {
@@ -73,17 +81,45 @@ enum CommandID {
LOADZONE, ///< Load a new version of zone into a memory,
/// the argument to the command is a map containing 'class'
/// and 'origin' elements, both should have been validated.
+ SEGMENT_INFO_UPDATE, ///< The memory manager sent an update about segments.
SHUTDOWN, ///< Shutdown the builder; no argument
NUM_COMMANDS
};
+/// \brief Callback to be called when the command is completed.
+typedef boost::function<void ()> FinishedCallback;
+
/// \brief The data type passed from DataSrcClientsMgr to
-/// DataSrcClientsBuilder.
+/// DataSrcClientsBuilder.
///
-/// The first element of the pair is the command ID, and the second element
-/// is its argument. If the command doesn't take an argument it should be
-/// a null pointer.
-typedef std::pair<CommandID, data::ConstElementPtr> Command;
+/// This just holds the data items together, no logic or protection
+/// is present here.
+struct Command {
+ /// \brief Constructor
+ ///
+ /// It just initializes the member variables of the same names
+ /// as the parameters.
+ Command(CommandID id, const data::ConstElementPtr& params,
+ const FinishedCallback& callback) :
+ id(id),
+ params(params),
+ callback(callback)
+ {}
+ /// \brief The command to execute
+ CommandID id;
+ /// \brief Argument of the command.
+ ///
+ /// If the command takes no argument, it should be null pointer.
+ ///
+ /// This may be a null pointer if the command takes no parameters.
+ data::ConstElementPtr params;
+ /// \brief A callback to be called once the command finishes.
+ ///
+ /// This may be an empty boost::function. In such case, no callback
+ /// will be called after completion.
+ FinishedCallback callback;
+};
+
} // namespace datasrc_clientmgr_internal
/// \brief Frontend to the manager object for data source clients.
@@ -113,6 +149,24 @@ private:
boost::shared_ptr<datasrc::ConfigurableClientList> >
ClientListsMap;
+ class FDGuard : boost::noncopyable {
+ public:
+ FDGuard(DataSrcClientsMgrBase *mgr) :
+ mgr_(mgr)
+ {}
+ ~FDGuard() {
+ if (mgr_->read_fd_ != -1) {
+ close(mgr_->read_fd_);
+ }
+ if (mgr_->write_fd_ != -1) {
+ close(mgr_->write_fd_);
+ }
+ }
+ private:
+ DataSrcClientsMgrBase* mgr_;
+ };
+ friend class FDGuard;
+
public:
/// \brief Thread-safe accessor to the data source client lists.
///
@@ -159,6 +213,24 @@ public:
return (it->second);
}
}
+ /// \brief Return list of classes that are present.
+ ///
+ /// Get the list of classes for which there's a client list. It is
+ /// returned in form of a vector, copied from the internals. As the
+ /// number of classes in there is expected to be small, it is not
+ /// a performance issue.
+ ///
+ /// \return The list of classes.
+ /// \throw std::bad_alloc for problems allocating the result.
+ std::vector<dns::RRClass> getClasses() const {
+ std::vector<dns::RRClass> result;
+ for (ClientListsMap::const_iterator it =
+ mgr_.clients_map_->begin(); it != mgr_.clients_map_->end();
+ ++it) {
+ result.push_back(it->first);
+ }
+ return (result);
+ }
private:
DataSrcClientsMgrBase& mgr_;
typename MutexType::Locker locker_;
@@ -176,12 +248,20 @@ public:
///
/// \throw std::bad_alloc internal memory allocation failure.
/// \throw isc::Unexpected general unexpected system errors.
- DataSrcClientsMgrBase() :
+ DataSrcClientsMgrBase(asiolink::IOService& service) :
clients_map_(new ClientListsMap),
- builder_(&command_queue_, &cond_, &queue_mutex_, &clients_map_,
- &map_mutex_),
- builder_thread_(boost::bind(&BuilderType::run, &builder_))
- {}
+ fd_guard_(new FDGuard(this)),
+ read_fd_(-1), write_fd_(-1),
+ builder_(&command_queue_, &callback_queue_, &cond_, &queue_mutex_,
+ &clients_map_, &map_mutex_, createFds()),
+ builder_thread_(boost::bind(&BuilderType::run, &builder_)),
+ wakeup_socket_(service, read_fd_)
+ {
+ // Schedule wakeups when callbacks are pushed.
+ wakeup_socket_.asyncRead(
+ boost::bind(&DataSrcClientsMgrBase::processCallbacks, this, _1),
+ buffer, 1);
+ }
/// \brief The destructor.
///
@@ -220,6 +300,7 @@ public:
AUTH_DATASRC_CLIENTS_SHUTDOWN_UNEXPECTED_ERROR);
}
+ processCallbacks(); // Any leftover callbacks
cleanup(); // see below
}
@@ -234,11 +315,18 @@ public:
/// \brief std::bad_alloc
///
/// \param config_arg The new data source configuration. Must not be NULL.
- void reconfigure(data::ConstElementPtr config_arg) {
+ /// \param callback Called once the reconfigure command completes. It is
+ /// called in the main thread (not in the work one). It should be
+ /// exceptionless.
+ void reconfigure(const data::ConstElementPtr& config_arg,
+ const datasrc_clientmgr_internal::FinishedCallback&
+ callback = datasrc_clientmgr_internal::FinishedCallback())
+ {
if (!config_arg) {
isc_throw(InvalidParameter, "Invalid null config argument");
}
- sendCommand(datasrc_clientmgr_internal::RECONFIGURE, config_arg);
+ sendCommand(datasrc_clientmgr_internal::RECONFIGURE, config_arg,
+ callback);
reconfigureHook(); // for test's customization
}
@@ -257,12 +345,18 @@ public:
/// \param args Element argument that should be a map of the form
/// { "class": "IN", "origin": "example.com" }
/// (but class is optional and will default to IN)
+ /// \param callback Called once the loadZone command completes. It
+ /// is called in the main thread, not in the work thread. It should
+ /// be exceptionless.
///
/// \exception CommandError if the args value is null, or not in
/// the expected format, or contains
/// a bad origin or class string
void
- loadZone(data::ConstElementPtr args) {
+ loadZone(const data::ConstElementPtr& args,
+ const datasrc_clientmgr_internal::FinishedCallback& callback =
+ datasrc_clientmgr_internal::FinishedCallback())
+ {
if (!args) {
isc_throw(CommandError, "loadZone argument empty");
}
@@ -303,7 +397,37 @@ public:
// implement it would be to factor out the code from
// the start of doLoadZone(), and call it here too
- sendCommand(datasrc_clientmgr_internal::LOADZONE, args);
+ sendCommand(datasrc_clientmgr_internal::LOADZONE, args, callback);
+ }
+
+ void segmentInfoUpdate(const data::ConstElementPtr& args,
+ const datasrc_clientmgr_internal::FinishedCallback&
+ callback =
+ datasrc_clientmgr_internal::FinishedCallback()) {
+ // Some minimal validation
+ if (!args) {
+ isc_throw(CommandError, "segmentInfoUpdate argument empty");
+ }
+ if (args->getType() != isc::data::Element::map) {
+ isc_throw(CommandError, "segmentInfoUpdate argument not a map");
+ }
+ const char* params[] = {
+ "data-source-name",
+ "data-source-class",
+ "segment-params",
+ NULL
+ };
+ for (const char** param = params; *param; ++param) {
+ if (!args->contains(*param)) {
+ isc_throw(CommandError,
+ "segmentInfoUpdate argument has no '" << param <<
+ "' value");
+ }
+ }
+
+
+ sendCommand(datasrc_clientmgr_internal::SEGMENT_INFO_UPDATE, args,
+ callback);
}
private:
@@ -317,30 +441,79 @@ private:
void reconfigureHook() {}
void sendCommand(datasrc_clientmgr_internal::CommandID command,
- data::ConstElementPtr arg)
+ const data::ConstElementPtr& arg,
+ const datasrc_clientmgr_internal::FinishedCallback&
+ callback = datasrc_clientmgr_internal::FinishedCallback())
{
// The lock will be held until the end of this method. Only
// push_back has to be protected, but we can avoid having an extra
// block this way.
typename MutexType::Locker locker(queue_mutex_);
command_queue_.push_back(
- datasrc_clientmgr_internal::Command(command, arg));
+ datasrc_clientmgr_internal::Command(command, arg, callback));
cond_.signal();
}
+ int createFds() {
+ int fds[2];
+ int result = socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
+ if (result != 0) {
+ isc_throw(Unexpected, "Can't create socket pair: " <<
+ strerror(errno));
+ }
+ read_fd_ = fds[0];
+ write_fd_ = fds[1];
+ return write_fd_;
+ }
+
+ void processCallbacks(const std::string& error = std::string()) {
+ // Schedule the next read.
+ wakeup_socket_.asyncRead(
+ boost::bind(&DataSrcClientsMgrBase::processCallbacks, this, _1),
+ buffer, 1);
+ if (!error.empty()) {
+ // Generally, there should be no errors (as we are the other end
+ // as well), but check just in case.
+ isc_throw(Unexpected, error);
+ }
+
+ // Steal the callbacks into local copy.
+ std::list<datasrc_clientmgr_internal::FinishedCallback> queue;
+ {
+ typename MutexType::Locker locker(queue_mutex_);
+ queue.swap(callback_queue_);
+ }
+
+ // Execute the callbacks
+ BOOST_FOREACH(const datasrc_clientmgr_internal::FinishedCallback&
+ callback, queue) {
+ callback();
+ }
+ }
+
//
// The following are shared with the builder.
//
// The list is used as a one-way queue: back-in, front-out
std::list<datasrc_clientmgr_internal::Command> command_queue_;
+ // Similar to above, for the callbacks that are ready to be called.
+ // While the command queue is for sending commands from the main thread
+ // to the work thread, this one is for the other direction. Protected
+ // by the same mutex (queue_mutex_).
+ std::list<datasrc_clientmgr_internal::FinishedCallback> callback_queue_;
CondVarType cond_; // condition variable for queue operations
MutexType queue_mutex_; // mutex to protect the queue
datasrc::ClientListMapPtr clients_map_;
// map of actual data source client objects
+ boost::scoped_ptr<FDGuard> fd_guard_; // A guard to close the fds.
+ int read_fd_, write_fd_; // Descriptors for wakeup
MutexType map_mutex_; // mutex to protect the clients map
BuilderType builder_;
ThreadType builder_thread_; // for safety this should be placed last
+ isc::asiolink::LocalSocket wakeup_socket_; // For integration of read_fd_
+ // to the asio loop
+ char buffer[1]; // Buffer for the wakeup socket.
};
namespace datasrc_clientmgr_internal {
@@ -385,12 +558,15 @@ public:
///
/// \throw None
DataSrcClientsBuilderBase(std::list<Command>* command_queue,
+ std::list<FinishedCallback>* callback_queue,
CondVarType* cond, MutexType* queue_mutex,
datasrc::ClientListMapPtr* clients_map,
- MutexType* map_mutex
+ MutexType* map_mutex,
+ int wake_fd
) :
- command_queue_(command_queue), cond_(cond), queue_mutex_(queue_mutex),
- clients_map_(clients_map), map_mutex_(map_mutex)
+ command_queue_(command_queue), callback_queue_(callback_queue),
+ cond_(cond), queue_mutex_(queue_mutex),
+ clients_map_(clients_map), map_mutex_(map_mutex), wake_fd_(wake_fd)
{}
/// \brief The main loop.
@@ -450,6 +626,44 @@ private:
}
}
+ void doSegmentUpdate(const isc::data::ConstElementPtr& arg) {
+ try {
+ const isc::dns::RRClass
+ rrclass(arg->get("data-source-class")->stringValue());
+ const std::string&
+ name(arg->get("data-source-name")->stringValue());
+ const isc::data::ConstElementPtr& segment_params =
+ arg->get("segment-params");
+ typename MutexType::Locker locker(*map_mutex_);
+ const boost::shared_ptr<isc::datasrc::ConfigurableClientList>&
+ list = (**clients_map_)[rrclass];
+ if (!list) {
+ LOG_FATAL(auth_logger,
+ AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_UNKNOWN_CLASS)
+ .arg(rrclass);
+ std::terminate();
+ }
+ if (!list->resetMemorySegment(name,
+ isc::datasrc::memory::ZoneTableSegment::READ_ONLY,
+ segment_params)) {
+ LOG_FATAL(auth_logger,
+ AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_NO_DATASRC)
+ .arg(rrclass).arg(name);
+ std::terminate();
+ }
+ } catch (const isc::dns::InvalidRRClass& irce) {
+ LOG_FATAL(auth_logger,
+ AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_BAD_CLASS)
+ .arg(arg->get("data-source-class"));
+ std::terminate();
+ } catch (const isc::Exception& e) {
+ LOG_FATAL(auth_logger,
+ AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_ERROR)
+ .arg(e.what());
+ std::terminate();
+ }
+ }
+
void doLoadZone(const isc::data::ConstElementPtr& arg);
boost::shared_ptr<datasrc::memory::ZoneWriter> getZoneWriter(
datasrc::ConfigurableClientList& client_list,
@@ -457,10 +671,12 @@ private:
// The following are shared with the manager
std::list<Command>* command_queue_;
+ std::list<FinishedCallback> *callback_queue_;
CondVarType* cond_;
MutexType* queue_mutex_;
datasrc::ClientListMapPtr* clients_map_;
MutexType* map_mutex_;
+ int wake_fd_;
};
// Shortcut typedef for normal use
@@ -494,6 +710,31 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::run() {
AUTH_DATASRC_CLIENTS_BUILDER_COMMAND_ERROR).
arg(e.what());
}
+ if (current_commands.front().callback) {
+ // Lock the queue
+ typename MutexType::Locker locker(*queue_mutex_);
+ callback_queue_->
+ push_back(current_commands.front().callback);
+ // Wake up the other end. If it would block, there are data
+ // and it'll wake anyway.
+ int result = send(wake_fd_, "w", 1, MSG_DONTWAIT);
+ if (result == -1 &&
+ (errno != EWOULDBLOCK && errno != EAGAIN)) {
+ // Note: the strerror might not be thread safe, as
+ // subsequent call to it might change the returned
+ // string. But that is unlikely and strerror_r is
+ // not portable and we are going to terminate anyway,
+ // so that's better than nothing.
+ //
+ // Also, this error handler is not tested. It should
+ // be generally impossible to happen, so it is hard
+ // to trigger in controlled way.
+ LOG_FATAL(auth_logger,
+ AUTH_DATASRC_CLIENTS_BUILDER_WAKE_ERR).
+ arg(strerror(errno));
+ std::terminate();
+ }
+ }
current_commands.pop_front();
}
}
@@ -515,23 +756,26 @@ bool
DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
const Command& command)
{
- const CommandID cid = command.first;
+ const CommandID cid = command.id;
if (cid >= NUM_COMMANDS) {
// This shouldn't happen except for a bug within this file.
isc_throw(Unexpected, "internal bug: invalid command, ID: " << cid);
}
const boost::array<const char*, NUM_COMMANDS> command_desc = {
- {"NOOP", "RECONFIGURE", "LOADZONE", "SHUTDOWN"}
+ {"NOOP", "RECONFIGURE", "LOADZONE", "SEGMENT_INFO_UPDATE", "SHUTDOWN"}
};
LOG_DEBUG(auth_logger, DBGLVL_TRACE_BASIC,
AUTH_DATASRC_CLIENTS_BUILDER_COMMAND).arg(command_desc.at(cid));
- switch (command.first) {
+ switch (command.id) {
case RECONFIGURE:
- doReconfigure(command.second);
+ doReconfigure(command.params);
break;
case LOADZONE:
- doLoadZone(command.second);
+ doLoadZone(command.params);
+ break;
+ case SEGMENT_INFO_UPDATE:
+ doSegmentUpdate(command.params);
break;
case SHUTDOWN:
return (false);
@@ -623,7 +867,7 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::getZoneWriter(
datasrc::ConfigurableClientList::ZoneWriterPair writerpair;
{
typename MutexType::Locker locker(*map_mutex_);
- writerpair = client_list.getCachedZoneWriter(origin);
+ writerpair = client_list.getCachedZoneWriter(origin, false);
}
switch (writerpair.first) {
@@ -639,12 +883,21 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::getZoneWriter(
AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE_NOCACHE)
.arg(origin).arg(rrclass);
break; // return NULL below
+ case datasrc::ConfigurableClientList::CACHE_NOT_WRITABLE:
+ // This is an internal error. Auth server should skip reloading zones
+ // on non writable caches.
+ isc_throw(InternalCommandError, "failed to load zone " << origin
+ << "/" << rrclass << ": internal failure, in-memory cache "
+ "is not writable");
case datasrc::ConfigurableClientList::CACHE_DISABLED:
// This is an internal error. Auth server must have the cache
// enabled.
isc_throw(InternalCommandError, "failed to load zone " << origin
<< "/" << rrclass << ": internal failure, in-memory cache "
"is somehow disabled");
+ default: // other cases can really never happen
+ isc_throw(Unexpected, "Impossible result in getting data source "
+ "ZoneWriter: " << writerpair.first);
}
return (boost::shared_ptr<datasrc::memory::ZoneWriter>());
diff --git a/src/bin/auth/gen-statisticsitems.py.pre.in b/src/bin/auth/gen-statisticsitems.py.pre.in
index e2d1623..ca5e013 100755
--- a/src/bin/auth/gen-statisticsitems.py.pre.in
+++ b/src/bin/auth/gen-statisticsitems.py.pre.in
@@ -289,7 +289,7 @@ def generate_cxx(itemsfile, ccfile, utfile, def_mtime):
This method recursively builds two lists:
- msg_counter_types consists of strings for all leaf items, each
defines one enum element with a comment, e.g.
- COUNTER_ITEM, ///< item's descriptin
+ COUNTER_ITEM, ///< item's description
- item_names consists of tuples of three elements, depending on
whether it's a leaf element (no child from it) or not:
(leaf) ( "item_name", NULL, COUNTER_ITEM )
diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc
index dc03be2..d6b1119 100644
--- a/src/bin/auth/main.cc
+++ b/src/bin/auth/main.cc
@@ -109,9 +109,11 @@ datasrcConfigHandler(AuthSrv* server, bool* first_time,
assert(config_session != NULL);
*first_time = false;
server->getDataSrcClientsMgr().reconfigure(
- config_session->getRemoteConfigValue("data_sources", "classes"));
+ config_session->getRemoteConfigValue("data_sources", "classes"),
+ boost::bind(&AuthSrv::listsReconfigured, server));
} else if (config->contains("classes")) {
- server->getDataSrcClientsMgr().reconfigure(config->get("classes"));
+ server->getDataSrcClientsMgr().reconfigure(config->get("classes"),
+ boost::bind(&AuthSrv::listsReconfigured, server));
}
}
@@ -173,12 +175,11 @@ main(int argc, char* argv[]) {
auth_server = auth_server_.get();
LOG_INFO(auth_logger, AUTH_SERVER_CREATED);
- SimpleCallback* checkin = auth_server->getCheckinProvider();
IOService& io_service = auth_server->getIOService();
DNSLookup* lookup = auth_server->getDNSLookupProvider();
DNSAnswer* answer = auth_server->getDNSAnswerProvider();
- DNSService dns_service(io_service, checkin, lookup, answer);
+ DNSService dns_service(io_service, lookup, answer);
auth_server->setDNSService(dns_service);
LOG_DEBUG(auth_logger, DBG_AUTH_START, AUTH_DNS_SERVICES_CREATED);
diff --git a/src/bin/auth/query.cc b/src/bin/auth/query.cc
index c5b9b16..65e5410 100644
--- a/src/bin/auth/query.cc
+++ b/src/bin/auth/query.cc
@@ -373,7 +373,7 @@ Query::process(datasrc::ClientList& client_list,
// If we have no matching authoritative zone for the query name, return
// REFUSED. In short, this is to be compatible with BIND 9, but the
// background discussion is not that simple. See the relevant topic
- // at the BIND 10 developers's ML:
+ // at the BIND 10 developers' ML:
// https://lists.isc.org/mailman/htdig/bind10-dev/2010-December/001633.html
if (result.dsrc_client_ == NULL) {
// If we tried to find a "parent zone" for a DS query and failed,
@@ -386,6 +386,12 @@ Query::process(datasrc::ClientList& client_list,
response_->setHeaderFlag(Message::HEADERFLAG_AA, false);
response_->setRcode(Rcode::REFUSED());
return;
+ } else if (!result.finder_) {
+ // We found a matching zone in a data source but its data are not
+ // available.
+ response_->setHeaderFlag(Message::HEADERFLAG_AA, false);
+ response_->setRcode(Rcode::SERVFAIL());
+ return;
}
ZoneFinder& zfinder = *result.finder_;
diff --git a/src/bin/auth/query.h b/src/bin/auth/query.h
index 5af2361..2c08312 100644
--- a/src/bin/auth/query.h
+++ b/src/bin/auth/query.h
@@ -329,8 +329,8 @@ public:
/// \short Bad zone data encountered.
///
- /// This is thrown when process encounteres misconfigured zone in a way
- /// it can't continue. This throws, not sets the Rcode, because such
+ /// This is thrown when a process encounters a misconfigured zone in a
+ /// way it can't continue. This throws, not sets the Rcode, because such
/// misconfigured zone should not be present in the data source and
/// should have been rejected sooner.
struct BadZone : public isc::Exception {
diff --git a/src/bin/auth/statistics.cc.pre b/src/bin/auth/statistics.cc.pre
index 21141b0..b5418a0 100644
--- a/src/bin/auth/statistics.cc.pre
+++ b/src/bin/auth/statistics.cc.pre
@@ -26,6 +26,8 @@
#include <boost/optional.hpp>
+#include <stdint.h>
+
using namespace isc::dns;
using namespace isc::auth;
using namespace isc::statistics;
@@ -53,8 +55,8 @@ fillNodes(const Counter& counter,
fillNodes(counter, type_tree[i].sub_counters, sub_counters);
} else {
trees->set(type_tree[i].name,
- Element::create(static_cast<long int>(
- counter.get(type_tree[i].counter_id)))
+ Element::create(static_cast<int64_t>(
+ counter.get(type_tree[i].counter_id) & 0x7fffffffffffffffLL))
);
}
}
@@ -138,7 +140,14 @@ Counters::incRequest(const MessageAttributes& msgattrs) {
// if a short message which does not contain DNS header is received, or
// a response message (i.e. QR bit is set) is received.
if (opcode) {
- server_msg_counter_.inc(opcode_to_msgcounter[opcode.get().getCode()]);
+ server_msg_counter_.inc(opcode_to_msgcounter[opcode->getCode()]);
+
+ if (opcode.get() == Opcode::QUERY()) {
+ // Recursion Desired bit
+ if (msgattrs.requestHasRD()) {
+ server_msg_counter_.inc(MSG_QRYRECURSION);
+ }
+ }
}
// TSIG
diff --git a/src/bin/auth/statistics.h b/src/bin/auth/statistics.h
index 52f9bad..2ab987d 100644
--- a/src/bin/auth/statistics.h
+++ b/src/bin/auth/statistics.h
@@ -66,6 +66,8 @@ private:
enum BitAttributes {
REQ_WITH_EDNS_0, // request with EDNS ver.0
REQ_WITH_DNSSEC_OK, // DNSSEC OK (DO) bit is set in request
+ REQ_WITH_RD, // Recursion Desired (RD) bit is set in
+ // request
REQ_TSIG_SIGNED, // request is signed with valid TSIG
REQ_BADSIG, // request is signed but bad signature
RES_IS_TRUNCATED, // response is truncated
@@ -170,6 +172,22 @@ public:
bit_attributes_[REQ_WITH_DNSSEC_OK] = with_dnssec_ok;
}
+ /// \brief Return Recursion Desired (RD) bit of the request.
+ ///
+ /// \return true if Recursion Desired (RD) bit of the request is set
+ /// \throw None
+ bool requestHasRD() const {
+ return (bit_attributes_[REQ_WITH_RD]);
+ }
+
+ /// \brief Set Recursion Desired (RD) bit of the request.
+ ///
+ /// \param with_rd true if Recursion Desired (RD)bit of the request is set
+ /// \throw None
+ void setRequestRD(const bool with_rd) {
+ bit_attributes_[REQ_WITH_RD] = with_rd;
+ }
+
/// \brief Return whether the request is TSIG signed or not.
///
/// \return true if the request is TSIG signed
diff --git a/src/bin/auth/statistics_msg_items.def b/src/bin/auth/statistics_msg_items.def
index d8d3597..05d96c9 100644
--- a/src/bin/auth/statistics_msg_items.def
+++ b/src/bin/auth/statistics_msg_items.def
@@ -31,6 +31,7 @@ qrynoauthans MSG_QRYNOAUTHANS Number of queries received by the b10-auth server
qryreferral MSG_QRYREFERRAL Number of queries received by the b10-auth server resulted in referral answer.
qrynxrrset MSG_QRYNXRRSET Number of queries received by the b10-auth server resulted in NoError and AA bit is set in the response, but the number of answer RR == 0.
authqryrej MSG_QRYREJECT Number of authoritative queries rejected by the b10-auth server.
+qryrecursion MSG_QRYRECURSION Number of queries received by the b10-auth server with "Recursion Desired" (RD) bit was set.
rcode msg_counter_rcode Rcode statistics =
noerror MSG_RCODE_NOERROR Number of requests received by the b10-auth server resulted in RCODE = 0 (NoError).
formerr MSG_RCODE_FORMERR Number of requests received by the b10-auth server resulted in RCODE = 1 (FormErr).
diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc
index 0a77a11..9a58692 100644
--- a/src/bin/auth/tests/auth_srv_unittest.cc
+++ b/src/bin/auth/tests/auth_srv_unittest.cc
@@ -26,6 +26,8 @@
#include <dns/rdataclass.h>
#include <dns/tsig.h>
+#include <cc/proto_defs.h>
+
#include <server_common/portconfig.h>
#include <server_common/keyring.h>
@@ -37,6 +39,9 @@
#include <auth/statistics_items.h>
#include <auth/datasrc_config.h>
+#include <config/tests/fake_session.h>
+#include <config/ccsession.h>
+
#include <util/unittests/mock_socketsession.h>
#include <dns/tests/unittest_util.h>
#include <testutils/dnsmessage_test.h>
@@ -76,7 +81,6 @@ using namespace isc::asiolink;
using namespace isc::testutils;
using namespace isc::server_common::portconfig;
using namespace isc::auth::unittest;
-using isc::datasrc::memory::ZoneTableSegment;
using isc::UnitTestUtil;
using boost::scoped_ptr;
using isc::auth::statistics::Counters;
@@ -244,6 +248,61 @@ createBuiltinVersionResponse(const qid_t qid, vector<uint8_t>& data) {
renderer.getLength());
}
+void
+installDataSrcClientLists(AuthSrv& server, ClientListMapPtr lists) {
+ // For now, we use explicit swap than reconfigure() because the latter
+ // involves a separate thread and cannot guarantee the new config is
+ // available for the subsequent test.
+ server.getDataSrcClientsMgr().setDataSrcClientLists(lists);
+}
+
+void
+updateDatabase(AuthSrv& server, const char* params) {
+ const ConstElementPtr config(Element::fromJSON("{"
+ "\"IN\": [{"
+ " \"type\": \"sqlite3\","
+ " \"params\": " + string(params) +
+ "}]}"));
+ installDataSrcClientLists(server, configureDataSource(config));
+}
+
+void
+updateInMemory(AuthSrv& server, const char* origin, const char* filename,
+ bool with_static = true, bool mapped = false)
+{
+ string spec_txt = "{"
+ "\"IN\": [{"
+ " \"type\": \"MasterFiles\","
+ " \"params\": {"
+ " \"" + string(origin) + "\": \"" + string(filename) + "\""
+ " }," +
+ string(mapped ? "\"cache-type\": \"mapped\"," : "") +
+ " \"cache-enable\": true"
+ "}]";
+ if (with_static) {
+ spec_txt += ", \"CH\": [{"
+ " \"type\": \"MasterFiles\","
+ " \"cache-enable\": true,"
+ " \"params\": {\"BIND\": \"" + string(STATIC_DSRC_FILE) + "\"}"
+ "}]";
+ }
+ spec_txt += "}";
+
+ const ConstElementPtr config(Element::fromJSON(spec_txt));
+ installDataSrcClientLists(server, configureDataSource(config));
+}
+
+void
+updateBuiltin(AuthSrv& server) {
+ const ConstElementPtr config(Element::fromJSON("{"
+ "\"CH\": [{"
+ " \"type\": \"MasterFiles\","
+ " \"cache-enable\": true,"
+ " \"params\": {\"BIND\": \"" + string(STATIC_DSRC_FILE) + "\"}"
+ "}]}"));
+ installDataSrcClientLists(server, configureDataSource(config));
+}
+
// We did not configure any client lists. Therefore it should be REFUSED
TEST_F(AuthSrvTest, noClientList) {
UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
@@ -647,8 +706,10 @@ TEST_F(AuthSrvTest, IXFRDisconnectFail) {
}
TEST_F(AuthSrvTest, notify) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
@@ -664,7 +725,7 @@ TEST_F(AuthSrvTest, notify) {
stringValue());
ConstElementPtr notify_args =
notify_session.getSentMessage()->get("command")->get(1);
- EXPECT_EQ("example.com.", notify_args->get("zone_name")->stringValue());
+ EXPECT_EQ("example.", notify_args->get("zone_name")->stringValue());
EXPECT_EQ(DEFAULT_REMOTE_ADDRESS,
notify_args->get("master")->stringValue());
EXPECT_EQ("IN", notify_args->get("zone_class")->stringValue());
@@ -675,7 +736,7 @@ TEST_F(AuthSrvTest, notify) {
// The question must be identical to that of the received notify
ConstQuestionPtr question = *parse_message->beginQuestion();
- EXPECT_EQ(Name("example.com"), question->getName());
+ EXPECT_EQ(Name("example"), question->getName());
EXPECT_EQ(RRClass::IN(), question->getClass());
EXPECT_EQ(RRType::SOA(), question->getType());
@@ -691,9 +752,12 @@ TEST_F(AuthSrvTest, notify) {
}
TEST_F(AuthSrvTest, notifyForCHClass) {
- // Same as the previous test, but for the CH RRClass.
+ // Same as the previous test, but for the CH RRClass (so we install the
+ // builtin (static) data source.
+ updateBuiltin(server);
+
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("bind"),
RRClass::CH(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
@@ -773,9 +837,11 @@ TEST_F(AuthSrvTest, notifyNonSOAQuestion) {
}
TEST_F(AuthSrvTest, notifyWithoutAA) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
// implicitly leave the AA bit off. our implementation will accept it.
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
createRequestPacket(request_message, IPPROTO_UDP);
server.processMessage(*io_message, *parse_message, *response_obuffer,
@@ -786,8 +852,10 @@ TEST_F(AuthSrvTest, notifyWithoutAA) {
}
TEST_F(AuthSrvTest, notifyWithErrorRcode) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
request_message.setRcode(Rcode::SERVFAIL());
@@ -799,11 +867,15 @@ TEST_F(AuthSrvTest, notifyWithErrorRcode) {
Opcode::NOTIFY().getCode(), QR_FLAG | AA_FLAG, 1, 0, 0, 0);
}
-TEST_F(AuthSrvTest, notifyWithoutSession) {
- server.setXfrinSession(NULL);
+TEST_F(AuthSrvTest, notifyWithoutRecipient) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
+ // Emulate the case where msgq tells auth there's no Zonemgr module.
+ notify_session.setMessage(isc::config::createAnswer(CC_REPLY_NO_RECPT,
+ "no recipient"));
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
@@ -812,14 +884,19 @@ TEST_F(AuthSrvTest, notifyWithoutSession) {
// happens.
server.processMessage(*io_message, *parse_message, *response_obuffer,
&dnsserv);
+ // want_answer should have been set to true so auth can catch it if zonemgr
+ // is not running.
+ EXPECT_TRUE(notify_session.wasAnswerWanted());
EXPECT_FALSE(dnsserv.hasAnswer());
}
TEST_F(AuthSrvTest, notifySendFail) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
notify_session.disableSend();
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
@@ -830,10 +907,12 @@ TEST_F(AuthSrvTest, notifySendFail) {
}
TEST_F(AuthSrvTest, notifyReceiveFail) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
notify_session.disableReceive();
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
@@ -843,10 +922,12 @@ TEST_F(AuthSrvTest, notifyReceiveFail) {
}
TEST_F(AuthSrvTest, notifyWithBogusSessionMessage) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
notify_session.setMessage(Element::fromJSON("{\"foo\": 1}"));
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
@@ -856,11 +937,13 @@ TEST_F(AuthSrvTest, notifyWithBogusSessionMessage) {
}
TEST_F(AuthSrvTest, notifyWithSessionMessageError) {
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
notify_session.setMessage(
Element::fromJSON("{\"result\": [1, \"FAIL\"]}"));
UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
- default_qid, Name("example.com"),
+ default_qid, Name("example"),
RRClass::IN(), RRType::SOA());
request_message.setHeaderFlag(Message::HEADERFLAG_AA);
createRequestPacket(request_message, IPPROTO_UDP);
@@ -869,58 +952,55 @@ TEST_F(AuthSrvTest, notifyWithSessionMessageError) {
EXPECT_FALSE(dnsserv.hasAnswer());
}
-void
-installDataSrcClientLists(AuthSrv& server, ClientListMapPtr lists) {
- // For now, we use explicit swap than reconfigure() because the latter
- // involves a separate thread and cannot guarantee the new config is
- // available for the subsequent test.
- server.getDataSrcClientsMgr().setDataSrcClientLists(lists);
-}
+TEST_F(AuthSrvTest, notifyNotAuth) {
+ // If the server doesn't have authority of the specified zone in NOTIFY,
+ // it will return NOTAUTH
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
-void
-updateDatabase(AuthSrv& server, const char* params) {
- const ConstElementPtr config(Element::fromJSON("{"
- "\"IN\": [{"
- " \"type\": \"sqlite3\","
- " \"params\": " + string(params) +
- "}]}"));
- installDataSrcClientLists(server, configureDataSource(config));
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
+ default_qid, Name("example.com"),
+ RRClass::IN(), RRType::SOA());
+ request_message.setHeaderFlag(Message::HEADERFLAG_AA);
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::NOTAUTH(),
+ Opcode::NOTIFY().getCode(), QR_FLAG /* no AA */, 1, 0, 0, 0);
}
-void
-updateInMemory(AuthSrv& server, const char* origin, const char* filename) {
- const ConstElementPtr config(Element::fromJSON("{"
- "\"IN\": [{"
- " \"type\": \"MasterFiles\","
- " \"params\": {"
- " \"" + string(origin) + "\": \"" + string(filename) + "\""
- " },"
- " \"cache-enable\": true"
- "}],"
- "\"CH\": [{"
- " \"type\": \"static\","
- " \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
- "}]}"));
- installDataSrcClientLists(server, configureDataSource(config));
+TEST_F(AuthSrvTest, notifyNotAuthSubDomain) {
+ // Similar to the previous case, but checking partial match doesn't confuse
+ // the processing.
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
+ default_qid, Name("child.example"),
+ RRClass::IN(), RRType::SOA());
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
+ headerCheck(*parse_message, default_qid, Rcode::NOTAUTH(),
+ Opcode::NOTIFY().getCode(), QR_FLAG, 1, 0, 0, 0);
}
-void
-updateBuiltin(AuthSrv& server) {
- const ConstElementPtr config(Element::fromJSON("{"
- "\"CH\": [{"
- " \"type\": \"static\","
- " \"params\": \"" + string(STATIC_DSRC_FILE) + "\""
- "}]}"));
- installDataSrcClientLists(server, configureDataSource(config));
+TEST_F(AuthSrvTest, notifyNotAuthNoClass) {
+ // Likewise, and there's not even a data source in the specified class.
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false);
+
+ UnitTestUtil::createRequestMessage(request_message, Opcode::NOTIFY(),
+ default_qid, Name("example"),
+ RRClass::CH(), RRType::SOA());
+ createRequestPacket(request_message, IPPROTO_UDP);
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
+ headerCheck(*parse_message, default_qid, Rcode::NOTAUTH(),
+ Opcode::NOTIFY().getCode(), QR_FLAG, 1, 0, 0, 0);
}
// Try giving the server a TSIG signed request and see it can anwer signed as
// well
-#ifdef USE_STATIC_LINK
-TEST_F(AuthSrvTest, DISABLED_TSIGSigned) { // Needs builtin
-#else
TEST_F(AuthSrvTest, TSIGSigned) {
-#endif
// Prepare key, the client message, etc
updateBuiltin(server);
const TSIGKey key("key:c2VjcmV0Cg==:hmac-sha1");
@@ -978,11 +1058,7 @@ TEST_F(AuthSrvTest, TSIGSigned) {
// authoritative only server in terms of performance, and it's quite likely
// we need to drop it for the authoritative server implementation.
// At that point we can drop this test, too.
-#ifdef USE_STATIC_LINK
-TEST_F(AuthSrvTest, DISABLED_builtInQueryViaDNSServer) {
-#else
TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
-#endif
updateBuiltin(server);
UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
default_qid, Name("VERSION.BIND."),
@@ -1010,11 +1086,7 @@ TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
// The most primitive check: checking the result of the processMessage()
// method
-#ifdef USE_STATIC_LINK
-TEST_F(AuthSrvTest, DISABLED_builtInQuery) {
-#else
TEST_F(AuthSrvTest, builtInQuery) {
-#endif
updateBuiltin(server);
UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
default_qid, Name("VERSION.BIND."),
@@ -1031,11 +1103,7 @@ TEST_F(AuthSrvTest, builtInQuery) {
}
// Same type of test as builtInQueryViaDNSServer but for an error response.
-#ifdef USE_STATIC_LINK
-TEST_F(AuthSrvTest, DISABLED_iqueryViaDNSServer) { // Needs builtin
-#else
-TEST_F(AuthSrvTest, iqueryViaDNSServer) { // Needs builtin
-#endif
+TEST_F(AuthSrvTest, iqueryViaDNSServer) {
updateBuiltin(server);
createDataFromFile("iquery_fromWire.wire");
(*server.getDNSLookupProvider())(*io_message, parse_message,
@@ -1146,11 +1214,39 @@ TEST_F(AuthSrvTest, updateWithInMemoryClient) {
opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
}
-#ifdef USE_STATIC_LINK
-TEST_F(AuthSrvTest, DISABLED_queryWithInMemoryClientNoDNSSEC) {
-#else
+TEST_F(AuthSrvTest, emptyZone) {
+ // Similar to the previous setup, but the configuration has an error
+ // (zone file doesn't exist) and the query should result in SERVFAIL.
+ // Here we check the rcode other header parameters, and statistics.
+
+ const ConstElementPtr config(Element::fromJSON("{"
+ "\"IN\": [{"
+ " \"type\": \"MasterFiles\","
+ " \"params\": {\"example.com\": \"nosuchfile.zone\"},"
+ " \"cache-enable\": true"
+ "}]}"));
+ installDataSrcClientLists(server, configureDataSource(config));
+ createDataFromFile("examplequery_fromWire.wire");
+ server.processMessage(*io_message, *parse_message, *response_obuffer,
+ &dnsserv);
+ EXPECT_TRUE(dnsserv.hasAnswer());
+ headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
+ opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+
+ checkAllRcodeCountersZeroExcept(Rcode::SERVFAIL(), 1);
+ ConstElementPtr stats = server.getStatistics()->get("zones")->
+ get("_SERVER_");
+ std::map<std::string, int> expect;
+ expect["request.v4"] = 1;
+ expect["request.udp"] = 1;
+ expect["opcode.query"] = 1;
+ expect["responses"] = 1;
+ expect["qrynoauthans"] = 1;
+ expect["rcode.servfail"] = 1;
+ checkStatisticsCounters(stats, expect);
+}
+
TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
-#endif
// In this example, we do simple check that query is handled from the
// query handler class, and confirm it returns no error and a non empty
// answer section. Detailed examination on the response content
@@ -1166,11 +1262,7 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientNoDNSSEC) {
opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
}
-#ifdef USE_STATIC_LINK
-TEST_F(AuthSrvTest, DISABLED_queryWithInMemoryClientDNSSEC) {
-#else
TEST_F(AuthSrvTest, queryWithInMemoryClientDNSSEC) {
-#endif
// Similar to the previous test, but the query has the DO bit on.
// The response should contain RRSIGs, and should have more RRs than
// the previous case.
@@ -1185,14 +1277,7 @@ TEST_F(AuthSrvTest, queryWithInMemoryClientDNSSEC) {
opcode.getCode(), QR_FLAG | AA_FLAG, 1, 2, 3, 3);
}
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
- DISABLED_chQueryWithInMemoryClient
-#else
- chQueryWithInMemoryClient
-#endif
- )
-{
+TEST_F(AuthSrvTest, chQueryWithInMemoryClient) {
// Set up the in-memory
updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
@@ -1665,9 +1750,7 @@ public:
real_list, ThrowWhen throw_when, bool isc_exception,
ConstRRsetPtr fake_rrset = ConstRRsetPtr()) :
ConfigurableClientList(RRClass::IN()),
- real_(real_list),
- config_(Element::fromJSON("{}")),
- ztable_segment_(ZoneTableSegment::create(*config_, RRClass::IN()))
+ real_(real_list)
{
BOOST_FOREACH(const DataSourceInfo& info, real_->getDataSources()) {
const isc::datasrc::DataSourceClientPtr
@@ -1679,13 +1762,13 @@ public:
data_sources_.push_back(
DataSourceInfo(client.get(),
isc::datasrc::DataSourceClientContainerPtr(),
- false, RRClass::IN(), ztable_segment_));
+ boost::shared_ptr<
+ isc::datasrc::internal::CacheConfig>(),
+ RRClass::IN(), ""));
}
}
private:
const boost::shared_ptr<isc::datasrc::ConfigurableClientList> real_;
- const ConstElementPtr config_;
- boost::shared_ptr<ZoneTableSegment> ztable_segment_;
vector<isc::datasrc::DataSourceClientPtr> clients_;
};
@@ -1695,14 +1778,7 @@ private:
//
// Set the proxies to never throw, this should have the same result as
// queryWithInMemoryClientNoDNSSEC, and serves to test the two proxy classes
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
- DISABLED_queryWithInMemoryClientProxy
-#else
- queryWithInMemoryClientProxy
-#endif
- )
-{
+TEST_F(AuthSrvTest, queryWithInMemoryClientProxy) {
// Set real inmem client to proxy
updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
boost::shared_ptr<isc::datasrc::ConfigurableClientList> list;
@@ -1749,14 +1825,7 @@ setupThrow(AuthSrv& server, ThrowWhen throw_when, bool isc_exception,
mgr.setDataSrcClientLists(lists);
}
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
- DISABLED_queryWithThrowingProxyServfails
-#else
- queryWithThrowingProxyServfails
-#endif
- )
-{
+TEST_F(AuthSrvTest, queryWithThrowingProxyServfails) {
// Test the common cases, all of which should simply return SERVFAIL
// Use THROW_NEVER as end marker
ThrowWhen throws[] = { THROW_AT_FIND_ZONE,
@@ -1780,14 +1849,7 @@ TEST_F(AuthSrvTest,
// Throw isc::Exception in getClass(). (Currently?) getClass is not called
// in the processMessage path, so this should result in a normal answer
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
- DISABLED_queryWithInMemoryClientProxyGetClass
-#else
- queryWithInMemoryClientProxyGetClass
-#endif
- )
-{
+TEST_F(AuthSrvTest, queryWithInMemoryClientProxyGetClass) {
createDataFromFile("nsec3query_nodnssec_fromWire.wire");
setupThrow(server, THROW_AT_GET_CLASS, true);
@@ -1800,14 +1862,7 @@ TEST_F(AuthSrvTest,
opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 2, 1);
}
-TEST_F(AuthSrvTest,
-#ifdef USE_STATIC_LINK
- DISABLED_queryWithThrowingInToWire
-#else
- queryWithThrowingInToWire
-#endif
- )
-{
+TEST_F(AuthSrvTest, queryWithThrowingInToWire) {
// Set up a faked data source. It will return an empty RRset for the
// query.
ConstRRsetPtr empty_rrset(new RRset(Name("foo.example"),
@@ -2087,4 +2142,46 @@ TEST_F(AuthSrvTest, loadZoneCommand) {
sendCommand(server, "loadzone", args, 0);
}
+// Test that the auth server subscribes to the segment readers group when
+// there's a remotely mapped segment.
+#ifdef USE_SHARED_MEMORY
+TEST_F(AuthSrvTest, postReconfigure) {
+#else
+TEST_F(AuthSrvTest, DISABLED_postReconfigure) {
+#endif
+ FakeSession session(ElementPtr(new ListElement),
+ ElementPtr(new ListElement),
+ ElementPtr(new ListElement));
+ const string specfile(string(TEST_OWN_DATA_DIR) + "/spec.spec");
+ session.getMessages()->add(isc::config::createAnswer());
+ isc::config::ModuleCCSession mccs(specfile, session, NULL, NULL, false,
+ false);
+ server.setConfigSession(&mccs);
+ // First, no lists are there, so no reason to subscribe
+ server.listsReconfigured();
+ EXPECT_FALSE(session.haveSubscription("SegmentReader", "*"));
+ // Enable remote segment
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false, true);
+ {
+ DataSrcClientsMgr &mgr(server.getDataSrcClientsMgr());
+ DataSrcClientsMgr::Holder holder(mgr);
+ EXPECT_EQ(SEGMENT_WAITING, holder.findClientList(RRClass::IN())->
+ getStatus()[0].getSegmentState());
+ }
+ server.listsReconfigured();
+ EXPECT_TRUE(session.haveSubscription("SegmentReader", "*"));
+ // Set the segment to local again
+ updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
+ {
+ DataSrcClientsMgr &mgr(server.getDataSrcClientsMgr());
+ DataSrcClientsMgr::Holder holder(mgr);
+ EXPECT_EQ(SEGMENT_INUSE, holder.findClientList(RRClass::IN())->
+ getStatus()[0].getSegmentState());
+ EXPECT_EQ("local", holder.findClientList(RRClass::IN())->
+ getStatus()[0].getSegmentType());
+ }
+ server.listsReconfigured();
+ EXPECT_FALSE(session.haveSubscription("SegmentReader", "*"));
+}
+
}
diff --git a/src/bin/auth/tests/config_unittest.cc b/src/bin/auth/tests/config_unittest.cc
index 0d6cbf8..65b6539 100644
--- a/src/bin/auth/tests/config_unittest.cc
+++ b/src/bin/auth/tests/config_unittest.cc
@@ -21,7 +21,7 @@
#include <cc/data.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <xfr/xfrout_client.h>
diff --git a/src/bin/auth/tests/datasrc_clients_builder_unittest.cc b/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
index 01ac47e..6f85adb 100644
--- a/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
+++ b/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
+
#include <util/unittests/check_valgrind.h>
#include <dns/name.h>
@@ -34,9 +36,14 @@
#include <boost/function.hpp>
+#include <sys/types.h>
+#include <sys/socket.h>
+
#include <cstdlib>
#include <string>
#include <sstream>
+#include <cerrno>
+#include <unistd.h>
using isc::data::ConstElementPtr;
using namespace isc::dns;
@@ -52,17 +59,29 @@ protected:
DataSrcClientsBuilderTest() :
clients_map(new std::map<RRClass,
boost::shared_ptr<ConfigurableClientList> >),
- builder(&command_queue, &cond, &queue_mutex, &clients_map, &map_mutex),
+ write_end(-1), read_end(-1),
+ builder(&command_queue, &callback_queue, &cond, &queue_mutex,
+ &clients_map, &map_mutex, generateSockets()),
cond(command_queue, delayed_command_queue), rrclass(RRClass::IN()),
- shutdown_cmd(SHUTDOWN, ConstElementPtr()),
- noop_cmd(NOOP, ConstElementPtr())
+ shutdown_cmd(SHUTDOWN, ConstElementPtr(), FinishedCallback()),
+ noop_cmd(NOOP, ConstElementPtr(), FinishedCallback())
{}
+ ~ DataSrcClientsBuilderTest() {
+
+ }
+
+ void TearDown() {
+ // Some tests create this file. Delete it if it exists.
+ unlink(TEST_DATA_BUILDDIR "/test1.zone.image");
+ }
void configureZones(); // used for loadzone related tests
ClientListMapPtr clients_map; // configured clients
std::list<Command> command_queue; // test command queue
std::list<Command> delayed_command_queue; // commands available after wait
+ std::list<FinishedCallback> callback_queue; // Callbacks from commands
+ int write_end, read_end;
TestDataSrcClientsBuilder builder;
TestCondVar cond;
TestMutex queue_mutex;
@@ -70,6 +89,15 @@ protected:
const RRClass rrclass;
const Command shutdown_cmd;
const Command noop_cmd;
+private:
+ int generateSockets() {
+ int pair[2];
+ int result = socketpair(AF_UNIX, SOCK_STREAM, 0, pair);
+ assert(result == 0);
+ write_end = pair[0];
+ read_end = pair[1];
+ return write_end;
+ }
};
TEST_F(DataSrcClientsBuilderTest, runSingleCommand) {
@@ -80,6 +108,45 @@ TEST_F(DataSrcClientsBuilderTest, runSingleCommand) {
EXPECT_EQ(0, cond.wait_count); // no wait because command queue is not empty
EXPECT_EQ(1, queue_mutex.lock_count);
EXPECT_EQ(1, queue_mutex.unlock_count);
+ // No callback scheduled, none called.
+ EXPECT_TRUE(callback_queue.empty());
+ // Not woken up.
+ char c;
+ int result = recv(read_end, &c, 1, MSG_DONTWAIT);
+ EXPECT_EQ(-1, result);
+ EXPECT_TRUE(errno == EAGAIN || errno == EWOULDBLOCK);
+}
+
+// Just to have a valid function callback to pass
+void emptyCallsback() {}
+
+// Check a command finished callback is passed
+TEST_F(DataSrcClientsBuilderTest, commandFinished) {
+ command_queue.push_back(Command(SHUTDOWN, ConstElementPtr(),
+ emptyCallsback));
+ builder.run();
+ EXPECT_EQ(0, cond.wait_count); // no wait because command queue is not empty
+ // Once for picking up data, once for putting the callback there
+ EXPECT_EQ(2, queue_mutex.lock_count);
+ EXPECT_EQ(2, queue_mutex.unlock_count);
+ // There's one callback in the queue
+ ASSERT_EQ(1, callback_queue.size());
+ // Not using EXPECT_EQ, as that produces warning in printing out the result
+ EXPECT_TRUE(emptyCallsback == callback_queue.front());
+ // And we are woken up.
+ char c;
+ int result = recv(read_end, &c, 1, MSG_DONTWAIT);
+ EXPECT_EQ(1, result);
+}
+
+// Test that low-level errors with the synchronization socket
+// (an unexpected condition) is detected and program aborted.
+TEST_F(DataSrcClientsBuilderTest, finishedCrash) {
+ command_queue.push_back(Command(SHUTDOWN, ConstElementPtr(),
+ emptyCallsback));
+ // Break the socket
+ close(write_end);
+ EXPECT_DEATH_IF_SUPPORTED({builder.run();}, "");
}
TEST_F(DataSrcClientsBuilderTest, runMultiCommands) {
@@ -136,7 +203,7 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
// the error handling
// A command structure we'll modify to send different commands
- Command reconfig_cmd(RECONFIGURE, ConstElementPtr());
+ Command reconfig_cmd(RECONFIGURE, ConstElementPtr(), FinishedCallback());
// Initially, no clients should be there
EXPECT_TRUE(clients_map->empty());
@@ -164,7 +231,7 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
"}"
);
- reconfig_cmd.second = good_config;
+ reconfig_cmd.params = good_config;
EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
EXPECT_EQ(1, clients_map->size());
EXPECT_EQ(1, map_mutex.lock_count);
@@ -175,7 +242,7 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
// If a 'bad' command argument got here, the config validation should
// have failed already, but still, the handler should return true,
// and the clients_map should not be updated.
- reconfig_cmd.second = Element::create("{ \"foo\": \"bar\" }");
+ reconfig_cmd.params = Element::create("{ \"foo\": \"bar\" }");
EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
EXPECT_EQ(working_config_clients, clients_map);
// Building failed, so map mutex should not have been locked again
@@ -183,7 +250,7 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
// The same for a configuration that has bad data for the type it
// specifies
- reconfig_cmd.second = bad_config;
+ reconfig_cmd.params = bad_config;
builder.handleCommand(reconfig_cmd);
EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
EXPECT_EQ(working_config_clients, clients_map);
@@ -192,21 +259,21 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
// The same goes for an empty parameter (it should at least be
// an empty map)
- reconfig_cmd.second = ConstElementPtr();
+ reconfig_cmd.params = ConstElementPtr();
EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
EXPECT_EQ(working_config_clients, clients_map);
EXPECT_EQ(1, map_mutex.lock_count);
// Reconfigure again with the same good clients, the result should
// be a different map than the original, but not an empty one.
- reconfig_cmd.second = good_config;
+ reconfig_cmd.params = good_config;
EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
EXPECT_NE(working_config_clients, clients_map);
EXPECT_EQ(1, clients_map->size());
EXPECT_EQ(2, map_mutex.lock_count);
// And finally, try an empty config to disable all datasource clients
- reconfig_cmd.second = Element::createMap();
+ reconfig_cmd.params = Element::createMap();
EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
EXPECT_EQ(0, clients_map->size());
EXPECT_EQ(3, map_mutex.lock_count);
@@ -222,7 +289,8 @@ TEST_F(DataSrcClientsBuilderTest, shutdown) {
TEST_F(DataSrcClientsBuilderTest, badCommand) {
// out-of-range command ID
EXPECT_THROW(builder.handleCommand(Command(NUM_COMMANDS,
- ConstElementPtr())),
+ ConstElementPtr(),
+ FinishedCallback())),
isc::Unexpected);
}
@@ -306,7 +374,8 @@ TEST_F(DataSrcClientsBuilderTest, loadZone) {
const Command loadzone_cmd(LOADZONE, Element::fromJSON(
"{\"class\": \"IN\","
- " \"origin\": \"test1.example\"}"));
+ " \"origin\": \"test1.example\"}"),
+ FinishedCallback());
EXPECT_TRUE(builder.handleCommand(loadzone_cmd));
// loadZone involves two critical sections: one for getting the zone
@@ -367,7 +436,8 @@ TEST_F(DataSrcClientsBuilderTest,
// Now send the command to reload it
const Command loadzone_cmd(LOADZONE, Element::fromJSON(
"{\"class\": \"IN\","
- " \"origin\": \"example.org\"}"));
+ " \"origin\": \"example.org\"}"),
+ FinishedCallback());
EXPECT_TRUE(builder.handleCommand(loadzone_cmd));
// And now it should be present too.
EXPECT_EQ(ZoneFinder::SUCCESS,
@@ -378,7 +448,8 @@ TEST_F(DataSrcClientsBuilderTest,
// An error case: the zone has no configuration. (note .com here)
const Command nozone_cmd(LOADZONE, Element::fromJSON(
"{\"class\": \"IN\","
- " \"origin\": \"example.com\"}"));
+ " \"origin\": \"example.com\"}"),
+ FinishedCallback());
EXPECT_THROW(builder.handleCommand(nozone_cmd),
TestDataSrcClientsBuilder::InternalCommandError);
// The previous zone is not hurt in any way
@@ -401,11 +472,29 @@ TEST_F(DataSrcClientsBuilderTest,
builder.handleCommand(
Command(LOADZONE, Element::fromJSON(
"{\"class\": \"IN\","
- " \"origin\": \"example.org\"}")));
+ " \"origin\": \"example.org\"}"),
+ FinishedCallback()));
// Only one mutex was needed because there was no actual reload.
EXPECT_EQ(orig_lock_count + 1, map_mutex.lock_count);
EXPECT_EQ(orig_unlock_count + 1, map_mutex.unlock_count);
+ // zone doesn't exist in the data source
+ const ConstElementPtr config_nozone(Element::fromJSON("{"
+ "\"IN\": [{"
+ " \"type\": \"sqlite3\","
+ " \"params\": {\"database_file\": \"" + test_db + "\"},"
+ " \"cache-enable\": true,"
+ " \"cache-zones\": [\"nosuchzone.example\"]"
+ "}]}"));
+ clients_map = configureDataSource(config_nozone);
+ EXPECT_THROW(
+ builder.handleCommand(
+ Command(LOADZONE, Element::fromJSON(
+ "{\"class\": \"IN\","
+ " \"origin\": \"nosuchzone.example\"}"),
+ FinishedCallback())),
+ TestDataSrcClientsBuilder::InternalCommandError);
+
// basically impossible case: in-memory cache is completely disabled.
// In this implementation of manager-builder, this should never happen,
// but it catches it like other configuration error and keeps going.
@@ -423,7 +512,8 @@ TEST_F(DataSrcClientsBuilderTest,
EXPECT_THROW(builder.handleCommand(
Command(LOADZONE, Element::fromJSON(
"{\"class\": \"IN\","
- " \"origin\": \"example.org\"}"))),
+ " \"origin\": \"example.org\"}"),
+ FinishedCallback())),
TestDataSrcClientsBuilder::InternalCommandError);
}
@@ -436,13 +526,21 @@ TEST_F(DataSrcClientsBuilderTest, loadBrokenZone) {
// there's an error in the new zone file. reload will be rejected.
const Command loadzone_cmd(LOADZONE, Element::fromJSON(
"{\"class\": \"IN\","
- " \"origin\": \"test1.example\"}"));
+ " \"origin\": \"test1.example\"}"),
+ FinishedCallback());
EXPECT_THROW(builder.handleCommand(loadzone_cmd),
TestDataSrcClientsBuilder::InternalCommandError);
zoneChecks(clients_map, rrclass); // zone shouldn't be replaced
}
TEST_F(DataSrcClientsBuilderTest, loadUnreadableZone) {
+ // If the test is run as the root user, it will fail as insufficient
+ // permissions will not stop the root user from using a file.
+ if (getuid() == 0) {
+ std::cerr << "Skipping test as it's run as the root user" << std::endl;
+ return;
+ }
+
configureZones();
// install the zone file as unreadable
@@ -451,7 +549,8 @@ TEST_F(DataSrcClientsBuilderTest, loadUnreadableZone) {
TEST_DATA_BUILDDIR "/test1.zone.copied"));
const Command loadzone_cmd(LOADZONE, Element::fromJSON(
"{\"class\": \"IN\","
- " \"origin\": \"test1.example\"}"));
+ " \"origin\": \"test1.example\"}"),
+ FinishedCallback());
EXPECT_THROW(builder.handleCommand(loadzone_cmd),
TestDataSrcClientsBuilder::InternalCommandError);
zoneChecks(clients_map, rrclass); // zone shouldn't be replaced
@@ -464,7 +563,8 @@ TEST_F(DataSrcClientsBuilderTest, loadZoneWithoutDataSrc) {
Command(LOADZONE,
Element::fromJSON(
"{\"class\": \"IN\", "
- " \"origin\": \"test1.example\"}"))),
+ " \"origin\": \"test1.example\"}"),
+ FinishedCallback())),
TestDataSrcClientsBuilder::InternalCommandError);
}
@@ -474,7 +574,8 @@ TEST_F(DataSrcClientsBuilderTest, loadZoneInvalidParams) {
if (!isc::util::unittests::runningOnValgrind()) {
// null arg: this causes assertion failure
EXPECT_DEATH_IF_SUPPORTED({
- builder.handleCommand(Command(LOADZONE, ElementPtr()));
+ builder.handleCommand(Command(LOADZONE, ElementPtr(),
+ FinishedCallback()));
}, "");
}
@@ -483,7 +584,8 @@ TEST_F(DataSrcClientsBuilderTest, loadZoneInvalidParams) {
Command(LOADZONE,
Element::fromJSON(
"{\"origin\": \"test1.example\","
- " \"class\": \"no_such_class\"}"))),
+ " \"class\": \"no_such_class\"}"),
+ FinishedCallback())),
InvalidRRClass);
// not a string
@@ -491,7 +593,8 @@ TEST_F(DataSrcClientsBuilderTest, loadZoneInvalidParams) {
Command(LOADZONE,
Element::fromJSON(
"{\"origin\": \"test1.example\","
- " \"class\": 1}"))),
+ " \"class\": 1}"),
+ FinishedCallback())),
isc::data::TypeError);
// class or origin is missing: result in assertion failure
@@ -499,29 +602,158 @@ TEST_F(DataSrcClientsBuilderTest, loadZoneInvalidParams) {
EXPECT_DEATH_IF_SUPPORTED({
builder.handleCommand(Command(LOADZONE,
Element::fromJSON(
- "{\"class\": \"IN\"}")));
+ "{\"class\": \"IN\"}"),
+ FinishedCallback()));
}, "");
}
- // zone doesn't exist in the data source
- EXPECT_THROW(
- builder.handleCommand(
- Command(LOADZONE,
- Element::fromJSON(
- "{\"class\": \"IN\", \"origin\": \"xx\"}"))),
- TestDataSrcClientsBuilder::InternalCommandError);
-
// origin is bogus
EXPECT_THROW(builder.handleCommand(
Command(LOADZONE,
Element::fromJSON(
- "{\"class\": \"IN\", \"origin\": \"...\"}"))),
+ "{\"class\": \"IN\", \"origin\": \"...\"}"),
+ FinishedCallback())),
EmptyLabel);
EXPECT_THROW(builder.handleCommand(
Command(LOADZONE,
Element::fromJSON(
- "{\"origin\": 10, \"class\": 1}"))),
+ "{\"origin\": 10, \"class\": 1}"),
+ FinishedCallback())),
isc::data::TypeError);
}
+// This works only if mapped memory segment is compiled.
+// Note also that this test case may fail as we make b10-auth more aware
+// of shared-memory cache.
+TEST_F(DataSrcClientsBuilderTest,
+#ifdef USE_SHARED_MEMORY
+ loadInNonWritableCache
+#else
+ DISABLED_loadInNonWritableCache
+#endif
+ )
+{
+ const ConstElementPtr config = Element::fromJSON(
+ "{"
+ "\"IN\": [{"
+ " \"type\": \"MasterFiles\","
+ " \"params\": {"
+ " \"test1.example\": \"" +
+ std::string(TEST_DATA_BUILDDIR "/test1.zone.copied") + "\"},"
+ " \"cache-enable\": true,"
+ " \"cache-type\": \"mapped\""
+ "}]}");
+ clients_map = configureDataSource(config);
+
+ EXPECT_THROW(builder.handleCommand(
+ Command(LOADZONE,
+ Element::fromJSON(
+ "{\"origin\": \"test1.example\","
+ " \"class\": \"IN\"}"),
+ FinishedCallback())),
+ TestDataSrcClientsBuilder::InternalCommandError);
+}
+
+// Test the SEGMENT_INFO_UPDATE command. This test is little bit
+// indirect. It doesn't seem possible to fake the client list inside
+// easily. So we create a real image to load and load it. Then we check
+// the segment is used.
+TEST_F(DataSrcClientsBuilderTest,
+#ifdef USE_SHARED_MEMORY
+ segmentInfoUpdate
+#else
+ DISABLED_segmentInfoUpdate
+#endif
+ )
+{
+ // First, prepare the file image to be mapped
+ const ConstElementPtr config = Element::fromJSON(
+ "{"
+ "\"IN\": [{"
+ " \"type\": \"MasterFiles\","
+ " \"params\": {"
+ " \"test1.example\": \""
+ TEST_DATA_BUILDDIR "/test1.zone.copied\"},"
+ " \"cache-enable\": true,"
+ " \"cache-type\": \"mapped\""
+ "}]}");
+ const ConstElementPtr segment_config = Element::fromJSON(
+ "{"
+ " \"mapped-file\": \""
+ TEST_DATA_BUILDDIR "/test1.zone.image" "\"}");
+ clients_map = configureDataSource(config);
+ {
+ const boost::shared_ptr<ConfigurableClientList> list =
+ (*clients_map)[RRClass::IN()];
+ list->resetMemorySegment("MasterFiles",
+ memory::ZoneTableSegment::CREATE,
+ segment_config);
+ const ConfigurableClientList::ZoneWriterPair result =
+ list->getCachedZoneWriter(isc::dns::Name("test1.example"), false,
+ "MasterFiles");
+ ASSERT_EQ(ConfigurableClientList::ZONE_SUCCESS, result.first);
+ result.second->load();
+ result.second->install();
+ // not absolutely necessary, but just in case
+ result.second->cleanup();
+ } // Release this list. That will release the file with the image too,
+ // so we can map it read only from somewhere else.
+
+ // Create a new map, with the same configuration, but without the segments
+ // set
+ clients_map = configureDataSource(config);
+ const boost::shared_ptr<ConfigurableClientList> list =
+ (*clients_map)[RRClass::IN()];
+ EXPECT_EQ(SEGMENT_WAITING, list->getStatus()[0].getSegmentState());
+ // Send the command
+ const ElementPtr command_args = Element::fromJSON(
+ "{"
+ " \"data-source-name\": \"MasterFiles\","
+ " \"data-source-class\": \"IN\""
+ "}");
+ command_args->set("segment-params", segment_config);
+ builder.handleCommand(Command(SEGMENT_INFO_UPDATE, command_args,
+ FinishedCallback()));
+ // The segment is now used.
+ EXPECT_EQ(SEGMENT_INUSE, list->getStatus()[0].getSegmentState());
+
+ // Some invalid inputs (wrong class, different name of data source, etc).
+
+ // Copy the confing and modify
+ const ElementPtr bad_name = Element::fromJSON(command_args->toWire());
+ // Set bad name
+ bad_name->set("data-source-name", Element::create("bad"));
+ EXPECT_DEATH_IF_SUPPORTED({
+ builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_name,
+ FinishedCallback()));
+ }, "");
+
+ // Another copy with wrong class
+ const ElementPtr bad_class = Element::fromJSON(command_args->toWire());
+ // Set bad class
+ bad_class->set("data-source-class", Element::create("bad"));
+ // Aborts (we are out of sync somehow).
+ EXPECT_DEATH_IF_SUPPORTED({
+ builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_class,
+ FinishedCallback()));
+ }, "");
+
+ // Class CH is valid, but not present.
+ bad_class->set("data-source-class", Element::create("CH"));
+ EXPECT_DEATH_IF_SUPPORTED({
+ builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_class,
+ FinishedCallback()));
+ }, "");
+
+ // And break the segment params
+ const ElementPtr bad_params = Element::fromJSON(command_args->toWire());
+ bad_params->set("segment-params",
+ Element::fromJSON("{\"mapped-file\": \"/bad/file\"}"));
+
+ EXPECT_DEATH_IF_SUPPORTED({
+ builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_class,
+ FinishedCallback()));
+ }, "");
+}
+
} // unnamed namespace
diff --git a/src/bin/auth/tests/datasrc_clients_mgr_unittest.cc b/src/bin/auth/tests/datasrc_clients_mgr_unittest.cc
index c37ef11..565deb5 100644
--- a/src/bin/auth/tests/datasrc_clients_mgr_unittest.cc
+++ b/src/bin/auth/tests/datasrc_clients_mgr_unittest.cc
@@ -38,13 +38,13 @@ void
shutdownCheck() {
// Check for common points on shutdown. The manager should have acquired
// the lock, put a SHUTDOWN command to the queue, and should have signaled
- // the builder.
- EXPECT_EQ(1, FakeDataSrcClientsBuilder::queue_mutex->lock_count);
+ // the builder. It should check again for the callback queue, with the lock
+ EXPECT_EQ(2, FakeDataSrcClientsBuilder::queue_mutex->lock_count);
EXPECT_EQ(1, FakeDataSrcClientsBuilder::cond->signal_count);
EXPECT_EQ(1, FakeDataSrcClientsBuilder::command_queue->size());
const Command& cmd = FakeDataSrcClientsBuilder::command_queue->front();
- EXPECT_EQ(SHUTDOWN, cmd.first);
- EXPECT_FALSE(cmd.second); // no argument
+ EXPECT_EQ(SHUTDOWN, cmd.id);
+ EXPECT_FALSE(cmd.params); // no argument
// Finally, the manager should wait for the thread to terminate.
EXPECT_TRUE(FakeDataSrcClientsBuilder::thread_waited);
@@ -130,8 +130,8 @@ TEST(DataSrcClientsMgrTest, reconfigure) {
// touch or refer to the map, so it shouldn't acquire the map lock.
checkSharedMembers(1, 1, 0, 0, 1, 1);
const Command& cmd1 = FakeDataSrcClientsBuilder::command_queue->front();
- EXPECT_EQ(RECONFIGURE, cmd1.first);
- EXPECT_EQ(reconfigure_arg, cmd1.second);
+ EXPECT_EQ(RECONFIGURE, cmd1.id);
+ EXPECT_EQ(reconfigure_arg, cmd1.params);
// Non-null, but semantically invalid argument. The manager doesn't do
// this check, so it should result in the same effect.
@@ -140,8 +140,8 @@ TEST(DataSrcClientsMgrTest, reconfigure) {
mgr.reconfigure(reconfigure_arg);
checkSharedMembers(2, 2, 0, 0, 2, 1);
const Command& cmd2 = FakeDataSrcClientsBuilder::command_queue->front();
- EXPECT_EQ(RECONFIGURE, cmd2.first);
- EXPECT_EQ(reconfigure_arg, cmd2.second);
+ EXPECT_EQ(RECONFIGURE, cmd2.id);
+ EXPECT_EQ(reconfigure_arg, cmd2.params);
// Passing NULL argument is immediately rejected
EXPECT_THROW(mgr.reconfigure(ConstElementPtr()), isc::InvalidParameter);
@@ -156,6 +156,7 @@ TEST(DataSrcClientsMgrTest, holder) {
TestDataSrcClientsMgr::Holder holder(mgr);
EXPECT_FALSE(holder.findClientList(RRClass::IN()));
EXPECT_FALSE(holder.findClientList(RRClass::CH()));
+ EXPECT_TRUE(holder.getClasses().empty());
// map should be protected here
EXPECT_EQ(1, FakeDataSrcClientsBuilder::map_mutex->lock_count);
EXPECT_EQ(0, FakeDataSrcClientsBuilder::map_mutex->unlock_count);
@@ -174,6 +175,7 @@ TEST(DataSrcClientsMgrTest, holder) {
TestDataSrcClientsMgr::Holder holder(mgr);
EXPECT_TRUE(holder.findClientList(RRClass::IN()));
EXPECT_TRUE(holder.findClientList(RRClass::CH()));
+ EXPECT_EQ(2, holder.getClasses().size());
}
// We need to clear command queue by hand
FakeDataSrcClientsBuilder::command_queue->clear();
@@ -188,6 +190,7 @@ TEST(DataSrcClientsMgrTest, holder) {
TestDataSrcClientsMgr::Holder holder(mgr);
EXPECT_TRUE(holder.findClientList(RRClass::IN()));
EXPECT_FALSE(holder.findClientList(RRClass::CH()));
+ EXPECT_EQ(RRClass::IN(), holder.getClasses()[0]);
}
// Duplicate lock acquisition is prohibited (only test mgr can detect
@@ -245,10 +248,76 @@ TEST(DataSrcClientsMgrTest, reload) {
EXPECT_EQ(3, FakeDataSrcClientsBuilder::command_queue->size());
}
+TEST(DataSrcClientsMgrTest, segmentUpdate) {
+ TestDataSrcClientsMgr mgr;
+ EXPECT_TRUE(FakeDataSrcClientsBuilder::started);
+ EXPECT_TRUE(FakeDataSrcClientsBuilder::command_queue->empty());
+
+ isc::data::ElementPtr args =
+ isc::data::Element::fromJSON("{\"data-source-class\": \"IN\","
+ " \"data-source-name\": \"sqlite3\","
+ " \"segment-params\": {}}");
+ mgr.segmentInfoUpdate(args);
+ EXPECT_EQ(1, FakeDataSrcClientsBuilder::command_queue->size());
+ // Some invalid inputs
+ EXPECT_THROW(mgr.segmentInfoUpdate(isc::data::Element::fromJSON(
+ "{\"data-source-class\": \"IN\","
+ " \"data-source-name\": \"sqlite3\"}")), CommandError);
+ EXPECT_THROW(mgr.segmentInfoUpdate(isc::data::Element::fromJSON(
+ "{\"data-source-name\": \"sqlite3\","
+ " \"segment-params\": {}}")), CommandError);
+ EXPECT_THROW(mgr.segmentInfoUpdate(isc::data::Element::fromJSON(
+ "{\"data-source-class\": \"IN\","
+ " \"segment-params\": {}}")), CommandError);
+ EXPECT_EQ(1, FakeDataSrcClientsBuilder::command_queue->size());
+}
+
+void
+callback(bool* called, int *tag_target, int tag_value) {
+ *called = true;
+ *tag_target = tag_value;
+}
+
+// Test we can wake up the main thread by writing to the file descriptor and
+// that the callbacks are executed and removed when woken up.
+TEST(DataSrcClientsMgrTest, wakeup) {
+ bool called = false;
+ int tag;
+ {
+ TestDataSrcClientsMgr mgr;
+ // There's some real file descriptor (or something that looks so)
+ ASSERT_GT(FakeDataSrcClientsBuilder::wakeup_fd, 0);
+ // Push a callback in and wake the manager
+ FakeDataSrcClientsBuilder::callback_queue->
+ push_back(boost::bind(callback, &called, &tag, 1));
+ EXPECT_EQ(1, write(FakeDataSrcClientsBuilder::wakeup_fd, "w", 1));
+ mgr.run_one();
+ EXPECT_TRUE(called);
+ EXPECT_EQ(1, tag);
+ EXPECT_TRUE(FakeDataSrcClientsBuilder::callback_queue->empty());
+
+ called = false;
+ // If we wake up and don't push anything, it doesn't break.
+ EXPECT_EQ(1, write(FakeDataSrcClientsBuilder::wakeup_fd, "w", 1));
+ mgr.run_one();
+ EXPECT_FALSE(called);
+
+ // When we terminate, it should process whatever is left
+ // of the callbacks. So push and terminate (and don't directly
+ // wake).
+ FakeDataSrcClientsBuilder::callback_queue->
+ push_back(boost::bind(callback, &called, &tag, 2));
+ }
+ EXPECT_TRUE(called);
+ EXPECT_EQ(2, tag);
+ EXPECT_TRUE(FakeDataSrcClientsBuilder::callback_queue->empty());
+}
+
TEST(DataSrcClientsMgrTest, realThread) {
// Using the non-test definition with a real thread. Just checking
// no disruption happens.
- DataSrcClientsMgr mgr;
+ isc::asiolink::IOService service;
+ DataSrcClientsMgr mgr(service);
}
} // unnamed namespace
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 9bf1358..f374a87 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -80,6 +80,12 @@ public:
return (FindResult());
}
}
+ virtual ConstZoneTableAccessorPtr
+ getZoneTableAccessor(const std::string&, bool) const {
+ isc_throw(isc::NotImplemented,
+ "getZoneTableAccessor not implemented for SingletonList");
+ }
+
private:
DataSourceClient& client_;
};
@@ -133,6 +139,12 @@ const char* const unsigned_delegation_nsec3_txt =
"q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 "
"aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG\n";
+// Name of an "empty" zone: used to simulate the case of
+// configured-but-available zone (due to load errors, etc).
+// Each tested data source client is expected to have this zone (SQLite3
+// currently doesn't have this concept so it's skipped)
+const char* const EMPTY_ZONE_NAME = "empty.example.org";
+
// A helper function that generates a textual representation of RRSIG RDATA
// for the given covered type. The resulting RRSIG may not necessarily make
// sense in terms of the DNSSEC protocol, but for our testing purposes it's
@@ -799,11 +811,14 @@ createDataSrcClientList(DataSrcType type, DataSourceClient& client) {
return (boost::shared_ptr<ClientList>(new SingletonList(client)));
case INMEMORY:
list.reset(new ConfigurableClientList(RRClass::IN()));
+ // Configure one normal zone and one "empty" zone.
list->configure(isc::data::Element::fromJSON(
"[{\"type\": \"MasterFiles\","
" \"cache-enable\": true, "
" \"params\": {\"example.com\": \"" +
- string(TEST_OWN_DATA_BUILDDIR "/example.zone") +
+ string(TEST_OWN_DATA_BUILDDIR "/example.zone\",") +
+ + "\"" + EMPTY_ZONE_NAME + "\": \"" +
+ string(TEST_OWN_DATA_BUILDDIR "/nosuchfile.zone") +
"\"}}]"), true);
return (list);
case SQLITE3:
@@ -834,39 +849,38 @@ createDataSrcClientList(DataSrcType type, DataSourceClient& client) {
class MockClient : public DataSourceClient {
public:
virtual FindResult findZone(const isc::dns::Name& origin) const {
- const Name r_origin(origin.reverse());
- std::map<Name, ZoneFinderPtr>::const_iterator it =
- zone_finders_.lower_bound(r_origin);
-
- if (it != zone_finders_.end()) {
- const NameComparisonResult result =
- origin.compare((it->first).reverse());
- if (result.getRelation() == NameComparisonResult::EQUAL) {
- return (FindResult(result::SUCCESS, it->second));
- } else if (result.getRelation() == NameComparisonResult::SUBDOMAIN) {
- return (FindResult(result::PARTIALMATCH, it->second));
- }
- }
+ // Identify the next (strictly) larger name than the given 'origin' in
+ // the map. Its predecessor (if any) is the longest matching name
+ // if it's either an exact match or a super domain; otherwise there's
+ // no match in the map. See also datasrc/tests/mock_client.cc.
- // If it is at the beginning of the map, then the name was not
- // found (we have already handled the element the iterator
- // points to).
- if (it == zone_finders_.begin()) {
+ // Eliminate the case of empty map to simply the rest of the code
+ if (zone_finders_.empty()) {
return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
}
- // Check if the previous element is a partial match.
- --it;
- const NameComparisonResult result =
- origin.compare((it->first).reverse());
- if (result.getRelation() == NameComparisonResult::SUBDOMAIN) {
- return (FindResult(result::PARTIALMATCH, it->second));
+ std::map<Name, ZoneFinderPtr>::const_iterator it =
+ zone_finders_.upper_bound(origin);
+ if (it == zone_finders_.begin()) { // no predecessor
+ return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
}
- return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+ --it; // get the predecessor
+ const result::ResultFlags flags =
+ it->second ? result::FLAGS_DEFAULT : result::ZONE_EMPTY;
+ const NameComparisonResult compar(it->first.compare(origin));
+ switch (compar.getRelation()) {
+ case NameComparisonResult::EQUAL:
+ return (FindResult(result::SUCCESS, it->second, flags));
+ case NameComparisonResult::SUPERDOMAIN:
+ return (FindResult(result::PARTIALMATCH, it->second, flags));
+ default:
+ return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+ }
}
- virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name&, bool, bool) const {
+ virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name&, bool, bool) const
+ {
isc_throw(isc::NotImplemented,
"Updater isn't supported in the MockClient");
}
@@ -878,18 +892,21 @@ public:
}
result::Result addZone(ZoneFinderPtr finder) {
- // Use the reverse of the name as the key, so we can quickly
- // find partial matches in the map.
- zone_finders_[finder->getOrigin().reverse()] = finder;
+ zone_finders_[finder->getOrigin()] = finder;
+ return (result::SUCCESS);
+ }
+
+ // "configure" a zone with no data. This will cause the ZONE_EMPTY flag
+ // on in finZone().
+ result::Result addEmptyZone(const Name& zone_name) {
+ zone_finders_[zone_name] = ZoneFinderPtr();
return (result::SUCCESS);
}
private:
// Note that because we no longer have the old RBTree, and the new
// in-memory DomainTree is not useful as it returns const nodes, we
- // use a std::map instead. In this map, the key is a name stored in
- // reverse order of labels to aid in finding partial matches
- // quickly.
+ // use a std::map instead.
std::map<Name, ZoneFinderPtr> zone_finders_;
};
@@ -916,9 +933,10 @@ protected:
response.setRcode(Rcode::NOERROR());
response.setOpcode(Opcode::QUERY());
- // create and add a matching zone.
+ // create and add a matching zone. One is a "broken, empty" zone.
mock_finder = new MockZoneFinder();
mock_client.addZone(ZoneFinderPtr(mock_finder));
+ mock_client.addEmptyZone(Name(EMPTY_ZONE_NAME));
}
virtual void SetUp() {
@@ -949,6 +967,12 @@ protected:
setNSEC3HashCreator(NULL);
}
+ bool isEmptyZoneSupported() const {
+ // Not all data sources support the concept of empty zones.
+ // Specifically for this test, SQLite3-based data source doesn't.
+ return (GetParam() != SQLITE3);
+ }
+
void enableNSEC3(const vector<string>& rrsets_to_add) {
boost::shared_ptr<ConfigurableClientList> new_list;
switch (GetParam()) {
@@ -1144,11 +1168,29 @@ TEST_P(QueryTest, noZone) {
// REFUSED.
MockClient empty_mock_client;
SingletonList empty_list(empty_mock_client);
- EXPECT_NO_THROW(query.process(empty_list, qname, qtype,
- response));
+ EXPECT_NO_THROW(query.process(empty_list, qname, qtype, response));
EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
}
+TEST_P(QueryTest, emptyZone) {
+ // Query for an "empty (broken)" zone. If the concept is supported by
+ // the underlying data source, the result should be SERVFAIL; otherwise
+ // it would be handled as a nonexistent zone, resulting in REFUSED.
+ const Rcode expected_rcode =
+ isEmptyZoneSupported() ? Rcode::SERVFAIL() : Rcode::REFUSED();
+
+ query.process(*list_, Name(EMPTY_ZONE_NAME), qtype, response);
+ responseCheck(response, expected_rcode, 0, 0, 0, 0, NULL, NULL, NULL);
+
+ // Same for the partial match case
+ response.clear(isc::dns::Message::RENDER);
+ response.setRcode(Rcode::NOERROR());
+ response.setOpcode(Opcode::QUERY());
+ query.process(*list_, Name(string("www.") + EMPTY_ZONE_NAME), qtype,
+ response);
+ responseCheck(response, expected_rcode, 0, 0, 0, 0, NULL, NULL, NULL);
+}
+
TEST_P(QueryTest, exactMatch) {
EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
@@ -1400,7 +1442,6 @@ TEST_F(QueryTestForMockOnly, badSecureDelegation) {
qtype, response));
}
-
TEST_P(QueryTest, nxdomain) {
EXPECT_NO_THROW(query.process(*list_,
Name("nxdomain.example.com"), qtype,
diff --git a/src/bin/auth/tests/statistics_unittest.cc.pre b/src/bin/auth/tests/statistics_unittest.cc.pre
index cf6f29a..654bcd9 100644
--- a/src/bin/auth/tests/statistics_unittest.cc.pre
+++ b/src/bin/auth/tests/statistics_unittest.cc.pre
@@ -361,6 +361,64 @@ TEST_F(CountersTest, incrementTSIG) {
}
}
+TEST_F(CountersTest, incrementRD) {
+ Message response(Message::RENDER);
+ MessageAttributes msgattrs;
+ std::map<std::string, int> expect;
+
+ // Test these patterns:
+ // OpCode Recursion Desired
+ // ---------------------------
+ // 0 (Query) false
+ // 0 (Query) true
+ // 2 (Status) false
+ // 2 (Status) true
+ // Make sure the counter will be incremented only for the requests with
+ // OpCode=Query and Recursion Desired (RD) bit=1.
+ int count_opcode_query = 0;
+ int count_opcode_status = 0;
+ for (int i = 0; i < 4; ++i) {
+ const bool is_recursion_desired = i & 1;
+ const uint8_t opcode_code = i & 0x2;
+ const Opcode opcode(opcode_code);
+ buildSkeletonMessage(msgattrs);
+ msgattrs.setRequestRD(is_recursion_desired);
+ msgattrs.setRequestOpCode(opcode);
+
+ response.setRcode(Rcode::REFUSED());
+ response.addQuestion(Question(Name("example.com"),
+ RRClass::IN(), RRType::AAAA()));
+ response.setHeaderFlag(Message::HEADERFLAG_QR);
+
+ counters.inc(msgattrs, response, true);
+
+ if (opcode == Opcode::QUERY()) {
+ ++count_opcode_query;
+ } else {
+ ++count_opcode_status;
+ }
+
+ expect.clear();
+ expect["opcode.query"] = count_opcode_query;
+ expect["opcode.status"] = count_opcode_status;
+ expect["request.v4"] = i+1;
+ expect["request.udp"] = i+1;
+ expect["request.edns0"] = i+1;
+ expect["request.dnssec_ok"] = i+1;
+ expect["responses"] = i+1;
+ // qryrecursion will (only) be incremented if i == 1: OpCode=Query and
+ // RD bit=1
+ expect["qryrecursion"] = (i == 0) ? 0 : 1;
+ expect["rcode.refused"] = i+1;
+ // these counters are for queries; the value will be equal to the
+ // number of requests with OpCode=Query
+ expect["qrynoauthans"] = count_opcode_query;
+ expect["authqryrej"] = count_opcode_query;
+ checkStatisticsCounters(counters.get()->get("zones")->get("_SERVER_"),
+ expect);
+ }
+}
+
TEST_F(CountersTest, incrementOpcode) {
Message response(Message::RENDER);
MessageAttributes msgattrs;
diff --git a/src/bin/auth/tests/test_datasrc_clients_mgr.cc b/src/bin/auth/tests/test_datasrc_clients_mgr.cc
index 82937c0..80bc97c 100644
--- a/src/bin/auth/tests/test_datasrc_clients_mgr.cc
+++ b/src/bin/auth/tests/test_datasrc_clients_mgr.cc
@@ -26,7 +26,9 @@ namespace datasrc_clientmgr_internal {
// Define static DataSrcClientsBuilder member variables.
bool FakeDataSrcClientsBuilder::started = false;
std::list<Command>* FakeDataSrcClientsBuilder::command_queue = NULL;
+std::list<FinishedCallback>* FakeDataSrcClientsBuilder::callback_queue = NULL;
std::list<Command> FakeDataSrcClientsBuilder::command_queue_copy;
+std::list<FinishedCallback> FakeDataSrcClientsBuilder::callback_queue_copy;
TestCondVar* FakeDataSrcClientsBuilder::cond = NULL;
TestCondVar FakeDataSrcClientsBuilder::cond_copy;
TestMutex* FakeDataSrcClientsBuilder::queue_mutex = NULL;
@@ -38,6 +40,7 @@ bool FakeDataSrcClientsBuilder::thread_waited = false;
FakeDataSrcClientsBuilder::ExceptionFromWait
FakeDataSrcClientsBuilder::thread_throw_on_wait =
FakeDataSrcClientsBuilder::NOTHROW;
+int FakeDataSrcClientsBuilder::wakeup_fd = -1;
template<>
void
@@ -58,7 +61,7 @@ TestDataSrcClientsBuilder::doNoop() {
template<>
void
-TestDataSrcClientsMgr::cleanup() {
+TestDataSrcClientsMgrBase::cleanup() {
using namespace datasrc_clientmgr_internal;
// Make copy of some of the manager's member variables and reset the
// corresponding pointers. The currently pointed objects are in the
@@ -73,17 +76,21 @@ TestDataSrcClientsMgr::cleanup() {
FakeDataSrcClientsBuilder::cond_copy = cond_;
FakeDataSrcClientsBuilder::cond =
&FakeDataSrcClientsBuilder::cond_copy;
+ FakeDataSrcClientsBuilder::callback_queue_copy =
+ *FakeDataSrcClientsBuilder::callback_queue;
+ FakeDataSrcClientsBuilder::callback_queue =
+ &FakeDataSrcClientsBuilder::callback_queue_copy;
}
template<>
void
-TestDataSrcClientsMgr::reconfigureHook() {
+TestDataSrcClientsMgrBase::reconfigureHook() {
using namespace datasrc_clientmgr_internal;
// Simply replace the local map, ignoring bogus config value.
- assert(command_queue_.front().first == RECONFIGURE);
+ assert(command_queue_.front().id == RECONFIGURE);
try {
- clients_map_ = configureDataSource(command_queue_.front().second);
+ clients_map_ = configureDataSource(command_queue_.front().params);
} catch (...) {}
}
diff --git a/src/bin/auth/tests/test_datasrc_clients_mgr.h b/src/bin/auth/tests/test_datasrc_clients_mgr.h
index 9b1a367..34097da 100644
--- a/src/bin/auth/tests/test_datasrc_clients_mgr.h
+++ b/src/bin/auth/tests/test_datasrc_clients_mgr.h
@@ -20,6 +20,8 @@
#include <auth/datasrc_clients_mgr.h>
#include <datasrc/datasrc_config.h>
+#include <asiolink/io_service.h>
+
#include <boost/function.hpp>
#include <list>
@@ -131,15 +133,18 @@ public:
// true iff a builder has started.
static bool started;
- // These three correspond to the resource shared with the manager.
+ // These five correspond to the resource shared with the manager.
// xxx_copy will be set in the manager's destructor to record the
// final state of the manager.
static std::list<Command>* command_queue;
+ static std::list<FinishedCallback>* callback_queue;
static TestCondVar* cond;
static TestMutex* queue_mutex;
+ static int wakeup_fd;
static isc::datasrc::ClientListMapPtr* clients_map;
static TestMutex* map_mutex;
static std::list<Command> command_queue_copy;
+ static std::list<FinishedCallback> callback_queue_copy;
static TestCondVar cond_copy;
static TestMutex queue_mutex_copy;
@@ -153,15 +158,18 @@ public:
FakeDataSrcClientsBuilder(
std::list<Command>* command_queue,
+ std::list<FinishedCallback>* callback_queue,
TestCondVar* cond,
TestMutex* queue_mutex,
isc::datasrc::ClientListMapPtr* clients_map,
- TestMutex* map_mutex)
+ TestMutex* map_mutex, int wakeup_fd)
{
FakeDataSrcClientsBuilder::started = false;
FakeDataSrcClientsBuilder::command_queue = command_queue;
+ FakeDataSrcClientsBuilder::callback_queue = callback_queue;
FakeDataSrcClientsBuilder::cond = cond;
FakeDataSrcClientsBuilder::queue_mutex = queue_mutex;
+ FakeDataSrcClientsBuilder::wakeup_fd = wakeup_fd;
FakeDataSrcClientsBuilder::clients_map = clients_map;
FakeDataSrcClientsBuilder::map_mutex = map_mutex;
FakeDataSrcClientsBuilder::thread_waited = false;
@@ -201,18 +209,30 @@ typedef DataSrcClientsMgrBase<
datasrc_clientmgr_internal::TestThread,
datasrc_clientmgr_internal::FakeDataSrcClientsBuilder,
datasrc_clientmgr_internal::TestMutex,
- datasrc_clientmgr_internal::TestCondVar> TestDataSrcClientsMgr;
+ datasrc_clientmgr_internal::TestCondVar> TestDataSrcClientsMgrBase;
// A specialization of manager's "cleanup" called at the end of the
// destructor. We use this to record the final values of some of the class
// member variables.
template<>
void
-TestDataSrcClientsMgr::cleanup();
+TestDataSrcClientsMgrBase::cleanup();
template<>
void
-TestDataSrcClientsMgr::reconfigureHook();
+TestDataSrcClientsMgrBase::reconfigureHook();
+
+// A (hackish) trick how to not require the IOService to be passed from the
+// tests. We can't create the io service as a member, because it would
+// get initialized too late.
+class TestDataSrcClientsMgr :
+ public asiolink::IOService,
+ public TestDataSrcClientsMgrBase {
+public:
+ TestDataSrcClientsMgr() :
+ TestDataSrcClientsMgrBase(*static_cast<asiolink::IOService*>(this))
+ {}
+};
} // namespace auth
} // namespace isc
diff --git a/src/bin/bind10/TODO b/src/bin/bind10/TODO
index 6f50dbd..82abdb1 100644
--- a/src/bin/bind10/TODO
+++ b/src/bin/bind10/TODO
@@ -6,7 +6,7 @@
- Stop a component
- Force-stop a component
- Mechanism to wait for child to start before continuing
-- Use .spec file to define comands
+- Use .spec file to define commands
- Rename "c-channel" stuff to msgq for clarity
- Reply to shutdown message?
- Some sort of group creation so termination signals can be sent to
diff --git a/src/bin/bind10/init.py.in b/src/bin/bind10/init.py.in
index f47de31..3bb7ea7 100755
--- a/src/bin/bind10/init.py.in
+++ b/src/bin/bind10/init.py.in
@@ -38,6 +38,7 @@ __main__.
import sys; sys.path.append ('@@PYTHONPATH@@')
import os
+from isc.util.address_formatter import AddressFormatter
# If B10_FROM_SOURCE is set in the environment, we use data files
# from a directory relative to that, otherwise we use the ones
@@ -88,7 +89,8 @@ logger = isc.log.Logger("init")
DBG_PROCESS = logger.DBGLVL_TRACE_BASIC
DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL
-# Messages sent over the unix domain socket to indicate if it is followed by a real socket
+# Messages sent over the unix domain socket to indicate if it is followed by a
+# real socket
CREATOR_SOCKET_OK = b"1\n"
CREATOR_SOCKET_UNAVAILABLE = b"0\n"
@@ -151,7 +153,7 @@ class ProcessInfo:
"""Function used before running a program that needs to run as a
different user."""
# First, put us into a separate process group so we don't get
- # SIGINT signals on Ctrl-C (b10-init will shut everthing down by
+ # SIGINT signals on Ctrl-C (b10-init will shut everything down by
# other means).
os.setpgrp()
@@ -199,7 +201,8 @@ class Init:
verbose=False, nokill=False, setuid=None, setgid=None,
username=None, cmdctl_port=None, wait_time=10):
"""
- Initialize the Init of BIND. This is a singleton (only one can run).
+ Initialize the Init of BIND. This is a singleton (only one can
+ run).
The msgq_socket_file specifies the UNIX domain socket file that the
msgq process listens on. If verbose is True, then b10-init reports
@@ -222,12 +225,13 @@ class Init:
self.component_config = {}
# Some time in future, it may happen that a single component has
# multple processes (like a pipeline-like component). If so happens,
- # name "components" may be inapropriate. But as the code isn't probably
- # completely ready for it, we leave it at components for now. We also
- # want to support multiple instances of a single component. If it turns
- # out that we'll have a single component with multiple same processes
- # or if we start multiple components with the same configuration (we do
- # this now, but it might change) is an open question.
+ # name "components" may be inappropriate. But as the code isn't
+ # probably completely ready for it, we leave it at components for
+ # now. We also want to support multiple instances of a single
+ # component. If it turns out that we'll have a single component with
+ # multiple same processes or if we start multiple components with the
+ # same configuration (we do this now, but it might change) is an open
+ # question.
self.components = {}
# Simply list of components that died and need to wait for a
# restart. Components manage their own restart schedule now
@@ -350,7 +354,8 @@ class Init:
def command_handler(self, command, args):
logger.debug(DBG_COMMANDS, BIND10_RECEIVED_COMMAND, command)
- answer = isc.config.ccsession.create_answer(1, "command not implemented")
+ answer = isc.config.ccsession.create_answer(1,
+ "command not implemented")
if type(command) != str:
answer = isc.config.ccsession.create_answer(1, "bad command")
else:
@@ -428,7 +433,7 @@ class Init:
port)
else:
logger.info(BIND10_STARTING_PROCESS_PORT_ADDRESS,
- self.curproc, address, port)
+ self.curproc, AddressFormatter((address, port)))
def log_started(self, pid = None):
"""
@@ -439,7 +444,8 @@ class Init:
if pid is None:
logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS, self.curproc)
else:
- logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS_PID, self.curproc, pid)
+ logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS_PID, self.curproc,
+ pid)
def process_running(self, msg, who):
"""
@@ -498,7 +504,8 @@ class Init:
if msgq_proc.process:
msgq_proc.process.kill()
logger.error(BIND10_CONNECTING_TO_CC_FAIL)
- raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
+ raise CChannelConnectError("Unable to connect to c-channel " +
+ "after 5 seconds")
# try to connect, and if we can't wait a short while
try:
@@ -506,13 +513,43 @@ class Init:
except isc.cc.session.SessionError:
time.sleep(0.1)
- # Subscribe to the message queue. The only messages we expect to receive
- # on this channel are once relating to process startup.
+ # Subscribe to the message queue. The only messages we expect to
+ # receive on this channel are once relating to process startup.
if self.cc_session is not None:
self.cc_session.group_subscribe("Init")
return msgq_proc
+ def wait_msgq(self):
+ """
+ Wait for the message queue to fully start. It does so only after
+ the config manager connects to it. We know it is ready when it
+ starts answering commands.
+
+ We don't add a specific command for it here, an error response is
+ as good as positive one to know it is alive.
+ """
+ # We do 10 times shorter sleep here (since the start should be fast
+ # now), so we have 10 times more attempts.
+ time_remaining = self.wait_time * 10
+ retry = True
+ while time_remaining > 0 and retry:
+ try:
+ self.ccs.rpc_call('AreYouThere?', 'Msgq')
+ # We don't expect this to succeed. If it does, it's programmer
+ # error
+ raise Exception("Non-existing RPC call succeeded")
+ except isc.config.RPCRecipientMissing:
+ retry = True # Not there yet
+ time.sleep(0.1)
+ time_remaining -= 1
+ except isc.config.RPCError:
+ retry = False # It doesn't like the RPC, so it's alive now
+
+ if retry: # Still not started
+ raise ProcessStartError("Msgq didn't complete the second stage " +
+ "of startup")
+
def start_cfgmgr(self):
"""
Starts the configuration manager process
@@ -535,14 +572,16 @@ class Init:
# time to wait can be set on the command line.
time_remaining = self.wait_time
msg, env = self.cc_session.group_recvmsg()
- while time_remaining > 0 and not self.process_running(msg, "ConfigManager"):
+ while time_remaining > 0 and not self.process_running(msg,
+ "ConfigManager"):
logger.debug(DBG_PROCESS, BIND10_WAIT_CFGMGR)
time.sleep(1)
time_remaining = time_remaining - 1
msg, env = self.cc_session.group_recvmsg()
if not self.process_running(msg, "ConfigManager"):
- raise ProcessStartError("Configuration manager process has not started")
+ raise ProcessStartError("Configuration manager process has not " +
+ "started")
return bind_cfgd
@@ -566,7 +605,8 @@ class Init:
# A couple of utility methods for starting processes...
- def start_process(self, name, args, c_channel_env, port=None, address=None):
+ def start_process(self, name, args, c_channel_env, port=None,
+ address=None):
"""
Given a set of command arguments, start the process and output
appropriate log messages. If the start is successful, the process
@@ -611,9 +651,9 @@ class Init:
# The next few methods start up the rest of the BIND-10 processes.
# Although many of these methods are little more than a call to
- # start_simple, they are retained (a) for testing reasons and (b) as a place
- # where modifications can be made if the process start-up sequence changes
- # for a given process.
+ # start_simple, they are retained (a) for testing reasons and (b) as a
+ # place where modifications can be made if the process start-up sequence
+ # changes for a given process.
def start_auth(self):
"""
@@ -665,6 +705,10 @@ class Init:
# inside the configurator.
self.start_ccsession(self.c_channel_env)
+ # Make sure msgq is fully started before proceeding to the rest
+ # of the components.
+ self.wait_msgq()
+
# Extract the parameters associated with Init. This can only be
# done after the CC Session is started. Note that the logging
# configuration may override the "-v" switch set on the command line.
@@ -688,7 +732,8 @@ class Init:
try:
self.cc_session = isc.cc.Session(self.msgq_socket_file)
logger.fatal(BIND10_MSGQ_ALREADY_RUNNING)
- return "b10-msgq already running, or socket file not cleaned , cannot start"
+ return "b10-msgq already running, or socket file not cleaned , " +\
+ "cannot start"
except isc.cc.session.SessionError:
# this is the case we want, where the msgq is not running
pass
@@ -947,8 +992,8 @@ class Init:
def set_creator(self, creator):
"""
- Registeres a socket creator into the b10-init. The socket creator is not
- used directly, but through a cache. The cache is created in this
+ Registeres a socket creator into the b10-init. The socket creator is
+ not used directly, but through a cache. The cache is created in this
method.
If called more than once, it raises a ValueError.
@@ -1120,9 +1165,12 @@ def parse_args(args=sys.argv[1:], Parser=OptionParser):
parser = Parser(version=VERSION)
parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
type="string", default=None,
- help="UNIX domain socket file the b10-msgq daemon will use")
+ help="UNIX domain socket file the b10-msgq daemon " +
+ "will use")
parser.add_option("-i", "--no-kill", action="store_true", dest="nokill",
- default=False, help="do not send SIGTERM and SIGKILL signals to modules during shutdown")
+ default=False,
+ help="do not send SIGTERM and SIGKILL signals to " +
+ "modules during shutdown")
parser.add_option("-u", "--user", dest="user", type="string", default=None,
help="Change user after startup (must run as root)")
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
@@ -1146,7 +1194,9 @@ def parse_args(args=sys.argv[1:], Parser=OptionParser):
default=None,
help="file to dump the PID of the BIND 10 process")
parser.add_option("-w", "--wait", dest="wait_time", type="int",
- default=10, help="Time (in seconds) to wait for config manager to start up")
+ default=10,
+ help="Time (in seconds) to wait for config manager to "
+ "start up")
(options, args) = parser.parse_args(args)
diff --git a/src/bin/bind10/init_messages.mes b/src/bin/bind10/init_messages.mes
index 9cdb7ef..267790d 100644
--- a/src/bin/bind10/init_messages.mes
+++ b/src/bin/bind10/init_messages.mes
@@ -34,7 +34,7 @@ The named component is about to be started by the b10-init process.
% BIND10_COMPONENT_START_EXCEPTION component %1 failed to start: %2
An exception (mentioned in the message) happened during the startup of the
-named component. The componet is not considered started and further actions
+named component. The component is not considered started and further actions
will be taken about it.
% BIND10_COMPONENT_STOP component %1 is being stopped
@@ -285,9 +285,9 @@ The b10-init module is starting the given process.
The b10-init module is starting the given process, which will listen on the
given port number.
-% BIND10_STARTING_PROCESS_PORT_ADDRESS starting process %1 (to listen on %2#%3)
+% BIND10_STARTING_PROCESS_PORT_ADDRESS starting process %1 (to listen on %2)
The b10-init module is starting the given process, which will listen on the
-given address and port number (written as <address>#<port>).
+given address and port number (written as <address>:<port>).
% BIND10_STARTUP_COMPLETE BIND 10 started
All modules have been successfully started, and BIND 10 is now running.
diff --git a/src/bin/bind10/run_bind10.sh.in b/src/bin/bind10/run_bind10.sh.in
index bb00e23..09c9708 100755
--- a/src/bin/bind10/run_bind10.sh.in
+++ b/src/bin/bind10/run_bind10.sh.in
@@ -20,7 +20,7 @@ export PYTHON_EXEC
BIND10_PATH=@abs_top_builddir@/src/bin/bind10
-PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:$PATH
+PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:@abs_top_builddir@/src/bin/memmgr:$PATH
export PATH
PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python/isc/cc:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:@abs_top_builddir@/src/lib/python/isc/datasrc/.libs
@@ -30,7 +30,7 @@ export PYTHONPATH
# required by loadable python modules.
SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
if test $SET_ENV_LIBRARY_PATH = yes; then
- @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/threads/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
export @ENV_LIBRARY_PATH@
fi
diff --git a/src/bin/bind10/tests/Makefile.am b/src/bin/bind10/tests/Makefile.am
index 6d59dbd..d7e51d5 100644
--- a/src/bin/bind10/tests/Makefile.am
+++ b/src/bin/bind10/tests/Makefile.am
@@ -8,7 +8,7 @@ noinst_SCRIPTS = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/bind10/tests/init_test.py.in b/src/bin/bind10/tests/init_test.py.in
index 9a591ef..913e642 100644
--- a/src/bin/bind10/tests/init_test.py.in
+++ b/src/bin/bind10/tests/init_test.py.in
@@ -16,7 +16,8 @@
# Most of the time, we omit the "init" for brevity. Sometimes,
# we want to be explicit about what we do, like when hijacking a library
# call used by the b10-init.
-from init import Init, ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
+from init import Init, ProcessInfo, parse_args, dump_pid, unlink_pid_file, \
+ _BASETIME
import init
# XXX: environment tests are currently disabled, due to the preprocessor
@@ -941,6 +942,7 @@ class TestStartStopProcessesInit(unittest.TestCase):
init.start_ccsession = lambda _: start_ccsession()
# We need to return the original _read_bind10_config
init._read_bind10_config = lambda: Init._read_bind10_config(init)
+ init.wait_msgq = lambda: None
init.start_all_components()
self.check_started(init, True, start_auth, start_resolver)
self.check_environment_unchanged()
@@ -967,6 +969,7 @@ class TestStartStopProcessesInit(unittest.TestCase):
init = MockInit()
self.check_preconditions(init)
+ init.wait_msgq = lambda: None
init.start_all_components()
init.runnable = True
init.config_handler(self.construct_config(False, False))
@@ -1028,6 +1031,7 @@ class TestStartStopProcessesInit(unittest.TestCase):
init = MockInit()
self.check_preconditions(init)
+ init.wait_msgq = lambda: None
init.start_all_components()
init.runnable = True
@@ -1066,6 +1070,7 @@ class TestStartStopProcessesInit(unittest.TestCase):
init = MockInit()
self.check_preconditions(init)
+ init.wait_msgq = lambda: None
init.start_all_components()
init.config_handler(self.construct_config(False, False))
self.check_started_dhcp(init, False, False)
@@ -1075,6 +1080,7 @@ class TestStartStopProcessesInit(unittest.TestCase):
init = MockInit()
self.check_preconditions(init)
# v6 only enabled
+ init.wait_msgq = lambda: None
init.start_all_components()
init.runnable = True
init._Init_started = True
@@ -1347,6 +1353,7 @@ class TestInitComponents(unittest.TestCase):
# Start it
orig = init._component_configurator.startup
init._component_configurator.startup = self.__unary_hook
+ init.wait_msgq = lambda: None
init.start_all_components()
init._component_configurator.startup = orig
self.__check_core(self.__param)
@@ -1499,6 +1506,7 @@ class TestInitComponents(unittest.TestCase):
pass
init.ccs = CC()
init.ccs.get_full_config = lambda: {'components': self.__compconfig}
+ init.wait_msgq = lambda: None
init.start_all_components()
self.__check_extended(self.__param)
@@ -1595,7 +1603,7 @@ class TestInitComponents(unittest.TestCase):
init.components[53] = component
# case where the returned pid is unknown to us. nothing should
- # happpen then.
+ # happen then.
init.get_process_exit_status_called = False
init._get_process_exit_status = init._get_process_exit_status_unknown_pid
init.components_to_restart = []
@@ -1768,6 +1776,51 @@ class TestInitComponents(unittest.TestCase):
# this is set by ProcessInfo.spawn()
self.assertEqual(42147, pi.pid)
+ def test_wait_msgq(self):
+ """
+ Test we can wait for msgq to provide its own alias.
+
+ It is not available the first time, the second it is.
+ """
+ class RpcSession:
+ def __init__(self):
+ # Not yet called
+ self.called = 0
+
+ def rpc_call(self, command, recipient):
+ self.called += 1
+ if self.called == 1:
+ raise isc.config.RPCRecipientMissing("Not yet")
+ elif self.called == 2:
+ raise isc.config.RPCError(1, "What?")
+ else:
+ raise Exception("Called too many times")
+
+ init = MockInitSimple()
+ init.wait_time = 1
+ init.ccs = RpcSession()
+ init.wait_msgq()
+ self.assertEqual(2, init.ccs.called)
+
+ def test_wait_msgq_fail(self):
+ """
+ Test the wait_msgq fails in case the msgq does not appear
+ after so many attempts.
+ """
+ class RpcSession:
+ def __init__(self):
+ self.called = 0
+
+ def rpc_call(self, command, recipient):
+ self.called += 1
+ raise isc.config.RPCRecipientMissing("Not yet")
+
+ b10init = MockInitSimple()
+ b10init.wait_time = 1
+ b10init.ccs = RpcSession()
+ self.assertRaises(init.ProcessStartError, b10init.wait_msgq)
+ self.assertEqual(10, b10init.ccs.called)
+
def test_start_cfgmgr(self):
'''Test that b10-cfgmgr is started.'''
class DummySession():
@@ -2339,6 +2392,16 @@ class SocketSrvTest(unittest.TestCase):
self.assertEqual({}, self.__b10_init._unix_sockets)
self.assertTrue(sock.closed)
+ def test_log_starting(self):
+ """
+ Checks the log_starting call doesn't raise any errors
+ (does not check actual log output)
+ """
+ self.__b10_init.log_starting("foo")
+ self.__b10_init.log_starting("foo", 1)
+ self.__b10_init.log_starting("foo", 1, "192.0.2.1")
+
+
class TestFunctions(unittest.TestCase):
def setUp(self):
self.lockfile_testpath = \
diff --git a/src/bin/bindctl/README b/src/bin/bindctl/README
index 8b98ff5..a9a185f 100644
--- a/src/bin/bindctl/README
+++ b/src/bin/bindctl/README
@@ -1,2 +1,2 @@
1. Start bindctl by run command "sh bindctl".
-2. Login to b10-cmdctld with the username and password. The username and password will be saved in file default_user.csv automatcally after logining successfully, so next time bindctl will login with the username and password saved in default_user.csv. For more usage information, please turn to "man bindctl".
\ No newline at end of file
+2. Login to b10-cmdctl with the username and password. The username and password will be saved in file default_user.csv automatically after logining successfully, so next time bindctl will login with the username and password saved in default_user.csv. For more usage information, please turn to "man bindctl".
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index f382e2a..03b5d6b 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -33,6 +33,7 @@ import inspect
import pprint
import ssl, socket
import os, time, random, re
+import os.path
import getpass
from hashlib import sha1
import csv
@@ -147,9 +148,9 @@ class BindCmdInterpreter(Cmd):
# is processed by a script that expects a specific format.
if my_readline == sys.stdin.readline and sys.stdin.isatty():
sys.stdout.write("""\
-WARNING: Python readline module isn't available, so the command line editor
- (including command history management) does not work. See BIND 10
- guide for more details.\n\n""")
+WARNING: The Python readline module isn't available, so some command line
+ editing features (including command history management) will not
+ work. See the BIND 10 guide for more details.\n\n""")
try:
if not self.login_to_cmdctl():
@@ -214,22 +215,16 @@ WARNING: Python readline module isn't available, so the command line editor
return True
- def __print_check_ssl_msg(self):
- self._print("Please check the logs of b10-cmdctl, there may "
- "be a problem accepting SSL connections, such "
- "as a permission problem on the server "
- "certificate file.")
-
def _try_login(self, username, password):
'''
- Attempts to log in to cmdctl by sending a POST with
- the given username and password.
- On success of the POST (mind, not the login, only the network
- operation), returns a tuple (response, data).
- On failure, raises a FailToLogin exception, and prints some
- information on the failure.
- This call is essentially 'private', but made 'protected' for
- easier testing.
+ Attempts to log into cmdctl by sending a POST with the given
+ username and password. On success of the POST (not the login,
+ but the network operation), it returns a tuple (response, data).
+ We check for some failures such as SSL errors and socket errors
+ which could happen due to the environment in which BIND 10 runs.
+ On failure, it raises a FailToLogin exception and prints some
+ information on the failure. This call is essentially 'private',
+ but made 'protected' for easier testing.
'''
param = {'username': username, 'password' : password}
try:
@@ -237,16 +232,8 @@ WARNING: Python readline module isn't available, so the command line editor
data = response.read().decode()
# return here (will raise error after try block)
return (response, data)
- except ssl.SSLError as err:
- self._print("SSL error while sending login information: ", err)
- if err.errno == ssl.SSL_ERROR_EOF:
- self.__print_check_ssl_msg()
- except socket.error as err:
- self._print("Socket error while sending login information: ", err)
- # An SSL setup error can also bubble up as a plain CONNRESET...
- # (on some systems it usually does)
- if err.errno == errno.ECONNRESET:
- self.__print_check_ssl_msg()
+ except (ssl.SSLError, socket.error) as err:
+ self._print('Error while sending login information:', err)
pass
raise FailToLogin()
@@ -270,9 +257,21 @@ WARNING: Python readline module isn't available, so the command line editor
# No valid logins were found, prompt the user for a username/password
count = 0
- self._print('No stored password file found, please see sections '
- '"Configuration specification for b10-cmdctl" and "bindctl '
- 'command-line options" of the BIND 10 guide.')
+ if not os.path.exists(self.csv_file_dir + CSV_FILE_NAME):
+ self._print('\nNo stored password file found.\n\n'
+ 'When the system is first set up you need to create '
+ 'at least one user account.\n'
+ 'For information on how to set up a BIND 10 system, '
+ 'please check see the\n'
+ 'BIND 10 Guide: \n\n'
+ 'http://bind10.isc.org/docs/bind10-guide.html#quick-start-auth-dns\n\n'
+
+ 'If a user account has been set up, please check the '
+ 'b10-cmdctl log for other\n'
+ 'information.\n')
+ else:
+ self._print('Login failed: either the user name or password is '
+ 'invalid.\n')
while True:
count = count + 1
if count > 3:
@@ -317,14 +316,14 @@ WARNING: Python readline module isn't available, so the command line editor
return {}
- def send_POST(self, url, post_param = None):
+ def send_POST(self, url, post_param=None):
'''Send POST request to cmdctl, session id is send with the name
'cookie' in header.
Format: /module_name/command_name
parameters of command is encoded as a map
'''
param = None
- if (len(post_param) != 0):
+ if post_param is not None and len(post_param) != 0:
param = json.dumps(post_param)
headers = {"cookie" : self.session_id}
@@ -938,5 +937,3 @@ WARNING: Python readline module isn't available, so the command line editor
if data != "" and data != "{}":
self._print(json.dumps(json.loads(data), sort_keys=True,
indent=4))
-
-
diff --git a/src/bin/bindctl/run_bindctl.sh.in b/src/bin/bindctl/run_bindctl.sh.in
index 97e9250..0bd8818 100755
--- a/src/bin/bindctl/run_bindctl.sh.in
+++ b/src/bin/bindctl/run_bindctl.sh.in
@@ -27,7 +27,7 @@ export PYTHONPATH
# required by loadable python modules.
SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
if test $SET_ENV_LIBRARY_PATH = yes; then
- @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/threads/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
export @ENV_LIBRARY_PATH@
fi
diff --git a/src/bin/bindctl/tests/Makefile.am b/src/bin/bindctl/tests/Makefile.am
index 3d08a17..c781d7c 100644
--- a/src/bin/bindctl/tests/Makefile.am
+++ b/src/bin/bindctl/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/bindctl/tests/bindctl_test.py b/src/bin/bindctl/tests/bindctl_test.py
index 0ec9b58..c3262e1 100644
--- a/src/bin/bindctl/tests/bindctl_test.py
+++ b/src/bin/bindctl/tests/bindctl_test.py
@@ -26,6 +26,7 @@ import http.client
import pwd
import getpass
import re
+import json
from optparse import OptionParser
from isc.config.config_data import ConfigData, MultiConfigData
from isc.config.module_spec import ModuleSpec
@@ -386,7 +387,7 @@ class TestConfigCommands(unittest.TestCase):
self.tool.send_POST = send_POST_raiseImmediately
self.assertRaises(FailToLogin, self.tool._try_login, "foo", "bar")
expected_printed_messages.append(
- 'Socket error while sending login information: test error')
+ 'Error while sending login information: test error')
self.__check_printed_messages(expected_printed_messages)
def create_send_POST_raiseOnRead(exception):
@@ -405,7 +406,7 @@ class TestConfigCommands(unittest.TestCase):
create_send_POST_raiseOnRead(socket.error("read error"))
self.assertRaises(FailToLogin, self.tool._try_login, "foo", "bar")
expected_printed_messages.append(
- 'Socket error while sending login information: read error')
+ 'Error while sending login information: read error')
self.__check_printed_messages(expected_printed_messages)
# connection reset
@@ -415,13 +416,7 @@ class TestConfigCommands(unittest.TestCase):
create_send_POST_raiseOnRead(exc)
self.assertRaises(FailToLogin, self.tool._try_login, "foo", "bar")
expected_printed_messages.append(
- 'Socket error while sending login information: '
- 'connection reset')
- expected_printed_messages.append(
- 'Please check the logs of b10-cmdctl, there may be a '
- 'problem accepting SSL connections, such as a permission '
- 'problem on the server certificate file.'
- )
+ 'Error while sending login information: connection reset')
self.__check_printed_messages(expected_printed_messages)
# 'normal' SSL error
@@ -430,7 +425,7 @@ class TestConfigCommands(unittest.TestCase):
create_send_POST_raiseOnRead(exc)
self.assertRaises(FailToLogin, self.tool._try_login, "foo", "bar")
expected_printed_messages.append(
- 'SSL error while sending login information: .*')
+ 'Error while sending login information: .*')
self.__check_printed_messages(expected_printed_messages)
# 'EOF' SSL error
@@ -440,12 +435,7 @@ class TestConfigCommands(unittest.TestCase):
create_send_POST_raiseOnRead(exc)
self.assertRaises(FailToLogin, self.tool._try_login, "foo", "bar")
expected_printed_messages.append(
- 'SSL error while sending login information: .*')
- expected_printed_messages.append(
- 'Please check the logs of b10-cmdctl, there may be a '
- 'problem accepting SSL connections, such as a permission '
- 'problem on the server certificate file.'
- )
+ 'Error while sending login information: .*')
self.__check_printed_messages(expected_printed_messages)
# any other exception should be passed through
@@ -457,6 +447,44 @@ class TestConfigCommands(unittest.TestCase):
finally:
self.tool.send_POST = orig_send_POST
+ def test_try_login_calls_cmdctl(self):
+ # Make sure _try_login() makes the right API call to cmdctl.
+ orig_conn = self.tool.conn
+ try:
+ class MyConn:
+ def __init__(self):
+ self.method = None
+ self.url = None
+ self.param = None
+ self.headers = None
+
+ def request(self, method, url, param, headers):
+ self.method = method
+ self.url = url
+ self.param = param
+ self.headers = headers
+
+ def getresponse(self):
+ class MyResponse:
+ def __init__(self):
+ self.status = http.client.OK
+ def read(self):
+ class MyData:
+ def decode(self):
+ return json.dumps(True)
+ return MyData()
+ return MyResponse()
+
+ self.tool.conn = MyConn()
+ self.assertTrue(self.tool._try_login('user32', 'pass64'))
+ self.assertEqual(self.tool.conn.method, 'POST')
+ self.assertEqual(self.tool.conn.url, '/login')
+ self.assertEqual(json.loads(self.tool.conn.param),
+ {"password": "pass64", "username": "user32"})
+ self.assertIn('cookie', self.tool.conn.headers)
+ finally:
+ self.tool.conn = orig_conn
+
def test_run(self):
def login_to_cmdctl():
return True
diff --git a/src/bin/cfgmgr/plugins/datasrc.spec.pre.in b/src/bin/cfgmgr/plugins/datasrc.spec.pre.in
index 6d5bd77..3f16758 100644
--- a/src/bin/cfgmgr/plugins/datasrc.spec.pre.in
+++ b/src/bin/cfgmgr/plugins/datasrc.spec.pre.in
@@ -18,9 +18,9 @@
],
"CH": [
{
- "type": "static",
- "cache-enable": false,
- "params": "@@STATIC_ZONE_FILE@@"
+ "type": "MasterFiles",
+ "cache-enable": true,
+ "params": {"BIND": "@@STATIC_ZONE_FILE@@"}
}
]
},
@@ -63,6 +63,17 @@
"item_optional": false,
"item_default": ""
}
+ },
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": true
+ },
+ {
+ "item_name": "cache-type",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "local"
}
]
}
diff --git a/src/bin/cfgmgr/plugins/tests/Makefile.am b/src/bin/cfgmgr/plugins/tests/Makefile.am
index 9b8b925..978dc4b 100644
--- a/src/bin/cfgmgr/plugins/tests/Makefile.am
+++ b/src/bin/cfgmgr/plugins/tests/Makefile.am
@@ -7,10 +7,12 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
+# We need to set B10_FROM_BUILD as some of the tests need to create lock file
+# for logging.
check-local:
if ENABLE_PYTHON_COVERAGE
touch $(abs_top_srcdir)/.coverage
@@ -20,6 +22,7 @@ endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
B10_TEST_PLUGIN_DIR=$(abs_srcdir)/..:$(abs_builddir)/.. \
+ B10_FROM_BUILD=$(abs_top_builddir) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/cfgmgr:$(abs_top_builddir)/src/lib/dns/python/.libs \
$(LIBRARY_PATH_PLACEHOLDER) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
diff --git a/src/bin/cfgmgr/plugins/tests/datasrc_test.py b/src/bin/cfgmgr/plugins/tests/datasrc_test.py
index f4381b4..546e534 100644
--- a/src/bin/cfgmgr/plugins/tests/datasrc_test.py
+++ b/src/bin/cfgmgr/plugins/tests/datasrc_test.py
@@ -96,15 +96,6 @@ class DatasrcTest(unittest.TestCase):
"params": {}
}]})
- def test_dstype_bad(self):
- """
- The configuration is correct by the spec, but it would be rejected
- by the client list. Check we reject it.
- """
- self.reject({"IN": [{
- "type": "No such type"
- }]})
-
def test_invalid_mem_params(self):
"""
The client list skips in-memory sources. So we check it locally that
@@ -142,7 +133,7 @@ class DatasrcTest(unittest.TestCase):
def test_no_such_file_mem(self):
"""
- We also check the existance of master files. Not the actual content,
+ We also check the existence of master files. Not the actual content,
though.
"""
self.reject({"IN": [{
@@ -153,6 +144,101 @@ class DatasrcTest(unittest.TestCase):
}
}]})
+ def test_names_present(self):
+ """
+ Test we don't choke on configuration with the "name" being present on
+ some items.
+ """
+ self.accept({"IN": [{
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {},
+ "name": "Whatever"
+ }]})
+
+ def test_names_default_classes(self):
+ """
+ Test we can have a client of the same type in different classes
+ without specified name. The defaults should be derived both from
+ the type and the class.
+ """
+ self.accept({
+ "IN": [{
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {}
+ }],
+ "CH": [{
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {}
+ }]})
+
+ def test_names_collision(self):
+ """
+ Reject when two names are the same.
+
+ Cases are:
+ - Explicit names.
+ - Two default names turn out to be the same (same type and class).
+ - One explicit is set to the same as the default one.
+ """
+ self.reject({"IN": [
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {},
+ "name": "Whatever"
+ },
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {},
+ "name": "Whatever"
+ }]})
+ # The same, but across different classes is allowed (we would
+ # identify the data source by class+name tuple)
+ self.accept({
+ "IN": [
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {},
+ "name": "Whatever"
+ }
+ ],
+ "CH": [
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {},
+ "name": "Whatever"
+ }
+ ]})
+ self.reject({"IN": [
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {}
+ },
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {}
+ }]})
+ self.reject({"IN": [
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {},
+ "name": "MasterFiles"
+ },
+ {
+ "type": "MasterFiles",
+ "cache-enable": True,
+ "params": {}
+ }]})
+
if __name__ == '__main__':
isc.log.init("bind10")
isc.log.resetUnitTestRootLogger()
diff --git a/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py b/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py
index 808f28a..575879d 100644
--- a/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py
+++ b/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py
@@ -86,13 +86,13 @@ class TSigKeysTest(unittest.TestCase):
self.assertEqual("TSIG: Invalid TSIG key string: invalid.key",
tsig_keys.check({'keys': ['invalid.key']}))
self.assertEqual(
- "TSIG: Unexpected end of input in BASE decoder",
+ "TSIG: Incomplete input for base64: 123",
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.
+ as this should not get in through config manager anyway.
"""
self.assertNotEqual(None, tsig_keys.check({'bad_name': {}}))
self.assertNotEqual(None, tsig_keys.check({'keys': 'not_list'}))
diff --git a/src/bin/cfgmgr/tests/Makefile.am b/src/bin/cfgmgr/tests/Makefile.am
index a2e43ff..6293571 100644
--- a/src/bin/cfgmgr/tests/Makefile.am
+++ b/src/bin/cfgmgr/tests/Makefile.am
@@ -8,7 +8,7 @@ EXTRA_DIST = testdata/plugins/testplugin.py
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
index 02b48bd..c7c1081 100644
--- a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
+++ b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
@@ -70,7 +70,7 @@ class TestPlugins(unittest.TestCase):
class TestConfigManagerStartup(unittest.TestCase):
def test_cfgmgr(self):
# some creative module use;
- # b10-cfgmgr has a hypen, so we use __import__
+ # b10-cfgmgr has a hyphen, so we use __import__
# this also gives us the chance to override the imported
# module ConfigManager in it.
b = __import__("b10-cfgmgr")
diff --git a/src/bin/cmdctl/Makefile.am b/src/bin/cmdctl/Makefile.am
index 8cadf89..54be50f 100644
--- a/src/bin/cmdctl/Makefile.am
+++ b/src/bin/cmdctl/Makefile.am
@@ -11,20 +11,18 @@ pylogmessagedir = $(pyexecdir)/isc/log_messages/
b10_cmdctldir = $(pkgdatadir)
-USERSFILES = cmdctl-accounts.csv
CERTFILES = cmdctl-keyfile.pem cmdctl-certfile.pem
b10_cmdctl_DATA = cmdctl.spec
-EXTRA_DIST = $(USERSFILES)
-
CLEANFILES= b10-cmdctl cmdctl.pyc cmdctl.spec
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.pyc
man_MANS = b10-cmdctl.8 b10-certgen.1
DISTCLEANFILES = $(man_MANS) cmdctl-certfile.pem cmdctl-keyfile.pem
-EXTRA_DIST += $(man_MANS) b10-certgen.xml b10-cmdctl.xml cmdctl_messages.mes
+EXTRA_DIST = $(man_MANS) b10-certgen.xml b10-cmdctl.xml cmdctl_messages.mes
+EXTRA_DIST += cmdctl-accounts.csv
if GENERATE_DOCS
@@ -59,12 +57,19 @@ b10_certgen_CXXFLAGS = $(BOTAN_INCLUDES)
b10_certgen_LDFLAGS = $(BOTAN_LIBS)
# Generate the initial certificates immediately
-cmdctl-certfile.pem: b10-certgen
- ./b10-certgen -q -w
-
cmdctl-keyfile.pem: b10-certgen
./b10-certgen -q -w
+# This is a hack, as b10-certgen creates both cmdctl-keyfile.pem and
+# cmdctl-certfile.pem, and in a parallel make, making these targets
+# simultaneously may result in corrupted files. With GNU make, there is
+# a non-portable way of working around this with pattern rules, but we
+# adopt this hack instead. The downside is that cmdctl-certfile.pem will
+# not be re-generated if cmdctl-keyfile.pem exists and is older. See
+# Trac ticket #2962.
+cmdctl-certfile.pem: cmdctl-keyfile.pem
+ touch $(builddir)/cmdctl-keyfile.pem
+
if INSTALL_CONFIGURATIONS
# Below we intentionally use ${INSTALL} -m 640 instead of $(INSTALL_DATA)
diff --git a/src/bin/cmdctl/b10-certgen.cc b/src/bin/cmdctl/b10-certgen.cc
index 579ae60..59bba9e 100644
--- a/src/bin/cmdctl/b10-certgen.cc
+++ b/src/bin/cmdctl/b10-certgen.cc
@@ -62,7 +62,7 @@ usage() {
std::cout << "Options:" << std::endl;
std::cout << "-c, --certfile=FILE\t\tfile to read or store the certificate"
<< std::endl;
- std::cout << "-f, --force\t\t\toverwrite existing certficate even if it"
+ std::cout << "-f, --force\t\t\toverwrite existing certificate even if it"
<< std::endl <<"\t\t\t\tis valid" << std::endl;
std::cout << "-h, --help\t\t\tshow this help" << std::endl;
std::cout << "-k, --keyfile=FILE\t\tfile to store the generated private key"
@@ -194,7 +194,7 @@ public:
print("Creating certificate file " + cert_file_name);
- // The exact call changed aftert 1.8, adding the
+ // The exact call changed after 1.8, adding the
// hash function option
#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0)
X509_Certificate cert =
@@ -229,7 +229,7 @@ public:
validateCertificate(const std::string& certfile) {
// Since we are dealing with a self-signed certificate here, we
// also use the certificate to check itself; i.e. we add it
- // as a trusted certificate, then validate the certficate itself.
+ // as a trusted certificate, then validate the certificate itself.
//const X509_Certificate cert(certfile);
try {
X509_Store store;
diff --git a/src/bin/cmdctl/b10-cmdctl.xml b/src/bin/cmdctl/b10-cmdctl.xml
index 871265c..c66837c 100644
--- a/src/bin/cmdctl/b10-cmdctl.xml
+++ b/src/bin/cmdctl/b10-cmdctl.xml
@@ -169,8 +169,6 @@
The configuration command is:
</para>
-<!-- NOTE: print_settings is not documented since I think will be removed -->
-
<para>
<command>shutdown</command> exits <command>b10-cmdctl</command>.
This has an optional <varname>pid</varname> argument to
diff --git a/src/bin/cmdctl/cmdctl.py.in b/src/bin/cmdctl/cmdctl.py.in
index 457873b..ea56da9 100755
--- a/src/bin/cmdctl/cmdctl.py.in
+++ b/src/bin/cmdctl/cmdctl.py.in
@@ -36,12 +36,12 @@ import re
import ssl, socket
import isc
import pprint
-import select
import csv
import random
import time
import signal
from isc.config import ccsession
+import isc.cc.proto_defs
import isc.util.process
import isc.net.parse
from optparse import OptionParser, OptionValueError
@@ -97,6 +97,11 @@ def check_file(file_name):
class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
'''https connection request handler.
Currently only GET and POST are supported. '''
+ def __init__(self, request, client_address, server):
+ http.server.BaseHTTPRequestHandler.__init__(self, request,
+ client_address, server)
+ self.session_id = None
+
def do_GET(self):
'''The client should send its session id in header with
the name 'cookie'
@@ -121,7 +126,7 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
return self.server.get_reply_data_for_GET(id, module)
def _is_session_valid(self):
- return self.session_id
+ return self.session_id is not None
def _is_user_logged_in(self):
login_time = self.server.user_sessions.get(self.session_id)
@@ -171,7 +176,7 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
is_user_valid, error_info = self._check_user_name_and_pwd()
if is_user_valid:
self.server.save_user_session_id(self.session_id)
- return http.client.OK, ["login success "]
+ return http.client.OK, ["login success"]
else:
return http.client.UNAUTHORIZED, error_info
@@ -241,6 +246,7 @@ class CommandControl():
CommandControl to communicate with other modules. '''
self._verbose = verbose
self._httpserver = httpserver
+ self.__msg_handler_thread = None # set in _start_msg_handle_thread
self._lock = threading.Lock()
self._setup_session()
self.modules_spec = self._get_modules_specification()
@@ -320,7 +326,26 @@ class CommandControl():
self._cmdctl_config_data[key] = new_config[key]
return answer
+ def _get_current_thread(self):
+ """A simple wrapper of returning the 'current' thread object.
+
+ This is extracted as a 'protected' method so tests can override for
+ their convenience.
+
+ """
+ return threading.currentThread()
+
def command_handler(self, command, args):
+ """Handle commands from other modules.
+
+ This method must not be called by any other threads than
+ __msg_handler_thread invoked at the intialization of the class;
+ otherwise it would cause critical race or dead locks.
+
+ """
+ # Check the restriction described above.
+ assert self._get_current_thread() == self.__msg_handler_thread
+
answer = ccsession.create_answer(0)
if command == ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE:
# The 'value' of a specification update can be either
@@ -345,8 +370,6 @@ class CommandControl():
self._httpserver.shutdown()
self._serving = False
- elif command == 'print_settings':
- answer = ccsession.create_answer(0, self._cmdctl_config_data)
else:
answer = ccsession.create_answer(1, 'unknown command: ' + command)
@@ -356,6 +379,7 @@ class CommandControl():
''' Start one thread to handle received message from msgq.'''
td = threading.Thread(target=self._handle_msg_from_msgq)
td.daemon = True
+ self.__msg_handler_thread = td
td.start()
def _handle_msg_from_msgq(self):
@@ -396,7 +420,7 @@ class CommandControl():
rcode, reply = self.send_command('ConfigManager', ccsession.COMMAND_GET_MODULE_SPEC)
return self._parse_command_result(rcode, reply)
- def send_command_with_check(self, module_name, command_name, params = None):
+ def send_command_with_check(self, module_name, command_name, params=None):
'''Before send the command to modules, check if module_name, command_name
parameters are legal according the spec file of the module.
Return rcode, dict. TODO, the rcode should be defined properly.
@@ -418,31 +442,34 @@ class CommandControl():
return self.send_command(module_name, command_name, params)
- def send_command(self, module_name, command_name, params = None):
- '''Send the command from bindctl to proper module. '''
+ def send_command(self, module_name, command_name, params=None):
+ """Send the command from bindctl to proper module.
+
+ Note that commands sent to Cmdctl itself are also delivered via the
+ CC session. Since this method is called from a thread handling a
+ particular HTTP session, it cannot directly call command_handler().
+
+ """
errstr = 'unknown error'
answer = None
logger.debug(DBG_CMDCTL_MESSAGING, CMDCTL_SEND_COMMAND,
command_name, module_name)
- if module_name == self._module_name:
- # Process the command sent to cmdctl directly.
- answer = self.command_handler(command_name, params)
- else:
- # FIXME: Due to the fact that we use a separate session
- # from the module one (due to threads and blocking), and
- # because the plain cc session does not have the high-level
- # rpc-call method, we use the low-level way and create the
- # command ourselves.
- msg = ccsession.create_command(command_name, params)
- seq = self._cc.group_sendmsg(msg, module_name, want_answer=True)
- logger.debug(DBG_CMDCTL_MESSAGING, CMDCTL_COMMAND_SENT,
- command_name, module_name)
- #TODO, it may be blocked, msqg need to add a new interface waiting in timeout.
- try:
- answer, env = self._cc.group_recvmsg(False, seq)
- except isc.cc.session.SessionTimeout:
- errstr = "Module '%s' not responding" % module_name
+ # FIXME: Due to the fact that we use a separate session
+ # from the module one (due to threads and blocking), and
+ # because the plain cc session does not have the high-level
+ # rpc-call method, we use the low-level way and create the
+ # command ourselves.
+ msg = ccsession.create_command(command_name, params)
+ seq = self._cc.group_sendmsg(msg, module_name, want_answer=True)
+ logger.debug(DBG_CMDCTL_MESSAGING, CMDCTL_COMMAND_SENT, command_name,
+ module_name)
+ # TODO, it may be blocked, msqg need to add a new interface waiting
+ # in timeout.
+ try:
+ answer, env = self._cc.group_recvmsg(False, seq)
+ except isc.cc.session.SessionTimeout:
+ errstr = "Module '%s' not responding" % module_name
if answer:
try:
@@ -454,7 +481,8 @@ class CommandControl():
else:
return rcode, {}
else:
- errstr = str(answer['result'][1])
+ errstr = \
+ str(answer[isc.cc.proto_defs.CC_PAYLOAD_RESULT][1])
except ccsession.ModuleCCSessionError as mcse:
errstr = str("Error in ccsession answer:") + str(mcse)
@@ -502,12 +530,25 @@ class SecureHTTPServer(socketserver_mixin.NoPollMixIn,
self._verbose = verbose
self._lock = threading.Lock()
self._user_infos = {}
- self._accounts_file = None
+ self.__accounts_file = None
+ self.__accounts_file_mtime = 0
def _create_user_info(self, accounts_file):
'''Read all user's name and its' salt, hashed password
from accounts file.'''
- if (self._accounts_file == accounts_file) and (len(self._user_infos) > 0):
+
+ # If the file does not exist, set accounts to empty, and return
+ if not os.path.exists(accounts_file):
+ self._user_infos = {}
+ self.__accounts_file = None
+ self.__accounts_file_mtime = 0
+ return
+
+ # If the filename hasn't changed, and the file itself
+ # has neither, do nothing
+ accounts_file_mtime = os.stat(accounts_file).st_mtime
+ if self.__accounts_file == accounts_file and\
+ accounts_file_mtime <= self.__accounts_file_mtime:
return
with self._lock:
@@ -525,7 +566,8 @@ class SecureHTTPServer(socketserver_mixin.NoPollMixIn,
if csvfile:
csvfile.close()
- self._accounts_file = accounts_file
+ self.__accounts_file = accounts_file
+ self.__accounts_file_mtime = accounts_file_mtime
if len(self._user_infos) == 0:
logger.error(CMDCTL_NO_USER_ENTRIES_READ)
@@ -557,12 +599,13 @@ class SecureHTTPServer(socketserver_mixin.NoPollMixIn,
# error)
return ssl_sock
except ssl.SSLError as err:
+ self.close_request(sock)
logger.error(CMDCTL_SSL_SETUP_FAILURE_USER_DENIED, err)
- except (CmdctlException, IOError) as cce:
+ raise
+ except (CmdctlException, IOError, socket.error) as cce:
+ self.close_request(sock)
logger.error(CMDCTL_SSL_SETUP_FAILURE_READING_CERT, cce)
- self.close_request(sock)
- # raise socket error to finish the request
- raise socket.error
+ raise
def get_request(self):
'''Get client request socket and wrap it in SSL context. '''
diff --git a/src/bin/cmdctl/cmdctl.spec.pre.in b/src/bin/cmdctl/cmdctl.spec.pre.in
index d04e2e3..87aeb11 100644
--- a/src/bin/cmdctl/cmdctl.spec.pre.in
+++ b/src/bin/cmdctl/cmdctl.spec.pre.in
@@ -24,11 +24,6 @@
],
"commands": [
{
- "command_name": "print_settings",
- "command_description": "Print some_string and some_int to stdout",
- "command_args": []
- },
- {
"command_name": "shutdown",
"command_description": "shutdown cmdctl",
"command_args": [
diff --git a/src/bin/cmdctl/run_b10-cmdctl.sh.in b/src/bin/cmdctl/run_b10-cmdctl.sh.in
index 7dcf1d5..454e02c 100644
--- a/src/bin/cmdctl/run_b10-cmdctl.sh.in
+++ b/src/bin/cmdctl/run_b10-cmdctl.sh.in
@@ -26,7 +26,7 @@ export PYTHONPATH
# required by loadable python modules.
SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
if test $SET_ENV_LIBRARY_PATH = yes; then
- @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/threads/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
export @ENV_LIBRARY_PATH@
fi
diff --git a/src/bin/cmdctl/tests/Makefile.am b/src/bin/cmdctl/tests/Makefile.am
index 74b801a..93bf040 100644
--- a/src/bin/cmdctl/tests/Makefile.am
+++ b/src/bin/cmdctl/tests/Makefile.am
@@ -9,7 +9,7 @@ EXTRA_DIST += testdata/noca-certfile.pem
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
CLEANFILES = test-keyfile.pem test-certfile.pem
@@ -25,8 +25,8 @@ endif
echo Running test: $$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/cmdctl \
- CMDCTL_BUILD_PATH=$(abs_top_builddir)/src/bin/cmdctl \
CMDCTL_SRC_PATH=$(abs_top_srcdir)/src/bin/cmdctl \
+ CMDCTL_BUILD_PATH=$(abs_top_builddir)/src/bin/cmdctl \
B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/cmdctl/tests/b10-certgen_test.py b/src/bin/cmdctl/tests/b10-certgen_test.py
index d54efa3..8054aa1 100644
--- a/src/bin/cmdctl/tests/b10-certgen_test.py
+++ b/src/bin/cmdctl/tests/b10-certgen_test.py
@@ -172,10 +172,7 @@ class TestCertGenTool(unittest.TestCase):
"""
Tests a few pre-created certificates with the -c option
"""
- if ('CMDCTL_SRC_PATH' in os.environ):
- path = os.environ['CMDCTL_SRC_PATH'] + "/tests/testdata/"
- else:
- path = "testdata/"
+ path = os.environ['CMDCTL_SRC_PATH'] + '/tests/testdata/'
self.validate_certificate(10, path + 'expired-certfile.pem')
self.validate_certificate(100, path + 'mangled-certfile.pem')
self.validate_certificate(17, path + 'noca-certfile.pem')
@@ -203,6 +200,8 @@ class TestCertGenTool(unittest.TestCase):
# No such file
self.run_check(105, None, None, [self.TOOL, '-c', 'foo'])
+ @unittest.skipIf(os.getuid() == 0,
+ 'test cannot be run as root user')
def test_permissions(self):
"""
Test some combinations of correct and bad permissions.
diff --git a/src/bin/cmdctl/tests/cmdctl_test.py b/src/bin/cmdctl/tests/cmdctl_test.py
index ee44f45..7af8b0d 100644
--- a/src/bin/cmdctl/tests/cmdctl_test.py
+++ b/src/bin/cmdctl/tests/cmdctl_test.py
@@ -15,8 +15,9 @@
import unittest
-import socket
+import ssl, socket
import tempfile
+import time
import stat
import sys
from cmdctl import *
@@ -33,7 +34,7 @@ BUILD_FILE_PATH = os.environ['CMDCTL_BUILD_PATH'] + os.sep
# Rewrite the class for unittest.
class MySecureHTTPRequestHandler(SecureHTTPRequestHandler):
def __init__(self):
- pass
+ self.session_id = None
def send_response(self, rcode):
self.rcode = rcode
@@ -41,19 +42,6 @@ class MySecureHTTPRequestHandler(SecureHTTPRequestHandler):
def end_headers(self):
pass
- def do_GET(self):
- self.wfile = open('tmp.file', 'wb')
- super().do_GET()
- self.wfile.close()
- os.remove('tmp.file')
-
- def do_POST(self):
- self.wfile = open("tmp.file", 'wb')
- super().do_POST()
- self.wfile.close()
- os.remove('tmp.file')
-
-
class FakeSecureHTTPServer(SecureHTTPServer):
def __init__(self):
self.user_sessions = {}
@@ -84,6 +72,26 @@ class UnreadableFile:
def __exit__(self, type, value, traceback):
os.chmod(self.file_name, self.orig_mode)
+class TmpTextFile:
+ """
+ Context class for temporarily creating a text file with some
+ lines of content.
+
+ The file is automatically deleted if the context is left, so
+ make sure to not use the path of an existing file!
+ """
+ def __init__(self, path, contents):
+ self.__path = path
+ self.__contents = contents
+
+ def __enter__(self):
+ with open(self.__path, 'w') as f:
+ f.write("\n".join(self.__contents) + "\n")
+
+ def __exit__(self, type, value, traceback):
+ os.unlink(self.__path)
+
+
class TestSecureHTTPRequestHandler(unittest.TestCase):
def setUp(self):
self.old_stdout = sys.stdout
@@ -93,13 +101,22 @@ class TestSecureHTTPRequestHandler(unittest.TestCase):
self.handler.server.user_sessions = {}
self.handler.server._user_infos = {}
self.handler.headers = {}
- self.handler.rfile = open("check.tmp", 'w+b')
+ self.handler.rfile = open('input.tmp', 'w+b')
+ self.handler.wfile = open('output.tmp', 'w+b')
def tearDown(self):
sys.stdout.close()
sys.stdout = self.old_stdout
+ self.handler.wfile.close()
+ os.remove('output.tmp')
self.handler.rfile.close()
- os.remove('check.tmp')
+ os.remove('input.tmp')
+
+ def test_is_session_valid(self):
+ self.assertIsNone(self.handler.session_id)
+ self.assertFalse(self.handler._is_session_valid())
+ self.handler.session_id = 4234
+ self.assertTrue(self.handler._is_session_valid())
def test_parse_request_path(self):
self.handler.path = ''
@@ -160,7 +177,7 @@ class TestSecureHTTPRequestHandler(unittest.TestCase):
self.handler.do_GET()
self.assertEqual(self.handler.rcode, http.client.OK)
- def test_user_logged_in(self):
+ def test_is_user_logged_in(self):
self.handler.server.user_sessions = {}
self.handler.session_id = 12345
self.assertTrue(self.handler._is_user_logged_in() == False)
@@ -294,7 +311,92 @@ class TestSecureHTTPRequestHandler(unittest.TestCase):
rcode, reply = self.handler._handle_post_request()
self.assertEqual(http.client.BAD_REQUEST, rcode)
+ def test_handle_login(self):
+ orig_is_user_logged_in = self.handler._is_user_logged_in
+ orig_check_user_name_and_pwd = self.handler._check_user_name_and_pwd
+ try:
+ def create_is_user_logged_in(status):
+ '''Create a replacement _is_user_logged_in() method.'''
+ def my_is_user_logged_in():
+ return status
+ return my_is_user_logged_in
+
+ # Check case where _is_user_logged_in() returns True
+ self.handler._is_user_logged_in = create_is_user_logged_in(True)
+ self.handler.headers['cookie'] = 12345
+ self.handler.path = '/login'
+ self.handler.do_POST()
+ self.assertEqual(self.handler.rcode, http.client.OK)
+ self.handler.wfile.seek(0, 0)
+ d = self.handler.wfile.read()
+ self.assertEqual(json.loads(d.decode()),
+ ['user has already login'])
+
+ # Clear the output
+ self.handler.wfile.seek(0, 0)
+ self.handler.wfile.truncate()
+
+ # Check case where _is_user_logged_in() returns False
+ self.handler._is_user_logged_in = create_is_user_logged_in(False)
+
+ def create_check_user_name_and_pwd(status, error_info=None):
+ '''Create a replacement _check_user_name_and_pwd() method.'''
+ def my_check_user_name_and_pwd():
+ return status, error_info
+ return my_check_user_name_and_pwd
+
+ # (a) Check case where _check_user_name_and_pwd() returns
+ # valid user status
+ self.handler._check_user_name_and_pwd = \
+ create_check_user_name_and_pwd(True)
+ self.handler.do_POST()
+ self.assertEqual(self.handler.rcode, http.client.OK)
+ self.handler.wfile.seek(0, 0)
+ d = self.handler.wfile.read()
+ self.assertEqual(json.loads(d.decode()), ['login success'])
+
+ # Clear the output
+ self.handler.wfile.seek(0, 0)
+ self.handler.wfile.truncate()
+
+ # (b) Check case where _check_user_name_and_pwd() returns
+ # invalid user status
+ self.handler._check_user_name_and_pwd = \
+ create_check_user_name_and_pwd(False, ['login failed'])
+ self.handler.do_POST()
+ self.assertEqual(self.handler.rcode, http.client.UNAUTHORIZED)
+ self.handler.wfile.seek(0, 0)
+ d = self.handler.wfile.read()
+ self.assertEqual(json.loads(d.decode()), ['login failed'])
+
+ finally:
+ self.handler._is_user_logged_in = orig_is_user_logged_in
+ self.handler._check_user_name_and_pwd = orig_check_user_name_and_pwd
+
+class MockSession:
+ """Act like isc.cc.Session, stealing group_sendmsg/recvmsg().
+
+ The initial simple version only records given parameters in
+ group_sendmsg() for later inspection and raise a timeout exception
+ from recvmsg(). As we see the need for more test cases these methods
+ should be extended.
+
+ """
+ def __init__(self, sent_messages):
+ self.__sent_messages = sent_messages
+
+ def group_sendmsg(self, msg, module_name, want_answer):
+ self.__sent_messages.append((msg, module_name))
+
+ def group_recvmsg(self, nonblock, seq):
+ raise isc.cc.session.SessionTimeout('dummy timeout')
+
class MyCommandControl(CommandControl):
+ def __init__(self, httpserver, verbose):
+ super().__init__(httpserver, verbose)
+ self.sent_messages = [] # for inspection; allow tests to see it
+ self._cc = MockSession(self.sent_messages)
+
def _get_modules_specification(self):
return {}
@@ -311,6 +413,12 @@ class MyCommandControl(CommandControl):
def _handle_msg_from_msgq(self):
pass
+ def _start_msg_handle_thread(self): # just not bother to be threads
+ pass
+
+ def _get_current_thread(self):
+ return None
+
class TestCommandControl(unittest.TestCase):
def setUp(self):
@@ -354,10 +462,22 @@ class TestCommandControl(unittest.TestCase):
answer = self.cmdctl.command_handler('unknown-command', None)
self._check_answer(answer, 1, 'unknown command: unknown-command')
- answer = self.cmdctl.command_handler('print_settings', None)
+ # Send a real command. Mock stuff so the shutdown command doesn't
+ # cause an exception.
+ class ModuleCC:
+ def send_stopping():
+ pass
+ self.cmdctl._module_cc = ModuleCC
+ called = []
+ class Server:
+ def shutdown():
+ called.append('shutdown')
+ self.cmdctl._httpserver = Server
+ answer = self.cmdctl.command_handler('shutdown', None)
rcode, msg = ccsession.parse_answer(answer)
self.assertEqual(rcode, 0)
- self.assertTrue(msg != None)
+ self.assertIsNone(msg)
+ self.assertEqual(['shutdown'], called)
def test_command_handler_spec_update(self):
# Should not be present
@@ -423,8 +543,24 @@ class TestCommandControl(unittest.TestCase):
os.remove(file_name)
def test_send_command(self):
- rcode, value = self.cmdctl.send_command('Cmdctl', 'print_settings', None)
- self.assertEqual(rcode, 0)
+ # Send a command to other module. We check an expected message
+ # is sent via the session (cmdct._cc). Due to the behavior of
+ # our mock session object the anser will be "fail", but it's not
+ # the subject of this test, and so it's okay.
+ # TODO: more detailed cases should be tested.
+ rcode, value = self.cmdctl.send_command('Init', 'shutdown', None)
+ self.assertEqual(1, len(self.cmdctl.sent_messages))
+ self.assertEqual(({'command': ['shutdown']}, 'Init'),
+ self.cmdctl.sent_messages[-1])
+ self.assertEqual(1, rcode)
+
+ # Send a command to cmdctl itself. Should be the same effect.
+ rcode, value = self.cmdctl.send_command('Cmdctl', 'shutdown',
+ None)
+ self.assertEqual(2, len(self.cmdctl.sent_messages))
+ self.assertEqual(({'command': ['shutdown']}, 'Cmdctl'),
+ self.cmdctl.sent_messages[-1])
+ self.assertEqual(1, rcode)
class MySecureHTTPServer(SecureHTTPServer):
def server_bind(self):
@@ -470,15 +606,101 @@ class TestSecureHTTPServer(unittest.TestCase):
self.assertEqual(1, len(self.server._user_infos))
self.assertTrue('root' in self.server._user_infos)
+ def test_get_user_info(self):
+ self.assertIsNone(self.server.get_user_info('root'))
+ self.server._create_user_info(SRC_FILE_PATH + 'cmdctl-accounts.csv')
+ self.assertIn('6f0c73bd33101a5ec0294b3ca39fec90ef4717fe',
+ self.server.get_user_info('root'))
+
+ # When the file is not changed calling _create_user_info() again
+ # should have no effect. In order to test this, we overwrite the
+ # user-infos that were just set and make sure it isn't touched by
+ # the call (so make sure it isn't set to some empty value)
+ fake_users_val = { 'notinfile': [] }
+ self.server._user_infos = fake_users_val
+ self.server._create_user_info(SRC_FILE_PATH + 'cmdctl-accounts.csv')
+ self.assertEqual(fake_users_val, self.server._user_infos)
+
+ def test_create_user_info_changing_file_time(self):
+ self.assertEqual(0, len(self.server._user_infos))
+
+ # Create a file
+ accounts_file = BUILD_FILE_PATH + 'new_file.csv'
+ with TmpTextFile(accounts_file, ['root,foo,bar']):
+ self.server._create_user_info(accounts_file)
+ self.assertEqual(1, len(self.server._user_infos))
+ self.assertTrue('root' in self.server._user_infos)
+
+ # Make sure re-reading is a noop if file was not modified
+ fake_users_val = { 'notinfile': [] }
+ self.server._user_infos = fake_users_val
+ self.server._create_user_info(accounts_file)
+ self.assertEqual(fake_users_val, self.server._user_infos)
+
+ # create the file again, this time read should not be a noop
+ with TmpTextFile(accounts_file, ['otherroot,foo,bar']):
+ # Set mtime in future
+ stat = os.stat(accounts_file)
+ os.utime(accounts_file, (stat.st_atime, stat.st_mtime + 10))
+ self.server._create_user_info(accounts_file)
+ self.assertEqual(1, len(self.server._user_infos))
+ self.assertTrue('otherroot' in self.server._user_infos)
+
+ def test_create_user_info_changing_file_name(self):
+ """
+ Check that the accounts file is re-read if the file name is different
+ """
+ self.assertEqual(0, len(self.server._user_infos))
+
+ # Create two files
+ accounts_file1 = BUILD_FILE_PATH + 'new_file.csv'
+ accounts_file2 = BUILD_FILE_PATH + 'new_file2.csv'
+ with TmpTextFile(accounts_file2, ['otherroot,foo,bar']):
+ with TmpTextFile(accounts_file1, ['root,foo,bar']):
+ self.server._create_user_info(accounts_file1)
+ self.assertEqual(1, len(self.server._user_infos))
+ self.assertTrue('root' in self.server._user_infos)
+
+ # Make sure re-reading is a noop if file was not modified
+ fake_users_val = { 'notinfile': [] }
+ self.server._user_infos = fake_users_val
+ self.server._create_user_info(accounts_file1)
+ self.assertEqual(fake_users_val, self.server._user_infos)
+
+ # But a different file should be read
+ self.server._create_user_info(accounts_file2)
+ self.assertEqual(1, len(self.server._user_infos))
+ self.assertTrue('otherroot' in self.server._user_infos)
+
+ def test_create_user_info_nonexistent_file(self):
+ # Even if there was data initially, if set to a nonexistent
+ # file it should result in no users
+ accounts_file = BUILD_FILE_PATH + 'new_file.csv'
+ self.assertFalse(os.path.exists(accounts_file))
+ fake_users_val = { 'notinfile': [] }
+ self.server._user_infos = fake_users_val
+ self.server._create_user_info(accounts_file)
+ self.assertEqual({}, self.server._user_infos)
+
+ # Should it now be created it should be read
+ with TmpTextFile(accounts_file, ['root,foo,bar']):
+ self.server._create_user_info(accounts_file)
+ self.assertEqual(1, len(self.server._user_infos))
+ self.assertTrue('root' in self.server._user_infos)
+
def test_check_file(self):
# Just some file that we know exists
file_name = BUILD_FILE_PATH + 'cmdctl-keyfile.pem'
check_file(file_name)
- with UnreadableFile(file_name):
- self.assertRaises(CmdctlException, check_file, file_name)
self.assertRaises(CmdctlException, check_file, '/local/not-exist')
self.assertRaises(CmdctlException, check_file, '/')
+ @unittest.skipIf(os.getuid() == 0,
+ 'test cannot be run as root user')
+ def test_check_file_for_unreadable(self):
+ file_name = BUILD_FILE_PATH + 'cmdctl-keyfile.pem'
+ with UnreadableFile(file_name):
+ self.assertRaises(CmdctlException, check_file, file_name)
def test_check_key_and_cert(self):
keyfile = BUILD_FILE_PATH + 'cmdctl-keyfile.pem'
@@ -496,6 +718,15 @@ class TestSecureHTTPServer(unittest.TestCase):
self.assertRaises(CmdctlException, self.server._check_key_and_cert,
'/', certfile)
+ # All OK (also happens to check the context code above works)
+ self.server._check_key_and_cert(keyfile, certfile)
+
+ @unittest.skipIf(os.getuid() == 0,
+ 'test cannot be run as root user')
+ def test_check_key_and_cert_for_unreadable(self):
+ keyfile = BUILD_FILE_PATH + 'cmdctl-keyfile.pem'
+ certfile = BUILD_FILE_PATH + 'cmdctl-certfile.pem'
+
# no read permission
with UnreadableFile(certfile):
self.assertRaises(CmdctlException,
@@ -507,21 +738,17 @@ class TestSecureHTTPServer(unittest.TestCase):
self.server._check_key_and_cert,
keyfile, certfile)
- # All OK (also happens to check the context code above works)
- self.server._check_key_and_cert(keyfile, certfile)
-
def test_wrap_sock_in_ssl_context(self):
sock = socket.socket()
- # Bad files should result in a socket.error raised by our own
- # code in the basic file checks
- self.assertRaises(socket.error,
+ # Bad files should result in a CmdctlException in the basic file
+ # checks
+ self.assertRaises(CmdctlException,
self.server._wrap_socket_in_ssl_context,
sock,
'no_such_file', 'no_such_file')
- # Using a non-certificate file would cause an SSLError, which
- # is caught by our code which then raises a basic socket.error
+ # Using a non-certificate file would cause an SSLError
self.assertRaises(socket.error,
self.server._wrap_socket_in_ssl_context,
sock,
@@ -541,7 +768,7 @@ class TestSecureHTTPServer(unittest.TestCase):
orig_check_func = self.server._check_key_and_cert
try:
self.server._check_key_and_cert = lambda x,y: None
- self.assertRaises(socket.error,
+ self.assertRaises(IOError,
self.server._wrap_socket_in_ssl_context,
sock,
'no_such_file', 'no_such_file')
diff --git a/src/bin/d2/.gitignore b/src/bin/d2/.gitignore
new file mode 100644
index 0000000..d147802
--- /dev/null
+++ b/src/bin/d2/.gitignore
@@ -0,0 +1,6 @@
+/b10-dhcp-ddns
+/b10-dhcp-ddns.8
+/d2_messages.cc
+/d2_messages.h
+/spec_config.h
+/spec_config.h.pre
diff --git a/src/bin/d2/Makefile.am b/src/bin/d2/Makefile.am
new file mode 100644
index 0000000..f289815
--- /dev/null
+++ b/src/bin/d2/Makefile.am
@@ -0,0 +1,79 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+if USE_CLANGPP
+# Disable unused parameter warning caused by some Boost headers when compiling with clang
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+CLEANFILES = *.gcno *.gcda spec_config.h d2_messages.h d2_messages.cc
+
+man_MANS = b10-dhcp-ddns.8
+DISTCLEANFILES = $(man_MANS)
+EXTRA_DIST = $(man_MANS) b10-dhcp-ddns.xml dhcp-ddns.spec
+
+if GENERATE_DOCS
+b10-dhcp-ddns.8: b10-dhcp-ddns.xml
+ @XSLTPROC@ --novalid --xinclude --nonet -o $@ \
+ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl \
+ $(srcdir)/b10-dhcp-ddns.xml
+
+else
+
+$(man_MANS):
+ @echo Man generation disabled. Creating dummy $@. Configure with --enable-generate-docs to enable it.
+ @echo Man generation disabled. Remove this file, configure with --enable-generate-docs, and rebuild BIND 10 > $@
+
+endif
+
+spec_config.h: spec_config.h.pre
+ $(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
+
+d2_messages.h d2_messages.cc: d2_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/bin/d2/d2_messages.mes
+
+BUILT_SOURCES = spec_config.h d2_messages.h d2_messages.cc
+
+pkglibexec_PROGRAMS = b10-dhcp-ddns
+
+b10_dhcp_ddns_SOURCES = main.cc
+b10_dhcp_ddns_SOURCES += d2_log.cc d2_log.h
+b10_dhcp_ddns_SOURCES += d2_process.cc d2_process.h
+b10_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
+b10_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
+b10_dhcp_ddns_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
+b10_dhcp_ddns_SOURCES += d2_config.cc d2_config.h
+b10_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
+b10_dhcp_ddns_SOURCES += d2_queue_mgr.cc d2_queue_mgr.h
+b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
+b10_dhcp_ddns_SOURCES += d2_update_mgr.cc d2_update_mgr.h
+b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
+b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
+
+nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
+EXTRA_DIST += d2_messages.mes
+
+b10_dhcp_ddns_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiodns/libb10-asiodns.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
+
+b10_dhcp_ddnsdir = $(pkgdatadir)
+b10_dhcp_ddns_DATA = dhcp-ddns.spec
diff --git a/src/bin/d2/b10-dhcp-ddns.xml b/src/bin/d2/b10-dhcp-ddns.xml
new file mode 100644
index 0000000..b77ff17
--- /dev/null
+++ b/src/bin/d2/b10-dhcp-ddns.xml
@@ -0,0 +1,121 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+ [<!ENTITY mdash "—">]>
+<!--
+ - Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+ -
+ - Permission to use, copy, modify, and/or distribute this software for any
+ - purpose with or without fee is hereby granted, provided that the above
+ - copyright notice and this permission notice appear in all copies.
+ -
+ - THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ - PERFORMANCE OF THIS SOFTWARE.
+-->
+
+<refentry>
+
+ <refentryinfo>
+ <date>May 15, 2013</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>b10-dhcp-ddns</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo>BIND10</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>b10-dhcp-ddns</refname>
+ <refpurpose>DHCP-DDNS process in BIND 10 architecture</refpurpose>
+ </refnamediv>
+
+ <docinfo>
+ <copyright>
+ <year>2013</year>
+ <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+ </copyright>
+ </docinfo>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>b10-dhcp-ddns</command>
+ <arg><option>-v</option></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>b10-dhcp-ddns</command>
+ <arg><option>-s</option></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>
+ The <command>b10-dhcp-ddns</command> service processes requests to
+ to update DNS mapping based on DHCP lease change events. The service
+ may run either as a BIND10 module (integrated mode) or as a individual
+ process (stand-alone mode) dependent upon command line arguments. The
+ default is integrated mode. Stand alone operation is strictly for
+ development purposes and is not suited for production.
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>ARGUMENTS</title>
+
+ <para>The arguments are as follows:</para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><option>-v</option></term>
+ <listitem><para>
+ Verbose mode sets the logging level to debug. This is primarily
+ for development purposes in stand-alone mode.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-s</option></term>
+ <listitem><para>
+ Causes the process to run without attempting to connect to the
+ BIND10 message queue. This is for development purposes.
+ </para></listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>b10-dhcp-ddns</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>HISTORY</title>
+ <para>
+ The <command>b10-dhcp-ddns</command> process was first coded in
+ May 2013 by the ISC Kea/Dhcp team.
+ </para>
+ </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->
diff --git a/src/bin/d2/d2_cfg_mgr.cc b/src/bin/d2/d2_cfg_mgr.cc
new file mode 100644
index 0000000..ef63a0a
--- /dev/null
+++ b/src/bin/d2/d2_cfg_mgr.cc
@@ -0,0 +1,214 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+#include <util/encode/hex.h>
+
+#include <boost/foreach.hpp>
+
+namespace isc {
+namespace d2 {
+
+namespace {
+
+typedef std::vector<uint8_t> ByteAddress;
+
+} // end of unnamed namespace
+
+// *********************** D2CfgContext *************************
+
+D2CfgContext::D2CfgContext()
+ : forward_mgr_(new DdnsDomainListMgr("forward_mgr")),
+ reverse_mgr_(new DdnsDomainListMgr("reverse_mgr")),
+ keys_(new TSIGKeyInfoMap()) {
+}
+
+D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : DCfgContextBase(rhs) {
+ if (rhs.forward_mgr_) {
+ forward_mgr_.reset(new DdnsDomainListMgr(rhs.forward_mgr_->getName()));
+ forward_mgr_->setDomains(rhs.forward_mgr_->getDomains());
+ }
+
+ if (rhs.reverse_mgr_) {
+ reverse_mgr_.reset(new DdnsDomainListMgr(rhs.reverse_mgr_->getName()));
+ reverse_mgr_->setDomains(rhs.reverse_mgr_->getDomains());
+ }
+
+ keys_ = rhs.keys_;
+}
+
+D2CfgContext::~D2CfgContext() {
+}
+
+// *********************** D2CfgMgr *************************
+
+const char* D2CfgMgr::IPV4_REV_ZONE_SUFFIX = "in-addr.arpa.";
+
+const char* D2CfgMgr::IPV6_REV_ZONE_SUFFIX = "ip6.arpa.";
+
+D2CfgMgr::D2CfgMgr() : DCfgMgrBase(DCfgContextBasePtr(new D2CfgContext())) {
+ // TSIG keys need to parse before the Domains, so we can catch Domains
+ // that specify undefined keys. Create the necessary parsing order now.
+ addToParseOrder("interface");
+ addToParseOrder("ip_address");
+ addToParseOrder("port");
+ addToParseOrder("tsig_keys");
+ addToParseOrder("forward_ddns");
+ addToParseOrder("reverse_ddns");
+}
+
+D2CfgMgr::~D2CfgMgr() {
+}
+
+bool
+D2CfgMgr::matchForward(const std::string& fqdn, DdnsDomainPtr& domain) {
+ if (fqdn.empty()) {
+ // This is a programmatic error and should not happen.
+ isc_throw(D2CfgError, "matchForward passed an empty fqdn");
+ }
+
+ // Fetch the forward manager from the D2 context.
+ DdnsDomainListMgrPtr mgr = getD2CfgContext()->getForwardMgr();
+
+ // Call the manager's match method and return the result.
+ return (mgr->matchDomain(fqdn, domain));
+}
+
+bool
+D2CfgMgr::matchReverse(const std::string& ip_address, DdnsDomainPtr& domain) {
+ // Note, reverseIpAddress will throw if the ip_address is invalid.
+ std::string reverse_address = reverseIpAddress(ip_address);
+
+ // Fetch the reverse manager from the D2 context.
+ DdnsDomainListMgrPtr mgr = getD2CfgContext()->getReverseMgr();
+
+ return (mgr->matchDomain(reverse_address, domain));
+}
+
+std::string
+D2CfgMgr::reverseIpAddress(const std::string& address) {
+ try {
+ // Convert string address into an IOAddress and invoke the
+ // appropriate reverse method.
+ isc::asiolink::IOAddress ioaddr(address);
+ if (ioaddr.isV4()) {
+ return (reverseV4Address(ioaddr));
+ }
+
+ return (reverseV6Address(ioaddr));
+
+ } catch (const isc::Exception& ex) {
+ isc_throw(D2CfgError, "D2CfgMgr cannot reverse address: "
+ << address << " : " << ex.what());
+ }
+}
+
+std::string
+D2CfgMgr::reverseV4Address(const isc::asiolink::IOAddress& ioaddr) {
+ if (!ioaddr.isV4()) {
+ isc_throw(D2CfgError, "D2CfgMgr address is not IPv4 address :"
+ << ioaddr.toText());
+ }
+
+ // Get the address in byte vector form.
+ const ByteAddress bytes = ioaddr.toBytes();
+
+ // Walk backwards through vector outputting each octet and a dot.
+ std::ostringstream stream;
+
+ // We have to set the following variable to get
+ // const_reverse_iterator type of rend(), otherwise Solaris GCC
+ // complains on operator!= by trying to use the non-const variant.
+ const ByteAddress::const_reverse_iterator end = bytes.rend();
+
+ for (ByteAddress::const_reverse_iterator rit = bytes.rbegin();
+ rit != end;
+ ++rit)
+ {
+ stream << static_cast<unsigned int>(*rit) << ".";
+ }
+
+ // Tack on the suffix and we're done.
+ stream << IPV4_REV_ZONE_SUFFIX;
+ return(stream.str());
+}
+
+std::string
+D2CfgMgr::reverseV6Address(const isc::asiolink::IOAddress& ioaddr) {
+ if (!ioaddr.isV6()) {
+ isc_throw(D2CfgError, "D2Cfg address is not IPv6 address: "
+ << ioaddr.toText());
+ }
+
+ // Turn the address into a string of digits.
+ const ByteAddress bytes = ioaddr.toBytes();
+ const std::string digits = isc::util::encode::encodeHex(bytes);
+
+ // Walk backwards through string outputting each digits and a dot.
+ std::ostringstream stream;
+
+ // We have to set the following variable to get
+ // const_reverse_iterator type of rend(), otherwise Solaris GCC
+ // complains on operator!= by trying to use the non-const variant.
+ const std::string::const_reverse_iterator end = digits.rend();
+
+ for (std::string::const_reverse_iterator rit = digits.rbegin();
+ rit != end;
+ ++rit)
+ {
+ stream << static_cast<char>(*rit) << ".";
+ }
+
+ // Tack on the suffix and we're done.
+ stream << IPV6_REV_ZONE_SUFFIX;
+ return(stream.str());
+}
+
+
+isc::dhcp::ParserPtr
+D2CfgMgr::createConfigParser(const std::string& config_id) {
+ // Get D2 specific context.
+ D2CfgContextPtr context = getD2CfgContext();
+
+ // Create parser instance based on element_id.
+ isc::dhcp::DhcpConfigParser* parser = NULL;
+ if ((config_id == "interface") ||
+ (config_id == "ip_address")) {
+ parser = new isc::dhcp::StringParser(config_id,
+ context->getStringStorage());
+ } else if (config_id == "port") {
+ parser = new isc::dhcp::Uint32Parser(config_id,
+ context->getUint32Storage());
+ } else if (config_id == "forward_ddns") {
+ parser = new DdnsDomainListMgrParser("forward_mgr",
+ context->getForwardMgr(),
+ context->getKeys());
+ } else if (config_id == "reverse_ddns") {
+ parser = new DdnsDomainListMgrParser("reverse_mgr",
+ context->getReverseMgr(),
+ context->getKeys());
+ } else if (config_id == "tsig_keys") {
+ parser = new TSIGKeyInfoListParser("tsig_key_list", context->getKeys());
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: D2CfgMgr parameter not supported: "
+ << config_id);
+ }
+
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/bin/d2/d2_cfg_mgr.h b/src/bin/d2/d2_cfg_mgr.h
new file mode 100644
index 0000000..c9b794f
--- /dev/null
+++ b/src/bin/d2/d2_cfg_mgr.h
@@ -0,0 +1,237 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 D2_CFG_MGR_H
+#define D2_CFG_MGR_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <d2/d_cfg_mgr.h>
+#include <d2/d2_config.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+class D2CfgContext;
+/// @brief Pointer to a configuration context.
+typedef boost::shared_ptr<D2CfgContext> D2CfgContextPtr;
+
+/// @brief DHCP-DDNS Configuration Context
+///
+/// Implements the storage container for configuration context.
+/// It provides a single enclosure for the storage of configuration parameters
+/// and any other DHCP-DDNS specific information that needs to be accessible
+/// during configuration parsing as well as to the application as a whole.
+/// It is derived from the context base class, DCfgContextBase.
+class D2CfgContext : public DCfgContextBase {
+public:
+ /// @brief Constructor
+ D2CfgContext();
+
+ /// @brief Destructor
+ virtual ~D2CfgContext();
+
+ /// @brief Creates a clone of this context object.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone() {
+ return (DCfgContextBasePtr(new D2CfgContext(*this)));
+ }
+
+ /// @brief Fetches the forward DNS domain list manager.
+ ///
+ /// @return returns a pointer to the forward manager.
+ DdnsDomainListMgrPtr getForwardMgr() {
+ return (forward_mgr_);
+ }
+
+ /// @brief Fetches the reverse DNS domain list manager.
+ ///
+ /// @return returns a pointer to the reverse manager.
+ DdnsDomainListMgrPtr getReverseMgr() {
+ return (reverse_mgr_);
+ }
+
+ /// @brief Fetches the map of TSIG keys.
+ ///
+ /// @return returns a pointer to the key map.
+ TSIGKeyInfoMapPtr getKeys() {
+ return (keys_);
+ }
+
+protected:
+ /// @brief Copy constructor for use by derivations in clone().
+ D2CfgContext(const D2CfgContext& rhs);
+
+private:
+ /// @brief Private assignment operator to avoid potential for slicing.
+ D2CfgContext& operator=(const D2CfgContext& rhs);
+
+ /// @brief Forward domain list manager.
+ DdnsDomainListMgrPtr forward_mgr_;
+
+ /// @brief Reverse domain list manager.
+ DdnsDomainListMgrPtr reverse_mgr_;
+
+ /// @brief Storage for the map of TSIGKeyInfos
+ TSIGKeyInfoMapPtr keys_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+
+
+/// @brief DHCP-DDNS Configuration Manager
+///
+/// Provides the mechanisms for managing the DHCP-DDNS application's
+/// configuration. This includes services for parsing sets of configuration
+/// values, storing the parsed information in its converted form,
+/// and retrieving the information on demand.
+class D2CfgMgr : public DCfgMgrBase {
+public:
+ /// @brief Reverse zone suffix added to IPv4 addresses for reverse lookups
+ /// @todo This should be configurable.
+ static const char* IPV4_REV_ZONE_SUFFIX;
+
+ /// @brief Reverse zone suffix added to IPv6 addresses for reverse lookups
+ /// @todo This should be configurable.
+ static const char* IPV6_REV_ZONE_SUFFIX;
+
+ /// @brief Constructor
+ D2CfgMgr();
+
+ /// @brief Destructor
+ virtual ~D2CfgMgr();
+
+ /// @brief Convenience method that returns the D2 configuration context.
+ ///
+ /// @return returns a pointer to the configuration context.
+ D2CfgContextPtr getD2CfgContext() {
+ return (boost::dynamic_pointer_cast<D2CfgContext>(getContext()));
+ }
+
+ /// @brief Matches a given FQDN to a forward domain.
+ ///
+ /// This calls the matchDomain method of the forward domain manager to
+ /// match the given FQDN to a forward domain.
+ ///
+ /// @param fqdn is the name for which to look.
+ /// @param domain receives the matching domain. Note that it will be reset
+ /// upon entry and only set if a match is subsequently found.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @throw throws D2CfgError if given an invalid fqdn.
+ bool matchForward(const std::string& fqdn, DdnsDomainPtr& domain);
+
+ /// @brief Matches a given IP address to a reverse domain.
+ ///
+ /// This calls the matchDomain method of the reverse domain manager to
+ /// match the given IPv4 or IPv6 address to a reverse domain.
+ ///
+ /// @param ip_address is the name for which to look.
+ /// @param domain receives the matching domain. Note that it will be reset
+ /// upon entry and only set if a match is subsequently found.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @throw throws D2CfgError if given an invalid fqdn.
+ bool matchReverse(const std::string& ip_address, DdnsDomainPtr& domain);
+
+ /// @brief Generate a reverse order string for the given IP address
+ ///
+ /// This method creates a string containing the given IP address
+ /// contents in reverse order. This format is used for matching
+ /// against reverse DDNS domains in DHCP_DDNS configuration.
+ /// After reversing the syllables of the address, it appends the
+ /// appropriate suffix.
+ ///
+ /// @param address string containing a valid IPv4 or IPv6 address.
+ ///
+ /// @return a std::string containing the reverse order address.
+ ///
+ /// @throw D2CfgError if given an invalid address.
+ static std::string reverseIpAddress(const std::string& address);
+
+ /// @brief Generate a reverse order string for the given IP address
+ ///
+ /// This method creates a string containing the given IP address
+ /// contents in reverse order. This format is used for matching
+ /// against reverse DDNS domains in DHCP_DDNS configuration.
+ /// After reversing the syllables of the address, it appends the
+ /// appropriate suffix.
+ ///
+ /// Example:
+ /// input: 192.168.1.15
+ /// output: 15.1.168.192.in-addr.arpa.
+ ///
+ /// @param ioaddr is the IPv4 IOaddress to convert
+ ///
+ /// @return a std::string containing the reverse order address.
+ ///
+ /// @throw D2CfgError if not given an IPv4 address.
+ static std::string reverseV4Address(const isc::asiolink::IOAddress& ioaddr);
+
+ /// @brief Generate a reverse order string for the given IP address
+ ///
+ /// This method creates a string containing the given IPv6 address
+ /// contents in reverse order. This format is used for matching
+ /// against reverse DDNS domains in DHCP_DDNS configuration.
+ /// After reversing the syllables of the address, it appends the
+ /// appropriate suffix.
+ ///
+ /// IPv6 example:
+ /// input: 2001:db8:302:99::
+ /// output:
+ ///0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.9.0.0.2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.
+ ///
+ /// @param address string containing a valid IPv6 address.
+ ///
+ /// @return a std::string containing the reverse order address.
+ ///
+ /// @throw D2CfgError if not given an IPv6 address.
+ static std::string reverseV6Address(const isc::asiolink::IOAddress& ioaddr);
+
+protected:
+ /// @brief Given an element_id returns an instance of the appropriate
+ /// parser.
+ ///
+ /// It is responsible for top-level or outermost DHCP-DDNS configuration
+ /// elements (see dhcp-ddns.spec):
+ /// 1. interface
+ /// 2. ip_address
+ /// 3. port
+ /// 4. forward_ddns
+ /// 5. reverse_ddns
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ ///
+ /// @return returns a ParserPtr to the parser instance.
+ /// @throw throws DCfgMgrBaseError if an error occurs.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& element_id);
+};
+
+/// @brief Defines a shared pointer to D2CfgMgr.
+typedef boost::shared_ptr<D2CfgMgr> D2CfgMgrPtr;
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D2_CFG_MGR_H
diff --git a/src/bin/d2/d2_config.cc b/src/bin/d2/d2_config.cc
new file mode 100644
index 0000000..b2e28d3
--- /dev/null
+++ b/src/bin/d2/d2_config.cc
@@ -0,0 +1,633 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+#include <asiolink/io_error.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace isc {
+namespace d2 {
+
+// *********************** TSIGKeyInfo *************************
+
+TSIGKeyInfo::TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+ const std::string& secret)
+ :name_(name), algorithm_(algorithm), secret_(secret) {
+}
+
+TSIGKeyInfo::~TSIGKeyInfo() {
+}
+
+
+// *********************** DnsServerInfo *************************
+
+const char* DnsServerInfo::EMPTY_IP_STR = "0.0.0.0";
+
+DnsServerInfo::DnsServerInfo(const std::string& hostname,
+ isc::asiolink::IOAddress ip_address, uint32_t port,
+ bool enabled)
+ :hostname_(hostname), ip_address_(ip_address), port_(port),
+ enabled_(enabled) {
+}
+
+DnsServerInfo::~DnsServerInfo() {
+}
+
+// *********************** DdnsDomain *************************
+
+DdnsDomain::DdnsDomain(const std::string& name, const std::string& key_name,
+ DnsServerInfoStoragePtr servers)
+ : name_(name), key_name_(key_name), servers_(servers) {
+}
+
+DdnsDomain::~DdnsDomain() {
+}
+
+// *********************** DdnsDomainLstMgr *************************
+
+const char* DdnsDomainListMgr::wildcard_domain_name_ = "*";
+
+DdnsDomainListMgr::DdnsDomainListMgr(const std::string& name) : name_(name),
+ domains_(new DdnsDomainMap()) {
+}
+
+
+DdnsDomainListMgr::~DdnsDomainListMgr () {
+}
+
+void
+DdnsDomainListMgr::setDomains(DdnsDomainMapPtr domains) {
+ if (!domains) {
+ isc_throw(D2CfgError,
+ "DdnsDomainListMgr::setDomains: Domain list may not be null");
+ }
+
+ domains_ = domains;
+
+ // Look for the wild card domain. If present, set the member variable
+ // to remember it. This saves us from having to look for it every time
+ // we attempt a match.
+ DdnsDomainMap::iterator gotit = domains_->find(wildcard_domain_name_);
+ if (gotit != domains_->end()) {
+ wildcard_domain_ = gotit->second;
+ }
+}
+
+bool
+DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
+ // First check the case of one domain to rule them all.
+ if ((size() == 1) && (wildcard_domain_)) {
+ domain = wildcard_domain_;
+ return (true);
+ }
+
+ // Iterate over the domain map looking for the domain which matches
+ // the longest portion of the given fqdn.
+
+ size_t req_len = fqdn.size();
+ size_t match_len = 0;
+ DdnsDomainMapPair map_pair;
+ DdnsDomainPtr best_match;
+ BOOST_FOREACH (map_pair, *domains_) {
+ std::string domain_name = map_pair.first;
+ size_t dom_len = domain_name.size();
+
+ // If the domain name is longer than the fqdn, then it cant be match.
+ if (req_len < dom_len) {
+ continue;
+ }
+
+ // If the lengths are identical and the names match we're done.
+ if (req_len == dom_len) {
+ if (fqdn == domain_name) {
+ // exact match, done
+ domain = map_pair.second;
+ return (true);
+ }
+ } else {
+ // The fqdn is longer than the domain name. Adjust the start
+ // point of comparison by the excess in length. Only do the
+ // comparison if the adjustment lands on a boundary. This
+ // prevents "onetwo.net" from matching "two.net".
+ size_t offset = req_len - dom_len;
+ if ((fqdn[offset - 1] == '.') &&
+ (fqdn.compare(offset, std::string::npos, domain_name) == 0)) {
+ // Fqdn contains domain name, keep it if its better than
+ // any we have matched so far.
+ if (dom_len > match_len) {
+ match_len = dom_len;
+ best_match = map_pair.second;
+ }
+ }
+ }
+ }
+
+ if (!best_match) {
+ // There's no match. If they specified a wild card domain use it
+ // otherwise there's no domain for this entry.
+ if (wildcard_domain_) {
+ domain = wildcard_domain_;
+ return (true);
+ }
+
+ LOG_WARN(dctl_logger, DHCP_DDNS_NO_MATCH).arg(fqdn);
+ return (false);
+ }
+
+ domain = best_match;
+ return (true);
+}
+
+// *************************** PARSERS ***********************************
+
+// *********************** TSIGKeyInfoParser *************************
+
+TSIGKeyInfoParser::TSIGKeyInfoParser(const std::string& entry_name,
+ TSIGKeyInfoMapPtr keys)
+ : entry_name_(entry_name), keys_(keys), local_scalars_() {
+ if (!keys_) {
+ isc_throw(D2CfgError, "TSIGKeyInfoParser ctor:"
+ " key storage cannot be null");
+ }
+}
+
+TSIGKeyInfoParser::~TSIGKeyInfoParser() {
+}
+
+void
+TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
+ isc::dhcp::ConfigPair config_pair;
+ // For each element in the key configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ BOOST_FOREACH (config_pair, key_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+}
+
+isc::dhcp::ParserPtr
+TSIGKeyInfoParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ // Based on the configuration id of the element, create the appropriate
+ // parser. Scalars are set to use the parser's local scalar storage.
+ if ((config_id == "name") ||
+ (config_id == "algorithm") ||
+ (config_id == "secret")) {
+ parser = new isc::dhcp::StringParser(config_id,
+ local_scalars_.getStringStorage());
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: TSIGKeyInfo parameter not supported: "
+ << config_id);
+ }
+
+ // Return the new parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+TSIGKeyInfoParser::commit() {
+ std::string name;
+ std::string algorithm;
+ std::string secret;
+
+ // Fetch the key configuration's parsed scalar values from parser's
+ // local storage.
+ local_scalars_.getParam("name", name);
+ local_scalars_.getParam("algorithm", algorithm);
+ local_scalars_.getParam("secret", secret);
+
+ // @todo Validation here is very superficial. This will expand as TSIG
+ // Key use is more fully implemented.
+
+ // Name cannot be blank.
+ if (name.empty()) {
+ isc_throw(D2CfgError, "TSIG Key Info must specify name");
+ }
+
+ // Algorithm cannot be blank.
+ if (algorithm.empty()) {
+ isc_throw(D2CfgError, "TSIG Key Info must specify algorithm");
+ }
+
+ // Secret cannot be blank.
+ if (secret.empty()) {
+ isc_throw(D2CfgError, "TSIG Key Info must specify secret");
+ }
+
+ // Currently, the premise is that key storage is always empty prior to
+ // parsing so we are always adding keys never replacing them. Duplicates
+ // are not allowed and should be flagged as a configuration error.
+ if (keys_->find(name) != keys_->end()) {
+ isc_throw(D2CfgError, "Duplicate TSIG key specified:" << name);
+ }
+
+ TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));
+
+ // Add the new TSIGKeyInfo to the key storage.
+ (*keys_)[name]=key_info;
+}
+
+// *********************** TSIGKeyInfoListParser *************************
+
+TSIGKeyInfoListParser::TSIGKeyInfoListParser(const std::string& list_name,
+ TSIGKeyInfoMapPtr keys)
+ :list_name_(list_name), keys_(keys), local_keys_(new TSIGKeyInfoMap()),
+ parsers_() {
+ if (!keys_) {
+ isc_throw(D2CfgError, "TSIGKeyInfoListParser ctor:"
+ " key storage cannot be null");
+ }
+}
+
+TSIGKeyInfoListParser::~TSIGKeyInfoListParser(){
+}
+
+void
+TSIGKeyInfoListParser::
+build(isc::data::ConstElementPtr key_list){
+ int i = 0;
+ isc::data::ConstElementPtr key_config;
+ // For each key element in the key list:
+ // 1. Create a parser for the key element.
+ // 2. Invoke the parser's build method passing in the key's
+ // configuration.
+ // 3. Add the parser to a local collection of parsers.
+ BOOST_FOREACH(key_config, key_list->listValue()) {
+ // Create a name for the parser based on its position in the list.
+ std::string entry_name = boost::lexical_cast<std::string>(i++);
+ isc::dhcp::ParserPtr parser(new TSIGKeyInfoParser(entry_name,
+ local_keys_));
+ parser->build(key_config);
+ parsers_.push_back(parser);
+ }
+}
+
+void
+TSIGKeyInfoListParser::commit() {
+ // Invoke commit on each server parser. This will cause each one to
+ // create it's server instance and commit it to storage.
+ BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+
+ // Now that we know we have a valid list, commit that list to the
+ // area given to us during construction (i.e. to the d2 context).
+ *keys_ = *local_keys_;
+}
+
+// *********************** DnsServerInfoParser *************************
+
+DnsServerInfoParser::DnsServerInfoParser(const std::string& entry_name,
+ DnsServerInfoStoragePtr servers)
+ : entry_name_(entry_name), servers_(servers), local_scalars_() {
+ if (!servers_) {
+ isc_throw(D2CfgError, "DnsServerInfoParser ctor:"
+ " server storage cannot be null");
+ }
+}
+
+DnsServerInfoParser::~DnsServerInfoParser() {
+}
+
+void
+DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
+ isc::dhcp::ConfigPair config_pair;
+ // For each element in the server configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ BOOST_FOREACH (config_pair, server_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+
+}
+
+isc::dhcp::ParserPtr
+DnsServerInfoParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ // Based on the configuration id of the element, create the appropriate
+ // parser. Scalars are set to use the parser's local scalar storage.
+ if ((config_id == "hostname") ||
+ (config_id == "ip_address")) {
+ parser = new isc::dhcp::StringParser(config_id,
+ local_scalars_.getStringStorage());
+ } else if (config_id == "port") {
+ parser = new isc::dhcp::Uint32Parser(config_id,
+ local_scalars_.getUint32Storage());
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: DnsServerInfo parameter not supported: "
+ << config_id);
+ }
+
+ // Return the new parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DnsServerInfoParser::commit() {
+ std::string hostname;
+ std::string ip_address;
+ uint32_t port = DnsServerInfo::STANDARD_DNS_PORT;
+
+ // Fetch the server configuration's parsed scalar values from parser's
+ // local storage.
+ local_scalars_.getParam("hostname", hostname, DCfgContextBase::OPTIONAL);
+ local_scalars_.getParam("ip_address", ip_address,
+ DCfgContextBase::OPTIONAL);
+ local_scalars_.getParam("port", port, DCfgContextBase::OPTIONAL);
+
+ // The configuration must specify one or the other.
+ if (hostname.empty() == ip_address.empty()) {
+ isc_throw(D2CfgError, "Dns Server must specify one or the other"
+ " of hostname and IP address");
+ }
+
+ DnsServerInfoPtr serverInfo;
+ if (!hostname.empty()) {
+ // When hostname is specified, create a valid, blank IOAddress and
+ // then create the DnsServerInfo.
+ isc::asiolink::IOAddress io_addr(DnsServerInfo::EMPTY_IP_STR);
+ serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
+ } else {
+ try {
+ // Create an IOAddress from the IP address string given and then
+ // create the DnsServerInfo.
+ isc::asiolink::IOAddress io_addr(ip_address);
+ serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
+ } catch (const isc::asiolink::IOError& ex) {
+ isc_throw(D2CfgError, "Invalid IP address:" << ip_address);
+ }
+ }
+
+ // Add the new DnsServerInfo to the server storage.
+ servers_->push_back(serverInfo);
+}
+
+// *********************** DnsServerInfoListParser *************************
+
+DnsServerInfoListParser::DnsServerInfoListParser(const std::string& list_name,
+ DnsServerInfoStoragePtr servers)
+ :list_name_(list_name), servers_(servers), parsers_() {
+ if (!servers_) {
+ isc_throw(D2CfgError, "DdnsServerInfoListParser ctor:"
+ " server storage cannot be null");
+ }
+}
+
+DnsServerInfoListParser::~DnsServerInfoListParser(){
+}
+
+void
+DnsServerInfoListParser::
+build(isc::data::ConstElementPtr server_list){
+ int i = 0;
+ isc::data::ConstElementPtr server_config;
+ // For each server element in the server list:
+ // 1. Create a parser for the server element.
+ // 2. Invoke the parser's build method passing in the server's
+ // configuration.
+ // 3. Add the parser to a local collection of parsers.
+ BOOST_FOREACH(server_config, server_list->listValue()) {
+ // Create a name for the parser based on its position in the list.
+ std::string entry_name = boost::lexical_cast<std::string>(i++);
+ isc::dhcp::ParserPtr parser(new DnsServerInfoParser(entry_name,
+ servers_));
+ parser->build(server_config);
+ parsers_.push_back(parser);
+ }
+}
+
+void
+DnsServerInfoListParser::commit() {
+ // Domains must have at least one server.
+ if (parsers_.size() == 0) {
+ isc_throw (D2CfgError, "Server List must contain at least one server");
+ }
+
+ // Invoke commit on each server parser. This will cause each one to
+ // create it's server instance and commit it to storage.
+ BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+}
+
+// *********************** DdnsDomainParser *************************
+
+DdnsDomainParser::DdnsDomainParser(const std::string& entry_name,
+ DdnsDomainMapPtr domains,
+ TSIGKeyInfoMapPtr keys)
+ : entry_name_(entry_name), domains_(domains), keys_(keys),
+ local_servers_(new DnsServerInfoStorage()), local_scalars_() {
+ if (!domains_) {
+ isc_throw(D2CfgError,
+ "DdnsDomainParser ctor, domain storage cannot be null");
+ }
+}
+
+
+DdnsDomainParser::~DdnsDomainParser() {
+}
+
+void
+DdnsDomainParser::build(isc::data::ConstElementPtr domain_config) {
+ // For each element in the domain configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ isc::dhcp::ConfigPair config_pair;
+ BOOST_FOREACH(config_pair, domain_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+}
+
+isc::dhcp::ParserPtr
+DdnsDomainParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ // Based on the configuration id of the element, create the appropriate
+ // parser. Scalars are set to use the parser's local scalar storage.
+ if ((config_id == "name") ||
+ (config_id == "key_name")) {
+ parser = new isc::dhcp::StringParser(config_id,
+ local_scalars_.getStringStorage());
+ } else if (config_id == "dns_servers") {
+ // Server list parser is given in our local server storage. It will pass
+ // this down to its server parsers and is where they will write their
+ // server instances upon commit.
+ parser = new DnsServerInfoListParser(config_id, local_servers_);
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: DdnsDomain parameter not supported: "
+ << config_id);
+ }
+
+ // Return the new domain parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DdnsDomainParser::commit() {
+ std::string name;
+ std::string key_name;
+
+ // Domain name is not optional. The get will throw if its not there.
+ local_scalars_.getParam("name", name);
+
+ // Blank domain names are not allowed.
+ if (name.empty()) {
+ isc_throw(D2CfgError, "Domain name cannot be blank");
+ }
+
+ // Currently, the premise is that domain storage is always empty
+ // prior to parsing so always adding domains never replacing them.
+ // Duplicates are not allowed and should be flagged as a configuration
+ // error.
+ if (domains_->find(name) != domains_->end()) {
+ isc_throw(D2CfgError, "Duplicate domain specified:" << name);
+ }
+
+ // Key name is optional. If it is not blank, then validate it against
+ // the defined list of keys.
+ local_scalars_.getParam("key_name", key_name, DCfgContextBase::OPTIONAL);
+ if (!key_name.empty()) {
+ if ((!keys_) || (keys_->find(key_name) == keys_->end())) {
+ isc_throw(D2CfgError, "DdnsDomain :" << name <<
+ " specifies and undefined key:" << key_name);
+ }
+ }
+
+ // Instantiate the new domain and add it to domain storage.
+ DdnsDomainPtr domain(new DdnsDomain(name, key_name, local_servers_));
+
+ // Add the new domain to the domain storage.
+ (*domains_)[name]=domain;
+}
+
+// *********************** DdnsDomainListParser *************************
+
+DdnsDomainListParser::DdnsDomainListParser(const std::string& list_name,
+ DdnsDomainMapPtr domains,
+ TSIGKeyInfoMapPtr keys)
+ :list_name_(list_name), domains_(domains), keys_(keys), parsers_() {
+ if (!domains_) {
+ isc_throw(D2CfgError, "DdnsDomainListParser ctor:"
+ " domain storage cannot be null");
+ }
+}
+
+DdnsDomainListParser::~DdnsDomainListParser(){
+}
+
+void
+DdnsDomainListParser::
+build(isc::data::ConstElementPtr domain_list){
+ // For each domain element in the domain list:
+ // 1. Create a parser for the domain element.
+ // 2. Invoke the parser's build method passing in the domain's
+ // configuration.
+ // 3. Add the parser to the local collection of parsers.
+ int i = 0;
+ isc::data::ConstElementPtr domain_config;
+ BOOST_FOREACH(domain_config, domain_list->listValue()) {
+ std::string entry_name = boost::lexical_cast<std::string>(i++);
+ isc::dhcp::ParserPtr parser(new DdnsDomainParser(entry_name,
+ domains_, keys_));
+ parser->build(domain_config);
+ parsers_.push_back(parser);
+ }
+}
+
+void
+DdnsDomainListParser::commit() {
+ // Invoke commit on each server parser. This will cause each one to
+ // create it's server instance and commit it to storage.
+ BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+}
+
+
+// *********************** DdnsDomainListMgrParser *************************
+
+DdnsDomainListMgrParser::DdnsDomainListMgrParser(const std::string& entry_name,
+ DdnsDomainListMgrPtr mgr, TSIGKeyInfoMapPtr keys)
+ : entry_name_(entry_name), mgr_(mgr), keys_(keys),
+ local_domains_(new DdnsDomainMap()), local_scalars_() {
+}
+
+
+DdnsDomainListMgrParser::~DdnsDomainListMgrParser() {
+}
+
+void
+DdnsDomainListMgrParser::build(isc::data::ConstElementPtr domain_config) {
+ // For each element in the domain manager configuration:
+ // 1. Create a parser for the element.
+ // 2. Invoke the parser's build method passing in the element's
+ // configuration.
+ // 3. Invoke the parser's commit method to store the element's parsed
+ // data to the parser's local storage.
+ isc::dhcp::ConfigPair config_pair;
+ BOOST_FOREACH(config_pair, domain_config->mapValue()) {
+ isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+}
+
+isc::dhcp::ParserPtr
+DdnsDomainListMgrParser::createConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ if (config_id == "ddns_domains") {
+ // Domain list parser is given our local domain storage. It will pass
+ // this down to its domain parsers and is where they will write their
+ // domain instances upon commit.
+ parser = new DdnsDomainListParser(config_id, local_domains_, keys_);
+ } else {
+ isc_throw(NotImplemented, "parser error: "
+ "DdnsDomainListMgr parameter not supported: " << config_id);
+ }
+
+ // Return the new domain parser instance.
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DdnsDomainListMgrParser::commit() {
+ // Add the new domain to the domain storage.
+ mgr_->setDomains(local_domains_);
+}
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/bin/d2/d2_config.h b/src/bin/d2/d2_config.h
new file mode 100644
index 0000000..8e3bd34
--- /dev/null
+++ b/src/bin/d2/d2_config.h
@@ -0,0 +1,952 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 D2_CONFIG_H
+#define D2_CONFIG_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <d2/d_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/foreach.hpp>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @file d2_config.h
+/// @brief A collection of classes for housing and parsing the application
+/// configuration necessary for the DHCP-DDNS application (aka D2).
+///
+/// This file contains the class declarations for the class hierarchy created
+/// from the D2 configuration and the parser classes used to create it.
+/// The application configuration consists of a set of scalar parameters,
+/// a list of TSIG keys, and two managed lists of domains: one list for
+/// forward domains and one list for reverse domains.
+///
+/// The key list consists of one or more TSIG keys, each entry described by
+/// a name, the algorithm method name, and its secret key component.
+///
+/// @todo NOTE that TSIG configuration parsing is functional, the use of
+/// TSIG Keys during the actual DNS update transactions is not. This will be
+/// implemented in a future release.
+///
+/// Each managed domain list consists of a list one or more domains and is
+/// represented by the class DdnsDomainListMgr.
+///
+/// Each domain consists of a set of scalars parameters and a list of DNS
+/// servers which support that domain. Among its scalars, is key_name, which
+/// is the name of the TSIG Key to use for with this domain. This value should
+/// map to one of the TSIG Keys in the key list. Domains are represented by
+/// the class, DdnsDomain.
+///
+/// Each server consists of a set of scalars used to describe the server such
+/// that the application can carry out DNS update exchanges with it. Servers
+/// are represented by the class, DnsServerInfo.
+///
+/// The configuration specification for use with BIND10 is detailed in the file
+/// dhcp-ddns.spec.
+///
+/// The parsing class hierarchy reflects this same scheme. Working top down:
+///
+/// A DdnsDomainListMgrParser parses a managed domain list entry. It handles
+/// any scalars which belong to the manager as well as creating and invoking a
+/// DdnsDomainListParser to parse its list of domain entries.
+///
+/// A DdnsDomainLiatParser creates and invokes DdnsDomainListParser for each
+/// domain entry in its list.
+///
+/// A DdnsDomainParser handles the scalars which belong to the domain as well as
+/// creating and invoking a DnsSeverInfoListParser to parse its list of server
+/// entries.
+///
+/// A DnsServerInfoListParser creates and invokes a DnsServerInfoParser for
+/// each server entry in its list.
+///
+/// A DdnsServerInfoParser handles the scalars which belong to the server.
+/// The following is sample configuration in JSON form with extra spacing
+/// for clarity:
+///
+/// @code
+/// {
+/// "interface" : "eth1" ,
+/// "ip_address" : "192.168.1.33" ,
+/// "port" : 88 ,
+/// "tsig_keys":
+//// [
+/// {
+/// "name": "d2_key.tmark.org" ,
+/// "algorithm": "md5" ,
+/// "secret": "0123456989"
+/// }
+/// ],
+/// "forward_ddns" :
+/// {
+/// "ddns_domains":
+/// [
+/// {
+/// "name": "tmark.org." ,
+/// "key_name": "d2_key.tmark.org" ,
+/// "dns_servers" :
+/// [
+/// { "hostname": "fserver.tmark.org" },
+/// { "hostname": "f2server.tmark.org" }
+/// ]
+/// },
+/// {
+/// "name": "pub.tmark.org." ,
+/// "key_name": "d2_key.tmark.org" ,
+/// "dns_servers" :
+/// [
+/// { "hostname": "f3server.tmark.org" }
+/// ]
+/// }
+/// ]
+/// },
+/// "reverse_ddns" :
+/// {
+/// "ddns_domains":
+/// [
+/// {
+/// "name": " 0.168.192.in.addr.arpa." ,
+/// "key_name": "d2_key.tmark.org" ,
+/// "dns_servers" :
+/// [
+/// { "ip_address": "127.0.0.101" , "port": 100 }
+/// ]
+/// }
+/// ]
+/// }
+/// }
+/// @endcode
+
+/// @brief Exception thrown when the error during configuration handling
+/// occurs.
+class D2CfgError : public isc::Exception {
+public:
+ D2CfgError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Represents a TSIG Key.
+///
+/// Currently, this is simple storage class containing the basic attributes of
+/// a TSIG Key. It is intended primarily as a reference for working with
+/// actual keys and may eventually be replaced by isc::dns::TSIGKey. TSIG Key
+/// functionality at this stage is strictly limited to configuration parsing.
+/// @todo full functionality for using TSIG during DNS updates will be added
+/// in a future release.
+class TSIGKeyInfo {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param name the unique label used to identify this key
+ /// @param algorithm the name of the encryption alogirthm this key uses.
+ /// (@todo This will be a fixed list of choices)
+ /// @param secret the secret component of this key
+ TSIGKeyInfo(const std::string& name, const std::string& algorithm,
+ const std::string& secret);
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfo();
+
+ /// @brief Getter which returns the key's name.
+ ///
+ /// @return returns the name as as std::string.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Getter which returns the key's algorithm.
+ ///
+ /// @return returns the algorithm as as std::string.
+ const std::string getAlgorithm() const {
+ return (algorithm_);
+ }
+
+ /// @brief Getter which returns the key's secret.
+ ///
+ /// @return returns the secret as as std::string.
+ const std::string getSecret() const {
+ return (secret_);
+ }
+
+private:
+ /// @brief The name of the key.
+ ///
+ /// This value is the unique identifeir thay domains use to
+ /// to specify which TSIG key they need.
+ std::string name_;
+
+ /// @brief The algorithm that should be used for this key.
+ std::string algorithm_;
+
+ /// @brief The secret value component of this key.
+ std::string secret_;
+};
+
+/// @brief Defines a pointer for TSIGKeyInfo instances.
+typedef boost::shared_ptr<TSIGKeyInfo> TSIGKeyInfoPtr;
+
+/// @brief Defines a map of TSIGKeyInfos, keyed by the name.
+typedef std::map<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMap;
+
+/// @brief Defines a iterator pairing of name and TSIGKeyInfo
+typedef std::pair<std::string, TSIGKeyInfoPtr> TSIGKeyInfoMapPair;
+
+/// @brief Defines a pointer to map of TSIGkeyInfos
+typedef boost::shared_ptr<TSIGKeyInfoMap> TSIGKeyInfoMapPtr;
+
+
+/// @brief Represents a specific DNS Server.
+/// It provides information about the server's network identity and typically
+/// belongs to a list of servers supporting DNS for a given domain. It will
+/// be used to establish communications with the server to carry out DNS
+/// updates.
+class DnsServerInfo {
+public:
+
+ /// @brief defines DNS standard port value
+ static const uint32_t STANDARD_DNS_PORT = 53;
+
+ /// @brief defines an "empty" string version of an ip address.
+ static const char* EMPTY_IP_STR;
+
+
+ /// @brief Constructor
+ ///
+ /// @param hostname is the resolvable name of the server. If not blank,
+ /// then the server address should be resolved at runtime.
+ /// @param ip_address is the static IP address of the server. If hostname
+ /// is blank, then this address should be used to connect to the server.
+ /// @param port is the port number on which the server listens.
+ /// primarily meant for testing purposes. Normally, DNS traffic is on
+ /// is port 53. (NOTE the constructing code is responsible for setting
+ /// the default.)
+ /// @param enabled is a flag that indicates whether this server is
+ /// enabled for use. It defaults to true.
+ DnsServerInfo(const std::string& hostname,
+ isc::asiolink::IOAddress ip_address,
+ uint32_t port = STANDARD_DNS_PORT,
+ bool enabled=true);
+
+ /// @brief Destructor
+ virtual ~DnsServerInfo();
+
+ /// @brief Getter which returns the server's hostname.
+ ///
+ /// @return returns the hostname as as std::string.
+ const std::string getHostname() const {
+ return (hostname_);
+ }
+
+ /// @brief Getter which returns the server's port number.
+ ///
+ /// @return returns the port number as a unsigned integer.
+ uint32_t getPort() const {
+ return (port_);
+ }
+
+ /// @brief Getter which returns the server's ip_address.
+ ///
+ /// @return returns the address as an IOAddress reference.
+ const isc::asiolink::IOAddress& getIpAddress() const {
+ return (ip_address_);
+ }
+
+ /// @brief Convenience method which returns whether or not the
+ /// server is enabled.
+ ///
+ /// @return returns true if the server is enabled, false otherwise.
+ bool isEnabled() const {
+ return (enabled_);
+ }
+
+ /// @brief Sets the server's enabled flag to true.
+ void enable() {
+ enabled_ = true;
+ }
+
+ /// @brief Sets the server's enabled flag to false.
+ void disable() {
+ enabled_ = false;
+ }
+
+
+private:
+ /// @brief The resolvable name of the server. If not blank, then the
+ /// server's IP address should be dynamically resolved at runtime.
+ std::string hostname_;
+
+ /// @brief The static IP address of the server. When hostname is blank,
+ /// then this address should be used to connect to the server.
+ isc::asiolink::IOAddress ip_address_;
+
+ /// @brief The port number on which the server listens for DNS traffic.
+ uint32_t port_;
+
+ /// @param enabled is a flag that indicates whether this server is
+ /// enabled for use. It defaults to true.
+ bool enabled_;
+};
+
+/// @brief Defines a pointer for DnsServerInfo instances.
+typedef boost::shared_ptr<DnsServerInfo> DnsServerInfoPtr;
+
+/// @brief Defines a storage container for DnsServerInfo pointers.
+typedef std::vector<DnsServerInfoPtr> DnsServerInfoStorage;
+
+/// @brief Defines a pointer to DnsServerInfo storage containers.
+typedef boost::shared_ptr<DnsServerInfoStorage> DnsServerInfoStoragePtr;
+
+
+/// @brief Represents a DNS domain that is may be updated dynamically.
+/// This class specifies a DNS domain and the list of DNS servers that support
+/// it. It's primary use is to map a domain to the DNS server(s) responsible
+/// for it.
+/// @todo Currently the name entry for a domain is just an std::string. It
+/// may be worthwhile to change this to a dns::Name for purposes of better
+/// validation and matching capabilities.
+class DdnsDomain {
+public:
+ /// @brief Constructor
+ ///
+ /// @param name is the domain name of the domain.
+ /// @param key_name is the TSIG key name for use with this domain.
+ /// @param servers is the list of server(s) supporting this domain.
+ DdnsDomain(const std::string& name, const std::string& key_name,
+ DnsServerInfoStoragePtr servers);
+
+ /// @brief Destructor
+ virtual ~DdnsDomain();
+
+ /// @brief Getter which returns the domain's name.
+ ///
+ /// @return returns the name in an std::string.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Getter which returns the domain's TSIG key name.
+ ///
+ /// @return returns the key name in an std::string.
+ const std::string getKeyName() const {
+ return (key_name_);
+ }
+
+ /// @brief Getter which returns the domain's list of servers.
+ ///
+ /// @return returns the pointer to the server storage.
+ const DnsServerInfoStoragePtr& getServers() {
+ return (servers_);
+ }
+
+private:
+ /// @brief The domain name of the domain.
+ std::string name_;
+
+ /// @brief The name of the TSIG key for use with this domain.
+ std::string key_name_;
+
+ /// @brief The list of server(s) supporting this domain.
+ DnsServerInfoStoragePtr servers_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomain> DdnsDomainPtr;
+
+/// @brief Defines a map of DdnsDomains, keyed by the domain name.
+typedef std::map<std::string, DdnsDomainPtr> DdnsDomainMap;
+
+/// @brief Defines a iterator pairing domain name and DdnsDomain
+typedef std::pair<std::string, DdnsDomainPtr> DdnsDomainMapPair;
+
+/// @brief Defines a pointer to DdnsDomain storage containers.
+typedef boost::shared_ptr<DdnsDomainMap> DdnsDomainMapPtr;
+
+/// @brief Provides storage for and management of a list of DNS domains.
+/// In addition to housing the domain list storage, it provides domain matching
+/// services. These services are used to match a FQDN to a domain. Currently
+/// it supports a single matching service, which will return the matching
+/// domain or a wild card domain if one is specified. The wild card domain is
+/// specified as a domain whose name is "*". The wild card domain will match
+/// any entry and is provided for flexibility in FQDNs If for instance, all
+/// forward requests are handled by the same servers, the configuration could
+/// specify the wild card domain as the only forward domain. All forward DNS
+/// updates would be sent to that one list of servers, regardless of the FQDN.
+/// As matching capabilities evolve this class is expected to expand.
+class DdnsDomainListMgr {
+public:
+ /// @brief defines the domain name for denoting the wildcard domain.
+ static const char* wildcard_domain_name_;
+
+ /// @brief Constructor
+ ///
+ /// @param name is an arbitrary label assigned to this manager.
+ DdnsDomainListMgr(const std::string& name);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListMgr ();
+
+ /// @brief Matches a given name to a domain based on a longest match
+ /// scheme.
+ ///
+ /// Given a FQDN, search the list of domains, successively removing a
+ /// sub-domain from the FQDN until a match is found. If no match is found
+ /// and the wild card domain is present in the list, then return it as the
+ /// match. If the wild card domain is the only domain in the list, then
+ /// it will be returned immediately for any FQDN.
+ ///
+ /// @param fqdn is the name for which to look.
+ /// @param domain receives the matching domain. If no match is found its
+ /// contents will be unchanged.
+ ///
+ /// @return returns true if a match is found, false otherwise.
+ /// @todo This is a very basic match method, which expects valid FQDNs
+ /// both as input and for the DdnsDomain::getName(). Currently both are
+ /// simple strings and there is no normalization (i.e. added trailing dots
+ /// if missing).
+ virtual bool matchDomain(const std::string& fqdn, DdnsDomainPtr& domain);
+
+ /// @brief Fetches the manager's name.
+ ///
+ /// @return returns a std::string containing the name of the manager.
+ const std::string getName() const {
+ return (name_);
+ }
+
+ /// @brief Returns the number of domains in the domain list.
+ ///
+ /// @brief returns an unsigned int containing the domain count.
+ uint32_t size() const {
+ return (domains_->size());
+ }
+
+ /// @brief Fetches the wild card domain.
+ ///
+ /// @return returns a pointer reference to the domain. The pointer will
+ /// empty if the wild card domain is not present.
+ const DdnsDomainPtr& getWildcardDomain() {
+ return (wildcard_domain_);
+ }
+
+ /// @brief Fetches the domain list.
+ ///
+ /// @return returns a pointer reference to the list of domains.
+ const DdnsDomainMapPtr &getDomains() {
+ return (domains_);
+ }
+
+ /// @brief Sets the manger's domain list to the given list of domains.
+ /// This method will scan the inbound list for the wild card domain and
+ /// set the internal wild card domain pointer accordingly.
+ void setDomains(DdnsDomainMapPtr domains);
+
+private:
+ /// @brief An arbitrary label assigned to this manager.
+ std::string name_;
+
+ /// @brief Map of the domains, keyed by name.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Pointer to the wild card domain.
+ DdnsDomainPtr wildcard_domain_;
+};
+
+/// @brief Defines a pointer for DdnsDomain instances.
+typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
+
+/// @brief Storage container for scalar configuration parameters.
+///
+/// This class is useful for implementing parsers for more complex configuration
+/// elements (e.g. those of item type "map"). It provides a convenient way to
+/// add storage to the parser for an arbitrary number and variety of scalar
+/// configuration items (e.g. ints, bools, strings...) without explicitly adding
+/// storage for each individual type needed by the parser.
+///
+/// This class implements a concrete version of the base class by supplying a
+/// "clone" method.
+class DScalarContext : public DCfgContextBase {
+public:
+
+ /// @brief Constructor
+ DScalarContext() {
+ };
+
+ /// @brief Destructor
+ virtual ~DScalarContext() {
+ }
+
+ /// @brief Creates a clone of a DStubContext.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone() {
+ return (DCfgContextBasePtr(new DScalarContext(*this)));
+ }
+
+protected:
+ /// @brief Copy constructor
+ DScalarContext(const DScalarContext& rhs) : DCfgContextBase(rhs) {
+ }
+
+private:
+ /// @brief Private assignment operator, not implemented.
+ DScalarContext& operator=(const DScalarContext& rhs);
+};
+
+/// @brief Defines a pointer for DScalarContext instances.
+typedef boost::shared_ptr<DScalarContext> DScalarContextPtr;
+
+/// @brief Parser for TSIGKeyInfo
+///
+/// This class parses the configuration element "tsig_key" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a TSIGKeyInfo.
+class TSIGKeyInfoParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition. Since servers are specified in a list this value is likely
+ /// be something akin to "key:0", set during parsing.
+ /// @param keys is a pointer to the storage area to which the parser
+ /// should commit the newly created TSIGKeyInfo instance.
+ TSIGKeyInfoParser(const std::string& entry_name, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfoParser();
+
+ /// @brief Performs the actual parsing of the given "tsig_key" element.
+ ///
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param key_config is the "tsig_key" configuration to parse
+ virtual void build(isc::data::ConstElementPtr key_config);
+
+ /// @brief Creates a parser for the given "tsig_key" member element id.
+ ///
+ /// The key elements currently supported are(see dhcp-ddns.spec):
+ /// 1. name
+ /// 2. algorithm
+ /// 3. secret
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the "tsig_key" specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Instantiates a DnsServerInfo from internal data values
+ /// saves it to the storage area pointed to by servers_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ /// Since servers are specified in a list this value is likely be something
+ /// akin to "key:0", set during parsing. Primarily here for diagnostics.
+ std::string entry_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the newly created TSIGKeyInfo instance. This is given to us as a
+ /// constructor argument by an upper level.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of TSIGKeyInfos
+///
+/// This class parses a list of "tsig_key" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The TSIGKeyInfo instances are added
+/// to the given storage upon commit.
+class TSIGKeyInfoListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param list_name is an arbitrary label assigned to this parser instance.
+ /// @param keys is a pointer to the storage area to which the parser
+ /// should commit the newly created TSIGKeyInfo instance.
+ TSIGKeyInfoListParser(const std::string& list_name, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfoListParser();
+
+ /// @brief Performs the parsing of the given list "tsig_key" elements.
+ ///
+ /// It iterates over each key entry in the list:
+ /// 1. Instantiate a TSIGKeyInfoParser for the entry
+ /// 2. Pass the element configuration to the parser's build method
+ /// 3. Add the parser instance to local storage
+ ///
+ /// The net effect is to parse all of the key entries in the list
+ /// prepping them for commit.
+ ///
+ /// @param key_list_config is the list of "tsig_key" elements to parse.
+ virtual void build(isc::data::ConstElementPtr key_list_config);
+
+ /// @brief Iterates over the internal list of TSIGKeyInfoParsers,
+ /// invoking commit on each. This causes each parser to instantiate a
+ /// TSIGKeyInfo from its internal data values and add that key
+ /// instance to the local key storage area, local_keys_. If all of the
+ /// key parsers commit cleanly, then update the context key map (keys_)
+ /// with the contents of local_keys_. This is done to allow for duplicate
+ /// key detection while parsing the keys, but not get stumped by it
+ /// updating the context with a valid list.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string list_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the list of newly created TSIGKeyInfo instances. This is given to us
+ /// as a constructor argument by an upper level.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage area to which individual key parsers commit.
+ TSIGKeyInfoMapPtr local_keys_;
+
+ /// @brief Local storage of TSIGKeyInfoParser instances
+ isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for DnsServerInfo
+///
+/// This class parses the configuration element "dns_server" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a DnsServerInfo.
+class DnsServerInfoParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition. Since servers are specified in a list this value is likely
+ /// be something akin to "server:0", set during parsing.
+ /// @param servers is a pointer to the storage area to which the parser
+ /// should commit the newly created DnsServerInfo instance.
+ DnsServerInfoParser(const std::string& entry_name,
+ DnsServerInfoStoragePtr servers);
+
+ /// @brief Destructor
+ virtual ~DnsServerInfoParser();
+
+ /// @brief Performs the actual parsing of the given "dns_server" element.
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param server_config is the "dns_server" configuration to parse
+ virtual void build(isc::data::ConstElementPtr server_config);
+
+ /// @brief Creates a parser for the given "dns_server" member element id.
+ ///
+ /// The server elements currently supported are(see dhcp-ddns.spec):
+ /// 1. hostname
+ /// 2. ip_address
+ /// 3. port
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the "dns_server" specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Instantiates a DnsServerInfo from internal data values
+ /// saves it to the storage area pointed to by servers_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ /// Since servers are specified in a list this value is likely be something
+ /// akin to "server:0", set during parsing. Primarily here for diagnostics.
+ std::string entry_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the newly created DnsServerInfo instance. This is given to us as a
+ /// constructor argument by an upper level.
+ DnsServerInfoStoragePtr servers_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of DnsServerInfos
+///
+/// This class parses a list of "dns_server" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The DnsServerInfo instances are added
+/// to the given storage upon commit.
+class DnsServerInfoListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param list_name is an arbitrary label assigned to this parser instance.
+ /// @param servers is a pointer to the storage area to which the parser
+ /// should commit the newly created DnsServerInfo instance.
+ DnsServerInfoListParser(const std::string& list_name,
+ DnsServerInfoStoragePtr servers);
+
+ /// @brief Destructor
+ virtual ~DnsServerInfoListParser();
+
+ /// @brief Performs the actual parsing of the given list "dns_server"
+ /// elements.
+ /// It iterates over each server entry in the list:
+ /// 1. Instantiate a DnsServerInfoParser for the entry
+ /// 2. Pass the element configuration to the parser's build method
+ /// 3. Add the parser instance to local storage
+ ///
+ /// The net effect is to parse all of the server entries in the list
+ /// prepping them for commit.
+ ///
+ /// @param server_list_config is the list of "dns_server" elements to parse.
+ virtual void build(isc::data::ConstElementPtr server_list_config);
+
+ /// @brief Iterates over the internal list of DnsServerInfoParsers,
+ /// invoking commit on each. This causes each parser to instantiate a
+ /// DnsServerInfo from its internal data values and add that that server
+ /// instance to the storage area, servers_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string list_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the list of newly created DnsServerInfo instances. This is given to us
+ /// as a constructor argument by an upper level.
+ DnsServerInfoStoragePtr servers_;
+
+ /// @brief Local storage of DnsServerInfoParser instances
+ isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for DdnsDomain
+///
+/// This class parses the configuration element "ddns_domain" defined in
+/// src/bin/d2/dhcp-ddns.spec and creates an instance of a DdnsDomain.
+class DdnsDomainParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition. Since domains are specified in a list this value is likely
+ /// be something akin to "forward_ddns:0", set during parsing.
+ /// @param domains is a pointer to the storage area to which the parser
+ /// @param keys is a pointer to a map of the defined TSIG keys.
+ /// should commit the newly created DdnsDomain instance.
+ DdnsDomainParser(const std::string& entry_name, DdnsDomainMapPtr domains,
+ TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainParser();
+
+ /// @brief Performs the actual parsing of the given "ddns_domain" element.
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param domain_config is the "ddns_domain" configuration to parse
+ virtual void build(isc::data::ConstElementPtr domain_config);
+
+ /// @brief Creates a parser for the given "ddns_domain" member element id.
+ ///
+ /// The domain elements currently supported are(see dhcp-ddns.spec):
+ /// 1. name
+ /// 2. key_name
+ /// 3. dns_servers
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the "ddns_domain" specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Instantiates a DdnsDomain from internal data values
+ /// saves it to the storage area pointed to by domains_.
+ virtual void commit();
+
+private:
+
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string entry_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the newly created DdnsDomain instance. This is given to us as a
+ /// constructor argument by an upper level.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Pointer to the map of defined TSIG keys.
+ /// This map is passed into us and contains all of the TSIG keys defined
+ /// for this configuration. It is used to validate the key name entry of
+ /// DdnsDomains that specify one.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage for DnsServerInfo instances. This is passed into
+ /// DnsServerInfoListParser(s), which in turn passes it into each
+ /// DnsServerInfoParser. When the DnsServerInfoParsers "commit" they add
+ /// their server instance to this storage.
+ DnsServerInfoStoragePtr local_servers_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ DScalarContext local_scalars_;
+};
+
+/// @brief Parser for a list of DdnsDomains
+///
+/// This class parses a list of "ddns_domain" configuration elements.
+/// (see src/bin/d2/dhcp-ddns.spec). The DdnsDomain instances are added
+/// to the given storage upon commit.
+class DdnsDomainListParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param list_name is an arbitrary label assigned to this parser instance.
+ /// @param domains is a pointer to the storage area to which the parser
+ /// @param keys is a pointer to a map of the defined TSIG keys.
+ /// should commit the newly created DdnsDomain instance.
+ DdnsDomainListParser(const std::string& list_name,
+ DdnsDomainMapPtr domains, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListParser();
+
+ /// @brief Performs the actual parsing of the given list "ddns_domain"
+ /// elements.
+ /// It iterates over each server entry in the list:
+ /// 1. Instantiate a DdnsDomainParser for the entry
+ /// 2. Pass the element configuration to the parser's build method
+ /// 3. Add the parser instance to local storage
+ ///
+ /// The net effect is to parse all of the domain entries in the list
+ /// prepping them for commit.
+ ///
+ /// @param domain_list_config is the list of "ddns_domain" elements to
+ /// parse.
+ virtual void build(isc::data::ConstElementPtr domain_list_config);
+
+ /// @brief Iterates over the internal list of DdnsDomainParsers, invoking
+ /// commit on each. This causes each parser to instantiate a DdnsDomain
+ /// from its internal data values and add that domain instance to the
+ /// storage area, domains_.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string list_name_;
+
+ /// @brief Pointer to the storage area to which the parser should commit
+ /// the list of newly created DdnsDomain instances. This is given to us
+ /// as a constructor argument by an upper level.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Pointer to the map of defined TSIG keys.
+ /// This map is passed into us and contains all of the TSIG keys defined
+ /// for this configuration. It is used to validate the key name entry of
+ /// DdnsDomains that specify one.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage of DdnsDomainParser instances
+ isc::dhcp::ParserCollection parsers_;
+};
+
+/// @brief Parser for DdnsDomainListMgr
+///
+/// This class parses the configuration elements "forward_ddns" and
+/// "reverse_ddns" as defined in src/bin/d2/dhcp-ddns.spec. It populates the
+/// given DdnsDomainListMgr with parsed information upon commit. Note that
+/// unlike other parsers, this parser does NOT instantiate the final object
+/// during the commit phase, it populates it. It must pre-exist.
+class DdnsDomainListMgrParser : public isc::dhcp::DhcpConfigParser {
+public:
+ /// @brief Constructor
+ ///
+ /// @param entry_name is an arbitrary label assigned to this configuration
+ /// definition.
+ /// @param mgr is a pointer to the DdnsDomainListMgr to populate.
+ /// @param keys is a pointer to a map of the defined TSIG keys.
+ /// @throw throws D2CfgError if mgr pointer is empty.
+ DdnsDomainListMgrParser(const std::string& entry_name,
+ DdnsDomainListMgrPtr mgr, TSIGKeyInfoMapPtr keys);
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListMgrParser();
+
+ /// @brief Performs the actual parsing of the given manager element.
+ /// The results of the parsing are retained internally for use during
+ /// commit.
+ ///
+ /// @param mgr_config is the manager configuration to parse
+ virtual void build(isc::data::ConstElementPtr mgr_config);
+
+ /// @brief Creates a parser for the given manager member element id.
+ ///
+ /// The manager elements currently supported are (see dhcp-ddns.spec):
+ /// 1. ddns_domains
+ ///
+ /// @param config_id is the "item_name" for a specific member element of
+ /// the manager specification.
+ ///
+ /// @return returns a pointer to newly created parser.
+ virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
+ config_id);
+
+ /// @brief Populates the DdnsDomainListMgr from internal data values
+ /// set during parsing.
+ virtual void commit();
+
+private:
+ /// @brief Arbitrary label assigned to this parser instance.
+ std::string entry_name_;
+
+ /// @brief Pointer to manager instance to which the parser should commit
+ /// the parsed data. This is given to us as a constructor argument by an
+ /// upper level.
+ DdnsDomainListMgrPtr mgr_;
+
+ /// @brief Pointer to the map of defined TSIG keys.
+ /// This map is passed into us and contains all of the TSIG keys defined
+ /// for this configuration. It is used to validate the key name entry of
+ /// DdnsDomains that specify one.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Local storage for DdnsDomain instances. This is passed into a
+ /// DdnsDomainListParser(s), which in turn passes it into each
+ /// DdnsDomainParser. When the DdnsDomainParsers "commit" they add their
+ /// domain instance to this storage.
+ DdnsDomainMapPtr local_domains_;
+
+ /// @brief Local storage area for scalar parameter values. Use to hold
+ /// data until time to commit.
+ /// @todo Currently, the manager has no scalars but this is likely to
+ /// change as matching capabilities expand.
+ DScalarContext local_scalars_;
+};
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D2_CONFIG_H
diff --git a/src/bin/d2/d2_controller.cc b/src/bin/d2/d2_controller.cc
new file mode 100644
index 0000000..2206207
--- /dev/null
+++ b/src/bin/d2/d2_controller.cc
@@ -0,0 +1,68 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <d2/d2_controller.h>
+#include <d2/d2_process.h>
+#include <d2/spec_config.h>
+
+#include <stdlib.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Defines the application name, this is passed into base class
+/// and appears in log statements.
+const char* D2Controller::d2_app_name_ = "DHCP-DDNS";
+
+/// @brief Defines the executable name. This is passed into the base class
+/// by convention this should match the BIND10 module name.
+const char* D2Controller::d2_bin_name_ = "b10-dhcp-ddns";
+
+DControllerBasePtr&
+D2Controller::instance() {
+ // If the instance hasn't been created yet, create it. Note this method
+ // must use the base class singleton instance methods. The base class
+ // must have access to the singleton in order to use it within BIND10
+ // static function callbacks.
+ if (!getController()) {
+ DControllerBasePtr controller_ptr(new D2Controller());
+ setController(controller_ptr);
+ }
+
+ return (getController());
+}
+
+DProcessBase* D2Controller::createProcess() {
+ // Instantiate and return an instance of the D2 application process. Note
+ // that the process is passed the controller's io_service.
+ return (new D2Process(getAppName().c_str(), getIOService()));
+}
+
+D2Controller::D2Controller()
+ : DControllerBase(d2_app_name_, d2_bin_name_) {
+ // set the BIND10 spec file either from the environment or
+ // use the production value.
+ if (getenv("B10_FROM_BUILD")) {
+ setSpecFileName(std::string(getenv("B10_FROM_BUILD")) +
+ "/src/bin/d2/dhcp-ddns.spec");
+ } else {
+ setSpecFileName(D2_SPECFILE_LOCATION);
+ }
+}
+
+D2Controller::~D2Controller() {
+}
+
+}; // end namespace isc::d2
+}; // end namespace isc
diff --git a/src/bin/d2/d2_controller.h b/src/bin/d2/d2_controller.h
new file mode 100644
index 0000000..0290f87
--- /dev/null
+++ b/src/bin/d2/d2_controller.h
@@ -0,0 +1,72 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 D2_CONTROLLER_H
+#define D2_CONTROLLER_H
+
+#include <d2/d_controller.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Process Controller for D2 Process
+/// This class is the DHCP-DDNS specific derivation of DControllerBase. It
+/// creates and manages an instance of the DHCP-DDNS application process,
+/// D2Process.
+/// @todo Currently, this class provides only the minimum required specialized
+/// behavior to run the DHCP-DDNS service. It may very well expand as the
+/// service implementation evolves. Some thought was given to making
+/// DControllerBase a templated class but the labor savings versus the
+/// potential number of virtual methods which may be overridden didn't seem
+/// worth the clutter at this point.
+class D2Controller : public DControllerBase {
+public:
+ /// @brief Static singleton instance method. This method returns the
+ /// base class singleton instance member. It instantiates the singleton
+ /// and sets the base class instance member upon first invocation.
+ ///
+ /// @return returns the pointer reference to the singleton instance.
+ static DControllerBasePtr& instance();
+
+ /// @brief Destructor.
+ virtual ~D2Controller();
+
+ /// @brief Defines the application name, this is passed into base class
+ /// and appears in log statements.
+ static const char* d2_app_name_;
+
+ /// @brief Defines the executable name. This is passed into the base class
+ /// by convention this should match the BIND10 module name.
+ static const char* d2_bin_name_;
+
+private:
+ /// @brief Creates an instance of the DHCP-DDNS specific application
+ /// process. This method is invoked during the process initialization
+ /// step of the controller launch.
+ ///
+ /// @return returns a DProcessBase* to the application process created.
+ /// Note the caller is responsible for destructing the process. This
+ /// is handled by the base class, which wraps this pointer with a smart
+ /// pointer.
+ virtual DProcessBase* createProcess();
+
+ /// @brief Constructor is declared private to maintain the integrity of
+ /// the singleton instance.
+ D2Controller();
+};
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif
diff --git a/src/bin/d2/d2_log.cc b/src/bin/d2/d2_log.cc
new file mode 100644
index 0000000..c938c2c
--- /dev/null
+++ b/src/bin/d2/d2_log.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 top-level component of b10-d2.
+
+#include <d2/d2_log.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Defines the logger used within D2.
+isc::log::Logger dctl_logger("dhcpddns");
+
+} // namespace d2
+} // namespace isc
+
diff --git a/src/bin/d2/d2_log.h b/src/bin/d2/d2_log.h
new file mode 100644
index 0000000..b91fc15
--- /dev/null
+++ b/src/bin/d2/d2_log.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 D2_LOG_H
+#define D2_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <d2/d2_messages.h>
+
+namespace isc {
+namespace d2 {
+
+/// Define the logger for the "d2" logging.
+extern isc::log::Logger dctl_logger;
+
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_LOG_H
diff --git a/src/bin/d2/d2_messages.mes b/src/bin/d2/d2_messages.mes
new file mode 100644
index 0000000..c2805fa
--- /dev/null
+++ b/src/bin/d2/d2_messages.mes
@@ -0,0 +1,254 @@
+# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::d2
+
+% DCTL_CCSESSION_ENDING %1 ending control channel session
+This debug message is issued just before the controller attempts
+to disconnect from its session with the BIND10 control channel.
+
+% DCTL_CCSESSION_STARTING %1 starting control channel session, specfile: %2
+This debug message is issued just before the controller attempts
+to establish a session with the BIND10 control channel.
+
+% DCTL_COMMAND_RECEIVED %1 received command: %2, arguments: %3
+A debug message listing the command (and possible arguments) received
+from the BIND10 control system by the controller.
+
+% DCTL_CONFIG_COMPLETE server has completed configuration: %1
+This is an informational message announcing the successful processing of a
+new configuration. It is output during server startup, and when an updated
+configuration is committed by the administrator. Additional information
+may be provided.
+
+% DCTL_CONFIG_LOAD_FAIL %1 configuration failed to load: %2
+This critical error message indicates that the initial application
+configuration has failed. The service will start, but will not
+process requests until the configuration has been corrected.
+
+% DCTL_CONFIG_START parsing new configuration: %1
+A debug message indicating that the application process has received an
+updated configuration and has passed it to its configuration manager
+for parsing.
+
+% DCTL_CONFIG_STUB %1 configuration stub handler called
+This debug message is issued when the dummy handler for configuration
+events is called. This only happens during initial startup.
+
+% DCTL_CONFIG_UPDATE %1 updated configuration received: %2
+A debug message indicating that the controller has received an
+updated configuration from the BIND10 configuration system.
+
+% DCTL_DISCONNECT_FAIL %1 controller failed to end session with BIND10: %2
+This message indicates that while shutting down, the Dhcp-Ddns controller
+encountered an error terminating communication with the BIND10. The service
+will still exit. While theoretically possible, this situation is rather
+unlikely.
+
+% DCTL_INIT_PROCESS %1 initializing the application
+This debug message is issued just before the controller attempts
+to create and initialize its application instance.
+
+% DCTL_INIT_PROCESS_FAIL %1 application initialization failed: %2
+This error message is issued if the controller could not initialize the
+application and will exit.
+
+% DCTL_NOT_RUNNING %1 application instance is not running
+A warning message is issued when an attempt is made to shut down the
+application when it is not running.
+
+% DCTL_ORDER_ERROR configuration contains more elements than the parsing order
+An error message which indicates that configuration being parsed includes
+element ids not specified the configuration manager's parse order list. This
+is a programmatic error.
+
+% DCTL_ORDER_NO_ELEMENT element: %1 is in the parsing order but is missing from the configuration
+An error message output during a configuration update. The program is
+expecting an item but has not found it in the new configuration. This may
+mean that the BIND 10 configuration database is corrupt.
+
+% DCTL_PARSER_FAIL configuration parsing failed for configuration element: %1, reason: %2
+On receipt of message containing details to a change of its configuration,
+the server failed to create a parser to decode the contents of the named
+configuration element, or the creation succeeded but the parsing actions
+and committal of changes failed. The reason for the failure is given in
+the message.
+
+% DCTL_PROCESS_FAILED %1 application execution failed: %2
+The controller has encountered a fatal error while running the
+application and is terminating. The reason for the failure is
+included in the message.
+
+% DCTL_RUN_PROCESS %1 starting application event loop
+This debug message is issued just before the controller invokes
+the application run method.
+
+% DCTL_SESSION_FAIL %1 controller failed to establish BIND10 session: %1
+The controller has failed to establish communication with the rest of BIND
+10 and will exit.
+
+% DCTL_STANDALONE %1 skipping message queue, running standalone
+This is a debug message indicating that the controller is running in the
+application in standalone mode. This means it will not connected to the BIND10
+message queue. Standalone mode is only useful during program development,
+and should not be used in a production environment.
+
+% DCTL_STARTING %1 controller starting, pid: %2
+This is an informational message issued when controller for the
+service first starts.
+
+% DCTL_STOPPING %1 controller is exiting
+This is an informational message issued when the controller is exiting
+following a shut down (normal or otherwise) of the service.
+
+% DHCP_DDNS_AT_MAX_TRANSACTIONS application has %1 queued requests but has reached maximum number of %2 concurrent transactions
+This is a debug message that indicates that the application has DHCP_DDNS
+requests in the queue but is working as many concurrent requests as allowed.
+
+% DHCP_DDNS_CLEARED_FOR_SHUTDOWN application has met shutdown criteria for shutdown type: %1
+This is an informational message issued when the application has been instructed
+to shutdown and has met the required criteria to exit.
+
+% DHCP_DDNS_COMMAND command directive received, command: %1 - args: %2
+This is a debug message issued when the Dhcp-Ddns application command method
+has been invoked.
+
+% DHCP_DDNS_CONFIGURE configuration update received: %1
+This is a debug message issued when the Dhcp-Ddns application configure method
+has been invoked.
+
+% DHCP_DDNS_FAILED application experienced a fatal error: %1
+This is a debug message issued when the Dhcp-Ddns application encounters an
+unrecoverable error from within the event loop.
+
+% DHCP_DDNS_INVALID_RESPONSE received response to DNS Update message is malformed: %1
+This is a debug message issued when the DHCP-DDNS application encountered an
+error while decoding a response to DNS Update message. Typically, this error
+will be encountered when a response message is malformed.
+
+% DHCP_DDNS_NO_ELIGIBLE_JOBS although there are queued requests, there are pending transactions for each Queue count: %1 Transaction count: %2
+This is a debug messge issued when all of the queued requests represent clients
+for which there is a an update already in progress. This may occur under
+normal operations but should be temporary situation.
+
+% DHCP_DDNS_NO_FWD_MATCH_ERROR the configured list of forward DDNS domains does not contain a match for FQDN %1 The request has been discarded.
+This is an error message that indicates that DHCP_DDNS received a request to
+update a the forward DNS information for the given FQDN but for which there are
+no configured DDNS domains in the DHCP_DDNS configuration. Either the DHCP_DDNS
+configuration needs to be updated or the source of the FQDN itself should be
+investigated.
+
+% DHCP_DDNS_NO_MATCH No DNS servers match FQDN %1
+This is warning message issued when there are no domains in the configuration
+which match the cited fully qualified domain name (FQDN). The DNS Update
+request for the FQDN cannot be processed.
+
+% DHCP_DDNS_NO_REV_MATCH_ERROR the configured list of reverse DDNS domains does not contain a match for FQDN %1 The request has been discarded.
+This is an error message that indicates that DHCP_DDNS received a request to
+update a the reverse DNS information for the given FQDN but for which there are
+no configured DDNS domains in the DHCP_DDNS configuration. Either the DHCP_DDNS
+configuration needs to be updated or the source of the FQDN itself should be
+investigated.
+
+% DHCP_DDNS_PROCESS_INIT application init invoked
+This is a debug message issued when the Dhcp-Ddns application enters
+its init method.
+
+% DHCP_DDNS_QUEUE_MGR_QUEUE_FULL application request queue has reached maximum number of entries %1
+This an error message indicating that DHCP-DDNS is receiving DNS update
+requests faster than they can be processed. This may mean the maximum queue
+needs to be increased, the DHCP-DDNS clients are simply generating too many
+requests too quickly, or perhaps upstream DNS servers are experiencing
+load issues.
+
+% DHCP_DDNS_QUEUE_MGR_RECONFIGURING application is reconfiguring the queue manager
+This is an informational message indicating that DHCP_DDNS is reconfiguring the
+queue manager as part of normal startup or in response to a new configuration.
+
+% DHCP_DDNS_QUEUE_MGR_RECOVERING application is attempting to recover from a
+queue manager IO error
+This is an informational message indicating that DHCP_DDNS is attempting to
+restart the queue manager after it suffered an IO error while receiving
+requests.
+
+% DHCP_DDNS_QUEUE_MGR_RECV_ERROR application's queue manager was notified of a request receive error by its listener.
+This is an error message indicating that the NameChangeRequest listener used by
+DHCP-DDNS to receive requests encountered a IO error. There should be
+corresponding log messages from the listener layer with more details. This may
+indicate a network connectivity or system resource issue.
+
+% DHCP_DDNS_QUEUE_MGR_RESUME_ERROR application could not restart the queue manager, reason: %1
+This is an error message indicating that DHCP_DDNS's Queue Manager could not
+be restarted after stopping due to an a full receive queue. This means that
+the application cannot receive requests. This is most likely due to DHCP_DDNS
+configuration parameters referring to resources such as an IP address or port,
+that is no longer unavailable. DHCP_DDNS will attempt to restart the queue
+manager if given a new configuration.
+
+% DHCP_DDNS_QUEUE_MGR_RESUMING application is resuming listening for requests now that the request queue size has reached %1 of a maximum %2 allowed
+This is an informational message indicating that DHCP_DDNS, which had stopped
+accepting new requests, has processed enough entries from the receive queue to
+resume accepting requests.
+
+% DHCP_DDNS_QUEUE_MGR_STARTED application's queue manager has begun listening for requests.
+This is a debug message indicating that DHCP_DDNS's Queue Manager has
+successfully started and is now listening for NameChangeRequests.
+
+% DHCP_DDNS_QUEUE_MGR_START_ERROR application could not start the queue manager, reason: %1
+This is an error message indicating that DHCP_DDNS's Queue Manager could not
+be started. This means that the application cannot receive requests. This is
+most likely due to DHCP_DDNS configuration parameters referring to resources
+such as an IP address or port, that are unavailable. DHCP_DDNS will attempt to
+restart the queue manager if given a new configuration.
+
+% DHCP_DDNS_QUEUE_MGR_STOPPED application's queue manager has stopped listening for requests.
+This is an informational message indicating that DHCP_DDNS's Queue Manager has
+stopped listening for NameChangeRequests. This may be because of normal event
+such as reconfiguration or as a result of an error. There should be log
+messages preceding this one to indicate why it has stopped.
+
+% DHCP_DDNS_QUEUE_MGR_STOPPING application is stopping the queue manager for %1
+This is an informational message indicating that DHCP_DDNS is stopping the
+queue manager either to reconfigure it or as part of application shutdown.
+
+% DHCP_DDNS_QUEUE_MGR_STOP_ERROR application encountered an error stopping the queue manager: %1
+This is an error message indicating that DHCP_DDNS encountered an error while
+trying to stop the queue manager. This error is unlikely to occur or to
+impair the application's ability to function but it should be reported for
+analysis.
+
+% DHCP_DDNS_QUEUE_MGR_UNEXPECTED_HANDLER_ERROR application's queue manager request receive handler experienced an unexpected exception %1:
+This is an error message indicating that an unexpected error occurred within the
+DHCP_DDNS's Queue Manager request receive completion handler. This is most
+likely a programmatic issue that should be reported. The application may
+recover on its own.
+
+% DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP application's queue manager receive was
+aborted unexpectedly while queue manager state is: %1
+This is an error message indicating that DHCP_DDNS's Queue Manager request
+receive was unexpected interrupted. Normally, the read is receive is only
+interrupted as a normal part of stopping the queue manager. This is most
+likely a programmatic issue that should be reported.
+
+% DHCP_DDNS_RUN_ENTER application has entered the event loop
+This is a debug message issued when the Dhcp-Ddns application enters
+its run method.
+
+% DHCP_DDNS_RUN_EXIT application is exiting the event loop
+This is a debug message issued when the Dhcp-Ddns exits the
+in event loop.
+
+% DHCP_DDNS_SHUTDOWN application received shutdown command with args: %1
+This is informational message issued when the application has been instructed
+to shut down by the controller.
diff --git a/src/bin/d2/d2_process.cc b/src/bin/d2/d2_process.cc
new file mode 100644
index 0000000..63bd6bd
--- /dev/null
+++ b/src/bin/d2/d2_process.cc
@@ -0,0 +1,394 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+#include <d2/d2_process.h>
+
+#include <asio.hpp>
+
+namespace isc {
+namespace d2 {
+
+// Setting to 80% for now. This is an arbitrary choice and should probably
+// be configurable.
+const unsigned int D2Process::QUEUE_RESTART_PERCENT = 80;
+
+D2Process::D2Process(const char* name, IOServicePtr io_service)
+ : DProcessBase(name, io_service, DCfgMgrBasePtr(new D2CfgMgr())),
+ reconf_queue_flag_(false), shutdown_type_(SD_NORMAL) {
+
+ // Instantiate queue manager. Note that queue manager does not start
+ // listening at this point. That can only occur after configuration has
+ // been received. This means that until we receive the configuration,
+ // D2 will neither receive nor process NameChangeRequests.
+ // Pass in IOService for NCR IO event processing.
+ queue_mgr_.reset(new D2QueueMgr(*getIoService()));
+
+ // Instantiate update manager.
+ // Pass in both queue manager and configuration manager.
+ // Pass in IOService for DNS update transaction IO event processing.
+ D2CfgMgrPtr tmp = getD2CfgMgr();
+ update_mgr_.reset(new D2UpdateMgr(queue_mgr_, tmp, *getIoService()));
+};
+
+void
+D2Process::init() {
+};
+
+void
+D2Process::run() {
+ LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DHCP_DDNS_RUN_ENTER);
+ // Loop forever until we are allowed to shutdown.
+ while (!canShutdown()) {
+ try {
+ // Check on the state of the request queue. Take any
+ // actions necessary regarding it.
+ checkQueueStatus();
+
+ // Give update manager a time slice to queue new jobs and
+ // process finished ones.
+ update_mgr_->sweep();
+
+ // Wait on IO event(s) - block until one or more of the following
+ // has occurred:
+ // a. NCR message has been received
+ // b. Transaction IO has completed
+ // c. Interval timer expired
+ // d. Something stopped IO service (runIO returns 0)
+ if (runIO() == 0) {
+ // Pretty sure this amounts to an unexpected stop and we
+ // should bail out now. Normal shutdowns do not utilize
+ // stopping the IOService.
+ isc_throw(DProcessBaseError,
+ "Primary IO service stopped unexpectedly");
+ }
+ } catch (const std::exception& ex) {
+ LOG_FATAL(dctl_logger, DHCP_DDNS_FAILED).arg(ex.what());
+ isc_throw (DProcessBaseError,
+ "Process run method failed: " << ex.what());
+ }
+ }
+
+ // @todo - if queue isn't empty, we may need to persist its contents
+ // this might be the place to do it, once there is a persistence mgr.
+ // This may also be better in checkQueueStatus.
+
+ LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DHCP_DDNS_RUN_EXIT);
+
+};
+
+size_t
+D2Process::runIO() {
+ // We want to block until at least one handler is called. We'll use
+ // asio::io_service directly for two reasons. First off
+ // asiolink::IOService::run_one is a void and asio::io_service::stopped
+ // is not present in older versions of boost. We need to know if any
+ // handlers ran or if the io_service was stopped. That latter represents
+ // some form of error and the application cannot proceed with a stopped
+ // service. Secondly, asiolink::IOService does not provide the poll
+ // method. This is a handy method which runs all ready handlers without
+ // blocking.
+ IOServicePtr& io = getIoService();
+ asio::io_service& asio_io_service = io->get_io_service();
+
+ // Poll runs all that are ready. If none are ready it returns immediately
+ // with a count of zero.
+ size_t cnt = asio_io_service.poll();
+ if (!cnt) {
+ // Poll ran no handlers either none are ready or the service has been
+ // stopped. Either way, call run_one to wait for a IO event. If the
+ // service is stopped it will return immediately with a cnt of zero.
+ cnt = asio_io_service.run_one();
+ }
+
+ return (cnt);
+}
+
+bool
+D2Process::canShutdown() const {
+ bool all_clear = false;
+
+ // If we have been told to shutdown, find out if we are ready to do so.
+ if (shouldShutdown()) {
+ switch (shutdown_type_) {
+ case SD_NORMAL:
+ // For a normal shutdown we need to stop the queue manager but
+ // wait until we have finished all the transactions in progress.
+ all_clear = (((queue_mgr_->getMgrState() != D2QueueMgr::RUNNING) &&
+ (queue_mgr_->getMgrState() != D2QueueMgr::STOPPING))
+ && (update_mgr_->getTransactionCount() == 0));
+ break;
+
+ case SD_DRAIN_FIRST:
+ // For a drain first shutdown we need to stop the queue manager but
+ // process all of the requests in the receive queue first.
+ all_clear = (((queue_mgr_->getMgrState() != D2QueueMgr::RUNNING) &&
+ (queue_mgr_->getMgrState() != D2QueueMgr::STOPPING))
+ && (queue_mgr_->getQueueSize() == 0)
+ && (update_mgr_->getTransactionCount() == 0));
+ break;
+
+ case SD_NOW:
+ // Get out right now, no niceties.
+ all_clear = true;
+ break;
+
+ default:
+ // shutdown_type_ is an enum and should only be one of the above.
+ // if its getting through to this, something is whacked.
+ break;
+ }
+
+ if (all_clear) {
+ LOG_INFO(dctl_logger,DHCP_DDNS_CLEARED_FOR_SHUTDOWN)
+ .arg(getShutdownTypeStr(shutdown_type_));
+ }
+ }
+
+ return (all_clear);
+}
+
+isc::data::ConstElementPtr
+D2Process::shutdown(isc::data::ConstElementPtr args) {
+ LOG_INFO(dctl_logger, DHCP_DDNS_SHUTDOWN).arg(args ? args->str()
+ : "(no args)");
+
+ // Default shutdown type is normal.
+ std::string type_str(getShutdownTypeStr(SD_NORMAL));
+ shutdown_type_ = SD_NORMAL;
+
+ if (args) {
+ if ((args->getType() == isc::data::Element::map) &&
+ args->contains("type")) {
+ type_str = args->get("type")->stringValue();
+
+ if (type_str == getShutdownTypeStr(SD_NORMAL)) {
+ shutdown_type_ = SD_NORMAL;
+ } else if (type_str == getShutdownTypeStr(SD_DRAIN_FIRST)) {
+ shutdown_type_ = SD_DRAIN_FIRST;
+ } else if (type_str == getShutdownTypeStr(SD_NOW)) {
+ shutdown_type_ = SD_NOW;
+ } else {
+ setShutdownFlag(false);
+ return (isc::config::createAnswer(1, "Invalid Shutdown type: "
+ + type_str));
+ }
+ }
+ }
+
+ // Set the base class's shutdown flag.
+ setShutdownFlag(true);
+ return (isc::config::createAnswer(0, "Shutdown initiated, type is: "
+ + type_str));
+}
+
+isc::data::ConstElementPtr
+D2Process::configure(isc::data::ConstElementPtr config_set) {
+ LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC,
+ DHCP_DDNS_CONFIGURE).arg(config_set->str());
+
+ int rcode = 0;
+ isc::data::ConstElementPtr comment;
+ isc::data::ConstElementPtr answer = getCfgMgr()->parseConfig(config_set);;
+ comment = isc::config::parseAnswer(rcode, answer);
+
+ if (rcode) {
+ // Non-zero means we got an invalid configuration, take no further
+ // action. In integrated mode, this will send a failed response back
+ // to BIND10.
+ reconf_queue_flag_ = false;
+ return (answer);
+ }
+
+ // Set the reconf_queue_flag to indicate that we need to reconfigure
+ // the queue manager. Reconfiguring the queue manager may be asynchronous
+ // and require one or more events to occur, therefore we set a flag
+ // indicating it needs to be done but we cannot do it here. It must
+ // be done over time, while events are being processed. Remember that
+ // the method we are in now is invoked as part of the configuration event
+ // callback. This means you can't wait for events here, you are already
+ // in one.
+ // (@todo NOTE This could be turned into a bitmask of flags if we find other
+ // things that need reconfiguration. It might also be useful if we
+ // did some analysis to decide what if anything we need to do.)
+ reconf_queue_flag_ = true;
+
+ // If we are here, configuration was valid, at least it parsed correctly
+ // and therefore contained no invalid values.
+ // Return the success answer from above.
+ return (answer);
+}
+
+void
+D2Process::checkQueueStatus() {
+ switch (queue_mgr_->getMgrState()){
+ case D2QueueMgr::RUNNING:
+ if (reconf_queue_flag_ || shouldShutdown()) {
+ // If we need to reconfigure the queue manager or we have been
+ // told to shutdown, then stop listening first. Stopping entails
+ // canceling active listening which may generate an IO event, so
+ // instigate the stop and get out.
+ try {
+ LOG_INFO(dctl_logger, DHCP_DDNS_QUEUE_MGR_STOPPING)
+ .arg(reconf_queue_flag_ ? "reconfiguration"
+ : "shutdown");
+ queue_mgr_->stopListening();
+ } catch (const isc::Exception& ex) {
+ // It is very unlikey that we would experience an error
+ // here, but theoretically possible.
+ LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_STOP_ERROR)
+ .arg(ex.what());
+ }
+ }
+ break;
+
+ case D2QueueMgr::STOPPED_QUEUE_FULL: {
+ // Resume receiving once the queue has decreased by twenty
+ // percent. This is an arbitrary choice. @todo this value should
+ // probably be configurable.
+ size_t threshold = (((queue_mgr_->getMaxQueueSize()
+ * QUEUE_RESTART_PERCENT)) / 100);
+ if (queue_mgr_->getQueueSize() <= threshold) {
+ LOG_INFO (dctl_logger, DHCP_DDNS_QUEUE_MGR_RESUMING)
+ .arg(threshold).arg(queue_mgr_->getMaxQueueSize());
+ try {
+ queue_mgr_->startListening();
+ } catch (const isc::Exception& ex) {
+ LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_RESUME_ERROR)
+ .arg(ex.what());
+ }
+ }
+
+ break;
+ }
+
+ case D2QueueMgr::STOPPED_RECV_ERROR:
+ // If the receive error is not due to some fallout from shutting
+ // down then we will attempt to recover by reconfiguring the listener.
+ // This will close and destruct the current listener and make a new
+ // one with new resources.
+ // @todo This may need a safety valve such as retry count or a timer
+ // to keep from endlessly retrying over and over, with little time
+ // in between.
+ if (!shouldShutdown()) {
+ LOG_INFO (dctl_logger, DHCP_DDNS_QUEUE_MGR_RECOVERING);
+ reconfigureQueueMgr();
+ }
+ break;
+
+ case D2QueueMgr::STOPPING:
+ // We are waiting for IO to cancel, so this is a NOP.
+ // @todo Possible timer for self-defense? We could conceivably
+ // get into a condition where we never get the event, which would
+ // leave us stuck in stopping. This is hugely unlikely but possible?
+ break;
+
+ default:
+ // If the reconfigure flag is set, then we are in a state now where
+ // we can do the reconfigure. In other words, we aren't RUNNING or
+ // STOPPING.
+ if (reconf_queue_flag_) {
+ LOG_INFO (dctl_logger, DHCP_DDNS_QUEUE_MGR_RECONFIGURING);
+ reconfigureQueueMgr();
+ }
+ break;
+ }
+}
+
+void
+D2Process::reconfigureQueueMgr() {
+ // Set reconfigure flag to false. We are only here because we have
+ // a valid configuration to work with so if we fail below, it will be
+ // an operational issue, such as a busy IP address. That will leave
+ // queue manager in INITTED state, which is fine.
+ // What we dont' want is to continually attempt to reconfigure so set
+ // the flag false now.
+ // @todo This method assumes only 1 type of listener. This will change
+ // to support at least a TCP version, possibly some form of RDBMS listener
+ // as well.
+ reconf_queue_flag_ = false;
+ try {
+ // Wipe out the current listener.
+ queue_mgr_->removeListener();
+
+ // Get the configuration parameters that affect Queue Manager.
+ // @todo Need to add parameters for listener TYPE, FORMAT, address reuse
+ std::string ip_address;
+ uint32_t port;
+ getCfgMgr()->getContext()->getParam("ip_address", ip_address);
+ getCfgMgr()->getContext()->getParam("port", port);
+ isc::asiolink::IOAddress addr(ip_address);
+
+ // Instantiate the listener.
+ queue_mgr_->initUDPListener(addr, port, dhcp_ddns::FMT_JSON, true);
+
+ // Now start it. This assumes that starting is a synchronous,
+ // blocking call that executes quickly. @todo Should that change then
+ // we will have to expand the state model to accommodate this.
+ queue_mgr_->startListening();
+ } catch (const isc::Exception& ex) {
+ // Queue manager failed to initialize and therefore not listening.
+ // This is most likely due to an unavailable IP address or port,
+ // which is a configuration issue.
+ LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_START_ERROR).arg(ex.what());
+ }
+}
+
+isc::data::ConstElementPtr
+D2Process::command(const std::string& command,
+ isc::data::ConstElementPtr args) {
+ // @todo This is the initial implementation. If and when D2 is extended
+ // to support its own commands, this implementation must change. Otherwise
+ // it should reject all commands as it does now.
+ LOG_DEBUG(dctl_logger, DBGLVL_TRACE_BASIC, DHCP_DDNS_COMMAND)
+ .arg(command).arg(args ? args->str() : "(no args)");
+
+ return (isc::config::createAnswer(COMMAND_INVALID, "Unrecognized command: "
+ + command));
+}
+
+D2Process::~D2Process() {
+};
+
+D2CfgMgrPtr
+D2Process::getD2CfgMgr() {
+ // The base class gives a base class pointer to our configuration manager.
+ // Since we are D2, and we need D2 specific extensions, we need a pointer
+ // to D2CfgMgr for some things.
+ return (boost::dynamic_pointer_cast<D2CfgMgr>(getCfgMgr()));
+}
+
+const char* D2Process::getShutdownTypeStr(const ShutdownType& type) {
+ const char* str = "invalid";
+ switch (type) {
+ case SD_NORMAL:
+ str = "normal";
+ break;
+ case SD_DRAIN_FIRST:
+ str = "drain_first";
+ break;
+ case SD_NOW:
+ str = "now";
+ break;
+ default:
+ break;
+ }
+
+ return (str);
+}
+
+}; // namespace isc::d2
+}; // namespace isc
diff --git a/src/bin/d2/d2_process.h b/src/bin/d2/d2_process.h
new file mode 100644
index 0000000..5c76af5
--- /dev/null
+++ b/src/bin/d2/d2_process.h
@@ -0,0 +1,333 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 D2_PROCESS_H
+#define D2_PROCESS_H
+
+#include <d2/d_process.h>
+#include <d2/d2_queue_mgr.h>
+#include <d2/d2_update_mgr.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief DHCP-DDNS Application Process
+///
+/// D2Process provides the top level application logic for DHCP-driven DDNS
+/// update processing. It provides the asynchronous event processing required
+/// to receive DNS mapping change requests and carry them out.
+/// It implements the DProcessBase interface, which structures it such that it
+/// is a managed "application", controlled by a management layer.
+class D2Process : public DProcessBase {
+public:
+
+ /// @brief Defines the shutdown types supported by D2Process
+ ///
+ /// * SD_NORMAL - Stops the queue manager and finishes all current
+ /// transactions before exiting. This is the default.
+ ///
+ /// * SD_DRAIN_FIRST - Stops the queue manager but continues processing
+ /// requests from the queue until it is empty.
+ ///
+ /// * SD_NOW - Exits immediately.
+ enum ShutdownType {
+ SD_NORMAL,
+ SD_DRAIN_FIRST,
+ SD_NOW
+ };
+
+ /// @brief Defines the point at which to resume receiving requests.
+ /// If the receive queue has become full, D2Process will "pause" the
+ /// reception of requests by putting the queue manager in the stopped
+ /// state. Once the number of entries has decreased to this percentage
+ /// of the maximum allowed, D2Process will "resume" receiving requests
+ /// by restarting the queue manager.
+ static const unsigned int QUEUE_RESTART_PERCENT;
+
+ /// @brief Constructor
+ ///
+ /// Construction creates the configuration manager, the queue
+ /// manager, and the update manager.
+ ///
+ /// @param name name is a text label for the process. Generally used
+ /// in log statements, but otherwise arbitrary.
+ /// @param io_service is the io_service used by the caller for
+ /// asynchronous event handling.
+ ///
+ /// @throw DProcessBaseError is io_service is NULL.
+ D2Process(const char* name, IOServicePtr io_service);
+
+ /// @brief Called after instantiation to perform initialization unique to
+ /// D2.
+ ///
+ /// This is invoked by the controller after command line arguments but
+ /// PRIOR to configuration reception. The base class provides this method
+ /// as a place to perform any derivation-specific initialization steps
+ /// that are inapppropriate for the constructor but necessary prior to
+ /// launch. So far, no such steps have been identified for D2, so its
+ /// implementantion is empty but required.
+ ///
+ /// @throw DProcessBaseError if the initialization fails.
+ virtual void init();
+
+ /// @brief Implements the process's event loop.
+ ///
+ /// Once entered, the main control thread remains inside this method
+ /// until shutdown. The event loop logic is as follows:
+ /// @code
+ /// while should not down {
+ /// process queue manager state change
+ /// process completed jobs
+ /// dequeue new jobs
+ /// wait for IO event(s)
+ ///
+ /// ON an exception, exit with fatal error
+ /// }
+ /// @endcode
+ ///
+ /// To summarize, each pass through the event loop first checks the state
+ /// of the received queue and takes any steps required to ensure it is
+ /// operating in the manner necessary. Next the update manager is given
+ /// a chance to clean up any completed transactions and start new
+ /// transactions by dequeuing jobs from the request queue. Lastly, it
+ /// allows IOService to process until one or more event handlers are
+ /// called. Note that this last step will block until at least one
+ /// ready handler is invoked. In other words, if no IO events have occurred
+ /// since it was last called, the event loop will block at this step until
+ /// an IO event occurs. At that time we return to the top of the loop.
+ ///
+ /// @throw DProcessBaseError if an error is encountered. Note that
+ /// exceptions thrown at this point are assumed to be FATAL exceptions.
+ /// This includes exceptions generated but not caught by IO callbacks.
+ /// Services which rely on callbacks are expected to be well behaved and
+ /// any errors they encounter handled internally.
+ virtual void run();
+
+ /// @brief Initiates the D2Process shutdown process.
+ ///
+ /// This is last step in the shutdown event callback chain. It is invoked
+ /// to notify D2Process that it needs to begin its shutdown procedure.
+ /// Note that shutting down may be neither instantaneous nor synchronous,
+ /// This method records the request for and the type of shutdown desired.
+ /// Generally it will require one or more subsequent events to complete,
+ /// dependent on the type of shutdown requested. The type of shutdown is
+ /// specified as an optional argument of the shutdown command. The types
+ /// of shutdown supported are:
+ ///
+ /// * "normal" - Stops the queue manager and finishes all current
+ /// transactions before exiting. This is the default.
+ ///
+ /// * "drain_first" - Stops the queue manager but continues processing
+ /// requests from the queue until it is empty.
+ ///
+ /// * "now" - Exits immediately.
+ ///
+ /// @param args Specifies the shutdown "type" as "normal", "drain_first",
+ /// or "now"
+ ///
+ /// @return an Element that contains the results of argument processing,
+ /// consisting of an integer status value (0 means successful,
+ /// non-zero means failure), and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr
+ shutdown(isc::data::ConstElementPtr args);
+
+ /// @brief Processes the given configuration.
+ ///
+ /// This method may be called multiple times during the process lifetime.
+ /// Certainly once during process startup, and possibly later if the user
+ /// alters configuration. This method must not throw, it should catch any
+ /// processing errors and return a success or failure answer as described
+ /// below.
+ ///
+ /// This method passes the newly received configuration to the configuration
+ /// manager instance for parsing. The configuration manager parses the
+ /// configuration and updates the necessary values within the context,
+ /// assuming it parses correctly. If that's the case this method sets the
+ /// flag to reconfigure the queue manager and returns a successful response
+ /// as described below.
+ ///
+ /// If the new configuration fails to parse, then the current configuration
+ /// is retained and a failure response is returned as described below.
+ ///
+ /// @param config_set a new configuration (JSON) for the process
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
+ config_set);
+
+ /// @brief Processes the given command.
+ ///
+ /// This method is called to execute any custom commands supported by the
+ /// process. This method must not throw, it should catch any processing
+ /// errors and return a success or failure answer as described below.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command. It can be a NULL pointer if no arguments exist for a command.
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr command(const std::string& command,
+ isc::data::ConstElementPtr args);
+ /// @brief Destructor
+ virtual ~D2Process();
+
+protected:
+ /// @brief Monitors current queue manager state, takes action accordingly
+ ///
+ /// This method ensures that the queue manager transitions to the state
+ /// most appropriate to the operational state of the D2Process and any
+ /// events that may have occurred since it was last called. It is called
+ /// once for each iteration of the event loop. It is essentially a
+ /// switch statement based on the D2QueueMgr's current state. The logic
+ /// is as follows:
+ ///
+ /// If the state is D2QueueMgr::RUNNING, and the queue manager needs to be
+ /// reconfigured or we have been told to shutdown, then instruct the queue
+ /// manager to stop listening. Exit the method.
+ ///
+ /// If the state is D2QueueMgr::STOPPED_QUEUE_FULL, then check if the
+ /// number of entries in the queue has fallen below the "resume threshold".
+ /// If it has, then instruct the queue manager to start listening. Exit
+ /// the method.
+ ///
+ /// If the state is D2QueueMgr::STOPPED_RECV_ERROR, then attempt to recover
+ /// by calling reconfigureQueueMgr(). Exit the method.
+ ///
+ /// If the state is D2QueueMgr::STOPPING, simply exit the method. This is
+ /// a NOP condition as we are waiting for the IO cancel event
+ ///
+ /// For any other state, (NOT_INITTED,INITTED,STOPPED), if the reconfigure
+ /// queue flag is set, call reconfigureQueueMgr(). Exit the method.
+ ///
+ /// This method is exception safe.
+ virtual void checkQueueStatus();
+
+ /// @brief Initializes then starts the queue manager.
+ ///
+ /// This method is initializes the queue manager with the current
+ /// configuration parameters and instructs it to start listening.
+ /// Note the existing listener instance (if it exists) is destroyed,
+ /// and that a new listener is created during initialization.
+ ///
+ /// This method is exception safe.
+ virtual void reconfigureQueueMgr();
+
+ /// @brief Allows IO processing to run until at least callback is invoked.
+ ///
+ /// This method is called from within the D2Process main event loop and is
+ /// the point at which the D2Process blocks, waiting for IO events to
+ /// cause IO event callbacks to be invoked.
+ ///
+ /// If callbacks are ready to be executed upon entry, the method will
+ /// return as soon as these callbacks have completed. If no callbacks
+ /// are ready, then it will wait (indefinitely) until at least one callback
+ /// is executed.
+ ///
+ /// @note: Should become desirable to periodically force an
+ /// event, an interval timer could be used to do so.
+ ///
+ /// @return The number of callback handlers executed, or 0 if the IO
+ /// service has been stopped.
+ ///
+ /// @throw This method does not throw directly, but the execution of
+ /// callbacks invoked in response to IO events might. If so, these
+ /// will propagate upward out of this method.
+ virtual size_t runIO();
+
+ /// @brief Indicates whether or not the process can perform a shutdown.
+ ///
+ /// Determines if the process has been instructed to shutdown and if
+ /// the criteria for performing the type of shutdown requested has been
+ /// met.
+ ///
+ /// @return Returns true if the criteria has been met, false otherwise.
+ virtual bool canShutdown() const;
+
+ /// @brief Sets queue reconfigure indicator to the given value.
+ ///
+ /// @param value is the new value to assign to the indicator
+ ///
+ /// @note this method is really only intended for testing purposes.
+ void setReconfQueueFlag(const bool value) {
+ reconf_queue_flag_ = value;
+ }
+
+ /// @brief Sets the shutdown type to the given value.
+ ///
+ /// @param value is the new value to assign to shutdown type.
+ ///
+ /// @note this method is really only intended for testing purposes.
+ void setShutdownType(const ShutdownType& value) {
+ shutdown_type_ = value;
+ }
+
+public:
+ /// @brief Returns a pointer to the configuration manager.
+ /// Note, this method cannot return a reference as it uses dynamic
+ /// pointer casting of the base class configuration manager.
+ D2CfgMgrPtr getD2CfgMgr();
+
+ /// @brief Returns a reference to the queue manager.
+ const D2QueueMgrPtr& getD2QueueMgr() const {
+ return (queue_mgr_);
+ }
+
+ /// @brief Returns a reference to the update manager.
+ const D2UpdateMgrPtr& getD2UpdateMgr() const {
+ return (update_mgr_);
+ }
+
+ /// @brief Returns true if the queue manager should be reconfigured.
+ bool getReconfQueueFlag() const {
+ return (reconf_queue_flag_);
+ }
+
+ /// @brief Returns the type of shutdown requested.
+ ///
+ /// Note, this value is meaningless unless shouldShutdown() returns true.
+ ShutdownType getShutdownType() const {
+ return (shutdown_type_);
+ }
+
+ /// @brief Returns a text label for the given shutdown type.
+ ///
+ /// @param type the numerical shutdown type for which the label is desired.
+ ///
+ /// @return A text label corresponding the value or "invalid" if the
+ /// value is not a valid value.
+ static const char* getShutdownTypeStr(const ShutdownType& type);
+
+private:
+ /// @brief Pointer to our queue manager instance.
+ D2QueueMgrPtr queue_mgr_;
+
+ /// @brief Pointer to our update manager instance.
+ D2UpdateMgrPtr update_mgr_;
+
+ /// @brief Indicates if the queue manager should be reconfigured.
+ bool reconf_queue_flag_;
+
+ /// @brief Indicates the type of shutdown requested.
+ ShutdownType shutdown_type_;
+};
+
+/// @brief Defines a shared pointer to D2Process.
+typedef boost::shared_ptr<D2Process> D2ProcessPtr;
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif
diff --git a/src/bin/d2/d2_queue_mgr.cc b/src/bin/d2/d2_queue_mgr.cc
new file mode 100644
index 0000000..4de9c42
--- /dev/null
+++ b/src/bin/d2/d2_queue_mgr.cc
@@ -0,0 +1,260 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <d2/d2_log.h>
+#include <d2/d2_queue_mgr.h>
+#include <dhcp_ddns/ncr_udp.h>
+
+namespace isc {
+namespace d2 {
+
+// Makes constant visible to Google test macros.
+const size_t D2QueueMgr::MAX_QUEUE_DEFAULT;
+
+D2QueueMgr::D2QueueMgr(isc::asiolink::IOService& io_service,
+ const size_t max_queue_size)
+ : io_service_(io_service), max_queue_size_(max_queue_size),
+ mgr_state_(NOT_INITTED), target_stop_state_(NOT_INITTED) {
+ // Use setter to do validation.
+ setMaxQueueSize(max_queue_size);
+}
+
+D2QueueMgr::~D2QueueMgr() {
+}
+
+void
+D2QueueMgr::operator()(const dhcp_ddns::NameChangeListener::Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr) {
+ try {
+ // Note that error conditions must be handled here without throwing
+ // exceptions. Remember this is the application level "link" in the
+ // callback chain. Throwing an exception here will "break" the
+ // io_service "run" we are operating under. With that in mind,
+ // if we hit a problem, we will stop the listener transition to
+ // the appropriate stopped state. Upper layer(s) must monitor our
+ // state as well as our queue size.
+ switch (result) {
+ case dhcp_ddns::NameChangeListener::SUCCESS:
+ // Receive was successful, attempt to queue the request.
+ if (getQueueSize() < getMaxQueueSize()) {
+ // There's room on the queue, add to the end
+ enqueue(ncr);
+ return;
+ }
+
+ // Queue is full, stop the listener.
+ // Note that we can move straight to a STOPPED state as there
+ // is no receive in progress.
+ LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_QUEUE_FULL)
+ .arg(max_queue_size_);
+ stopListening(STOPPED_QUEUE_FULL);
+ break;
+
+ case dhcp_ddns::NameChangeListener::STOPPED:
+ if (mgr_state_ == STOPPING) {
+ // This is confirmation that the listener has stopped and its
+ // callback will not be called again, unless its restarted.
+ updateStopState();
+ } else {
+ // We should not get an receive complete status of stopped
+ // unless we canceled the read as part of stopping. Therefore
+ // this is unexpected so we will treat it as a receive error.
+ // This is most likely an unforeseen programmatic issue.
+ LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP)
+ .arg(mgr_state_);
+ stopListening(STOPPED_RECV_ERROR);
+ }
+
+ break;
+
+ default:
+ // Receive failed, stop the listener.
+ // Note that we can move straight to a STOPPED state as there
+ // is no receive in progress.
+ LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_RECV_ERROR);
+ stopListening(STOPPED_RECV_ERROR);
+ break;
+ }
+ } catch (const std::exception& ex) {
+ // On the outside chance a throw occurs, let's log it and swallow it.
+ LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_UNEXPECTED_HANDLER_ERROR)
+ .arg(ex.what());
+ }
+}
+
+void
+D2QueueMgr::initUDPListener(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port,
+ const dhcp_ddns::NameChangeFormat format,
+ const bool reuse_address) {
+
+ if (listener_) {
+ isc_throw(D2QueueMgrError,
+ "D2QueueMgr listener is already initialized");
+ }
+
+ // Instantiate a UDP listener and set state to INITTED.
+ // Note UDP listener constructor does not throw.
+ listener_.reset(new dhcp_ddns::
+ NameChangeUDPListener(ip_address, port, format, *this,
+ reuse_address));
+ mgr_state_ = INITTED;
+}
+
+void
+D2QueueMgr::startListening() {
+ // We can't listen if we haven't initialized the listener yet.
+ if (!listener_) {
+ isc_throw(D2QueueMgrError, "D2QueueMgr "
+ "listener is not initialized, cannot start listening");
+ }
+
+ // If we are already listening, we do not want to "reopen" the listener
+ // and really we shouldn't be trying.
+ if (mgr_state_ == RUNNING) {
+ isc_throw(D2QueueMgrError, "D2QueueMgr "
+ "cannot call startListening from the RUNNING state");
+ }
+
+ // Instruct the listener to start listening and set state accordingly.
+ try {
+ listener_->startListening(io_service_);
+ mgr_state_ = RUNNING;
+ } catch (const isc::Exception& ex) {
+ isc_throw(D2QueueMgrError, "D2QueueMgr listener start failed: "
+ << ex.what());
+ }
+
+ LOG_INFO (dctl_logger, DHCP_DDNS_QUEUE_MGR_STARTED);
+}
+
+void
+D2QueueMgr::stopListening(const State target_stop_state) {
+ if (listener_) {
+ // Enforce only valid "stop" states.
+ // This is purely a programmatic error and should never happen.
+ if (target_stop_state != STOPPED &&
+ target_stop_state != STOPPED_QUEUE_FULL &&
+ target_stop_state != STOPPED_RECV_ERROR) {
+ isc_throw(D2QueueMgrError,
+ "D2QueueMgr invalid value for stop state: "
+ << target_stop_state);
+ }
+
+ // Remember the state we want to acheive.
+ target_stop_state_ = target_stop_state;
+
+ // Instruct the listener to stop. If the listener reports that it
+ // has IO pending, then we transition to STOPPING to wait for the
+ // cancellation event. Otherwise, we can move directly to the targeted
+ // state.
+ listener_->stopListening();
+ if (listener_->isIoPending()) {
+ mgr_state_ = STOPPING;
+ } else {
+ updateStopState();
+ }
+ }
+}
+
+void
+D2QueueMgr::updateStopState() {
+ mgr_state_ = target_stop_state_;
+ LOG_INFO (dctl_logger, DHCP_DDNS_QUEUE_MGR_STOPPED);
+}
+
+
+void
+D2QueueMgr::removeListener() {
+ // Force our managing layer(s) to stop us properly first.
+ if (mgr_state_ == RUNNING) {
+ isc_throw(D2QueueMgrError,
+ "D2QueueMgr cannot delete listener while state is RUNNING");
+ }
+
+ listener_.reset();
+ mgr_state_ = NOT_INITTED;
+}
+
+const dhcp_ddns::NameChangeRequestPtr&
+D2QueueMgr::peek() const {
+ if (getQueueSize() == 0) {
+ isc_throw(D2QueueMgrQueueEmpty,
+ "D2QueueMgr peek attempted on an empty queue");
+ }
+
+ return (ncr_queue_.front());
+}
+
+const dhcp_ddns::NameChangeRequestPtr&
+D2QueueMgr::peekAt(const size_t index) const {
+ if (index >= getQueueSize()) {
+ isc_throw(D2QueueMgrInvalidIndex,
+ "D2QueueMgr peek beyond end of queue attempted"
+ << " index: " << index << " queue size: " << getQueueSize());
+ }
+
+ return (ncr_queue_.at(index));
+}
+
+void
+D2QueueMgr::dequeueAt(const size_t index) {
+ if (index >= getQueueSize()) {
+ isc_throw(D2QueueMgrInvalidIndex,
+ "D2QueueMgr dequeue beyond end of queue attempted"
+ << " index: " << index << " queue size: " << getQueueSize());
+ }
+
+ RequestQueue::iterator pos = ncr_queue_.begin() + index;
+ ncr_queue_.erase(pos);
+}
+
+
+void
+D2QueueMgr::dequeue() {
+ if (getQueueSize() == 0) {
+ isc_throw(D2QueueMgrQueueEmpty,
+ "D2QueueMgr dequeue attempted on an empty queue");
+ }
+
+ ncr_queue_.pop_front();
+}
+
+void
+D2QueueMgr::enqueue(dhcp_ddns::NameChangeRequestPtr& ncr) {
+ ncr_queue_.push_back(ncr);
+}
+
+void
+D2QueueMgr::clearQueue() {
+ ncr_queue_.clear();
+}
+
+void
+D2QueueMgr::setMaxQueueSize(const size_t new_queue_max) {
+ if (new_queue_max < 1) {
+ isc_throw(D2QueueMgrError,
+ "D2QueueMgr maximum queue size must be greater than zero");
+ }
+
+ if (new_queue_max < getQueueSize()) {
+ isc_throw(D2QueueMgrError, "D2QueueMgr maximum queue size value cannot"
+ " be less than the current queue size :" << getQueueSize());
+ }
+
+ max_queue_size_ = new_queue_max;
+}
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/bin/d2/d2_queue_mgr.h b/src/bin/d2/d2_queue_mgr.h
new file mode 100644
index 0000000..8fe078b
--- /dev/null
+++ b/src/bin/d2/d2_queue_mgr.h
@@ -0,0 +1,355 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 D2_QUEUE_MGR_H
+#define D2_QUEUE_MGR_H
+
+/// @file d2_queue_mgr.h This file defines the class D2QueueMgr.
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <exceptions/exceptions.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <dhcp_ddns/ncr_io.h>
+
+#include <boost/noncopyable.hpp>
+#include <deque>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Defines a queue of requests.
+/// @todo This may be replaced with an actual class in the future.
+typedef std::deque<dhcp_ddns::NameChangeRequestPtr> RequestQueue;
+
+/// @brief Thrown if the queue manager encounters a general error.
+class D2QueueMgrError : public isc::Exception {
+public:
+ D2QueueMgrError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Thrown if the queue manager's receive handler is passed
+/// a failure result.
+class D2QueueMgrReceiveError : public isc::Exception {
+public:
+ D2QueueMgrReceiveError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Thrown if the request queue is full when an enqueue is attempted.
+class D2QueueMgrQueueFull : public isc::Exception {
+public:
+ D2QueueMgrQueueFull(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Thrown if the request queue empty and a read is attempted.
+class D2QueueMgrQueueEmpty : public isc::Exception {
+public:
+ D2QueueMgrQueueEmpty(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Thrown if a queue index is beyond the end of the queue
+class D2QueueMgrInvalidIndex : public isc::Exception {
+public:
+ D2QueueMgrInvalidIndex(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief D2QueueMgr creates and manages a queue of DNS update requests.
+///
+/// D2QueueMgr is class specifically designed as an integral part of DHCP-DDNS.
+/// Its primary responsibility is to listen for NameChangeRequests from
+/// DHCP-DDNS clients (e.g. DHCP servers) and queue them for processing. In
+/// addition it may provide a number services to locate entries in the queue
+/// such as by FQDN or DHCID. These services may eventually be used
+/// for processing optimization. The initial implementation will support
+/// simple FIFO access.
+///
+/// D2QueueMgr uses a NameChangeListener to asynchronously receive requests.
+/// It derives from NameChangeListener::RequestReceiveHandler and supplies an
+/// implementation of the operator()(Result, NameChangeRequestPtr). It is
+/// through this operator() that D2QueueMgr is passed inbound NCRs. D2QueueMgr
+/// will add each newly received request onto the back of the request queue
+///
+/// D2QueueMgr defines a simple state model constructed around the status of
+/// its NameChangeListener, consisting of the following states:
+///
+/// * NOT_INITTED - D2QueueMgr has been constructed, but its listener has
+/// not been initialized.
+///
+/// * INITTED - The listener has been initialized, but it is not open for
+/// listening. To move from NOT_INITTED to INITTED, one of the D2QueueMgr
+/// listener initialization methods must be invoked. Currently there is
+/// only one type of listener, NameChangeUDPListener, hence there is only
+/// one listener initialization method, initUDPListener. As more listener
+/// types are created, listener initialization methods will need to be
+/// added.
+///
+/// * RUNNING - The listener is open and listening for requests.
+/// Once initialized, in order to begin listening for requests, the
+/// startListener() method must be invoked. Upon successful completion of
+/// of this call, D2QueueMgr will begin receiving requests as they arrive
+/// without any further steps. This method may be called from the INITTED
+/// or one of the STOPPED states.
+///
+/// * STOPPING - The listener is in the process of stopping active
+/// listening. This is transitory state between RUNNING and STOPPED, which
+/// is completed by IO cancellation event.
+///
+/// * STOPPED - The listener has been listening but has been stopped
+/// without error. To return to listening, startListener() must be invoked.
+///
+/// * STOPPED_QUEUE_FULL - Request queue is full, the listener has been
+/// stopped. D2QueueMgr will enter this state when the request queue
+/// reaches the maximum queue size. Once this limit is reached, the
+/// listener will be closed and no further requests will be received.
+/// To return to listening, startListener() must be invoked. Note that so
+/// long as the queue is full, any attempt to queue a request will fail.
+///
+/// * STOPPED_RECV_ERROR - The listener has experienced a receive error
+/// and has been stopped. D2QueueMgr will enter this state when it is
+/// passed a failed status into the request completion handler. To return
+/// to listening, startListener() must be invoked.
+///
+/// D2QueueMgr does not attempt to recover from stopped conditions, this is left
+/// to upper layers.
+///
+/// It is important to note that the queue contents are preserved between
+/// state transitions. In other words entries in the queue remain there
+/// until they are removed explicitly via the deque() or implicitly by
+/// via the clearQueue() method.
+///
+class D2QueueMgr : public dhcp_ddns::NameChangeListener::RequestReceiveHandler,
+ boost::noncopyable {
+public:
+ /// @brief Maximum number of entries allowed in the request queue.
+ /// NOTE that 1024 is an arbitrary choice picked for the initial
+ /// implementation.
+ static const size_t MAX_QUEUE_DEFAULT = 1024;
+
+ /// @brief Defines the list of possible states for D2QueueMgr.
+ enum State {
+ NOT_INITTED,
+ INITTED,
+ RUNNING,
+ STOPPING,
+ STOPPED_QUEUE_FULL,
+ STOPPED_RECV_ERROR,
+ STOPPED,
+ };
+
+ /// @brief Constructor
+ ///
+ /// Creates a D2QueueMgr instance. Note that the listener is not created
+ /// in the constructor. The initial state will be NOT_INITTED.
+ ///
+ /// @param io_service IOService instance to be passed into the listener for
+ /// IO management.
+ /// @param max_queue_size the maximum number of entries allowed in the
+ /// queue.
+ /// This value must be greater than zero. It defaults to MAX_QUEUE_DEFAULT.
+ ///
+ /// @throw D2QueueMgrError if max_queue_size is zero.
+ D2QueueMgr(isc::asiolink::IOService& io_service,
+ const size_t max_queue_size = MAX_QUEUE_DEFAULT);
+
+ /// @brief Destructor
+ virtual ~D2QueueMgr();
+
+ /// @brief Initializes the listener as a UDP listener.
+ ///
+ /// Instantiates the listener_ member as NameChangeUDPListener passing
+ /// the given parameters. Upon successful completion, the D2QueueMgr state
+ /// will be INITTED.
+ ///
+ /// @param ip_address is the network address on which to listen
+ /// @param port is the IP port on which to listen
+ /// @param format is the wire format of the inbound requests.
+ /// @param reuse_address enables IP address sharing when true
+ /// It defaults to false.
+ void initUDPListener(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port,
+ const dhcp_ddns::NameChangeFormat format,
+ const bool reuse_address = false);
+
+ /// @brief Starts actively listening for requests.
+ ///
+ /// Invokes the listener's startListening method passing in our
+ /// IOService instance.
+ ///
+ /// @throw D2QueueMgrError if the listener has not been initialized,
+ /// state is already RUNNING, or the listener fails to actually start.
+ void startListening();
+
+ /// @brief Function operator implementing the NCR receive callback.
+ ///
+ /// This method is invoked by the listener as part of its receive
+ /// completion callback and is how the inbound NameChangeRequests are
+ /// passed up to the D2QueueMgr for queueing.
+ /// If the given result indicates a successful receive completion and
+ /// there is room left in the queue, the given request is queued.
+ ///
+ /// If the queue is at maximum capacity, stopListening() is invoked and
+ /// the state is set to STOPPED_QUEUE_FULL.
+ ///
+ /// If the result indicates IO stopped, then the state is set to STOPPED.
+ /// Note this is not an error, it results from a deliberate cancellation
+ /// of listener IO as part of a normal stopListener call.
+ ///
+ /// If the result indicates a failed receive, stopListening() is invoked
+ /// and the state is set to STOPPED_RECV_ERROR.
+ ///
+ /// This method specifically avoids throwing on an error as any such throw
+ /// would surface at the io_service::run (or run variant) method invocation
+ /// site. The upper layers are expected to monitor D2QueueMgr's state and
+ /// act accordingly.
+ ///
+ /// @param result contains that receive outcome status.
+ /// @param ncr is a pointer to the newly received NameChangeRequest if
+ /// result is NameChangeListener::SUCCESS. It is indeterminate other
+ /// wise.
+ virtual void operator ()(const dhcp_ddns::NameChangeListener::Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr);
+
+ /// @brief Stops listening for requests.
+ ///
+ /// Invokes the listener's stopListening method which will cause it to
+ /// cancel any pending IO and close its IO source. It the sets target
+ /// stop state to the given value.
+ ///
+ /// If there is no IO pending, the manager state is immediately set to the
+ /// target stop state, otherwise the manager state is set to STOPPING.
+ ///
+ /// @param target_stop_state is one of the three stopped state values.
+ ///
+ /// @throw D2QueueMgrError if stop_state is a valid stop state.
+ void stopListening(const State target_stop_state = STOPPED);
+
+
+ /// @brief Deletes the current listener
+ ///
+ /// This method will delete the current listener and returns the manager
+ /// to the NOT_INITTED state. This is provided to support reconfiguring
+ /// a new listener without losing queued requests.
+ ///
+ /// @throw D2QueMgrError if called when the manager state is RUNNING.
+ void removeListener();
+
+ /// @brief Returns the number of entries in the queue.
+ size_t getQueueSize() const {
+ return (ncr_queue_.size());
+ };
+
+ /// @brief Returns the maximum number of entries allowed in the queue.
+ size_t getMaxQueueSize() const {
+ return (max_queue_size_);
+ }
+
+ /// @brief Sets the maximum number of entries allowed in the queue.
+ ///
+ /// @param max_queue_size is the new maximum size of the queue.
+ ///
+ /// @throw D2QueueMgrError if the new value is less than one or if
+ /// the new value is less than the number of entries currently in the
+ /// queue.
+ void setMaxQueueSize(const size_t max_queue_size);
+
+ /// @brief Returns the current state.
+ State getMgrState() const {
+ return (mgr_state_);
+ }
+
+ /// @brief Returns the entry at the front of the queue.
+ ///
+ /// The entry returned is next in line to be processed, assuming a FIFO
+ /// approach to task selection. Note, the entry is not removed from the
+ /// queue.
+ ///
+ /// @return Pointer reference to the queue entry.
+ ///
+ /// @throw D2QueueMgrQueEmpty if there are no entries in the queue.
+ const dhcp_ddns::NameChangeRequestPtr& peek() const;
+
+ /// @brief Returns the entry at a given position in the queue.
+ ///
+ /// Note that the entry is not removed from the queue.
+ /// @param index the index of the entry in the queue to fetch.
+ /// Valid values are 0 (front of the queue) to (queue size - 1).
+ ///
+ /// @return Pointer reference to the queue entry.
+ ///
+ /// @throw D2QueueMgrInvalidIndex if the given index is beyond the
+ /// end of the queue.
+ const dhcp_ddns::NameChangeRequestPtr& peekAt(const size_t index) const;
+
+ /// @brief Removes the entry at a given position in the queue.
+ ///
+ /// @param index the index of the entry in the queue to remove.
+ /// Valid values are 0 (front of the queue) to (queue size - 1).
+ ///
+ /// @throw D2QueueMgrInvalidIndex if the given index is beyond the
+ /// end of the queue.
+ void dequeueAt(const size_t index);
+
+ /// @brief Removes the entry at the front of the queue.
+ ///
+ /// @throw D2QueueMgrQueEmpty if there are no entries in the queue.
+ void dequeue();
+
+ /// @brief Adds a request to the end of the queue.
+ ///
+ /// @param ncr pointer to the NameChangeRequest to add to the queue.
+ void enqueue(dhcp_ddns::NameChangeRequestPtr& ncr);
+
+ /// @brief Removes all entries from the queue.
+ void clearQueue();
+
+ private:
+ /// @brief Sets the manager state to the target stop state.
+ ///
+ /// Convenience method which sets the manager state to the target stop
+ /// state and logs that the manager is stopped.
+ void updateStopState();
+
+ /// @brief IOService that our listener should use for IO management.
+ isc::asiolink::IOService& io_service_;
+
+ /// @brief Dictates the maximum number of entries allowed in the queue.
+ size_t max_queue_size_;
+
+ /// @brief Queue of received NameChangeRequests.
+ RequestQueue ncr_queue_;
+
+ /// @brief Listener instance from which requests are received.
+ boost::shared_ptr<dhcp_ddns::NameChangeListener> listener_;
+
+ /// @brief Current state of the manager.
+ State mgr_state_;
+
+ /// @brief Tracks the state the manager should be in once stopped.
+ State target_stop_state_;
+};
+
+/// @brief Defines a pointer for manager instances.
+typedef boost::shared_ptr<D2QueueMgr> D2QueueMgrPtr;
+
+} // namespace isc::d2
+} // namespace isc
+
+#endif
diff --git a/src/bin/d2/d2_update_message.cc b/src/bin/d2/d2_update_message.cc
new file mode 100644
index 0000000..71fb9f3
--- /dev/null
+++ b/src/bin/d2/d2_update_message.cc
@@ -0,0 +1,221 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <d2/d2_update_message.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/question.h>
+
+namespace isc {
+namespace d2 {
+
+using namespace isc::dns;
+
+D2UpdateMessage::D2UpdateMessage(const Direction direction)
+ : message_(direction == INBOUND ?
+ dns::Message::PARSE : dns::Message::RENDER) {
+ // If this object is to create an outgoing message, we have to
+ // set the proper Opcode field and QR flag here.
+ if (direction == OUTBOUND) {
+ message_.setOpcode(Opcode(Opcode::UPDATE_CODE));
+ message_.setHeaderFlag(dns::Message::HEADERFLAG_QR, false);
+
+ }
+}
+
+D2UpdateMessage::QRFlag
+D2UpdateMessage::getQRFlag() const {
+ return (message_.getHeaderFlag(dns::Message::HEADERFLAG_QR) ?
+ RESPONSE : REQUEST);
+}
+
+uint16_t
+D2UpdateMessage::getId() const {
+ return (message_.getQid());
+}
+
+void
+D2UpdateMessage::setId(const uint16_t id) {
+ message_.setQid(id);
+}
+
+
+const dns::Rcode&
+D2UpdateMessage::getRcode() const {
+ return (message_.getRcode());
+}
+
+void
+D2UpdateMessage::setRcode(const dns::Rcode& rcode) {
+ message_.setRcode(rcode);
+}
+
+unsigned int
+D2UpdateMessage::getRRCount(const UpdateMsgSection section) const {
+ return (message_.getRRCount(ddnsToDnsSection(section)));
+}
+
+const dns::RRsetIterator
+D2UpdateMessage::beginSection(const UpdateMsgSection section) const {
+ return (message_.beginSection(ddnsToDnsSection(section)));
+}
+
+const dns::RRsetIterator
+D2UpdateMessage::endSection(const UpdateMsgSection section) const {
+ return (message_.endSection(ddnsToDnsSection(section)));
+}
+
+void
+D2UpdateMessage::setZone(const Name& zone, const RRClass& rrclass) {
+ // The Zone data is kept in the underlying Question class. If there
+ // is a record stored there already, we need to remove it, because
+ // we may have at most one Zone record in the DNS Update message.
+ if (message_.getRRCount(dns::Message::SECTION_QUESTION) > 0) {
+ message_.clearSection(dns::Message::SECTION_QUESTION);
+ }
+ // Add the new record...
+ Question question(zone, rrclass, RRType::SOA());
+ message_.addQuestion(question);
+ // ... and update the local class member holding the D2Zone object.
+ zone_.reset(new D2Zone(question.getName(), question.getClass()));
+}
+
+D2ZonePtr
+D2UpdateMessage::getZone() const {
+ return (zone_);
+}
+
+void
+D2UpdateMessage::addRRset(const UpdateMsgSection section,
+ const dns::RRsetPtr& rrset) {
+ if (section == SECTION_ZONE) {
+ isc_throw(isc::BadValue, "unable to add RRset to the Zone section"
+ " of the DNS Update message, use setZone instead");
+ }
+ message_.addRRset(ddnsToDnsSection(section), rrset);
+}
+
+void
+D2UpdateMessage::toWire(AbstractMessageRenderer& renderer) {
+ // We are preparing the wire format of the message, meaning
+ // that this message will be sent as a request to the DNS.
+ // Therefore, we expect that this message is a REQUEST.
+ if (getQRFlag() != REQUEST) {
+ isc_throw(InvalidQRFlag, "QR flag must be cleared for the outgoing"
+ " DNS Update message");
+ }
+ // According to RFC2136, the ZONE section may contain exactly one
+ // record.
+ if (getRRCount(SECTION_ZONE) != 1) {
+ isc_throw(InvalidZoneSection, "Zone section of the DNS Update message"
+ " must comprise exactly one record (RFC2136, section 2.3)");
+ }
+ message_.toWire(renderer);
+}
+
+void
+D2UpdateMessage::fromWire(isc::util::InputBuffer& buffer) {
+ // First, use the underlying dns::Message implementation to get the
+ // contents of the DNS response message. Note that it may or may
+ // not be the message that we are interested in, but needs to be
+ // parsed so as we can check its ID, Opcode etc.
+ message_.fromWire(buffer);
+ // This class exposes the getZone() function. This function will return
+ // pointer to the D2Zone object if non-empty Zone section exists in the
+ // received message. It will return NULL pointer if it doesn't exist.
+ // The pointer is held in the D2UpdateMessage class member. We need to
+ // update this pointer every time we parse the message.
+ if (getRRCount(D2UpdateMessage::SECTION_ZONE) > 0) {
+ // There is a Zone section in the received message. Replace
+ // Zone pointer with the new value.
+ QuestionPtr question = *message_.beginQuestion();
+ // If the Zone counter is greater than 0 (which we have checked)
+ // there must be a valid Question pointer stored in the message_
+ // object. If there isn't, it is a programming error.
+ assert(question);
+ zone_.reset(new D2Zone(question->getName(), question->getClass()));
+
+ } else {
+ // Zone section doesn't hold any pointers, so set the pointer to NULL.
+ zone_.reset();
+
+ }
+ // Check that the content of the received message is sane.
+ // One of the basic checks to do is to verify that we have
+ // received the DNS update message. If not, it can be dropped
+ // or an error message can be printed. Other than that, we
+ // will check that there is at most one Zone record and QR flag
+ // is set.
+ validateResponse();
+}
+
+dns::Message::Section
+D2UpdateMessage::ddnsToDnsSection(const UpdateMsgSection section) {
+ /// The following switch maps the enumerator values from the
+ /// DNS Update message to the corresponding enumerator values
+ /// representing fields of the DNS message.
+ switch(section) {
+ case SECTION_ZONE :
+ return (dns::Message::SECTION_QUESTION);
+
+ case SECTION_PREREQUISITE:
+ return (dns::Message::SECTION_ANSWER);
+
+ case SECTION_UPDATE:
+ return (dns::Message::SECTION_AUTHORITY);
+
+ case SECTION_ADDITIONAL:
+ return (dns::Message::SECTION_ADDITIONAL);
+
+ default:
+ ;
+ }
+ isc_throw(dns::InvalidMessageSection,
+ "unknown message section " << section);
+}
+
+void
+D2UpdateMessage::validateResponse() const {
+ // Verify that we are dealing with the DNS Update message. According to
+ // RFC 2136, section 3.8 server will copy the Opcode from the query.
+ // If we are dealing with a different type of message, we may simply
+ // stop further processing, because it is likely that the message was
+ // directed to someone else.
+ if (message_.getOpcode() != Opcode::UPDATE()) {
+ isc_throw(NotUpdateMessage, "received message is not a DDNS update,"
+ << " received message code is "
+ << message_.getOpcode().getCode());
+ }
+ // Received message should have QR flag set, which indicates that it is
+ // a RESPONSE.
+ if (getQRFlag() == REQUEST) {
+ isc_throw(InvalidQRFlag, "received message should have QR flag set,"
+ " to indicate that it is a RESPONSE message; the QR"
+ << " flag in received message is unset");
+ }
+ // DNS server may copy a Zone record from the query message. Since query
+ // must comprise exactly one Zone record (RFC 2136, section 2.3), the
+ // response message may contain 1 record at most. It may also contain no
+ // records if a server chooses not to copy Zone section.
+ if (getRRCount(SECTION_ZONE) > 1) {
+ isc_throw(InvalidZoneSection, "received message contains "
+ << getRRCount(SECTION_ZONE) << " Zone records,"
+ << " it should contain at most 1 record");
+ }
+}
+
+} // namespace d2
+} // namespace isc
+
diff --git a/src/bin/d2/d2_update_message.h b/src/bin/d2/d2_update_message.h
new file mode 100644
index 0000000..955e5c0
--- /dev/null
+++ b/src/bin/d2/d2_update_message.h
@@ -0,0 +1,341 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 D2_UPDATE_MESSAGE_H
+#define D2_UPDATE_MESSAGE_H
+
+#include <d2/d2_zone.h>
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+#include <map>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception indicating that Zone section contains invalid content.
+///
+/// This exception is thrown when ZONE section of the DNS Update message
+/// is invalid. According to RFC2136, section 2.3, the zone section is
+/// allowed to contain exactly one record. When Request message contains
+/// more records or is empty, this exception is thrown.
+class InvalidZoneSection : public Exception {
+public:
+ InvalidZoneSection(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception indicating that QR flag has invalid value.
+///
+/// This exception is thrown when QR flag has invalid value for
+/// the operation performed on the particular message. For instance,
+/// the QR flag must be set to indicate that the given message is
+/// a RESPONSE when @c D2UpdateMessage::fromWire is performed.
+/// The QR flag must be cleared when @c D2UpdateMessage::toWire
+/// is executed.
+class InvalidQRFlag : public Exception {
+public:
+ InvalidQRFlag(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception indicating that the parsed message is not DNS Update.
+///
+/// This exception is thrown when decoding the DNS message which is not
+/// a DNS Update.
+class NotUpdateMessage : public Exception {
+public:
+ NotUpdateMessage(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+class D2UpdateMessage;
+
+/// @brief Pointer to the DNS Update Message.
+typedef boost::shared_ptr<D2UpdateMessage> D2UpdateMessagePtr;
+
+/// @brief The @c D2UpdateMessage encapsulates a DNS Update message.
+///
+/// This class represents the DNS Update message. Functions exposed by this
+/// class allow to specify the data sections carried by the message and create
+/// an on-wire format of this message. This class is also used to decode
+/// messages received from the DNS server in the on-wire format.
+///
+/// <b>Design choice:</b> A dedicated class has been created to encapsulate
+/// DNS Update message because existing @c isc::dns::Message is designed to
+/// support regular DNS messages (described in RFC 1035) only. Although DNS
+/// Update has the same format, particular sections serve different purposes.
+/// In order to avoid rewrite of significant portions of @c isc::dns::Message
+/// class, this class is implemented in-terms-of @c isc::dns::Message class
+/// to reuse its functionality where possible.
+class D2UpdateMessage {
+public:
+
+ /// @brief Indicates if the @c D2UpdateMessage object encapsulates Inbound
+ /// or Outbound message.
+ enum Direction {
+ INBOUND,
+ OUTBOUND
+ };
+
+ /// @brief Indicates whether DNS Update message is a REQUEST or RESPONSE.
+ enum QRFlag {
+ REQUEST,
+ RESPONSE
+ };
+
+ /// @brief Identifies sections in the DNS Update Message.
+ ///
+ /// Each message comprises message Header and may contain the following
+ /// sections:
+ /// - ZONE
+ /// - PREREQUISITE
+ /// - UPDATE
+ /// - ADDITIONAL
+ ///
+ /// The enum elements are used by functions such as @c getRRCount (to get
+ /// the number of records in a corresponding section) and @c beginSection
+ /// and @c endSection (to access data in the corresponding section).
+ enum UpdateMsgSection {
+ SECTION_ZONE,
+ SECTION_PREREQUISITE,
+ SECTION_UPDATE,
+ SECTION_ADDITIONAL
+ };
+
+public:
+ /// @brief Constructor used to create an instance of the DNS Update Message
+ /// (either outgoing or incoming).
+ ///
+ /// This constructor is used to create an instance of either incoming or
+ /// outgoing DNS Update message. The boolean argument indicates wheteher it
+ /// is incoming (true) or outgoing (false) message. For incoming messages
+ /// the @c D2UpdateMessage::fromWire function is used to parse on-wire data.
+ /// For outgoing messages, modifier functions should be used to set the
+ /// message contents and @c D2UpdateMessage::toWire function to create
+ /// on-wire data.
+ ///
+ /// @param direction indicates if this is an inbound or outbound message.
+ D2UpdateMessage(const Direction direction = OUTBOUND);
+
+ ///
+ /// @name Copy constructor and assignment operator
+ ///
+ /// Copy constructor and assignment operator are private because we assume
+ /// there will be no need to copy messages on the client side.
+ //@{
+private:
+ D2UpdateMessage(const D2UpdateMessage& source);
+ D2UpdateMessage& operator=(const D2UpdateMessage& source);
+ //@}
+
+public:
+
+ /// @brief Returns enum value indicating if the message is a
+ /// REQUEST or RESPONSE
+ ///
+ /// The returned value is REQUEST if the message is created as an outgoing
+ /// message. In such case the QR flag bit in the message header is cleared.
+ /// The returned value is RESPONSE if the message is created as an incoming
+ /// message and the QR flag bit was set in the received message header.
+ ///
+ /// @return An enum value indicating whether the message is a
+ /// REQUEST or RESPONSE.
+ QRFlag getQRFlag() const;
+
+ /// @brief Returns message ID.
+ ///
+ /// @return message ID.
+ uint16_t getId() const;
+
+ /// @brief Sets message ID.
+ ///
+ /// @param id 16-bit value of the message id.
+ void setId(const uint16_t id);
+
+ /// @brief Returns an object representing message RCode.
+ ///
+ /// @return An object representing message RCode.
+ const dns::Rcode& getRcode() const;
+
+ /// @brief Sets message RCode.
+ ///
+ /// @param rcode An object representing message RCode.
+ void setRcode(const dns::Rcode& rcode);
+
+ /// @brief Returns number of RRsets in the specified message section.
+ ///
+ /// @param section An @c UpdateMsgSection enum specifying a message section
+ /// for which the number of RRsets is to be returned.
+ ///
+ /// @return A number of RRsets in the specified message section.
+ unsigned int getRRCount(const UpdateMsgSection section) const;
+
+ /// @name Functions returning iterators to RRsets in message sections.
+ ///
+ //@{
+ /// @brief Return iterators pointing to the beginning of the list of RRsets,
+ /// which belong to the specified section.
+ ///
+ /// @param section An @c UpdateMsgSection enum specifying a message section
+ /// for which the iterator should be returned.
+ ///
+ /// @return An iterator pointing to the beginning of the list of the
+ /// RRsets, which belong to the specified section.
+ const dns::RRsetIterator beginSection(const UpdateMsgSection section) const;
+
+ /// @brief Return iterators pointing to the end of the list of RRsets,
+ /// which belong to the specified section.
+ ///
+ /// @param section An @c UpdateMsgSection enum specifying a message section
+ /// for which the iterator should be returned.
+ ///
+ /// @return An iterator pointing to the end of the list of the
+ /// RRsets, which belong to the specified section.
+ const dns::RRsetIterator endSection(const UpdateMsgSection section) const;
+ //@}
+
+ /// @brief Sets the Zone record.
+ ///
+ /// This function creates the @c D2Zone object, representing a Zone record
+ /// for the outgoing message. If the Zone record is already set, it is
+ /// replaced by the new record being set by this function. The RRType for
+ /// the record is always SOA.
+ ///
+ /// @param zone A name of the zone being updated.
+ /// @param rrclass A class of the zone record.
+ void setZone(const dns::Name& zone, const dns::RRClass& rrclass);
+
+ /// @brief Returns a pointer to the object representing Zone record.
+ ///
+ /// @return A pointer to the object representing Zone record.
+ D2ZonePtr getZone() const;
+
+ /// @brief Adds an RRset to the specified section.
+ ///
+ /// This function may throw exception if the specified section is
+ /// out of bounds or Zone section update is attempted. For Zone
+ /// section @c D2UpdateMessage::setZone function should be used instead.
+ /// Also, this function expects that @c rrset argument is non-NULL.
+ ///
+ /// @param section A message section where the RRset should be added.
+ /// @param rrset A reference to a RRset which should be added.
+ void addRRset(const UpdateMsgSection section, const dns::RRsetPtr& rrset);
+
+ /// @name Functions to handle message encoding and decoding.
+ ///
+ //@{
+ /// @brief Encode outgoing message into wire format.
+ ///
+ /// This function encodes the DNS Update into the wire format. The format of
+ /// such a message is described in the RFC2136, section 2. Some of the
+ /// sections which belong to encoded message may be empty. If a particular
+ /// message section is empty (does not comprise any RRs), the corresponding
+ /// counter in the message header is set to 0. These counters are: PRCOUNT,
+ /// UPCOUNT, ADCOUNT for the Prerequisites, Update RRs and Additional Data
+ /// RRs respectively. The ZOCOUNT must be equal to 1 because RFC2136
+ /// requires that the message comprises exactly one Zone record.
+ ///
+ /// This function does not guarantee exception safety. However, exceptions
+ /// should be rare because @c D2UpdateMessage class API prevents invalid
+ /// use of the class. The typical case, when this function may throw an
+ /// exception is when this it is called on the object representing
+ /// incoming (instead of outgoing) message. In such case, the QR field
+ /// will be set to RESPONSE, which is invalid setting when calling this
+ /// function.
+ ///
+ /// @param renderer A renderer object used to generate the message wire
+ /// format.
+ void toWire(dns::AbstractMessageRenderer& renderer);
+
+ /// @brief Decode incoming message from the wire format.
+ ///
+ /// This function decodes the DNS Update message stored in the buffer
+ /// specified by the function argument. In the first turn, this function
+ /// parses message header and extracts the section counters: ZOCOUNT,
+ /// PRCOUNT, UPCOUNT and ADCOUNT. Using these counters, function identifies
+ /// message sections, which follow message header. These sections can be
+ /// later accessed using: @c D2UpdateMessage::getZone,
+ /// @c D2UpdateMessage::beginSection and @c D2UpdateMessage::endSection
+ /// functions.
+ ///
+ /// This function is NOT exception safe. It signals message decoding errors
+ /// through exceptions. Message decoding error may occur if the received
+ /// message does not conform to the general DNS Message format, specified in
+ /// RFC 1035. Errors which are specific to DNS Update messages include:
+ /// - Invalid Opcode - not an UPDATE.
+ /// - Invalid QR flag - the QR bit should be set to indicate that the
+ /// message is the server response.
+ /// - The number of records in the Zone section is greater than 1.
+ ///
+ /// @param buffer input buffer, holding DNS Update message to be parsed.
+ void fromWire(isc::util::InputBuffer& buffer);
+ //@}
+
+private:
+ /// Maps the values of the @c UpdateMessageSection field to the
+ /// corresponding values in the @c isc::dns::Message class. This
+ /// mapping is required here because this class uses @c isc::dns::Message
+ /// class to do the actual processing of the DNS Update message.
+ ///
+ /// @param section An enum indicating the section for which the
+ /// corresponding enum value from @c isc::dns::Message will be returned.
+ ///
+ /// @return The enum value indicating the section in the DNS message
+ /// represented by the @c isc::dns::Message class.
+ static
+ dns::Message::Section ddnsToDnsSection(const UpdateMsgSection section);
+
+ /// @brief Checks received response message for correctness.
+ ///
+ /// This function verifies that the received response from a server is
+ /// correct. Currently this function checks the following:
+ /// - Opcode is 'DNS Update',
+ /// - QR flag is RESPONSE (flag bit is set),
+ /// - Zone section comprises at most one record.
+ ///
+ /// The function will throw exception if any of the conditions above are
+ /// not met.
+ ///
+ /// @throw isc::d2::NotUpdateMessage if invalid Opcode.
+ /// @throw isc::d2::InvalidQRFlag if QR flag is not set to RESPONSE
+ /// @throw isc::d2::InvalidZone section, if Zone section comprises more
+ /// than one record.
+ void validateResponse() const;
+
+ /// @brief An object representing DNS Message which is used by the
+ /// implementation of @c D2UpdateMessage to perform low level.
+ ///
+ /// Declaration of this object pollutes the header with the details
+ /// of @c D2UpdateMessage implementation. It might be cleaner to use
+ /// Pimpl idiom to hide this object in an D2UpdateMessageImpl. However,
+ /// it would bring additional complications to the implementation
+ /// while the benefit would low - this header is not a part of any
+ /// common library. Therefore, if implementation is changed, modification of
+ /// private members of this class in the header has low impact.
+ dns::Message message_;
+
+ /// @brief Holds a pointer to the object, representing Zone in the DNS
+ /// Update.
+ D2ZonePtr zone_;
+
+};
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_UPDATE_MESSAGE_H
diff --git a/src/bin/d2/d2_update_mgr.cc b/src/bin/d2/d2_update_mgr.cc
new file mode 100644
index 0000000..e625c3e
--- /dev/null
+++ b/src/bin/d2/d2_update_mgr.cc
@@ -0,0 +1,230 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <d2/d2_update_mgr.h>
+
+#include <sstream>
+#include <iostream>
+#include <vector>
+
+namespace isc {
+namespace d2 {
+
+const size_t D2UpdateMgr::MAX_TRANSACTIONS_DEFAULT;
+
+D2UpdateMgr::D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
+ isc::asiolink::IOService& io_service,
+ const size_t max_transactions)
+ :queue_mgr_(queue_mgr), cfg_mgr_(cfg_mgr), io_service_(io_service) {
+ if (!queue_mgr_) {
+ isc_throw(D2UpdateMgrError, "D2UpdateMgr queue manager cannot be null");
+ }
+
+ if (!cfg_mgr_) {
+ isc_throw(D2UpdateMgrError,
+ "D2UpdateMgr configuration manager cannot be null");
+ }
+
+ // Use setter to do validation.
+ setMaxTransactions(max_transactions);
+}
+
+D2UpdateMgr::~D2UpdateMgr() {
+ transaction_list_.clear();
+}
+
+void D2UpdateMgr::sweep() {
+ // cleanup finished transactions;
+ checkFinishedTransactions();
+
+ // if the queue isn't empty, find the next suitable job and
+ // start a transaction for it.
+ // @todo - Do we want to queue max transactions? The logic here will only
+ // start one new transaction per invocation. On the other hand a busy
+ // system will generate many IO events and this method will be called
+ // frequently. It will likely achieve max transactions quickly on its own.
+ if (getQueueCount() > 0) {
+ if (getTransactionCount() >= max_transactions_) {
+ LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL_DATA,
+ DHCP_DDNS_AT_MAX_TRANSACTIONS).arg(getQueueCount())
+ .arg(getMaxTransactions());
+
+ return;
+ }
+
+ // We are not at maximum transactions, so pick and start the next job.
+ pickNextJob();
+ }
+}
+
+void
+D2UpdateMgr::checkFinishedTransactions() {
+ // Cycle through transaction list and do whatever needs to be done
+ // for finished transactions.
+ // At the moment all we do is remove them from the list. This is likely
+ // to expand as DHCP_DDNS matures.
+ // NOTE: One must use postfix increments of the iterator on the calls
+ // to erase. This replaces the old iterator which becomes invalid by the
+ // erase with a the next valid iterator. Prefix incrementing will not
+ // work.
+ TransactionList::iterator it = transaction_list_.begin();
+ while (it != transaction_list_.end()) {
+ NameChangeTransactionPtr trans = (*it).second;
+ switch (trans->getNcrStatus()) {
+ case dhcp_ddns::ST_COMPLETED:
+ transaction_list_.erase(it++);
+ break;
+ case dhcp_ddns::ST_FAILED:
+ transaction_list_.erase(it++);
+ break;
+ default:
+ ++it;
+ break;
+ }
+ }
+}
+
+void D2UpdateMgr::pickNextJob() {
+ // Start at the front of the queue, looking for the first entry for
+ // which no transaction is in progress. If we find an eligible entry
+ // remove it from the queue and make a transaction for it.
+ // Requests and transactions are associated by DHCID. If a request has
+ // the same DHCID as a transaction, they are presumed to be for the same
+ // "end user".
+ size_t queue_count = getQueueCount();
+ for (size_t index = 0; index < queue_count; ++index) {
+ dhcp_ddns::NameChangeRequestPtr found_ncr = queue_mgr_->peekAt(index);
+ if (!hasTransaction(found_ncr->getDhcid())) {
+ queue_mgr_->dequeueAt(index);
+ makeTransaction(found_ncr);
+ return;
+ }
+ }
+
+ // There were no eligible jobs. All of the current DHCIDs already have
+ // transactions pending.
+ LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL_DATA, DHCP_DDNS_NO_ELIGIBLE_JOBS)
+ .arg(getQueueCount()).arg(getTransactionCount());
+}
+
+void
+D2UpdateMgr::makeTransaction(dhcp_ddns::NameChangeRequestPtr& next_ncr) {
+ // First lets ensure there is not a transaction in progress for this
+ // DHCID. (pickNextJob should ensure this, as it is the only real caller
+ // but for safety's sake we'll check).
+ const TransactionKey& key = next_ncr->getDhcid();
+ if (findTransaction(key) != transactionListEnd()) {
+ // This is programmatic error. Caller(s) should be checking this.
+ isc_throw(D2UpdateMgrError, "Transaction already in progress for: "
+ << key.toStr());
+ }
+
+ // If forward change is enabled, match to forward servers.
+ DdnsDomainPtr forward_domain;
+ if (next_ncr->isForwardChange()) {
+ bool matched = cfg_mgr_->matchForward(next_ncr->getFqdn(),
+ forward_domain);
+ // Could not find a match for forward DNS server. Log it and get out.
+ // This has the net affect of dropping the request on the floor.
+ if (!matched) {
+ LOG_ERROR(dctl_logger, DHCP_DDNS_NO_FWD_MATCH_ERROR)
+ .arg(next_ncr->getFqdn());
+ return;
+ }
+ }
+
+ // If reverse change is enabled, match to reverse servers.
+ DdnsDomainPtr reverse_domain;
+ if (next_ncr->isReverseChange()) {
+ bool matched = cfg_mgr_->matchReverse(next_ncr->getIpAddress(),
+ reverse_domain);
+ // Could not find a match for reverse DNS server. Log it and get out.
+ // This has the net affect of dropping the request on the floor.
+ if (!matched) {
+ LOG_ERROR(dctl_logger, DHCP_DDNS_NO_REV_MATCH_ERROR)
+ .arg(next_ncr->getIpAddress());
+ return;
+ }
+ }
+
+ // We matched to the required servers, so construct the transaction.
+ NameChangeTransactionPtr trans(new NameChangeTransaction(io_service_,
+ next_ncr,
+ forward_domain,
+ reverse_domain));
+ // Add the new transaction to the list.
+ transaction_list_[key] = trans;
+}
+
+TransactionList::iterator
+D2UpdateMgr::findTransaction(const TransactionKey& key) {
+ return (transaction_list_.find(key));
+}
+
+bool
+D2UpdateMgr::hasTransaction(const TransactionKey& key) {
+ return (findTransaction(key) != transactionListEnd());
+}
+
+void
+D2UpdateMgr::removeTransaction(const TransactionKey& key) {
+ TransactionList::iterator pos = findTransaction(key);
+ if (pos != transactionListEnd()) {
+ transaction_list_.erase(pos);
+ }
+}
+
+TransactionList::iterator
+D2UpdateMgr::transactionListEnd() {
+ return (transaction_list_.end());
+}
+
+void
+D2UpdateMgr::clearTransactionList() {
+ // @todo for now this just wipes them out. We might need something
+ // more elegant, that allows a cancel first.
+ transaction_list_.clear();
+}
+
+void
+D2UpdateMgr::setMaxTransactions(const size_t new_trans_max) {
+ // Obviously we need at room for at least one transaction.
+ if (new_trans_max < 1) {
+ isc_throw(D2UpdateMgrError, "D2UpdateMgr"
+ " maximum transactions limit must be greater than zero");
+ }
+
+ // Do not allow the list maximum to be set to less then current list size.
+ if (new_trans_max < getTransactionCount()) {
+ isc_throw(D2UpdateMgrError, "D2UpdateMgr maximum transaction limit "
+ "cannot be less than the current transaction count :"
+ << getTransactionCount());
+ }
+
+ max_transactions_ = new_trans_max;
+}
+
+size_t
+D2UpdateMgr::getQueueCount() const {
+ return (queue_mgr_->getQueueSize());
+}
+
+size_t
+D2UpdateMgr::getTransactionCount() const {
+ return (transaction_list_.size());
+}
+
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/bin/d2/d2_update_mgr.h b/src/bin/d2/d2_update_mgr.h
new file mode 100644
index 0000000..555b7be
--- /dev/null
+++ b/src/bin/d2/d2_update_mgr.h
@@ -0,0 +1,294 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 D2_UPDATE_MGR_H
+#define D2_UPDATE_MGR_H
+
+/// @file d2_update_mgr.h This file defines the class D2UpdateMgr.
+
+#include <asiolink/io_service.h>
+#include <exceptions/exceptions.h>
+#include <d2/d2_log.h>
+#include <d2/d2_queue_mgr.h>
+#include <d2/d2_cfg_mgr.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <map>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the update manager encounters a general error.
+class D2UpdateMgrError : public isc::Exception {
+public:
+ D2UpdateMgrError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+//@{
+/// @todo This is a stub implementation of NameChangeTransaction that is here
+/// strictly to facilitate development of D2UpdateMgr. It will move to its own
+/// source file(s) once NameChangeTransaction class development begins.
+
+/// @brief Defines the key for transactions.
+typedef isc::dhcp_ddns::D2Dhcid TransactionKey;
+
+class NameChangeTransaction {
+public:
+ NameChangeTransaction(isc::asiolink::IOService& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr forward_domain,
+ DdnsDomainPtr reverse_domain)
+ : io_service_(io_service), ncr_(ncr), forward_domain_(forward_domain),
+ reverse_domain_(reverse_domain) {
+ }
+
+ ~NameChangeTransaction(){
+ }
+
+ const dhcp_ddns::NameChangeRequestPtr& getNcr() const {
+ return (ncr_);
+ }
+
+ const TransactionKey& getTransactionKey() const {
+ return (ncr_->getDhcid());
+ }
+
+ dhcp_ddns::NameChangeStatus getNcrStatus() const {
+ return (ncr_->getStatus());
+ }
+
+private:
+ isc::asiolink::IOService& io_service_;
+
+ dhcp_ddns::NameChangeRequestPtr ncr_;
+
+ DdnsDomainPtr forward_domain_;
+
+ DdnsDomainPtr reverse_domain_;
+};
+
+/// @brief Defines a pointer to a NameChangeTransaction.
+typedef boost::shared_ptr<NameChangeTransaction> NameChangeTransactionPtr;
+
+//@}
+
+/// @brief Defines a list of transactions.
+typedef std::map<TransactionKey, NameChangeTransactionPtr> TransactionList;
+
+
+/// @brief D2UpdateMgr creates and manages update transactions.
+///
+/// D2UpdateMgr is the DHCP_DDNS task master, instantiating and then supervising
+/// transactions that execute the DNS updates needed to fulfill the requests
+/// (NameChangeRequests) received from DHCP_DDNS clients (e.g. DHCP servers).
+///
+/// D2UpdateMgr uses the services of D2QueueMgr to monitor the queue of
+/// NameChangeRequests and select and dequeue requests for processing.
+/// When request is dequeued for processing it is removed from the queue and
+/// wrapped in NameChangeTransaction and added to the D2UpdateMgr's list of
+/// transactions.
+///
+/// As part of the process of forming transactions, D2UpdateMgr matches each
+/// request with the appropriate list of DNS servers. This matching is based
+/// upon request attributes, primarily the FQDN and update direction (forward
+/// or reverse). D2UpdateMgr uses the services of D2CfgMgr to match requests
+/// to DNS server lists.
+///
+/// Once created, each transaction is responsible for carrying out the steps
+/// required to fulfill its specific request. These steps typically consist of
+/// one or more DNS packet exchanges with the appropriate DNS server. As
+/// transactions complete, D2UpdateMgr removes them from the transaction list,
+/// replacing them with new transactions.
+///
+/// D2UpdateMgr carries out each of the above steps, from with a method called
+/// sweep(). This method is intended to be called as IO events complete.
+/// The upper layer(s) are responsible for calling sweep in a timely and cyclic
+/// manner.
+///
+class D2UpdateMgr : public boost::noncopyable {
+public:
+ /// @brief Maximum number of concurrent transactions
+ /// NOTE that 32 is an arbitrary choice picked for the initial
+ /// implementation.
+ static const size_t MAX_TRANSACTIONS_DEFAULT = 32;
+
+ // @todo This structure is not yet used. It is here in anticipation of
+ // enabled statistics capture.
+ struct Stats {
+ uint64_t start_time_;
+ uint64_t stop_time_;
+ uint64_t update_count_;
+ uint64_t min_update_time_;
+ uint64_t max_update_time_;
+ uint64_t server_rejects_;
+ uint64_t server_timeouts_;
+ };
+
+ /// @brief Constructor
+ ///
+ /// @param queue_mgr reference to the queue manager receiving requests
+ /// @param cfg_mgr reference to the configuration manager
+ /// @param io_service IO service used by the upper layer(s) to manage
+ /// IO events
+ /// @param max_transactions the maximum number of concurrent transactions
+ ///
+ /// @throw D2UpdateMgrError if either the queue manager or configuration
+ /// managers are NULL, or max transactions is less than one.
+ D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
+ isc::asiolink::IOService& io_service,
+ const size_t max_transactions = MAX_TRANSACTIONS_DEFAULT);
+
+ /// @brief Destructor
+ virtual ~D2UpdateMgr();
+
+ /// @brief Check current transactions; start transactions for new requests.
+ ///
+ /// This method is the primary public interface used by the upper layer. It
+ /// should be called as IO events complete. During each invocation it does
+ /// the following:
+ ///
+ /// - Removes all completed transactions from the transaction list.
+ ///
+ /// - If the request queue is not empty and the number of transactions
+ /// in the transaction list has not reached maximum allowed, then select
+ /// a request from the queue.
+ ///
+ /// - If a request was selected, start a new transaction for it and
+ /// add the transaction to the list of transactions.
+ void sweep();
+
+protected:
+ /// @brief Performs post-completion cleanup on completed transactions.
+ ///
+ /// Iterates through the list of transactions and removes any that have
+ /// reached completion. This method may expand in complexity or even
+ /// disappear altogether as the implementation matures.
+ void checkFinishedTransactions();
+
+ /// @brief Starts a transaction for the next eligible request in the queue.
+ ///
+ /// This method will scan the request queue for the next request to
+ /// dequeue. The current implementation starts at the front of the queue
+ /// and looks for the first request for whose DHCID there is no current
+ /// transaction in progress.
+ ///
+ /// If a request is selected, it is removed from the queue and transaction
+ /// is constructed for it.
+ ///
+ /// It is possible that no such request exists, though this is likely to be
+ /// rather rare unless a system is frequently seeing requests for the same
+ /// clients in quick succession.
+ void pickNextJob();
+
+ /// @brief Create a new transaction for the given request.
+ ///
+ /// This method will attempt to match the request to a list of configured
+ /// DNS servers. If a list of servers is found, it will instantiate a
+ /// transaction for it and add the transaction to the transaction list.
+ ///
+ /// If no servers are found that match the request, this constitutes a
+ /// configuration error. The error will be logged and the request will
+ /// be discarded.
+ ///
+ /// @param ncr the NameChangeRequest for which to create a transaction.
+ ///
+ /// @throw D2UpdateMgrError if a transaction for this DHCID already
+ /// exists. Note this would be programmatic error.
+ void makeTransaction(isc::dhcp_ddns::NameChangeRequestPtr& ncr);
+
+public:
+ /// @brief Returns the maximum number of concurrent transactions.
+ size_t getMaxTransactions() const {
+ return (max_transactions_);
+ }
+
+ /// @brief Sets the maximum number of entries allowed in the queue.
+ ///
+ /// @param max_transactions is the new maximum number of transactions
+ ///
+ /// @throw Throws D2QueueMgrError if the new value is less than one or if
+ /// the new value is less than the number of entries currently in the
+ /// queue.
+ void setMaxTransactions(const size_t max_transactions);
+
+ /// @brief Search the transaction list for the given key.
+ ///
+ /// @param key the transaction key value for which to search.
+ ///
+ /// @return Iterator pointing to the entry found. If no entry is
+ /// it will point to the list end position.
+ TransactionList::iterator findTransaction(const TransactionKey& key);
+
+ /// @brief Returns the transaction list end position.
+ TransactionList::iterator transactionListEnd();
+
+ /// @brief Convenience method that checks transaction list for the given key
+ ///
+ /// @param key the transaction key value for which to search.
+ ///
+ /// @return Returns true if the key is found within the list, false
+ /// otherwise.
+ bool hasTransaction(const TransactionKey& key);
+
+ /// @brief Removes the entry pointed to by key from the transaction list.
+ ///
+ /// Removes the entry referred to by key if it exists. It has no effect
+ /// if the entry is not found.
+ ///
+ /// @param key of the transaction to remove
+ void removeTransaction(const TransactionKey& key);
+
+ /// @brief Immediately discards all entries in the transaction list.
+ ///
+ /// @todo For now this just wipes them out. We might need something
+ /// more elegant, that allows a cancel first.
+ void clearTransactionList();
+
+ /// @brief Convenience method that returns the number of requests queued.
+ size_t getQueueCount() const;
+
+ /// @brief Returns the current number of transactions.
+ size_t getTransactionCount() const;
+
+private:
+ /// @brief Pointer to the queue manager.
+ D2QueueMgrPtr queue_mgr_;
+
+ /// @brief Pointer to the configuration manager.
+ D2CfgMgrPtr cfg_mgr_;
+
+ /// @brief Primary IOService instance.
+ /// This is the IOService that the upper layer(s) use for IO events, such
+ /// as shutdown and configuration commands. It is the IOService that is
+ /// passed into transactions to manager their IO events.
+ /// (For future reference, multi-threaded transactions would each use their
+ /// own IOService instance.)
+ isc::asiolink::IOService& io_service_;
+
+ /// @brief Maximum number of concurrent transactions.
+ size_t max_transactions_;
+
+ /// @brief List of transactions.
+ TransactionList transaction_list_;
+};
+
+/// @brief Defines a pointer to a D2UpdateMgr instance.
+typedef boost::shared_ptr<D2UpdateMgr> D2UpdateMgrPtr;
+
+
+} // namespace isc::d2
+} // namespace isc
+#endif
diff --git a/src/bin/d2/d2_zone.cc b/src/bin/d2/d2_zone.cc
new file mode 100644
index 0000000..96aa2bb
--- /dev/null
+++ b/src/bin/d2/d2_zone.cc
@@ -0,0 +1,36 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <d2/d2_zone.h>
+
+namespace isc {
+namespace d2 {
+
+D2Zone::D2Zone(const dns::Name& name, const dns::RRClass& rrclass)
+ : name_(name), rrclass_(rrclass) {
+}
+
+std::string D2Zone::toText() const {
+ return (name_.toText() + " " + rrclass_.toText() + " SOA\n");
+}
+
+std::ostream&
+operator<<(std::ostream& os, const D2Zone& zone) {
+ os << zone.toText();
+ return (os);
+}
+
+} // namespace d2
+} // namespace isc
+
diff --git a/src/bin/d2/d2_zone.h b/src/bin/d2/d2_zone.h
new file mode 100644
index 0000000..60d43c8
--- /dev/null
+++ b/src/bin/d2/d2_zone.h
@@ -0,0 +1,117 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 D2_ZONE_H
+#define D2_ZONE_H
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace d2 {
+
+/// @brief The @c D2Zone encapsulates the Zone section in DNS Update message.
+///
+/// This class is used by the @c D2UpdateMessage to encapsulate the Zone section
+/// of the DNS Update message. Class members hold corresponding values of
+/// section's fields: NAME, CLASS. This class does not hold the RTYPE field
+/// value because RTYPE is always equal to SOA for DNS Update message (see
+/// RFC 2136, section 2.3).
+///
+/// Note, that this @c D2Zone class neither exposes functions to decode messages
+/// from wire format nor to encode to wire format. This is not needed, because
+/// @c isc::d2::D2UpdateMessage class uses @c D2Zone only to return the parsed
+/// Zone information to the caller. Internally, D2UpdateMessage parses and
+/// stores Zone section using @c isc::dns::Question class, and the @c toWire
+/// and @c fromWire functions of the @c isc::dns::Question class are used.
+class D2Zone {
+public:
+ /// @brief Constructor from Name and RRClass.
+ ///
+ /// @param name The name of the Zone.
+ /// @param rrclass The RR class of the Zone.
+ D2Zone(const dns::Name& name, const dns::RRClass& rrclass);
+
+ ///
+ /// @name Getters
+ ///
+ //@{
+ /// @brief Returns the Zone name.
+ ///
+ /// @return A reference to the Zone name.
+ const dns::Name& getName() const { return (name_); }
+
+ /// @brief Returns the Zone class.
+ ///
+ /// @return A reference to the Zone class.
+ const dns::RRClass& getClass() const { return (rrclass_); }
+ //@}
+
+ /// @brief Returns text representation of the Zone.
+ ///
+ /// This function concatenates the name of the Zone, Class and Type.
+ /// The type is always SOA.
+ ///
+ /// @return A text representation of the Zone.
+ std::string toText() const;
+
+ ///
+ /// @name Comparison Operators
+ ///
+ //@{
+ /// @brief Equality operator to compare @c D2Zone objects in query and
+ /// response messages.
+ ///
+ /// @param rhs Zone to compare against.
+ ///
+ /// @return true if name and class are equal, false otherwise.
+ bool operator==(const D2Zone& rhs) const {
+ return ((rrclass_ == rhs.rrclass_) && (name_ == rhs.name_));
+ }
+
+ /// @brief Inequality operator to compare @c D2Zone objects in query and
+ /// response messages.
+ ///
+ /// @param rhs Zone to compare against.
+ ///
+ /// @return true if any of name or class are unequal, false otherwise.
+ bool operator!=(const D2Zone& rhs) const {
+ return (!operator==(rhs));
+ }
+ //@}
+
+private:
+ dns::Name name_; ///< Holds the Zone name.
+ dns::RRClass rrclass_; ///< Holds the Zone class.
+};
+
+typedef boost::shared_ptr<D2Zone> D2ZonePtr;
+
+/// @brief Insert the @c D2Zone as a string into stream.
+///
+/// @param os A @c std::ostream object on which the insertion operation is
+/// performed.
+/// @param zone A reference to the @c D2Zone object output by the
+/// operation.
+///
+/// @return A reference to the same @c std::ostream object referenced by
+/// parameter @c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const D2Zone& zone);
+
+} // namespace d2
+} // namespace isc
+
+#endif // D2_ZONE_H
diff --git a/src/bin/d2/d_cfg_mgr.cc b/src/bin/d2/d_cfg_mgr.cc
new file mode 100644
index 0000000..1e6bb57
--- /dev/null
+++ b/src/bin/d2/d_cfg_mgr.cc
@@ -0,0 +1,240 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <d2/d2_log.h>
+#include <dhcp/libdhcp++.h>
+#include <d2/d_cfg_mgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <limits>
+#include <iostream>
+#include <vector>
+#include <map>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace d2 {
+
+// *********************** DCfgContextBase *************************
+
+DCfgContextBase::DCfgContextBase():
+ boolean_values_(new BooleanStorage()),
+ uint32_values_(new Uint32Storage()),
+ string_values_(new StringStorage()) {
+ }
+
+DCfgContextBase::DCfgContextBase(const DCfgContextBase& rhs):
+ boolean_values_(new BooleanStorage(*(rhs.boolean_values_))),
+ uint32_values_(new Uint32Storage(*(rhs.uint32_values_))),
+ string_values_(new StringStorage(*(rhs.string_values_))) {
+}
+
+void
+DCfgContextBase::getParam(const std::string& name, bool& value, bool optional) {
+ try {
+ value = boolean_values_->getParam(name);
+ } catch (DhcpConfigError& ex) {
+ // If the parameter is not optional, re-throw the exception.
+ if (!optional) {
+ throw;
+ }
+ }
+}
+
+
+void
+DCfgContextBase::getParam(const std::string& name, uint32_t& value,
+ bool optional) {
+ try {
+ value = uint32_values_->getParam(name);
+ } catch (DhcpConfigError& ex) {
+ // If the parameter is not optional, re-throw the exception.
+ if (!optional) {
+ throw;
+ }
+ }
+}
+
+void
+DCfgContextBase::getParam(const std::string& name, std::string& value,
+ bool optional) {
+ try {
+ value = string_values_->getParam(name);
+ } catch (DhcpConfigError& ex) {
+ // If the parameter is not optional, re-throw the exception.
+ if (!optional) {
+ throw;
+ }
+ }
+}
+
+DCfgContextBase::~DCfgContextBase() {
+}
+
+// *********************** DCfgMgrBase *************************
+
+DCfgMgrBase::DCfgMgrBase(DCfgContextBasePtr context)
+ : parse_order_(), context_(context) {
+ if (!context_) {
+ isc_throw(DCfgMgrBaseError, "DCfgMgrBase ctor: context cannot be NULL");
+ }
+}
+
+DCfgMgrBase::~DCfgMgrBase() {
+}
+
+isc::data::ConstElementPtr
+DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
+ LOG_DEBUG(dctl_logger, DBGLVL_COMMAND,
+ DCTL_CONFIG_START).arg(config_set->str());
+
+ if (!config_set) {
+ return (isc::config::createAnswer(1,
+ std::string("Can't parse NULL config")));
+ }
+
+ // The parsers implement data inheritance by directly accessing
+ // configuration context. For this reason the data parsers must store
+ // the parsed data into context immediately. This may cause data
+ // inconsistency if the parsing operation fails after the context has been
+ // modified. We need to preserve the original context here
+ // so as we can rollback changes when an error occurs.
+ DCfgContextBasePtr original_context = context_->clone();
+
+ // Answer will hold the result returned to the caller.
+ ConstElementPtr answer;
+
+ // Holds the name of the element being parsed.
+ std::string element_id;
+
+ try {
+ // Grab a map of element_ids and their data values from the new
+ // configuration set.
+ const std::map<std::string, ConstElementPtr>& values_map =
+ config_set->mapValue();
+
+ // Use a pre-ordered list of element ids to parse the elements in a
+ // specific order if the list (parser_order_) is not empty; otherwise
+ // elements are parsed in the order the value_map presents them.
+
+ if (!parse_order_.empty()) {
+ // For each element_id in the parse order list, look for it in the
+ // value map. If the element exists in the map, pass it and it's
+ // associated data in for parsing.
+ // If there is no matching entry in the value map an error is
+ // thrown. Note, that elements tagged as "optional" from the user
+ // perspective must still have default or empty entries in the
+ // configuration set to be parsed.
+ int parsed_count = 0;
+ std::map<std::string, ConstElementPtr>::const_iterator it;
+ BOOST_FOREACH(element_id, parse_order_) {
+ it = values_map.find(element_id);
+ if (it != values_map.end()) {
+ ++parsed_count;
+ buildAndCommit(element_id, it->second);
+ }
+ else {
+ LOG_ERROR(dctl_logger, DCTL_ORDER_NO_ELEMENT)
+ .arg(element_id);
+ isc_throw(DCfgMgrBaseError, "Element:" << element_id <<
+ " is listed in the parse order but is not "
+ " present in the configuration");
+ }
+ }
+
+ // NOTE: When using ordered parsing, the parse order list MUST
+ // include every possible element id that the value_map may contain.
+ // Entries in the map that are not in the parse order, would not be
+ // parsed. For now we will flag this as a programmatic error. One
+ // could attempt to adjust for this, by identifying such entries
+ // and parsing them either first or last but which would be correct?
+ // Better to hold the engineer accountable. So, if we parsed none
+ // or we parsed fewer than are in the map; then either the parse i
+ // order is incomplete OR the map has unsupported values.
+ if (!parsed_count ||
+ (parsed_count && ((parsed_count + 1) < values_map.size()))) {
+ LOG_ERROR(dctl_logger, DCTL_ORDER_ERROR);
+ isc_throw(DCfgMgrBaseError,
+ "Configuration contains elements not in parse order");
+ }
+ } else {
+ // Order doesn't matter so iterate over the value map directly.
+ // Pass each element and it's associated data in to be parsed.
+ ConfigPair config_pair;
+ BOOST_FOREACH(config_pair, values_map) {
+ element_id = config_pair.first;
+ buildAndCommit(element_id, config_pair.second);
+ }
+ }
+
+ // Everything was fine. Configuration set processed successfully.
+ LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg("");
+ answer = isc::config::createAnswer(0, "Configuration committed.");
+
+ } catch (const isc::Exception& ex) {
+ LOG_ERROR(dctl_logger, DCTL_PARSER_FAIL).arg(element_id).arg(ex.what());
+ answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed: ") + ex.what());
+
+ // An error occurred, so make sure that we restore original context.
+ context_ = original_context;
+ return (answer);
+ }
+
+ return (answer);
+}
+
+void DCfgMgrBase::buildAndCommit(std::string& element_id,
+ isc::data::ConstElementPtr value) {
+ // Call derivation's implementation to create the appropriate parser
+ // based on the element id.
+ ParserPtr parser = createConfigParser(element_id);
+ if (!parser) {
+ isc_throw(DCfgMgrBaseError, "Could not create parser");
+ }
+
+ try {
+ // Invoke the parser's build method passing in the value. This will
+ // "convert" the Element form of value into the actual data item(s)
+ // and store them in parser's local storage.
+ parser->build(value);
+
+ // Invoke the parser's commit method. This "writes" the the data
+ // item(s) stored locally by the parser into the context. (Note that
+ // parsers are free to do more than update the context, but that is an
+ // nothing something we are concerned with here.)
+ parser->commit();
+ } catch (const isc::Exception& ex) {
+ isc_throw(DCfgMgrBaseError,
+ "Could not build and commit: " << ex.what());
+ } catch (...) {
+ isc_throw(DCfgMgrBaseError, "Non-ISC exception occurred");
+ }
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
diff --git a/src/bin/d2/d_cfg_mgr.h b/src/bin/d2/d_cfg_mgr.h
new file mode 100644
index 0000000..a0bd9bb
--- /dev/null
+++ b/src/bin/d2/d_cfg_mgr.h
@@ -0,0 +1,331 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 D_CFG_MGR_H
+#define D_CFG_MGR_H
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/dhcp_parsers.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown if the configuration manager encounters an error.
+class DCfgMgrBaseError : public isc::Exception {
+public:
+ DCfgMgrBaseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+class DCfgContextBase;
+/// @brief Pointer to a configuration context.
+typedef boost::shared_ptr<DCfgContextBase> DCfgContextBasePtr;
+
+/// @brief Abstract class that implements a container for configuration context.
+/// It provides a single enclosure for the storage of configuration parameters
+/// and any other context specific information that needs to be accessible
+/// during configuration parsing as well as to the application as a whole.
+/// The base class supports storage for a small set of simple data types.
+/// Derivations simply add additional storage as needed. Note that this class
+/// declares the pure virtual clone() method, its copy constructor is protected,
+/// and its copy operator is inaccessible. Derivations must supply an
+/// implementation of clone that calls the base class copy constructor.
+/// This allows the management class to perform context backup and restoration
+/// without derivation specific knowledge using logic like
+/// the following:
+///
+/// // Make a backup copy
+/// DCfgContextBasePtr backup_copy(context_->clone());
+/// :
+/// // Restore from backup
+/// context_ = backup_copy;
+///
+class DCfgContextBase {
+public:
+ /// @brief Indicator that a configuration parameter is optional.
+ static const bool OPTIONAL = true;
+ static const bool REQUIRED = false;
+
+ /// @brief Constructor
+ DCfgContextBase();
+
+ /// @brief Destructor
+ virtual ~DCfgContextBase();
+
+ /// @brief Fetches the value for a given boolean configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @param optional if true, the parameter is optional and the method
+ /// will not throw if the parameter is not found in the context. The
+ /// contents of the output parameter, value, will not be altered.
+ /// It defaults to false if not specified.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter and optional is false.
+ void getParam(const std::string& name, bool& value, bool optional=false);
+
+ /// @brief Fetches the value for a given uint32_t configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @param optional if true, the parameter is optional and the method
+ /// will not throw if the parameter is not found in the context. The
+ /// contents of the output parameter, value, will not be altered.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter and optional is false.
+ void getParam(const std::string& name, uint32_t& value,
+ bool optional=false);
+
+ /// @brief Fetches the value for a given string configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @param optional if true, the parameter is optional and the method
+ /// will not throw if the parameter is not found in the context. The
+ /// contents of the output parameter, value, will not be altered.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter and optional is false.
+ void getParam(const std::string& name, std::string& value,
+ bool optional=false);
+
+ /// @brief Fetches the Boolean Storage. Typically used for passing
+ /// into parsers.
+ ///
+ /// @return returns a pointer to the Boolean Storage.
+ isc::dhcp::BooleanStoragePtr getBooleanStorage() {
+ return (boolean_values_);
+ }
+
+ /// @brief Fetches the uint32 Storage. Typically used for passing
+ /// into parsers.
+ ///
+ /// @return returns a pointer to the uint32 Storage.
+ isc::dhcp::Uint32StoragePtr getUint32Storage() {
+ return (uint32_values_);
+ }
+
+ /// @brief Fetches the string Storage. Typically used for passing
+ /// into parsers.
+ ///
+ /// @return returns a pointer to the string Storage.
+ isc::dhcp::StringStoragePtr getStringStorage() {
+ return (string_values_);
+ }
+
+ /// @brief Creates a clone of this context object.
+ ///
+ /// As mentioned in the the class brief, derivation must supply an
+ /// implementation that initializes the base class storage as well as its
+ /// own. Typically the derivation's clone method would return the result
+ /// of passing "*this" into its own copy constructor:
+ ///
+ /// @code
+ /// class DStubContext : public DCfgContextBase {
+ /// public:
+ /// :
+ /// // Clone calls its own copy constructor
+ /// virtual DCfgContextBasePtr clone() {
+ /// return (DCfgContextBasePtr(new DStubContext(*this)));
+ /// }
+ ///
+ /// // Note that the copy constructor calls the base class copy ctor
+ /// // then initializes its additional storage.
+ /// DStubContext(const DStubContext& rhs) : DCfgContextBase(rhs),
+ /// extra_values_(new Uint32Storage(*(rhs.extra_values_))) {
+ /// }
+ /// :
+ /// // Here's the derivation's additional storage.
+ /// isc::dhcp::Uint32StoragePtr extra_values_;
+ /// :
+ /// @endcode
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone() = 0;
+
+protected:
+ /// @brief Copy constructor for use by derivations in clone().
+ DCfgContextBase(const DCfgContextBase& rhs);
+
+private:
+ /// @brief Private assignment operator to avoid potential for slicing.
+ DCfgContextBase& operator=(const DCfgContextBase& rhs);
+
+ /// @brief Storage for boolean parameters.
+ isc::dhcp::BooleanStoragePtr boolean_values_;
+
+ /// @brief Storage for uint32 parameters.
+ isc::dhcp::Uint32StoragePtr uint32_values_;
+
+ /// @brief Storage for string parameters.
+ isc::dhcp::StringStoragePtr string_values_;
+};
+
+/// @brief Defines an unsorted, list of string Element IDs.
+typedef std::vector<std::string> ElementIdList;
+
+/// @brief Configuration Manager
+///
+/// DCfgMgrBase is an abstract class that provides the mechanisms for managing
+/// an application's configuration. This includes services for parsing sets of
+/// configuration values, storing the parsed information in its converted form,
+/// and retrieving the information on demand. It is intended to be the worker
+/// class which is handed a set of configuration values to process by upper
+/// application management layers.
+///
+/// The class presents a public method for receiving new configurations,
+/// parseConfig. This method coordinates the parsing effort as follows:
+///
+/// @code
+/// make backup copy of configuration context
+/// for each top level element in new configuration
+/// get derivation-specific parser for element
+/// run parser
+/// update context with parsed results
+/// break on error
+///
+/// if an error occurred
+/// restore configuration context from backup
+/// @endcode
+///
+/// After making a backup of the current context, it iterates over the top-level
+/// elements in the new configuration. The order in which the elements are
+/// processed is either:
+///
+/// 1. Natural order presented by the configuration set
+/// 2. Specific order determined by a list of element ids
+///
+/// This allows a derivation to specify the order in which its elements are
+/// parsed if there are dependencies between elements.
+///
+/// To parse a given element, its id is passed into createConfigParser,
+/// which returns an instance of the appropriate parser. This method is
+/// abstract so the derivation's implementation determines the type of parser
+/// created. This isolates the knowledge of specific element ids and which
+/// application specific parsers to derivation.
+///
+/// Once the parser has been created, it is used to parse the data value
+/// associated with the element id and update the context with the parsed
+/// results.
+///
+/// In the event that an error occurs, parsing is halted and the
+/// configuration context is restored from backup.
+class DCfgMgrBase {
+public:
+ /// @brief Constructor
+ ///
+ /// @param context is a pointer to the configuration context the manager
+ /// will use for storing parsed results.
+ ///
+ /// @throw throws DCfgMgrBaseError if context is null
+ DCfgMgrBase(DCfgContextBasePtr context);
+
+ /// @brief Destructor
+ virtual ~DCfgMgrBase();
+
+ /// @brief Acts as the receiver of new configurations and coordinates
+ /// the parsing as described in the class brief.
+ ///
+ /// @param config_set is a set of configuration elements to parsed.
+ ///
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ isc::data::ConstElementPtr parseConfig(isc::data::ConstElementPtr
+ config_set);
+
+ /// @brief Adds a given element id to the end of the parse order list.
+ ///
+ /// The order in which elements are retrieved from this is the order in
+ /// which they are added to the list. Derivations should use this method
+ /// to populate the parse order as part of their constructor.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ void addToParseOrder(const std::string& element_id){
+ parse_order_.push_back(element_id);
+ }
+
+ /// @brief Fetches the parse order list.
+ ///
+ /// @return returns a const reference to the list.
+ const ElementIdList& getParseOrder() const {
+ return (parse_order_);
+ }
+
+ /// @brief Fetches the configuration context.
+ ///
+ /// @return returns a pointer reference to the configuration context.
+ DCfgContextBasePtr& getContext() {
+ return (context_);
+ }
+
+protected:
+ /// @brief Create a parser instance based on an element id.
+ ///
+ /// Given an element_id returns an instance of the appropriate parser.
+ /// This method is abstract, isolating any direct knowledge of element_ids
+ /// and parsers to within the application-specific derivation.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ ///
+ /// @return returns a ParserPtr to the parser instance.
+ /// @throw throws DCfgMgrBaseError if an error occurs.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& element_id) = 0;
+
+private:
+
+ /// @brief Parse a configuration element.
+ ///
+ /// Given an element_id and data value, instantiate the appropriate
+ /// parser, parse the data value, and commit the results.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ /// @param value is the data value to be parsed and associated with
+ /// element_id.
+ ///
+ /// @throw throws DCfgMgrBaseError if an error occurs.
+ void buildAndCommit(std::string& element_id,
+ isc::data::ConstElementPtr value);
+
+ /// @brief A list of element ids which specifies the element parsing order.
+ ///
+ /// If the list is empty, the natural order in the configuration set
+ /// it used.
+ ElementIdList parse_order_;
+
+ /// @brief Pointer to the configuration context instance.
+ DCfgContextBasePtr context_;
+};
+
+/// @brief Defines a shared pointer to DCfgMgrBase.
+typedef boost::shared_ptr<DCfgMgrBase> DCfgMgrBasePtr;
+
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
+
+#endif // D_CFG_MGR_H
diff --git a/src/bin/d2/d_controller.cc b/src/bin/d2/d_controller.cc
new file mode 100644
index 0000000..fe39dd0
--- /dev/null
+++ b/src/bin/d2/d_controller.cc
@@ -0,0 +1,423 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <d2/d2_log.h>
+#include <d2/d_controller.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_support.h>
+
+#include <sstream>
+
+namespace isc {
+namespace d2 {
+
+DControllerBasePtr DControllerBase::controller_;
+
+// Note that the constructor instantiates the controller's primary IOService.
+DControllerBase::DControllerBase(const char* app_name, const char* bin_name)
+ : app_name_(app_name), bin_name_(bin_name), stand_alone_(false),
+ verbose_(false), spec_file_name_(""),
+ io_service_(new isc::asiolink::IOService()){
+}
+
+
+void
+DControllerBase::setController(const DControllerBasePtr& controller) {
+ if (controller_) {
+ // This shouldn't happen, but let's make sure it can't be done.
+ // It represents a programmatic error.
+ isc_throw (DControllerBaseError,
+ "Multiple controller instances attempted.");
+ }
+
+ controller_ = controller;
+}
+
+void
+DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
+ // Step 1 is to parse the command line arguments.
+ try {
+ parseArgs(argc, argv);
+ } catch (const InvalidUsage& ex) {
+ usage(ex.what());
+ throw; // rethrow it
+ }
+
+ // Do not initialize logger here if we are running unit tests. It would
+ // replace an instance of unit test specific logger.
+ if (!test_mode) {
+ // Now that we know what the mode flags are, we can init logging.
+ // If standalone is enabled, do not buffer initial log messages
+ isc::log::initLogger(bin_name_,
+ ((verbose_ && stand_alone_)
+ ? isc::log::DEBUG : isc::log::INFO),
+ isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone_);
+ }
+
+ LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STARTING)
+ .arg(app_name_).arg(getpid());
+ try {
+ // Step 2 is to create and initialize the application process object.
+ initProcess();
+ } catch (const std::exception& ex) {
+ LOG_FATAL(dctl_logger, DCTL_INIT_PROCESS_FAIL)
+ .arg(app_name_).arg(ex.what());
+ isc_throw (ProcessInitError,
+ "Application Process initialization failed: " << ex.what());
+ }
+
+ // Next we connect if we are running integrated.
+ if (stand_alone_) {
+ LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_STANDALONE)
+ .arg(app_name_);
+ } else {
+ try {
+ establishSession();
+ } catch (const std::exception& ex) {
+ LOG_FATAL(dctl_logger, DCTL_SESSION_FAIL).arg(ex.what());
+ isc_throw (SessionStartError,
+ "Session start up failed: " << ex.what());
+ }
+ }
+
+ // Everything is clear for launch, so start the application's
+ // event loop.
+ try {
+ runProcess();
+ } catch (const std::exception& ex) {
+ LOG_FATAL(dctl_logger, DCTL_PROCESS_FAILED)
+ .arg(app_name_).arg(ex.what());
+ isc_throw (ProcessRunError,
+ "Application process event loop failed: " << ex.what());
+ }
+
+ // If running integrated, disconnect.
+ if (!stand_alone_) {
+ try {
+ disconnectSession();
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dctl_logger, DCTL_DISCONNECT_FAIL)
+ .arg(app_name_).arg(ex.what());
+ isc_throw (SessionEndError, "Session end failed: " << ex.what());
+ }
+ }
+
+ // All done, so bail out.
+ LOG_INFO(dctl_logger, DCTL_STOPPING).arg(app_name_);
+}
+
+
+void
+DControllerBase::parseArgs(int argc, char* argv[])
+{
+ // Iterate over the given command line options. If its a stock option
+ // ("s" or "v") handle it here. If its a valid custom option, then
+ // invoke customOption.
+ int ch;
+ opterr = 0;
+ optind = 1;
+ std::string opts(":vs" + getCustomOpts());
+ while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
+ switch (ch) {
+ case 'v':
+ // Enables verbose logging.
+ verbose_ = true;
+ break;
+
+ case 's':
+ // Enables stand alone or "BINDLESS" operation.
+ stand_alone_ = true;
+ break;
+
+ case '?': {
+ // We hit an invalid option.
+ isc_throw(InvalidUsage, "unsupported option: ["
+ << static_cast<char>(optopt) << "] "
+ << (!optarg ? "" : optarg));
+
+ break;
+ }
+
+ default:
+ // We hit a valid custom option
+ if (!customOption(ch, optarg)) {
+ // This would be a programmatic error.
+ isc_throw(InvalidUsage, " Option listed but implemented?: ["
+ << static_cast<char>(ch) << "] "
+ << (!optarg ? "" : optarg));
+ }
+ break;
+ }
+ }
+
+ // There was too much information on the command line.
+ if (argc > optind) {
+ isc_throw(InvalidUsage, "extraneous command line information");
+ }
+}
+
+bool
+DControllerBase::customOption(int /* option */, char* /*optarg*/)
+{
+ // Default implementation returns false.
+ return (false);
+}
+
+void
+DControllerBase::initProcess() {
+ LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_INIT_PROCESS).arg(app_name_);
+
+ // Invoke virtual method to instantiate the application process.
+ try {
+ process_.reset(createProcess());
+ } catch (const std::exception& ex) {
+ isc_throw(DControllerBaseError, std::string("createProcess failed: ")
+ + ex.what());
+ }
+
+ // This is pretty unlikely, but will test for it just to be safe..
+ if (!process_) {
+ isc_throw(DControllerBaseError, "createProcess returned NULL");
+ }
+
+ // Invoke application's init method (Note this call should throw
+ // DProcessBaseError if it fails).
+ process_->init();
+}
+
+void
+DControllerBase::establishSession() {
+ LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CCSESSION_STARTING)
+ .arg(app_name_).arg(spec_file_name_);
+
+ // Create the BIND10 command control session with the our IOService.
+ cc_session_ = SessionPtr(new isc::cc::Session(
+ io_service_->get_io_service()));
+
+ // Create the BIND10 config session with the stub configuration handler.
+ // This handler is internally invoked by the constructor and on success
+ // the constructor updates the current session with the configuration that
+ // had been committed in the previous session. If we do not install
+ // the dummy handler, the previous configuration would be lost.
+ config_session_ = ModuleCCSessionPtr(new isc::config::ModuleCCSession(
+ spec_file_name_, *cc_session_,
+ dummyConfigHandler, commandHandler,
+ false));
+ // Enable configuration even processing.
+ config_session_->start();
+
+ // We initially create ModuleCCSession() with a dummy configHandler, as
+ // the session module is too eager to send partial configuration.
+ // Replace the dummy config handler with the real handler.
+ config_session_->setConfigHandler(configHandler);
+
+ // Call the real configHandler with the full configuration retrieved
+ // from the config session.
+ isc::data::ConstElementPtr answer = configHandler(
+ config_session_->getFullConfig());
+
+ // Parse the answer returned from the configHandler. Log the error but
+ // keep running. This provides an opportunity for the user to correct
+ // the configuration dynamically.
+ int ret = 0;
+ isc::data::ConstElementPtr comment = isc::config::parseAnswer(ret, answer);
+ if (ret) {
+ LOG_ERROR(dctl_logger, DCTL_CONFIG_LOAD_FAIL)
+ .arg(app_name_).arg(comment->str());
+ }
+
+ // Lastly, call onConnect. This allows deriving class to execute custom
+ // logic predicated by session connect.
+ onSessionConnect();
+}
+
+void
+DControllerBase::runProcess() {
+ LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_RUN_PROCESS).arg(app_name_);
+ if (!process_) {
+ // This should not be possible.
+ isc_throw(DControllerBaseError, "Process not initialized");
+ }
+
+ // Invoke the application process's run method. This may throw
+ // DProcessBaseError
+ process_->run();
+}
+
+void DControllerBase::disconnectSession() {
+ LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CCSESSION_ENDING)
+ .arg(app_name_);
+
+ // Call virtual onDisconnect. Allows deriving class to execute custom
+ // logic prior to session loss.
+ onSessionDisconnect();
+
+ // Destroy the BIND10 config session.
+ if (config_session_) {
+ config_session_.reset();
+ }
+
+ // Destroy the BIND10 command and control session.
+ if (cc_session_) {
+ cc_session_->disconnect();
+ cc_session_.reset();
+ }
+}
+
+isc::data::ConstElementPtr
+DControllerBase::dummyConfigHandler(isc::data::ConstElementPtr) {
+ LOG_DEBUG(dctl_logger, DBGLVL_START_SHUT, DCTL_CONFIG_STUB)
+ .arg(controller_->getAppName());
+ return (isc::config::createAnswer(0, "Configuration accepted."));
+}
+
+isc::data::ConstElementPtr
+DControllerBase::configHandler(isc::data::ConstElementPtr new_config) {
+
+ LOG_DEBUG(dctl_logger, DBGLVL_COMMAND, DCTL_CONFIG_UPDATE)
+ .arg(controller_->getAppName()).arg(new_config->str());
+
+ // Invoke the instance method on the controller singleton.
+ return (controller_->updateConfig(new_config));
+}
+
+// Static callback which invokes non-static handler on singleton
+isc::data::ConstElementPtr
+DControllerBase::commandHandler(const std::string& command,
+ isc::data::ConstElementPtr args) {
+
+ LOG_DEBUG(dctl_logger, DBGLVL_COMMAND, DCTL_COMMAND_RECEIVED)
+ .arg(controller_->getAppName()).arg(command)
+ .arg(args ? args->str() : "(no args)");
+
+ // Invoke the instance method on the controller singleton.
+ return (controller_->executeCommand(command, args));
+}
+
+isc::data::ConstElementPtr
+DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
+ isc::data::ConstElementPtr full_config;
+ if (stand_alone_) {
+ // @todo Until there is a configuration manager to provide retrieval
+ // we'll just assume the incoming config is the full configuration set.
+ // It may also make more sense to isolate the controller from the
+ // configuration manager entirely. We could do something like
+ // process_->getFullConfig() here for stand-alone mode?
+ full_config = new_config;
+ } else {
+ if (!config_session_) {
+ // That should never happen as we install config_handler
+ // after we instantiate the server.
+ isc::data::ConstElementPtr answer =
+ isc::config::createAnswer(1, "Configuration rejected,"
+ " Session has not started.");
+ return (answer);
+ }
+
+ // Let's get the existing configuration.
+ full_config = config_session_->getFullConfig();
+ }
+
+ // The configuration passed to this handler function is partial.
+ // In other words, it just includes the values being modified.
+ // In the same time, there may be dependencies between various
+ // configuration parsers. For example: the option value can
+ // be set if the definition of this option is set. If someone removes
+ // an existing option definition then the partial configuration that
+ // removes that definition is triggered while a relevant option value
+ // may remain configured. This eventually results in the
+ // configuration being in the inconsistent state.
+ // In order to work around this problem we need to merge the new
+ // configuration with the existing (full) configuration.
+
+ // Let's create a new object that will hold the merged configuration.
+ boost::shared_ptr<isc::data::MapElement>
+ merged_config(new isc::data::MapElement());
+
+ // Merge an existing and new configuration.
+ merged_config->setValue(full_config->mapValue());
+ isc::data::merge(merged_config, new_config);
+
+ // Send the merged configuration to the application.
+ return (process_->configure(merged_config));
+}
+
+
+isc::data::ConstElementPtr
+DControllerBase::executeCommand(const std::string& command,
+ isc::data::ConstElementPtr args) {
+ // Shutdown is universal. If its not that, then try it as
+ // an custom command supported by the derivation. If that
+ // doesn't pan out either, than send to it the application
+ // as it may be supported there.
+ isc::data::ConstElementPtr answer;
+ if (command.compare(SHUT_DOWN_COMMAND) == 0) {
+ answer = shutdown(args);
+ } else {
+ // It wasn't shutdown, so may be a custom controller command.
+ int rcode = 0;
+ answer = customControllerCommand(command, args);
+ isc::config::parseAnswer(rcode, answer);
+ if (rcode == COMMAND_INVALID)
+ {
+ // It wasn't controller command, so may be an application command.
+ answer = process_->command(command, args);
+ }
+ }
+
+ return (answer);
+}
+
+isc::data::ConstElementPtr
+DControllerBase::customControllerCommand(const std::string& command,
+ isc::data::ConstElementPtr /* args */) {
+
+ // Default implementation always returns invalid command.
+ return (isc::config::createAnswer(COMMAND_INVALID,
+ "Unrecognized command: " + command));
+}
+
+isc::data::ConstElementPtr
+DControllerBase::shutdown(isc::data::ConstElementPtr args) {
+ if (process_) {
+ return (process_->shutdown(args));
+ }
+
+ // Not really a failure, but this condition is worth noting. In reality
+ // it should be pretty hard to cause this.
+ LOG_WARN(dctl_logger, DCTL_NOT_RUNNING).arg(app_name_);
+ return (isc::config::createAnswer(0, "Process has not been initialzed."));
+}
+
+void
+DControllerBase::usage(const std::string & text)
+{
+ if (text != "") {
+ std::cerr << "Usage error: " << text << std::endl;
+ }
+
+ std::cerr << "Usage: " << bin_name_ << std::endl;
+ std::cerr << " -v: verbose output" << std::endl;
+ std::cerr << " -s: stand-alone mode (don't connect to BIND10)"
+ << std::endl;
+
+ std::cerr << getUsageText() << std::endl;
+}
+
+DControllerBase::~DControllerBase() {
+}
+
+}; // namespace isc::d2
+}; // namespace isc
diff --git a/src/bin/d2/d_controller.h b/src/bin/d2/d_controller.h
new file mode 100644
index 0000000..100eb44
--- /dev/null
+++ b/src/bin/d2/d_controller.h
@@ -0,0 +1,557 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 D_CONTROLLER_H
+#define D_CONTROLLER_H
+
+#include <asiolink/asiolink.h>
+#include <cc/data.h>
+#include <cc/session.h>
+#include <config/ccsession.h>
+#include <d2/d2_log.h>
+#include <d2/d_process.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_support.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+
+namespace isc {
+namespace d2 {
+
+/// @brief DControllerBase launch exit status values. Upon service shutdown
+/// normal or otherwise, the Controller's launch method will return one of
+/// these values.
+
+/// @brief Exception thrown when the command line is invalid.
+class InvalidUsage : public isc::Exception {
+public:
+ InvalidUsage(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the application process fails.
+class ProcessInitError: public isc::Exception {
+public:
+ ProcessInitError (const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the session start up fails.
+class SessionStartError: public isc::Exception {
+public:
+ SessionStartError (const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the application process encounters an
+/// operation in its event loop (i.e. run method).
+class ProcessRunError: public isc::Exception {
+public:
+ ProcessRunError (const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown when the session end fails.
+class SessionEndError: public isc::Exception {
+public:
+ SessionEndError (const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Exception thrown when the controller encounters an operational error.
+class DControllerBaseError : public isc::Exception {
+public:
+ DControllerBaseError (const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Defines a shared pointer to DControllerBase.
+class DControllerBase;
+typedef boost::shared_ptr<DControllerBase> DControllerBasePtr;
+
+/// @brief Defines a shared pointer to a Session.
+typedef boost::shared_ptr<isc::cc::Session> SessionPtr;
+
+/// @brief Defines a shared pointer to a ModuleCCSession.
+typedef boost::shared_ptr<isc::config::ModuleCCSession> ModuleCCSessionPtr;
+
+
+/// @brief Application Controller
+///
+/// DControllerBase is an abstract singleton which provides the framework and
+/// services for managing an application process that implements the
+/// DProcessBase interface. It allows the process to run either in
+/// integrated mode as a BIND10 module or stand-alone. It coordinates command
+/// line argument parsing, process instantiation and initialization, and runtime
+/// control through external command and configuration event handling.
+/// It creates the IOService instance which is used for runtime control
+/// events and passes the IOService into the application process at process
+/// creation. In integrated mode it is responsible for establishing BIND10
+/// session(s) and passes this IOService into the session creation method(s).
+/// It also provides the callback handlers for command and configuration events
+/// received from the external framework (aka BIND10). For example, when
+/// running in integrated mode and a user alters the configuration with the
+/// bindctl tool, BIND10 will emit a configuration message which is sensed by
+/// the controller's IOService. The IOService in turn invokes the configuration
+/// callback, DControllerBase::configHandler(). If the user issues a command
+/// such as shutdown via bindctl, BIND10 will emit a command message, which is
+/// sensed by controller's IOService which invokes the command callback,
+/// DControllerBase::commandHandler().
+///
+/// NOTE: Derivations must supply their own static singleton instance method(s)
+/// for creating and fetching the instance. The base class declares the instance
+/// member in order for it to be available for BIND10 callback functions. This
+/// would not be required if BIND10 supported instance method callbacks.
+class DControllerBase : public boost::noncopyable {
+public:
+ /// @brief Constructor
+ ///
+ /// @param app_name is display name of the application under control. This
+ /// name appears in log statements.
+ /// @param bin_name is the name of the application executable. Typically
+ /// this matches the BIND10 module name.
+ DControllerBase(const char* app_name, const char* bin_name);
+
+ /// @brief Destructor
+ virtual ~DControllerBase();
+
+ /// @brief Acts as the primary entry point into the controller execution
+ /// and provides the outermost application control logic:
+ ///
+ /// 1. parse command line arguments
+ /// 2. instantiate and initialize the application process
+ /// 3. establish BIND10 session(s) if in integrated mode
+ /// 4. start and wait on the application process event loop
+ /// 5. upon event loop completion, disconnect from BIND10 (if needed)
+ /// 6. exit to the caller
+ ///
+ /// It is intended to be called from main() and be given the command line
+ /// arguments. Note this method is deliberately not virtual to ensure the
+ /// proper sequence of events occur.
+ ///
+ /// This function can be run in the test mode. It prevents initialization
+ /// of D2 module logger. This is used in unit tests which initialize logger
+ /// in their main function. Such logger uses environmental variables to
+ /// control severity, verbosity etc. Reinitialization of logger by this
+ /// function would replace unit tests specific logger configuration with
+ /// this suitable for D2 running as a bind10 module.
+ ///
+ /// @param argc is the number of command line arguments supplied
+ /// @param argv is the array of string (char *) command line arguments
+ /// @param test_mode is a bool value which indicates if
+ /// @c DControllerBase::launch should be run in the test mode (if true).
+ /// This parameter doesn't have default value to force test implementers to
+ /// enable test mode explicitly.
+ ///
+ /// @throw throws one of the following exceptions:
+ /// InvalidUsage - Indicates invalid command line.
+ /// ProcessInitError - Failed to create and initialize application
+ /// process object.
+ /// SessionStartError - Could not connect to BIND10 (integrated mode only).
+ /// ProcessRunError - A fatal error occurred while in the application
+ /// process event loop.
+ /// SessionEndError - Could not disconnect from BIND10 (integrated mode
+ /// only).
+ void launch(int argc, char* argv[], const bool test_mode);
+
+ /// @brief A dummy configuration handler that always returns success.
+ ///
+ /// This configuration handler does not perform configuration
+ /// parsing and always returns success. A dummy handler should
+ /// be installed using \ref isc::config::ModuleCCSession ctor
+ /// to get the initial configuration. This initial configuration
+ /// comprises values for only those elements that were modified
+ /// the previous session. The D2 configuration parsing can't be
+ /// used to parse the initial configuration because it may need the
+ /// full configuration to satisfy dependencies between the
+ /// various configuration values. Installing the dummy handler
+ /// that guarantees to return success causes initial configuration
+ /// to be stored for the session being created and that it can
+ /// be later accessed with \ref isc::config::ConfigData::getFullConfig.
+ ///
+ /// @param new_config new configuration.
+ ///
+ /// @return success configuration status.
+ static isc::data::ConstElementPtr
+ dummyConfigHandler(isc::data::ConstElementPtr new_config);
+
+ /// @brief A callback for handling all incoming configuration updates.
+ ///
+ /// As a pointer to this method is used as a callback in ASIO for
+ /// ModuleCCSession, it has to be static. It acts as a wrapper around
+ /// the virtual instance method, updateConfig.
+ ///
+ /// @param new_config textual representation of the new configuration
+ ///
+ /// @return status of the config update
+ static isc::data::ConstElementPtr
+ configHandler(isc::data::ConstElementPtr new_config);
+
+ /// @brief A callback for handling all incoming commands.
+ ///
+ /// As a pointer to this method is used as a callback in ASIO for
+ /// ModuleCCSession, it has to be static. It acts as a wrapper around
+ /// the virtual instance method, executeCommand.
+ ///
+ /// @param command textual representation of the command
+ /// @param args parameters of the command. It can be NULL pointer if no
+ /// arguments exist for a particular command.
+ ///
+ /// @return status of the processed command
+ static isc::data::ConstElementPtr
+ commandHandler(const std::string& command, isc::data::ConstElementPtr args);
+
+ /// @brief Instance method invoked by the configuration event handler and
+ /// which processes the actual configuration update. Provides behavioral
+ /// path for both integrated and stand-alone modes. The current
+ /// implementation will merge the configuration update into the existing
+ /// configuration and then invoke the application process' configure method.
+ ///
+ /// @todo This implementation is will evolve as the D2 configuration
+ /// management task is implemented (trac #2957).
+ ///
+ /// @param new_config is the new configuration
+ ///
+ /// @return returns an Element that contains the results of configuration
+ /// update composed of an integer status value (0 means successful,
+ /// non-zero means failure), and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr
+ updateConfig(isc::data::ConstElementPtr new_config);
+
+
+ /// @brief Instance method invoked by the command event handler and which
+ /// processes the actual command directive.
+ ///
+ /// It supports the execution of:
+ ///
+ /// 1. Stock controller commands - commands common to all DControllerBase
+ /// derivations. Currently there is only one, the shutdown command.
+ ///
+ /// 2. Custom controller commands - commands that the deriving controller
+ /// class implements. These commands are executed by the deriving
+ /// controller.
+ ///
+ /// 3. Custom application commands - commands supported by the application
+ /// process implementation. These commands are executed by the application
+ /// process.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command.
+ ///
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value and a string explanation of the outcome.
+ /// The status value is one of the following:
+ /// D2::COMMAND_SUCCESS - Command executed successfully
+ /// D2::COMMAND_ERROR - Command is valid but suffered an operational
+ /// failure.
+ /// D2::COMMAND_INVALID - Command is not recognized as valid be either
+ /// the controller or the application process.
+ virtual isc::data::ConstElementPtr
+ executeCommand(const std::string& command, isc::data::ConstElementPtr args);
+
+protected:
+ /// @brief Virtual method that provides derivations the opportunity to
+ /// support additional command line options. It is invoked during command
+ /// line argument parsing (see parseArgs method) if the option is not
+ /// recognized as a stock DControllerBase option.
+ ///
+ /// @param option is the option "character" from the command line, without
+ /// any prefixing hyphen(s)
+ /// @param optarg is the argument value (if one) associated with the option
+ ///
+ /// @return must return true if the option was valid, false is it is
+ /// invalid. (Note the default implementation always returns false.)
+ virtual bool customOption(int option, char *optarg);
+
+ /// @brief Abstract method that is responsible for instantiating the
+ /// application process object. It is invoked by the controller after
+ /// command line argument parsing as part of the process initialization
+ /// (see initProcess method).
+ ///
+ /// @return returns a pointer to the new process object (DProcessBase*)
+ /// or NULL if the create fails.
+ /// Note this value is subsequently wrapped in a smart pointer.
+ virtual DProcessBase* createProcess() = 0;
+
+ /// @brief Virtual method that provides derivations the opportunity to
+ /// support custom external commands executed by the controller. This
+ /// method is invoked by the processCommand if the received command is
+ /// not a stock controller command.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command.
+ ///
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value and a string explanation of the outcome.
+ /// The status value is one of the following:
+ /// D2::COMMAND_SUCCESS - Command executed successfully
+ /// D2::COMMAND_ERROR - Command is valid but suffered an operational
+ /// failure.
+ /// D2::COMMAND_INVALID - Command is not recognized as a valid custom
+ /// controller command.
+ virtual isc::data::ConstElementPtr customControllerCommand(
+ const std::string& command, isc::data::ConstElementPtr args);
+
+ /// @brief Virtual method which is invoked after the controller successfully
+ /// establishes BIND10 connectivity. It provides an opportunity for the
+ /// derivation to execute any custom behavior associated with session
+ /// establishment.
+ ///
+ /// Note, it is not called when running stand-alone.
+ ///
+ /// @throw should throw a DControllerBaseError if it fails.
+ virtual void onSessionConnect(){};
+
+ /// @brief Virtual method which is invoked as the first action taken when
+ /// the controller is terminating the session(s) with BIND10. It provides
+ /// an opportunity for the derivation to execute any custom behavior
+ /// associated with session termination.
+ ///
+ /// Note, it is not called when running stand-alone.
+ ///
+ /// @throw should throw a DControllerBaseError if it fails.
+ virtual void onSessionDisconnect(){};
+
+ /// @brief Virtual method which can be used to contribute derivation
+ /// specific usage text. It is invoked by the usage() method under
+ /// invalid usage conditions.
+ ///
+ /// @return returns the desired text.
+ virtual const std::string getUsageText() const {
+ return ("");
+ }
+
+ /// @brief Virtual method which returns a string containing the option
+ /// letters for any custom command line options supported by the derivation.
+ /// These are added to the stock options of "s" and "v" during command
+ /// line interpretation.
+ ///
+ /// @return returns a string containing the custom option letters.
+ virtual const std::string getCustomOpts() const {
+ return ("");
+ }
+
+ /// @brief Fetches the name of the application under control.
+ ///
+ /// @return returns the controller service name string
+ const std::string getAppName() const {
+ return (app_name_);
+ }
+
+ /// @brief Fetches the name of the application executable.
+ ///
+ /// @return returns the controller logger name string
+ const std::string getBinName() const {
+ return (bin_name_);
+ }
+
+ /// @brief Supplies whether or not the controller is in stand alone mode.
+ ///
+ /// @return returns true if in stand alone mode, false otherwise
+ bool isStandAlone() const {
+ return (stand_alone_);
+ }
+
+ /// @brief Method for enabling or disabling stand alone mode.
+ ///
+ /// @param value is the new value to assign the flag.
+ void setStandAlone(bool value) {
+ stand_alone_ = value;
+ }
+
+ /// @brief Supplies whether or not verbose logging is enabled.
+ ///
+ /// @return returns true if verbose logging is enabled.
+ bool isVerbose() const {
+ return (verbose_);
+ }
+
+ /// @brief Method for enabling or disabling verbose logging.
+ ///
+ /// @param value is the new value to assign the flag.
+ void setVerbose(bool value) {
+ verbose_ = value;
+ }
+
+ /// @brief Getter for fetching the controller's IOService
+ ///
+ /// @return returns a pointer reference to the IOService.
+ IOServicePtr& getIOService() {
+ return (io_service_);
+ }
+
+ /// @brief Getter for fetching the name of the controller's BIND10 spec
+ /// file.
+ ///
+ /// @return returns the file name string.
+ const std::string getSpecFileName() const {
+ return (spec_file_name_);
+ }
+
+ /// @brief Setter for setting the name of the controller's BIND10 spec file.
+ ///
+ /// @param spec_file_name the file name string.
+ void setSpecFileName(const std::string& spec_file_name) {
+ spec_file_name_ = spec_file_name;
+ }
+
+ /// @brief Static getter which returns the singleton instance.
+ ///
+ /// @return returns a pointer reference to the private singleton instance
+ /// member.
+ static DControllerBasePtr& getController() {
+ return (controller_);
+ }
+
+ /// @brief Static setter which sets the singleton instance.
+ ///
+ /// @param controller is a pointer to the singleton instance.
+ ///
+ /// @throw throws DControllerBase error if an attempt is made to set the
+ /// instance a second time.
+ static void setController(const DControllerBasePtr& controller);
+
+private:
+ /// @brief Processes the command line arguments. It is the first step
+ /// taken after the controller has been launched. It combines the stock
+ /// list of options with those returned by getCustomOpts(), and uses
+ /// cstdlib's getopt to loop through the command line. The stock options
+ /// It handles stock options directly, and passes any custom options into
+ /// the customOption method. Currently there are only two stock options
+ /// -s for stand alone mode, and -v for verbose logging.
+ ///
+ /// @param argc is the number of command line arguments supplied
+ /// @param argv is the array of string (char *) command line arguments
+ ///
+ /// @throw throws InvalidUsage when there are usage errors.
+ void parseArgs(int argc, char* argv[]);
+
+ /// @brief Instantiates the application process and then initializes it.
+ /// This is the second step taken during launch, following successful
+ /// command line parsing. It is used to invoke the derivation-specific
+ /// implementation of createProcess, following by an invoking of the
+ /// newly instantiated process's init method.
+ ///
+ /// @throw throws DControllerBaseError or indirectly DProcessBaseError
+ /// if there is a failure creating or initializing the application process.
+ void initProcess();
+
+ /// @brief Establishes connectivity with BIND10. This method is used
+ /// invoked during launch, if running in integrated mode, following
+ /// successful process initialization. It is responsible for establishing
+ /// the BIND10 control and config sessions. During the session creation,
+ /// it passes in the controller's IOService and the callbacks for command
+ /// directives and config events. Lastly, it will invoke the onConnect
+ /// method providing the derivation an opportunity to execute any custom
+ /// logic associated with session establishment.
+ ///
+ /// @throw the BIND10 framework may throw std::exceptions.
+ void establishSession();
+
+ /// @brief Invokes the application process's event loop,(DBaseProcess::run).
+ /// It is called during launch only after successfully completing the
+ /// requested setup: command line parsing, application initialization,
+ /// and session establishment (if not stand-alone).
+ /// The process event loop is expected to only return upon application
+ /// shutdown either in response to the shutdown command or due to an
+ /// unrecoverable error.
+ ///
+ // @throw throws DControllerBaseError or indirectly DProcessBaseError
+ void runProcess();
+
+ /// @brief Terminates connectivity with BIND10. This method is invoked
+ /// in integrated mode after the application event loop has exited. It
+ /// first calls the onDisconnect method providing the derivation an
+ /// opportunity to execute custom logic if needed, and then terminates the
+ /// BIND10 config and control sessions.
+ ///
+ /// @throw the BIND10 framework may throw std:exceptions.
+ void disconnectSession();
+
+ /// @brief Initiates shutdown procedure. This method is invoked
+ /// by executeCommand in response to the shutdown command. It will invoke
+ /// the application process's shutdown method which causes the process to
+ /// to begin its shutdown process.
+ ///
+ /// Note, it is assumed that the process of shutting down is neither
+ /// instanteneous nor synchronous. This method does not "block" waiting
+ /// until the process has halted. Rather it is used to convey the
+ /// need to shutdown. A successful return indicates that the shutdown
+ /// has successfully commenced, but does not indicate that the process
+ /// has actually exited.
+ ///
+ /// @return returns an Element that contains the results of shutdown
+ /// command composed of an integer status value (0 means successful,
+ /// non-zero means failure), and a string explanation of the outcome.
+ isc::data::ConstElementPtr shutdown(isc::data::ConstElementPtr args);
+
+ /// @brief Prints the program usage text to std error.
+ ///
+ /// @param text is a string message which will preceded the usage text.
+ /// This is intended to be used for specific usage violation messages.
+ void usage(const std::string& text);
+
+private:
+ /// @brief Display name of the service under control. This name
+ /// appears in log statements.
+ std::string app_name_;
+
+ /// @brief Name of the service executable. By convention this matches
+ /// the BIND10 module name. It is also used to establish the logger
+ /// name.
+ std::string bin_name_;
+
+ /// @brief Indicates if the controller stand alone mode is enabled. When
+ /// enabled, the controller will not establish connectivity with BIND10.
+ bool stand_alone_;
+
+ /// @brief Indicates if the verbose logging mode is enabled.
+ bool verbose_;
+
+ /// @brief The absolute file name of the BIND10 spec file.
+ std::string spec_file_name_;
+
+ /// @brief Pointer to the instance of the process.
+ ///
+ /// This is required for config and command handlers to gain access to
+ /// the process
+ DProcessBasePtr process_;
+
+ /// @brief Shared pointer to an IOService object, used for ASIO operations.
+ IOServicePtr io_service_;
+
+ /// @brief Helper session object that represents raw connection to msgq.
+ SessionPtr cc_session_;
+
+ /// @brief Session that receives configuration and commands.
+ ModuleCCSessionPtr config_session_;
+
+ /// @brief Singleton instance value.
+ static DControllerBasePtr controller_;
+
+// DControllerTest is named a friend class to facilitate unit testing while
+// leaving the intended member scopes intact.
+friend class DControllerTest;
+};
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif
diff --git a/src/bin/d2/d_process.h b/src/bin/d2/d_process.h
new file mode 100644
index 0000000..ce86118
--- /dev/null
+++ b/src/bin/d2/d_process.h
@@ -0,0 +1,219 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 D_PROCESS_H
+#define D_PROCESS_H
+
+#include <asiolink/asiolink.h>
+#include <cc/data.h>
+#include <d2/d_cfg_mgr.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+
+typedef boost::shared_ptr<isc::asiolink::IOService> IOServicePtr;
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown if the process encountered an operational error.
+class DProcessBaseError : public isc::Exception {
+public:
+ DProcessBaseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief String value for the shutdown command.
+static const std::string SHUT_DOWN_COMMAND("shutdown");
+
+/// @brief Returned by the process to indicate a command was successful.
+static const int COMMAND_SUCCESS = 0;
+
+/// @brief Returned by the process to indicates a command failed.
+static const int COMMAND_ERROR = 1;
+
+/// @brief Returned by the process to indicates a command is not valid.
+static const int COMMAND_INVALID = 2;
+
+/// @brief Application Process Interface
+///
+/// DProcessBase is an abstract class represents the primary "application"
+/// level object in a "managed" asynchronous application. It provides a uniform
+/// interface such that a managing layer can construct, initialize, and start
+/// the application's event loop. The event processing is centered around the
+/// use of isc::asiolink::io_service. The io_service is shared between the
+/// managing layer and the DProcessBase. This allows management layer IO such
+/// as directives to be sensed and handled, as well as processing IO activity
+/// specific to the application. In terms of management layer IO, there are
+/// methods shutdown, configuration updates, and commands unique to the
+/// application.
+class DProcessBase {
+public:
+ /// @brief Constructor
+ ///
+ /// @param app_name is a text label for the process. Generally used
+ /// in log statements, but otherwise arbitrary.
+ /// @param io_service is the io_service used by the caller for
+ /// asynchronous event handling.
+ /// @param cfg_mgr the configuration manager instance that handles
+ /// configuration parsing.
+ ///
+ /// @throw DProcessBaseError is io_service is NULL.
+ DProcessBase(const char* app_name, IOServicePtr io_service,
+ DCfgMgrBasePtr cfg_mgr)
+ : app_name_(app_name), io_service_(io_service), shut_down_flag_(false),
+ cfg_mgr_(cfg_mgr) {
+ if (!io_service_) {
+ isc_throw (DProcessBaseError, "IO Service cannot be null");
+ }
+
+ if (!cfg_mgr_) {
+ isc_throw (DProcessBaseError, "CfgMgr cannot be null");
+ }
+ };
+
+ /// @brief May be used after instantiation to perform initialization unique
+ /// to application. It must be invoked prior to invoking run. This would
+ /// likely include the creation of additional IO sources and their
+ /// integration into the io_service.
+ /// @throw DProcessBaseError if the initialization fails.
+ virtual void init() = 0;
+
+ /// @brief Implements the process's event loop. In its simplest form it
+ /// would an invocation io_service_->run(). This method should not exit
+ /// until the process itself is exiting due to a request to shutdown or
+ /// some anomaly is forcing an exit.
+ /// @throw DProcessBaseError if an operational error is encountered.
+ virtual void run() = 0;
+
+ /// @brief Initiates the process's shutdown process.
+ ///
+ /// This is last step in the shutdown event callback chain, that is
+ /// intended to notify the process it is to begin its shutdown process.
+ ///
+ /// @param args an Element set of shutdown arguments (if any) that are
+ /// supported by the process derivation.
+ ///
+ /// @return an Element that contains the results of argument processing,
+ /// consisting of an integer status value (0 means successful,
+ /// non-zero means failure), and a string explanation of the outcome.
+ ///
+ /// @throw DProcessBaseError if an operational error is encountered.
+ virtual isc::data::ConstElementPtr
+ shutdown(isc::data::ConstElementPtr args) = 0;
+
+ /// @brief Processes the given configuration.
+ ///
+ /// This method may be called multiple times during the process lifetime.
+ /// Certainly once during process startup, and possibly later if the user
+ /// alters configuration. This method must not throw, it should catch any
+ /// processing errors and return a success or failure answer as described
+ /// below.
+ ///
+ /// @param config_set a new configuration (JSON) for the process
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
+ config_set) = 0;
+
+ /// @brief Processes the given command.
+ ///
+ /// This method is called to execute any custom commands supported by the
+ /// process. This method must not throw, it should catch any processing
+ /// errors and return a success or failure answer as described below.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command.
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value:
+ ///
+ /// - COMMAND_SUCCESS indicates a command was successful.
+ /// - COMMAND_ERROR indicates a valid command failed execute.
+ /// - COMMAND_INVALID indicates a command is not valid.
+ ///
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr command(
+ const std::string& command, isc::data::ConstElementPtr args) = 0;
+
+ /// @brief Destructor
+ virtual ~DProcessBase(){};
+
+ /// @brief Checks if the process has been instructed to shut down.
+ ///
+ /// @return true if process shutdown flag is true.
+ bool shouldShutdown() const {
+ return (shut_down_flag_);
+ }
+
+ /// @brief Sets the process shut down flag to the given value.
+ ///
+ /// @param value is the new value to assign the flag.
+ void setShutdownFlag(bool value) {
+ shut_down_flag_ = value;
+ }
+
+ /// @brief Fetches the application name.
+ ///
+ /// @return application name string.
+ const std::string getAppName() const {
+ return (app_name_);
+ }
+
+ /// @brief Fetches the controller's IOService.
+ ///
+ /// @return a reference to the controller's IOService.
+ IOServicePtr& getIoService() {
+ return (io_service_);
+ }
+
+ /// @brief Convenience method for stopping IOservice processing.
+ /// Invoking this will cause the process to exit any blocking
+ /// IOService method such as run(). No further IO events will be
+ /// processed.
+ void stopIOService() {
+ io_service_->stop();
+ }
+
+ /// @brief Fetches the process's configuration manager.
+ ///
+ /// @return a reference to the configuration manager.
+ DCfgMgrBasePtr& getCfgMgr() {
+ return (cfg_mgr_);
+ }
+
+private:
+ /// @brief Text label for the process. Generally used in log statements,
+ /// but otherwise can be arbitrary.
+ std::string app_name_;
+
+ /// @brief The IOService to be used for asynchronous event handling.
+ IOServicePtr io_service_;
+
+ /// @brief Boolean flag set when shutdown has been requested.
+ bool shut_down_flag_;
+
+ /// @brief Pointer to the configuration manager.
+ DCfgMgrBasePtr cfg_mgr_;
+};
+
+/// @brief Defines a shared pointer to DProcessBase.
+typedef boost::shared_ptr<DProcessBase> DProcessBasePtr;
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif
diff --git a/src/bin/d2/dhcp-ddns.spec b/src/bin/d2/dhcp-ddns.spec
new file mode 100644
index 0000000..f60ea9f
--- /dev/null
+++ b/src/bin/d2/dhcp-ddns.spec
@@ -0,0 +1,212 @@
+{
+"module_spec":
+{
+ "module_name": "DhcpDdns",
+ "module_description": "DHPC-DDNS Service",
+ "config_data": [
+ {
+ "item_name": "interface",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "eth0"
+ },
+
+ {
+ "item_name": "ip_address",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "127.0.0.1"
+ },
+
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 51771
+ },
+ {
+ "item_name": "tsig_keys",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "tsig_key",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {"algorithm" : "hmac_md5"},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ {
+ "item_name": "algorithm",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ {
+ "item_name": "secret",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }]
+ }
+ },
+ {
+ "item_name": "forward_ddns",
+ "item_type": "map",
+ "item_optional": true,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "ddns_domains",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "ddns_domain",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "key_name",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "dns_servers",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "dns_server",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "hostname",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "ip_address",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 53
+ }]
+ }
+ }]
+ }
+ }]
+ },
+
+ {
+ "item_name": "reverse_ddns",
+ "item_type": "map",
+ "item_optional": true,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "ddns_domains",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "ddns_domain",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "key_name",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+
+ {
+ "item_name": "dns_servers",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "dns_server",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "hostname",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "ip_address",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 53
+ }]
+ }
+ }]
+ }
+ }]
+ }],
+
+ "commands": [
+ {
+ "command_name": "shutdown",
+ "command_description": "Shuts down b10-dhcp-ddns module server.",
+ "command_args": [
+ {
+ "item_name": "type",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "normal",
+ "item_description": "values: normal (default), now, or drain_first"
+ }
+ ]
+ }
+ ]
+ }
+}
+
diff --git a/src/bin/d2/dns_client.cc b/src/bin/d2/dns_client.cc
new file mode 100644
index 0000000..a3bff81
--- /dev/null
+++ b/src/bin/d2/dns_client.cc
@@ -0,0 +1,247 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <d2/dns_client.h>
+#include <d2/d2_log.h>
+#include <dns/messagerenderer.h>
+#include <limits>
+
+namespace isc {
+namespace d2 {
+
+namespace {
+
+// OutputBuffer objects are pre-allocated before data is written to them.
+// This is a default number of bytes for the buffers we create within
+// DNSClient class.
+const size_t DEFAULT_BUFFER_SIZE = 128;
+
+}
+
+using namespace isc::util;
+using namespace isc::asiolink;
+using namespace isc::asiodns;
+using namespace isc::dns;
+
+// This class provides the implementation for the DNSClient. This allows for
+// the separation of the DNSClient interface from the implementation details.
+// Currently, implementation uses IOFetch object to handle asynchronous
+// communication with the DNS. This design may be revisited in the future. If
+// implementation is changed, the DNSClient API will remain unchanged thanks
+// to this separation.
+class DNSClientImpl : public asiodns::IOFetch::Callback {
+public:
+ // A buffer holding response from a DNS.
+ util::OutputBufferPtr in_buf_;
+ // A caller-supplied object holding a parsed response from DNS.
+ D2UpdateMessagePtr response_;
+ // A caller-supplied external callback which is invoked when DNS message
+ // exchange is complete or interrupted.
+ DNSClient::Callback* callback_;
+ // A Transport Layer protocol used to communicate with a DNS.
+ DNSClient::Protocol proto_;
+
+ // Constructor and Destructor
+ DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
+ DNSClient::Callback* callback,
+ const DNSClient::Protocol proto);
+ virtual ~DNSClientImpl();
+
+ // This internal callback is called when the DNS update message exchange is
+ // complete. It further invokes the external callback provided by a caller.
+ // Before external callback is invoked, an object of the D2UpdateMessage
+ // type, representing a response from the server is set.
+ virtual void operator()(asiodns::IOFetch::Result result);
+
+ // Starts asynchronous DNS Update.
+ void doUpdate(asiolink::IOService& io_service,
+ const asiolink::IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait);
+
+ // This function maps the IO error to the DNSClient error.
+ DNSClient::Status getStatus(const asiodns::IOFetch::Result);
+};
+
+DNSClientImpl::DNSClientImpl(D2UpdateMessagePtr& response_placeholder,
+ DNSClient::Callback* callback,
+ const DNSClient::Protocol proto)
+ : in_buf_(new OutputBuffer(DEFAULT_BUFFER_SIZE)),
+ response_(response_placeholder), callback_(callback), proto_(proto) {
+
+ // @todo Currently we only support UDP. The support for TCP is planned for
+ // the future release.
+ if (proto_ == DNSClient::TCP) {
+ isc_throw(isc::NotImplemented, "TCP is currently not supported as a"
+ << " Transport protocol for DNS Updates; please use UDP");
+ }
+
+ // Given that we already eliminated the possibility that TCP is used, it
+ // would be sufficient to check that (proto != DNSClient::UDP). But, once
+ // support TCP is added the check above will disappear and the extra check
+ // will be needed here anyway.
+ // Note that cascaded check is used here instead of:
+ // if (proto_ != DNSClient::TCP && proto_ != DNSClient::UDP)..
+ // because some versions of GCC compiler complain that check above would
+ // be always 'false' due to limited range of enumeration. In fact, it is
+ // possible to pass out of range integral value through enum and it should
+ // be caught here.
+ if (proto_ != DNSClient::TCP) {
+ if (proto_ != DNSClient::UDP) {
+ isc_throw(isc::NotImplemented, "invalid transport protocol type '"
+ << proto_ << "' specified for DNS Updates");
+ }
+ }
+
+ if (!response_) {
+ isc_throw(BadValue, "a pointer to an object to encapsulate the DNS"
+ " server must be provided; found NULL value");
+
+ }
+}
+
+DNSClientImpl::~DNSClientImpl() {
+}
+
+void
+DNSClientImpl::operator()(asiodns::IOFetch::Result result) {
+ // Get the status from IO. If no success, we just call user's callback
+ // and pass the status code.
+ DNSClient::Status status = getStatus(result);
+ if (status == DNSClient::SUCCESS) {
+ InputBuffer response_buf(in_buf_->getData(), in_buf_->getLength());
+ // Server's response may be corrupted. In such case, fromWire will
+ // throw an exception. We want to catch this exception to return
+ // appropriate status code to the caller and log this event.
+ try {
+ response_->fromWire(response_buf);
+
+ } catch (const Exception& ex) {
+ status = DNSClient::INVALID_RESPONSE;
+ LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL,
+ DHCP_DDNS_INVALID_RESPONSE).arg(ex.what());
+
+ }
+ }
+
+ // Once we are done with internal business, let's call a callback supplied
+ // by a caller.
+ if (callback_ != NULL) {
+ (*callback_)(status);
+ }
+}
+
+DNSClient::Status
+DNSClientImpl::getStatus(const asiodns::IOFetch::Result result) {
+ switch (result) {
+ case IOFetch::SUCCESS:
+ return (DNSClient::SUCCESS);
+
+ case IOFetch::TIME_OUT:
+ return (DNSClient::TIMEOUT);
+
+ case IOFetch::STOPPED:
+ return (DNSClient::IO_STOPPED);
+
+ default:
+ ;
+ }
+ return (DNSClient::OTHER);
+}
+
+void
+DNSClientImpl::doUpdate(IOService& io_service,
+ const IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait) {
+ // A renderer is used by the toWire function which creates the on-wire data
+ // from the DNS Update message. A renderer has its internal buffer where it
+ // renders data by default. However, this buffer can't be directly accessed.
+ // Fortunately, the renderer's API accepts user-supplied buffers. So, let's
+ // create our own buffer and pass it to the renderer so as the message is
+ // rendered to this buffer. Finally, we pass this buffer to IOFetch.
+ dns::MessageRenderer renderer;
+ OutputBufferPtr msg_buf(new OutputBuffer(DEFAULT_BUFFER_SIZE));
+ renderer.setBuffer(msg_buf.get());
+
+ // Render DNS Update message. This may throw a bunch of exceptions if
+ // invalid message object is given.
+ update.toWire(renderer);
+
+ // IOFetch has all the mechanisms that we need to perform asynchronous
+ // communication with the DNS server. The last but one argument points to
+ // this object as a completion callback for the message exchange. As a
+ // result operator()(Status) will be called.
+
+ // Timeout value is explicitly cast to the int type to avoid warnings about
+ // overflows when doing implicit cast. It should have been checked by the
+ // caller that the unsigned timeout value will fit into int.
+ IOFetch io_fetch(IOFetch::UDP, io_service, msg_buf, ns_addr, ns_port,
+ in_buf_, this, static_cast<int>(wait));
+ // Post the task to the task queue in the IO service. Caller will actually
+ // run these tasks by executing IOService::run.
+ io_service.post(io_fetch);
+}
+
+
+DNSClient::DNSClient(D2UpdateMessagePtr& response_placeholder,
+ Callback* callback, const DNSClient::Protocol proto)
+ : impl_(new DNSClientImpl(response_placeholder, callback, proto)) {
+}
+
+DNSClient::~DNSClient() {
+ delete (impl_);
+}
+
+unsigned int
+DNSClient::getMaxTimeout() {
+ static const unsigned int max_timeout = std::numeric_limits<int>::max();
+ return (max_timeout);
+}
+
+void
+DNSClient::doUpdate(IOService&,
+ const IOAddress&,
+ const uint16_t,
+ D2UpdateMessage&,
+ const unsigned int,
+ const dns::TSIGKey&) {
+ isc_throw(isc::NotImplemented, "TSIG is currently not supported for"
+ "DNS Update message");
+}
+
+void
+DNSClient::doUpdate(IOService& io_service,
+ const IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait) {
+ // The underlying implementation which we use to send DNS Updates uses
+ // signed integers for timeout. If we want to avoid overflows we need to
+ // respect this limitation here.
+ if (wait > getMaxTimeout()) {
+ isc_throw(isc::BadValue, "A timeout value for DNS Update request must"
+ " not exceed " << getMaxTimeout()
+ << ". Provided timeout value is '" << wait << "'");
+ }
+ impl_->doUpdate(io_service, ns_addr, ns_port, update, wait);
+}
+
+
+
+} // namespace d2
+} // namespace isc
+
diff --git a/src/bin/d2/dns_client.h b/src/bin/d2/dns_client.h
new file mode 100644
index 0000000..c1c54f6
--- /dev/null
+++ b/src/bin/d2/dns_client.h
@@ -0,0 +1,192 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 DNS_CLIENT_H
+#define DNS_CLIENT_H
+
+#include <d2/d2_update_message.h>
+
+#include <asiolink/io_service.h>
+#include <util/buffer.h>
+
+#include <asiodns/io_fetch.h>
+#include <dns/tsig.h>
+
+namespace isc {
+namespace d2 {
+
+class DNSClient;
+typedef boost::shared_ptr<DNSClient> DNSClientPtr;
+
+/// DNSClient class implementation.
+class DNSClientImpl;
+
+/// @brief The @c DNSClient class handles communication with the DNS server.
+///
+/// Communication with the DNS server is asynchronous. Caller must provide a
+/// callback, which will be invoked when the response from the DNS server is
+/// received, a timeout has occurred or IO service has been stopped for any
+/// reason. The caller-supplied callback is called by the internal callback
+/// operator implemented by @c DNSClient. This callback is responsible for
+/// initializing the @c D2UpdateMessage instance which encapsulates the response
+/// from the DNS. This initialization does not take place if the response from
+/// DNS is not received.
+///
+/// Caller must supply a pointer to the @c D2UpdateMessage object, which will
+/// encapsulate DNS response, through class constructor. An exception will be
+/// thrown if the pointer is not initialized by the caller.
+///
+/// @todo Ultimately, this class will support both TCP and UDP Transport.
+/// Currently only UDP is supported and can be specified as a preferred
+/// protocol. @c DNSClient constructor will throw an exception if TCP is
+/// specified. Once both protocols are supported, the @c DNSClient logic will
+/// try to obey caller's preference. However, it may use the other protocol if
+/// on its own discretion, when there is a legitimate reason to do so. For
+/// example, if communication with the server using preferred protocol fails.
+class DNSClient {
+public:
+
+ /// @brief Transport layer protocol used by a DNS Client to communicate
+ /// with a server.
+ enum Protocol {
+ UDP,
+ TCP
+ };
+
+ /// @brief A status code of the DNSClient.
+ enum Status {
+ SUCCESS, ///< Response received and is ok.
+ TIMEOUT, ///< No response, timeout.
+ IO_STOPPED, ///< IO was stopped.
+ INVALID_RESPONSE, ///< Response received but invalid.
+ OTHER ///< Other, unclassified error.
+ };
+
+ /// @brief Callback for the @c DNSClient class.
+ ///
+ /// This is is abstract class which represents the external callback for the
+ /// @c DNSClient. Caller must implement this class and supply its instance
+ /// in the @c DNSClient constructor to get callbacks when the DNS Update
+ /// exchange is complete (@see @c DNSClient).
+ class Callback {
+ public:
+ /// @brief Virtual destructor.
+ virtual ~Callback() { }
+
+ /// @brief Function operator implementing a callback.
+ ///
+ /// @param status a @c DNSClient::Status enum representing status code
+ /// of DNSClient operation.
+ virtual void operator()(DNSClient::Status status) = 0;
+ };
+
+ /// @brief Constructor.
+ ///
+ /// @param response_placeholder Pointer to an object which will hold a
+ /// DNS server's response. Caller is responsible for allocating this object.
+ /// @param callback Pointer to an object implementing @c DNSClient::Callback
+ /// class. This object will be called when DNS message exchange completes or
+ /// if an error occurs. NULL value disables callback invocation.
+ /// @param proto caller's preference regarding Transport layer protocol to
+ /// be used by DNS Client to communicate with a server.
+ DNSClient(D2UpdateMessagePtr& response_placeholder, Callback* callback,
+ const Protocol proto = UDP);
+
+ /// @brief Virtual destructor, does nothing.
+ ~DNSClient();
+
+ ///
+ /// @name Copy constructor and assignment operator
+ ///
+ /// Copy constructor and assignment operator are private because there are
+ /// no use cases when @DNSClient instance will need to be copied. Also, it
+ /// is desired to avoid copying @DNSClient::impl_ pointer and external
+ /// callbacks.
+ ///
+ //@{
+private:
+ DNSClient(const DNSClient& source);
+ DNSClient& operator=(const DNSClient& source);
+ //@}
+
+public:
+
+ /// @brief Returns maximal allowed timeout value accepted by
+ /// @c DNSClient::doUpdate.
+ ///
+ /// @return maximal allowed timeout value accepted by @c DNSClient::doUpdate
+ static unsigned int getMaxTimeout();
+
+ /// @brief Start asynchronous DNS Update with TSIG.
+ ///
+ /// This function starts asynchronous DNS Update and returns. The DNS Update
+ /// will be executed by the specified IO service. Once the message exchange
+ /// with a DNS server is complete, timeout occurs or IO operation is
+ /// interrupted, the caller-supplied callback function will be invoked.
+ ///
+ /// An address and port of the DNS server is specified through the function
+ /// arguments so as the same instance of the @c DNSClient can be used to
+ /// initiate multiple message exchanges.
+ ///
+ /// @param io_service IO service to be used to run the message exchange.
+ /// @param ns_addr DNS server address.
+ /// @param ns_port DNS server port.
+ /// @param update A DNS Update message to be sent to the server.
+ /// @param wait A timeout (in seconds) for the response. If a response is
+ /// not received within the timeout, exchange is interrupted. This value
+ /// must not exceed maximal value for 'int' data type.
+ /// @param tsig_key An @c isc::dns::TSIGKey object representing TSIG
+ /// context which will be used to render the DNS Update message.
+ ///
+ /// @todo Implement TSIG Support. Currently any attempt to call this
+ /// function will result in exception.
+ void doUpdate(asiolink::IOService& io_service,
+ const asiolink::IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait,
+ const dns::TSIGKey& tsig_key);
+
+ /// @brief Start asynchronous DNS Update without TSIG.
+ ///
+ /// This function starts asynchronous DNS Update and returns. The DNS Update
+ /// will be executed by the specified IO service. Once the message exchange
+ /// with a DNS server is complete, timeout occurs or IO operation is
+ /// interrupted, the caller-supplied callback function will be invoked.
+ ///
+ /// An address and port of the DNS server is specified through the function
+ /// arguments so as the same instance of the @c DNSClient can be used to
+ /// initiate multiple message exchanges.
+ ///
+ /// @param io_service IO service to be used to run the message exchange.
+ /// @param ns_addr DNS server address.
+ /// @param ns_port DNS server port.
+ /// @param update A DNS Update message to be sent to the server.
+ /// @param wait A timeout (in seconds) for the response. If a response is
+ /// not received within the timeout, exchange is interrupted. This value
+ /// must not exceed maximal value for 'int' data type.
+ void doUpdate(asiolink::IOService& io_service,
+ const asiolink::IOAddress& ns_addr,
+ const uint16_t ns_port,
+ D2UpdateMessage& update,
+ const unsigned int wait);
+
+private:
+ DNSClientImpl* impl_; ///< Pointer to DNSClient implementation.
+};
+
+} // namespace d2
+} // namespace isc
+
+#endif // DNS_CLIENT_H
diff --git a/src/bin/d2/main.cc b/src/bin/d2/main.cc
new file mode 100644
index 0000000..4b5b8bc
--- /dev/null
+++ b/src/bin/d2/main.cc
@@ -0,0 +1,49 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <d2/d2_log.h>
+#include <d2/d2_controller.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_support.h>
+#include <log/logger_manager.h>
+
+#include <iostream>
+
+using namespace isc::d2;
+using namespace std;
+
+/// This file contains entry point (main() function) for standard DHCP-DDNS
+/// process, b10-dhcp-ddns, component for BIND10 framework. It fetches
+/// the D2Controller singleton instance and invokes its launch method.
+/// The exit value of the program will be EXIT_SUCCESS if there were no
+/// errors, EXIT_FAILURE otherwise.
+int main(int argc, char* argv[]) {
+ int ret = EXIT_SUCCESS;
+
+ // Instantiate/fetch the DHCP-DDNS application controller singleton.
+ DControllerBasePtr& controller = D2Controller::instance();
+
+ // Launch the controller passing in command line arguments.
+ // Exit program with the controller's return code.
+ try {
+ // 'false' value disables test mode.
+ controller->launch(argc, argv, false);
+ } catch (const isc::Exception& ex) {
+ std::cerr << "Service failed:" << ex.what() << std::endl;
+ ret = EXIT_FAILURE;
+ }
+
+ return (ret);
+}
diff --git a/src/bin/d2/spec_config.h.pre.in b/src/bin/d2/spec_config.h.pre.in
new file mode 100644
index 0000000..6d48a7e
--- /dev/null
+++ b/src/bin/d2/spec_config.h.pre.in
@@ -0,0 +1,15 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 D2_SPECFILE_LOCATION "@prefix@/share/@PACKAGE@/dhcp-ddns.spec"
diff --git a/src/bin/d2/tests/.gitignore b/src/bin/d2/tests/.gitignore
new file mode 100644
index 0000000..3e1a1b7
--- /dev/null
+++ b/src/bin/d2/tests/.gitignore
@@ -0,0 +1,2 @@
+/d2_unittests
+/test_data_files_config.h
diff --git a/src/bin/d2/tests/Makefile.am b/src/bin/d2/tests/Makefile.am
new file mode 100644
index 0000000..6d0b894
--- /dev/null
+++ b/src/bin/d2/tests/Makefile.am
@@ -0,0 +1,98 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+
+PYTESTS = d2_test.py
+EXTRA_DIST = $(PYTESTS)
+
+# Explicitly specify paths to dynamic libraries required by loadable python
+# modules. That is required on Mac OS systems. Otherwise we will get exception
+# about python not being able to load liblog library.
+LIBRARY_PATH_PLACEHOLDER =
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
+ B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/asiolink
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/d2/tests\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+if USE_CLANGPP
+# Disable unused parameter warning caused by some Boost headers when compiling with clang
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+
+TESTS += d2_unittests
+
+d2_unittests_SOURCES = ../d2_log.h ../d2_log.cc
+d2_unittests_SOURCES += ../d_process.h
+d2_unittests_SOURCES += ../d_controller.cc ../d2_controller.h
+d2_unittests_SOURCES += ../d2_process.cc ../d2_process.h
+d2_unittests_SOURCES += ../d2_controller.cc ../d2_controller.h
+d2_unittests_SOURCES += ../d_cfg_mgr.cc ../d_cfg_mgr.h
+d2_unittests_SOURCES += ../d2_config.cc ../d2_config.h
+d2_unittests_SOURCES += ../d2_cfg_mgr.cc ../d2_cfg_mgr.h
+d2_unittests_SOURCES += ../d2_queue_mgr.cc ../d2_queue_mgr.h
+d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
+d2_unittests_SOURCES += ../d2_update_mgr.cc ../d2_update_mgr.h
+d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
+d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
+d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
+d2_unittests_SOURCES += d2_unittests.cc
+d2_unittests_SOURCES += d2_process_unittests.cc
+d2_unittests_SOURCES += d_controller_unittests.cc
+d2_unittests_SOURCES += d2_controller_unittests.cc
+d2_unittests_SOURCES += d_cfg_mgr_unittests.cc
+d2_unittests_SOURCES += d2_cfg_mgr_unittests.cc
+d2_unittests_SOURCES += d2_queue_mgr_unittests.cc
+d2_unittests_SOURCES += d2_update_message_unittests.cc
+d2_unittests_SOURCES += d2_update_mgr_unittests.cc
+d2_unittests_SOURCES += d2_zone_unittests.cc
+d2_unittests_SOURCES += dns_client_unittests.cc
+nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
+
+d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+d2_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+d2_unittests_LDADD = $(GTEST_LDADD)
+d2_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/asiodns/libb10-asiodns.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
+
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/d2/tests/d2_cfg_mgr_unittests.cc b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
new file mode 100644
index 0000000..645bbae
--- /dev/null
+++ b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
@@ -0,0 +1,1260 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/module_spec.h>
+#include <d2/d2_config.h>
+#include <d2/d2_cfg_mgr.h>
+#include <d_test_stubs.h>
+#include <test_data_files_config.h>
+
+#include <boost/foreach.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+
+namespace {
+
+std::string specfile(const std::string& name) {
+ return (std::string(D2_SRC_DIR) + "/" + name);
+}
+
+/// @brief Test fixture class for testing D2CfgMgr class.
+/// It maintains an member instance of D2CfgMgr and provides methods for
+/// converting JSON strings to configuration element sets, checking parse
+/// results, and accessing the configuration context.
+class D2CfgMgrTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ D2CfgMgrTest():cfg_mgr_(new D2CfgMgr) {
+ }
+
+ /// @brief Destructor
+ ~D2CfgMgrTest() {
+ }
+
+ /// @brief Configuration manager instance.
+ D2CfgMgrPtr cfg_mgr_;
+};
+
+/// @brief Tests that the spec file is valid.
+/// Verifies that the BIND10 DHCP-DDNS configuration specification file
+// is valid.
+TEST(D2SpecTest, basicSpec) {
+ ASSERT_NO_THROW(isc::config::
+ moduleSpecFromFile(specfile("dhcp-ddns.spec")));
+}
+
+/// @brief Convenience function which compares the contents of the given
+/// DnsServerInfo against the given set of values.
+///
+/// It is structured in such a way that each value is checked, and output
+/// is generate for all that do not match.
+///
+/// @param server is a pointer to the server to check against.
+/// @param hostname is the value to compare against server's hostname_.
+/// @param ip_address is the string value to compare against server's
+/// ip_address_.
+/// @param port is the value to compare against server's port.
+///
+/// @return returns true if there is a match across the board, otherwise it
+/// returns false.
+bool checkServer(DnsServerInfoPtr server, const char* hostname,
+ const char *ip_address, uint32_t port)
+{
+ // Return value, assume its a match.
+ bool result = true;
+
+ if (!server) {
+ EXPECT_TRUE(server);
+ return false;
+ }
+
+ // Check hostname.
+ if (server->getHostname() != hostname) {
+ EXPECT_EQ(hostname, server->getHostname());
+ result = false;
+ }
+
+ // Check IP address.
+ if (server->getIpAddress().toText() != ip_address) {
+ EXPECT_EQ(ip_address, server->getIpAddress().toText());
+ result = false;
+ }
+
+ // Check port.
+ if (server->getPort() != port) {
+ EXPECT_EQ (port, server->getPort());
+ result = false;
+ }
+
+ return (result);
+}
+
+/// @brief Convenience function which compares the contents of the given
+/// TSIGKeyInfo against the given set of values.
+///
+/// It is structured in such a way that each value is checked, and output
+/// is generate for all that do not match.
+///
+/// @param key is a pointer to the key to check against.
+/// @param name is the value to compare against key's name_.
+/// @param algorithm is the string value to compare against key's algorithm.
+/// @param secret is the value to compare against key's secret.
+///
+/// @return returns true if there is a match across the board, otherwise it
+/// returns false.
+bool checkKey(TSIGKeyInfoPtr key, const char* name,
+ const char *algorithm, const char* secret)
+{
+ // Return value, assume its a match.
+ bool result = true;
+ if (!key) {
+ EXPECT_TRUE(key);
+ return false;
+ }
+
+ // Check name.
+ if (key->getName() != name) {
+ EXPECT_EQ(name, key->getName());
+ result = false;
+ }
+
+ // Check algorithm.
+ if (key->getAlgorithm() != algorithm) {
+ EXPECT_EQ(algorithm, key->getAlgorithm());
+ result = false;
+ }
+
+ // Check secret.
+ if (key->getSecret() != secret) {
+ EXPECT_EQ (secret, key->getSecret());
+ result = false;
+ }
+
+ return (result);
+}
+
+/// @brief Test fixture class for testing DnsServerInfo parsing.
+class TSIGKeyInfoTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ TSIGKeyInfoTest() {
+ reset();
+ }
+
+ /// @brief Destructor
+ ~TSIGKeyInfoTest() {
+ }
+
+ /// @brief Wipe out the current storage and parser and replace
+ /// them with new ones.
+ void reset() {
+ keys_.reset(new TSIGKeyInfoMap());
+ parser_.reset(new TSIGKeyInfoParser("test", keys_));
+ }
+
+ /// @brief Storage for "committing" keys.
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Pointer to the current parser instance.
+ isc::dhcp::ParserPtr parser_;
+};
+
+/// @brief Test fixture class for testing DnsServerInfo parsing.
+class DnsServerInfoTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ DnsServerInfoTest() {
+ reset();
+ }
+
+ /// @brief Destructor
+ ~DnsServerInfoTest() {
+ }
+
+ /// @brief Wipe out the current storage and parser and replace
+ /// them with new ones.
+ void reset() {
+ servers_.reset(new DnsServerInfoStorage());
+ parser_.reset(new DnsServerInfoParser("test", servers_));
+ }
+
+ /// @brief Storage for "committing" servers.
+ DnsServerInfoStoragePtr servers_;
+
+ /// @brief Pointer to the current parser instance.
+ isc::dhcp::ParserPtr parser_;
+};
+
+
+/// @brief Test fixture class for testing DDnsDomain parsing.
+class DdnsDomainTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ DdnsDomainTest() {
+ reset();
+ }
+
+ /// @brief Destructor
+ ~DdnsDomainTest() {
+ }
+
+ /// @brief Wipe out the current storage and parser and replace
+ /// them with new ones.
+ void reset() {
+ keys_.reset(new TSIGKeyInfoMap());
+ domains_.reset(new DdnsDomainMap());
+ parser_.reset(new DdnsDomainParser("test", domains_, keys_));
+ }
+
+ /// @brief Add TSIGKeyInfos to the key map
+ ///
+ /// @param name the name of the key
+ /// @param algorithm the algorithm of the key
+ /// @param secret the secret value of the key
+ void addKey(const std::string& name, const std::string& algorithm,
+ const std::string& secret) {
+ TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));
+ (*keys_)[name]=key_info;
+ }
+
+ /// @brief Storage for "committing" domains.
+ DdnsDomainMapPtr domains_;
+
+ /// @brief Storage for TSIGKeys
+ TSIGKeyInfoMapPtr keys_;
+
+ /// @brief Pointer to the current parser instance.
+ isc::dhcp::ParserPtr parser_;
+};
+
+/// @brief Tests the enforcement of data validation when parsing TSIGKeyInfos.
+/// It verifies that:
+/// 1. Name cannot be blank.
+/// 2. Algorithm cannot be blank.
+/// 3. Secret cannot be blank.
+/// @TODO TSIG keys are not fully functional. Only basic validation is
+/// currently supported. This test will need to expand as they evolve.
+TEST_F(TSIGKeyInfoTest, invalidEntry) {
+ // Config with a blank name entry.
+ std::string config = "{"
+ " \"name\": \"\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"0123456789\" "
+ "}";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that build succeeds but commit fails on blank name.
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+
+ // Config with a blank algorithm entry.
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"\" , "
+ " \"secret\": \"0123456789\" "
+ "}";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that build succeeds but commit fails on blank algorithm.
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+
+ // Config with a blank secret entry.
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"\" "
+ "}";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that build succeeds but commit fails on blank secret.
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+}
+
+/// @brief Verifies that TSIGKeyInfo parsing creates a proper TSIGKeyInfo
+/// when given a valid combination of entries.
+TEST_F(TSIGKeyInfoTest, validEntry) {
+ // Valid entries for TSIG key, all items are required.
+ std::string config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"0123456789\" "
+ "}";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that it builds and commits without throwing.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_NO_THROW(parser_->commit());
+
+ // Verify the correct number of keys are present
+ int count = keys_->size();
+ EXPECT_EQ(1, count);
+
+ // Find the key and retrieve it.
+ TSIGKeyInfoMap::iterator gotit = keys_->find("d2_key_one");
+ ASSERT_TRUE(gotit != keys_->end());
+ TSIGKeyInfoPtr& key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "d2_key_one", "md5", "0123456789"));
+}
+
+/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
+/// entries is detected.
+TEST_F(TSIGKeyInfoTest, invalidTSIGKeyList) {
+ // Construct a list of keys with an invalid key entry.
+ std::string config = "["
+
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo1\" ,"
+ " \"secret\": \"secret11\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"\" ,"
+ " \"secret\": \"secret12\" "
+ " },"
+ " { \"name\": \"key3\" , "
+ " \"algorithm\": \"algo3\" ,"
+ " \"secret\": \"secret13\" "
+ " }"
+ " ]";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the list parser.
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));
+
+ // Verify that the list builds without errors.
+ ASSERT_NO_THROW(parser->build(config_set_));
+
+ // Verify that the list commit fails.
+ EXPECT_THROW(parser->commit(), D2CfgError);
+}
+
+/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
+/// entries is detected.
+TEST_F(TSIGKeyInfoTest, duplicateTSIGKey) {
+ // Construct a list of keys with an invalid key entry.
+ std::string config = "["
+
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo1\" ,"
+ " \"secret\": \"secret11\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"algo2\" ,"
+ " \"secret\": \"secret12\" "
+ " },"
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo3\" ,"
+ " \"secret\": \"secret13\" "
+ " }"
+ " ]";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the list parser.
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));
+
+ // Verify that the list builds without errors.
+ ASSERT_NO_THROW(parser->build(config_set_));
+
+ // Verify that the list commit fails.
+ EXPECT_THROW(parser->commit(), D2CfgError);
+}
+
+/// @brief Verifies a valid list of TSIG Keys parses correctly.
+TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
+ // Construct a valid list of keys.
+ std::string config = "["
+
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"algo1\" ,"
+ " \"secret\": \"secret1\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"algo2\" ,"
+ " \"secret\": \"secret2\" "
+ " },"
+ " { \"name\": \"key3\" , "
+ " \"algorithm\": \"algo3\" ,"
+ " \"secret\": \"secret3\" "
+ " }"
+ " ]";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the list builds and commits without errors.
+ // Create the list parser.
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));
+ ASSERT_NO_THROW(parser->build(config_set_));
+ ASSERT_NO_THROW(parser->commit());
+
+ // Verify the correct number of keys are present
+ int count = keys_->size();
+ ASSERT_EQ(3, count);
+
+ // Find the 1st key and retrieve it.
+ TSIGKeyInfoMap::iterator gotit = keys_->find("key1");
+ ASSERT_TRUE(gotit != keys_->end());
+ TSIGKeyInfoPtr& key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key1", "algo1", "secret1"));
+
+ // Find the 2nd key and retrieve it.
+ gotit = keys_->find("key2");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key2", "algo2", "secret2"));
+
+ // Find the 3rd key and retrieve it.
+ gotit = keys_->find("key3");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key3", "algo3", "secret3"));
+}
+
+/// @brief Tests the enforcement of data validation when parsing DnsServerInfos.
+/// It verifies that:
+/// 1. Specifying both a hostname and an ip address is not allowed.
+/// 2. Specifying both blank a hostname and blank ip address is not allowed.
+/// 3. Specifying a negative port number is not allowed.
+TEST_F(DnsServerInfoTest, invalidEntry) {
+ // Create a config in which both host and ip address are supplied.
+ // Verify that it builds without throwing but commit fails.
+ std::string config = "{ \"hostname\": \"pegasus.tmark\", "
+ " \"ip_address\": \"127.0.0.1\" } ";
+ ASSERT_TRUE(fromJSON(config));
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+
+ // Neither host nor ip address supplied
+ // Verify that it builds without throwing but commit fails.
+ config = "{ \"hostname\": \"\", "
+ " \"ip_address\": \"\" } ";
+ ASSERT_TRUE(fromJSON(config));
+ EXPECT_NO_THROW(parser_->build(config_set_));
+ EXPECT_THROW(parser_->commit(), D2CfgError);
+
+ // Create a config with a negative port number.
+ // Verify that build fails.
+ config = "{ \"ip_address\": \"192.168.5.6\" ,"
+ " \"port\": -100 }";
+ ASSERT_TRUE(fromJSON(config));
+ EXPECT_THROW (parser_->build(config_set_), isc::BadValue);
+}
+
+
+/// @brief Verifies that DnsServerInfo parsing creates a proper DnsServerInfo
+/// when given a valid combination of entries.
+/// It verifies that:
+/// 1. A DnsServerInfo entry is correctly made, when given only a hostname.
+/// 2. A DnsServerInfo entry is correctly made, when given ip address and port.
+/// 3. A DnsServerInfo entry is correctly made, when given only an ip address.
+TEST_F(DnsServerInfoTest, validEntry) {
+ // Valid entries for dynamic host
+ std::string config = "{ \"hostname\": \"pegasus.tmark\" }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that it builds and commits without throwing.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_NO_THROW(parser_->commit());
+
+ // Verify the correct number of servers are present
+ int count = servers_->size();
+ EXPECT_EQ(1, count);
+
+ // Verify the server exists and has the correct values.
+ DnsServerInfoPtr server = (*servers_)[0];
+ EXPECT_TRUE(checkServer(server, "pegasus.tmark",
+ DnsServerInfo::EMPTY_IP_STR,
+ DnsServerInfo::STANDARD_DNS_PORT));
+
+ // Start over for a new test.
+ reset();
+
+ // Valid entries for static ip
+ config = " { \"ip_address\": \"127.0.0.1\" , "
+ " \"port\": 100 }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that it builds and commits without throwing.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_NO_THROW(parser_->commit());
+
+ // Verify the correct number of servers are present
+ count = servers_->size();
+ EXPECT_EQ(1, count);
+
+ // Verify the server exists and has the correct values.
+ server = (*servers_)[0];
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+
+ // Start over for a new test.
+ reset();
+
+ // Valid entries for static ip, no port
+ config = " { \"ip_address\": \"192.168.2.5\" }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that it builds and commits without throwing.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_NO_THROW(parser_->commit());
+
+ // Verify the correct number of servers are present
+ count = servers_->size();
+ EXPECT_EQ(1, count);
+
+ // Verify the server exists and has the correct values.
+ server = (*servers_)[0];
+ EXPECT_TRUE(checkServer(server, "", "192.168.2.5",
+ DnsServerInfo::STANDARD_DNS_PORT));
+}
+
+/// @brief Verifies that attempting to parse an invalid list of DnsServerInfo
+/// entries is detected.
+TEST_F(ConfigParseTest, invalidServerList) {
+ // Construct a list of servers with an invalid server entry.
+ std::string config = "[ { \"hostname\": \"one.tmark\" }, "
+ "{ \"hostname\": \"\" }, "
+ "{ \"hostname\": \"three.tmark\" } ]";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the server storage and list parser.
+ DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new DnsServerInfoListParser("test", servers)));
+
+ // Verify that the list builds without errors.
+ ASSERT_NO_THROW(parser->build(config_set_));
+
+ // Verify that the list commit fails.
+ EXPECT_THROW(parser->commit(), D2CfgError);
+}
+
+/// @brief Verifies that a list of DnsServerInfo entries parses correctly given
+/// a valid configuration.
+TEST_F(ConfigParseTest, validServerList) {
+ // Create a valid list of servers.
+ std::string config = "[ { \"hostname\": \"one.tmark\" }, "
+ "{ \"hostname\": \"two.tmark\" }, "
+ "{ \"hostname\": \"three.tmark\" } ]";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the server storage and list parser.
+ DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+ isc::dhcp::ParserPtr parser;
+ ASSERT_NO_THROW(parser.reset(new DnsServerInfoListParser("test", servers)));
+
+ // Verfiy that the list builds and commits without error.
+ ASSERT_NO_THROW(parser->build(config_set_));
+ ASSERT_NO_THROW(parser->commit());
+
+ // Verify that the server storage contains the correct number of servers.
+ int count = servers->size();
+ EXPECT_EQ(3, count);
+
+ // Verify the first server exists and has the correct values.
+ DnsServerInfoPtr server = (*servers)[0];
+ EXPECT_TRUE(checkServer(server, "one.tmark", DnsServerInfo::EMPTY_IP_STR,
+ DnsServerInfo::STANDARD_DNS_PORT));
+
+ // Verify the second server exists and has the correct values.
+ server = (*servers)[1];
+ EXPECT_TRUE(checkServer(server, "two.tmark", DnsServerInfo::EMPTY_IP_STR,
+ DnsServerInfo::STANDARD_DNS_PORT));
+
+ // Verify the third server exists and has the correct values.
+ server = (*servers)[2];
+ EXPECT_TRUE(checkServer(server, "three.tmark", DnsServerInfo::EMPTY_IP_STR,
+ DnsServerInfo::STANDARD_DNS_PORT));
+}
+
+/// @brief Tests the enforcement of data validation when parsing DdnsDomains.
+/// It verifies that:
+/// 1. Domain storage cannot be null when constructing a DdnsDomainParser.
+/// 2. The name entry is not optional.
+/// 3. The server list man not be empty.
+/// 4. That a mal-formed server entry is detected.
+/// 5. That an undefined key name is detected.
+TEST_F(DdnsDomainTest, invalidDdnsDomainEntry) {
+ // Verify that attempting to construct the parser with null storage fails.
+ DdnsDomainMapPtr domains;
+ ASSERT_THROW(isc::dhcp::ParserPtr(
+ new DdnsDomainParser("test", domains, keys_)), D2CfgError);
+
+ // Create a domain configuration without a name
+ std::string config = "{ \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" , "
+ " \"port\": 100 },"
+ " { \"ip_address\": \"127.0.0.2\" , "
+ " \"port\": 200 },"
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the domain configuration builds but commit fails.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_THROW(parser_->commit(), isc::dhcp::DhcpConfigError);
+
+ // Create a domain configuration with an empty server list.
+ config = "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the domain configuration build fails.
+ ASSERT_THROW(parser_->build(config_set_), D2CfgError);
+
+ // Create a domain configuration with a mal-formed server entry.
+ config = "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": -1 } ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the domain configuration build fails.
+ ASSERT_THROW(parser_->build(config_set_), isc::BadValue);
+
+ // Create a domain configuration without an defined key name
+ config = "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the domain configuration build succeeds but commit fails.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_THROW(parser_->commit(), D2CfgError);
+}
+
+/// @brief Verifies the basics of parsing DdnsDomains.
+/// It verifies that:
+/// 1. Valid construction of DdnsDomainParser functions.
+/// 2. Given a valid, configuration entry, DdnsDomainParser parses
+/// correctly.
+/// (It indirectly verifies the operation of DdnsDomainMap).
+TEST_F(DdnsDomainTest, ddnsDomainParsing) {
+ // Create a valid domain configuration entry containing three valid
+ // servers.
+ std::string config =
+ "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" , "
+ " \"port\": 100 },"
+ " { \"ip_address\": \"127.0.0.2\" , "
+ " \"port\": 200 },"
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Add a TSIG key to the test key map, so key validation will pass.
+ addKey("d2_key.tmark.org", "md5", "0123456789");
+
+ // Verify that the domain configuration builds and commits without error.
+ ASSERT_NO_THROW(parser_->build(config_set_));
+ ASSERT_NO_THROW(parser_->commit());
+
+ // Verify that the domain storage contains the correct number of domains.
+ int count = domains_->size();
+ EXPECT_EQ(1, count);
+
+ // Verify that the expected domain exists and can be retrieved from
+ // the storage.
+ DdnsDomainMap::iterator gotit = domains_->find("tmark.org");
+ ASSERT_TRUE(gotit != domains_->end());
+ DdnsDomainPtr& domain = gotit->second;
+
+ // Verify the name and key_name values.
+ EXPECT_EQ("tmark.org", domain->getName());
+ EXPECT_EQ("d2_key.tmark.org", domain->getKeyName());
+
+ // Verify that the server list exists and contains the correct number of
+ // servers.
+ const DnsServerInfoStoragePtr& servers = domain->getServers();
+ EXPECT_TRUE(servers);
+ count = servers->size();
+ EXPECT_EQ(3, count);
+
+ // Fetch each server and verify its contents.
+ DnsServerInfoPtr server = (*servers)[0];
+ EXPECT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+
+ server = (*servers)[1];
+ EXPECT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));
+
+ server = (*servers)[2];
+ EXPECT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));
+}
+
+/// @brief Tests the fundamentals of parsing DdnsDomain lists.
+/// This test verifies that given a valid domain list configuration
+/// it will accurately parse and populate each domain in the list.
+TEST_F(DdnsDomainTest, DdnsDomainListParsing) {
+ // Create a valid domain list configuration, with two domains
+ // that have three servers each.
+ std::string config =
+ "[ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" , "
+ " \"port\": 100 },"
+ " { \"ip_address\": \"127.0.0.2\" , "
+ " \"port\": 200 },"
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ ", "
+ "{ \"name\": \"billcat.net\" , "
+ " \"key_name\": \"d2_key.billcat.net\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.4\" , "
+ " \"port\": 400 },"
+ " { \"ip_address\": \"127.0.0.5\" , "
+ " \"port\": 500 },"
+ " { \"ip_address\": \"127.0.0.6\" , "
+ " \"port\": 600 } ] } "
+ "] ";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Add keys to key map so key validation passes.
+ addKey("d2_key.tmark.org", "algo1", "secret1");
+ addKey("d2_key.billcat.net", "algo2", "secret2");
+
+ // Create the list parser
+ isc::dhcp::ParserPtr list_parser;
+ ASSERT_NO_THROW(list_parser.reset(
+ new DdnsDomainListParser("test", domains_, keys_)));
+
+ // Verify that the domain configuration builds and commits without error.
+ ASSERT_NO_THROW(list_parser->build(config_set_));
+ ASSERT_NO_THROW(list_parser->commit());
+
+ // Verify that the domain storage contains the correct number of domains.
+ int count = domains_->size();
+ EXPECT_EQ(2, count);
+
+ // Verify that the first domain exists and can be retrieved.
+ DdnsDomainMap::iterator gotit = domains_->find("tmark.org");
+ ASSERT_TRUE(gotit != domains_->end());
+ DdnsDomainPtr& domain = gotit->second;
+
+ // Verify the name and key_name values of the first domain.
+ EXPECT_EQ("tmark.org", domain->getName());
+ EXPECT_EQ("d2_key.tmark.org", domain->getKeyName());
+
+ // Verify the each of the first domain's servers
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ EXPECT_TRUE(servers);
+ count = servers->size();
+ EXPECT_EQ(3, count);
+
+ DnsServerInfoPtr server = (*servers)[0];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+
+ server = (*servers)[1];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));
+
+ server = (*servers)[2];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));
+
+ // Verify second domain
+ gotit = domains_->find("billcat.net");
+ ASSERT_TRUE(gotit != domains_->end());
+ domain = gotit->second;
+
+ // Verify the name and key_name values of the second domain.
+ EXPECT_EQ("billcat.net", domain->getName());
+ EXPECT_EQ("d2_key.billcat.net", domain->getKeyName());
+
+ // Verify the each of second domain's servers
+ servers = domain->getServers();
+ EXPECT_TRUE(servers);
+ count = servers->size();
+ EXPECT_EQ(3, count);
+
+ server = (*servers)[0];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.4", 400));
+
+ server = (*servers)[1];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.5", 500));
+
+ server = (*servers)[2];
+ EXPECT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.6", 600));
+}
+
+/// @brief Tests that a domain list configuration cannot contain duplicates.
+TEST_F(DdnsDomainTest, duplicateDomain) {
+ // Create a domain list configuration that contains two domains with
+ // the same name.
+ std::string config =
+ "[ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ ", "
+ "{ \"name\": \"tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ "] ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Create the list parser
+ isc::dhcp::ParserPtr list_parser;
+ ASSERT_NO_THROW(list_parser.reset(
+ new DdnsDomainListParser("test", domains_, keys_)));
+
+ // Verify that the parse build succeeds but the commit fails.
+ ASSERT_NO_THROW(list_parser->build(config_set_));
+ ASSERT_THROW(list_parser->commit(), D2CfgError);
+}
+
+/// @brief Tests construction of D2CfgMgr
+/// This test verifies that a D2CfgMgr constructs properly.
+TEST(D2CfgMgr, construction) {
+ D2CfgMgr *cfg_mgr = NULL;
+
+ // Verify that configuration manager constructions without error.
+ ASSERT_NO_THROW(cfg_mgr = new D2CfgMgr());
+
+ // Verify that the context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr->getD2CfgContext());
+ EXPECT_TRUE(context);
+
+ // Verify that the forward manager can be retrieved and is not null.
+ EXPECT_TRUE(context->getForwardMgr());
+
+ // Verify that the reverse manager can be retrieved and is not null.
+ EXPECT_TRUE(context->getReverseMgr());
+
+ // Verify that the manager can be destructed without error.
+ EXPECT_NO_THROW(delete cfg_mgr);
+}
+
+/// @brief Tests the parsing of a complete, valid DHCP-DDNS configuration.
+/// This tests passes the configuration into an instance of D2CfgMgr just
+/// as it would be done by d2_process in response to a configuration update
+/// event.
+TEST_F(D2CfgMgrTest, fullConfig) {
+ // Create a configuration with all of application level parameters, plus
+ // both the forward and reverse ddns managers. Both managers have two
+ // domains with three servers per domain.
+ std::string config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": ["
+ "{"
+ " \"name\": \"d2_key.tmark.org\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"ssh-dont-tell\" "
+ "},"
+ "{"
+ " \"name\": \"d2_key.billcat.net\" , "
+ " \"algorithm\": \"md5\" , "
+ " \"secret\": \"ollie-ollie-in-free\" "
+ "}"
+ "],"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"one.tmark\" } , "
+ " { \"hostname\": \"two.tmark\" } , "
+ " { \"hostname\": \"three.tmark\"} "
+ " ] } "
+ ", "
+ "{ \"name\": \"billcat.net\" , "
+ " \"key_name\": \"d2_key.billcat.net\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"four.billcat\" } , "
+ " { \"hostname\": \"five.billcat\" } , "
+ " { \"hostname\": \"six.billcat\" } "
+ " ] } "
+ "] },"
+ "\"reverse_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"one.rev\" } , "
+ " { \"hostname\": \"two.rev\" } , "
+ " { \"hostname\": \"three.rev\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \" 0.247.106.in.addr.arpa.\" , "
+ " \"key_name\": \"d2_key.billcat.net\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"four.rev\" }, "
+ " { \"hostname\": \"five.rev\" } , "
+ " { \"hostname\": \"six.rev\" } "
+ " ] } "
+ "] } }";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse the configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ // Verify that the application level scalars have the proper values.
+ std::string interface;
+ EXPECT_NO_THROW (context->getParam("interface", interface));
+ EXPECT_EQ("eth1", interface);
+
+ std::string ip_address;
+ EXPECT_NO_THROW (context->getParam("ip_address", ip_address));
+ EXPECT_EQ("192.168.1.33", ip_address);
+
+ uint32_t port = 0;
+ EXPECT_NO_THROW (context->getParam("port", port));
+ EXPECT_EQ(88, port);
+
+ // Verify that the forward manager can be retrieved.
+ DdnsDomainListMgrPtr mgr = context->getForwardMgr();
+ ASSERT_TRUE(mgr);
+
+ // Verify that the forward manager has the correct number of domains.
+ DdnsDomainMapPtr domains = mgr->getDomains();
+ ASSERT_TRUE(domains);
+ int count = domains->size();
+ EXPECT_EQ(2, count);
+
+ // Verify that the server count in each of the forward manager domains.
+ // NOTE that since prior tests have validated server parsing, we are are
+ // assuming that the servers did in fact parse correctly if the correct
+ // number of them are there.
+ DdnsDomainMapPair domain_pair;
+ BOOST_FOREACH(domain_pair, (*domains)) {
+ DdnsDomainPtr domain = domain_pair.second;
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ count = servers->size();
+ EXPECT_TRUE(servers);
+ EXPECT_EQ(3, count);
+ }
+
+ // Verify that the reverse manager can be retrieved.
+ mgr = context->getReverseMgr();
+ ASSERT_TRUE(mgr);
+
+ // Verify that the reverse manager has the correct number of domains.
+ domains = mgr->getDomains();
+ count = domains->size();
+ EXPECT_EQ(2, count);
+
+ // Verify that the server count in each of the reverse manager domains.
+ // NOTE that since prior tests have validated server parsing, we are are
+ // assuming that the servers did in fact parse correctly if the correct
+ // number of them are there.
+ BOOST_FOREACH(domain_pair, (*domains)) {
+ DdnsDomainPtr domain = domain_pair.second;
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ count = servers->size();
+ EXPECT_TRUE(servers);
+ EXPECT_EQ(3, count);
+ }
+
+ // Verify that parsing the exact same configuration a second time
+ // does not cause a duplicate value errors.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+}
+
+/// @brief Tests the basics of the D2CfgMgr FQDN-domain matching
+/// This test uses a valid configuration to exercise the D2CfgMgr
+/// forward FQDN-to-domain matching.
+/// It verifies that:
+/// 1. Given an FQDN which exactly matches a domain's name, that domain is
+/// returned as match.
+/// 2. Given a FQDN for sub-domain in the list, returns the proper match.
+/// 3. Given a FQDN that matches no domain name, returns the wild card domain
+/// as a match.
+TEST_F(D2CfgMgrTest, forwardMatch) {
+ // Create configuration with one domain, one sub domain, and the wild
+ // card.
+ std::string config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": [] ,"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \"one.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.2\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \"*\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"global.net\" } "
+ " ] } "
+ "] }, "
+ "\"reverse_ddns\" : {} "
+ "}";
+
+
+ ASSERT_TRUE(fromJSON(config));
+ // Verify that we can parse the configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ DdnsDomainPtr match;
+ // Verify that an exact match works.
+ EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
+ EXPECT_EQ("tmark.org", match->getName());
+
+ // Verify that an exact match works.
+ EXPECT_TRUE(cfg_mgr_->matchForward("one.tmark.org", match));
+ EXPECT_EQ("one.tmark.org", match->getName());
+
+ // Verify that a FQDN for sub-domain matches.
+ EXPECT_TRUE(cfg_mgr_->matchForward("blue.tmark.org", match));
+ EXPECT_EQ("tmark.org", match->getName());
+
+ // Verify that a FQDN for sub-domain matches.
+ EXPECT_TRUE(cfg_mgr_->matchForward("red.one.tmark.org", match));
+ EXPECT_EQ("one.tmark.org", match->getName());
+
+ // Verify that an FQDN with no match, returns the wild card domain.
+ EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify that an attempt to match an empty FQDN throws.
+ ASSERT_THROW(cfg_mgr_->matchForward("", match), D2CfgError);
+}
+
+/// @brief Tests domain matching when there is no wild card domain.
+/// This test verifies that matches are found only for FQDNs that match
+/// some or all of a domain name. FQDNs without matches should not return
+/// a match.
+TEST_F(D2CfgMgrTest, matchNoWildcard) {
+ // Create a configuration with one domain, one sub-domain, and NO wild card.
+ std::string config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": [] ,"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \"one.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.2\" } "
+ " ] } "
+ "] }, "
+ "\"reverse_ddns\" : {} "
+ " }";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse the configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ DdnsDomainPtr match;
+ // Verify that full or partial matches, still match.
+ EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
+ EXPECT_EQ("tmark.org", match->getName());
+
+ EXPECT_TRUE(cfg_mgr_->matchForward("blue.tmark.org", match));
+ EXPECT_EQ("tmark.org", match->getName());
+
+ EXPECT_TRUE(cfg_mgr_->matchForward("red.one.tmark.org", match));
+ EXPECT_EQ("one.tmark.org", match->getName());
+
+ // Verify that a FQDN with no match, fails to match.
+ EXPECT_FALSE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+}
+
+/// @brief Tests domain matching when there is ONLY a wild card domain.
+/// This test verifies that any FQDN matches the wild card.
+TEST_F(D2CfgMgrTest, matchAll) {
+ std::string config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": [] ,"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"*\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] } "
+ "] }, "
+ "\"reverse_ddns\" : {} "
+ "}";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse the configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ // Verify that wild card domain is returned for any FQDN.
+ DdnsDomainPtr match;
+ EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
+ EXPECT_EQ("*", match->getName());
+ EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify that an attempt to match an empty FQDN still throws.
+ ASSERT_THROW(cfg_mgr_->matchReverse("", match), D2CfgError);
+
+}
+
+/// @brief Tests the basics of the D2CfgMgr reverse FQDN-domain matching
+/// This test uses a valid configuration to exercise the D2CfgMgr's
+/// reverse FQDN-to-domain matching.
+/// It verifies that:
+/// 1. Given an FQDN which exactly matches a domain's name, that domain is
+/// returned as match.
+/// 2. Given a FQDN for sub-domain in the list, returns the proper match.
+/// 3. Given a FQDN that matches no domain name, returns the wild card domain
+/// as a match.
+TEST_F(D2CfgMgrTest, matchReverse) {
+ std::string config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": [] ,"
+ "\"forward_ddns\" : {}, "
+ "\"reverse_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"5.100.168.192.in-addr.arpa.\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] }, "
+ "{ \"name\": \"100.200.192.in-addr.arpa.\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] }, "
+ "{ \"name\": \"170.192.in-addr.arpa.\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] }, "
+ "{ \"name\": \"2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] },"
+ "{ \"name\": \"*\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] } "
+ "] } }";
+
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse the configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ DdnsDomainPtr match;
+
+ // Verify an exact match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("192.168.100.5", match));
+ EXPECT_EQ("5.100.168.192.in-addr.arpa.", match->getName());
+
+ // Verify a sub-domain match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("192.200.100.27", match));
+ EXPECT_EQ("100.200.192.in-addr.arpa.", match->getName());
+
+ // Verify a sub-domain match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("192.170.50.30", match));
+ EXPECT_EQ("170.192.in-addr.arpa.", match->getName());
+
+ // Verify a wild card match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("1.1.1.1", match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify a IPv6 match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("2001:db8:302:99::",match));
+ EXPECT_EQ("2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.", match->getName());
+
+ // Verify a IPv6 wild card match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("2001:db8:99:302::",match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify that an attempt to match an invalid IP address throws.
+ ASSERT_THROW(cfg_mgr_->matchReverse("", match), D2CfgError);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d2_controller_unittests.cc b/src/bin/d2/tests/d2_controller_unittests.cc
new file mode 100644
index 0000000..f4b4d41
--- /dev/null
+++ b/src/bin/d2/tests/d2_controller_unittests.cc
@@ -0,0 +1,225 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <d_test_stubs.h>
+#include <d2/d2_controller.h>
+#include <d2/spec_config.h>
+
+#include <boost/pointer_cast.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <config.h>
+#include <sstream>
+
+using namespace boost::posix_time;
+
+namespace isc {
+namespace d2 {
+
+/// @brief Test fixture class for testing D2Controller class. This class
+/// derives from DControllerTest and wraps a D2Controller. Much of the
+/// underlying functionality is in the DControllerBase class which has an
+/// extensive set of unit tests that are independent of DHCP-DDNS.
+/// @TODO Currently These tests are relatively light and duplicate some of
+/// the testing done on the base class. These tests are sufficient to ensure
+/// that D2Controller properly derives from its base class and to test the
+/// logic that is unique to D2Controller. These tests will be augmented and
+/// or new tests added as additional functionality evolves.
+/// Unlike the stub testing, there is no use of SimFailure to induce error
+/// conditions as this is production code.
+class D2ControllerTest : public DControllerTest {
+public:
+ /// @brief Constructor
+ /// Note the constructor passes in the static D2Controller instance
+ /// method.
+ D2ControllerTest() : DControllerTest(D2Controller::instance) {
+ }
+
+ /// @brief Destructor
+ ~D2ControllerTest() {
+ }
+};
+
+/// @brief Basic Controller instantiation testing.
+/// Verifies that the controller singleton gets created and that the
+/// basic derivation from the base class is intact.
+TEST_F(D2ControllerTest, basicInstanceTesting) {
+ // Verify the we can the singleton instance can be fetched and that
+ // it is the correct type.
+ DControllerBasePtr& controller = DControllerTest::getController();
+ ASSERT_TRUE(controller);
+ ASSERT_NO_THROW(boost::dynamic_pointer_cast<D2Controller>(controller));
+
+ // Verify that controller's app name is correct.
+ EXPECT_TRUE(checkAppName(D2Controller::d2_app_name_));
+
+ // Verify that controller's bin name is correct.
+ EXPECT_TRUE(checkBinName(D2Controller::d2_bin_name_));
+
+ // Verify that controller's spec file name is correct.
+ EXPECT_TRUE(checkSpecFileName(D2_SPECFILE_LOCATION));
+
+ // Verify that controller's IOService exists.
+ EXPECT_TRUE(checkIOService());
+
+ // Verify that the Process does NOT exist.
+ EXPECT_FALSE(checkProcess());
+}
+
+/// @brief Tests basic command line processing.
+/// Verifies that:
+/// 1. Standard command line options are supported.
+/// 2. Invalid options are detected.
+TEST_F(D2ControllerTest, commandLineArgs) {
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-s"),
+ const_cast<char*>("-v") };
+ int argc = 3;
+
+ // Verify that both flags are false initially.
+ EXPECT_TRUE(checkStandAlone(false));
+ EXPECT_TRUE(checkVerbose(false));
+
+ // Verify that standard options can be parsed without error.
+ EXPECT_NO_THROW(parseArgs(argc, argv));
+
+ // Verify that flags are now true.
+ EXPECT_TRUE(checkStandAlone(true));
+ EXPECT_TRUE(checkVerbose(true));
+
+ // Verify that an unknown option is detected.
+ char* argv2[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-x") };
+ argc = 2;
+ EXPECT_THROW(parseArgs(argc, argv2), InvalidUsage);
+}
+
+/// @brief Tests application process creation and initialization.
+/// Verifies that the process can be successfully created and initialized.
+TEST_F(D2ControllerTest, initProcessTesting) {
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+}
+
+/// @brief Tests launch and normal shutdown (stand alone mode).
+/// This creates an interval timer to generate a normal shutdown and then
+/// launches with a valid, stand-alone command line and no simulated errors.
+TEST_F(D2ControllerTest, launchNormalShutdown) {
+ // command line to run standalone
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-s"),
+ const_cast<char*>("-v") };
+ int argc = 3;
+
+ // Use an asiolink IntervalTimer and callback to generate the
+ // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIOService());
+ timer.setup(genShutdownCallback, 2 * 1000);
+
+ // Record start time, and invoke launch().
+ ptime start = microsec_clock::universal_time();
+ EXPECT_NO_THROW(launch(argc, argv));
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2100);
+}
+
+/// @brief Configuration update event testing.
+/// This really tests just the ability of the handlers to invoke the necessary
+/// chain of methods and handle error conditions. Configuration parsing and
+/// retrieval should be tested as part of the d2 configuration management
+/// implementation. Note that this testing calls the configuration update event
+/// callback, configHandler, directly.
+/// This test verifies that:
+/// 1. Configuration will be rejected in integrated mode when there is no
+/// session established. (This is a very contrived situation).
+/// 2. In stand-alone mode a configuration update results in successful
+/// status return.
+/// 3. That an application process error in configuration updating is handled
+/// properly.
+TEST_F(D2ControllerTest, configUpdateTests) {
+ int rcode = -1;
+ isc::data::ConstElementPtr answer;
+
+ // Initialize the application process.
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ // Create a configuration set using a small, valid D2 configuration.
+ isc::data::ElementPtr config_set =
+ isc::data::Element::fromJSON(valid_d2_config);
+
+ // We are not stand-alone, so configuration should be rejected as there is
+ // no session. This is a pretty contrived situation that shouldn't be
+ // possible other than the handler being called directly (like this does).
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+
+ // Verify that in stand alone we get a successful update result.
+ setStandAlone(true);
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode);
+
+ // Use an invalid configuration to verify parsing error return.
+ std::string config = "{ \"bogus\": 1000 } ";
+ config_set = isc::data::Element::fromJSON(config);
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+}
+
+/// @brief Command execution tests.
+/// This really tests just the ability of the handler to invoke the necessary
+/// chain of methods and to handle error conditions. Note that this testing
+/// calls the command callback, commandHandler, directly.
+/// This test verifies that:
+/// 1. That an unrecognized command is detected and returns a status of
+/// d2::COMMAND_INVALID.
+/// 2. Shutdown command is recognized and returns a d2::COMMAND_SUCCESS status.
+TEST_F(D2ControllerTest, executeCommandTests) {
+ int rcode = -1;
+ isc::data::ConstElementPtr answer;
+ isc::data::ElementPtr arg_set;
+
+ // Initialize the application process.
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ // Verify that an unknown command returns an COMMAND_INVALID response.
+ std::string bogus_command("bogus");
+ answer = DControllerBase::commandHandler(bogus_command, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_INVALID, rcode);
+
+ // Verify that shutdown command returns COMMAND_SUCCESS response.
+ //answer = executeCommand(SHUT_DOWN_COMMAND, isc::data::ElementPtr());
+ answer = DControllerBase::commandHandler(SHUT_DOWN_COMMAND, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_SUCCESS, rcode);
+
+}
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
diff --git a/src/bin/d2/tests/d2_process_unittests.cc b/src/bin/d2/tests/d2_process_unittests.cc
new file mode 100644
index 0000000..cbbdd3f
--- /dev/null
+++ b/src/bin/d2/tests/d2_process_unittests.cc
@@ -0,0 +1,605 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+
+#include <config/ccsession.h>
+#include <d2/d2_process.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <d_test_stubs.h>
+
+#include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <config.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::config;
+using namespace isc::d2;
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief Valid configuration containing an unavailable IP address.
+const char* bad_ip_d2_config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"1.1.1.1\" , "
+ "\"port\" : 5031, "
+ "\"tsig_keys\": ["
+ "{ \"name\": \"d2_key.tmark.org\" , "
+ " \"algorithm\": \"md5\" ,"
+ " \"secret\": \"0123456989\" "
+ "} ],"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"one.tmark\" } "
+ "] } ] }, "
+ "\"reverse_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.101\" , "
+ " \"port\": 100 } ] } "
+ "] } }";
+
+/// @brief D2Process test fixture class
+//class D2ProcessTest : public D2Process, public ::testing::Test {
+class D2ProcessTest : public D2Process, public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ D2ProcessTest() : D2Process("d2test",
+ IOServicePtr(new isc::asiolink::IOService())) {
+ }
+
+ /// @brief Destructor
+ virtual ~D2ProcessTest() {
+ }
+
+ /// @brief Callback that will invoke shutdown method.
+ void genShutdownCallback() {
+ shutdown(isc::data::ConstElementPtr());
+ }
+
+ /// @brief Callback that throws an exception.
+ void genFatalErrorCallback() {
+ isc_throw (DProcessBaseError, "simulated fatal error");
+ }
+
+ /// @brief Reconfigures and starts the queue manager given a configuration.
+ ///
+ /// This method emulates the reception of a new configuration and should
+ /// conclude with the Queue manager placed in the RUNNING state.
+ ///
+ /// @param config is the configuration to use
+ ///
+ /// @return Returns AssertionSuccess if the queue manager was successfully
+ /// reconfigured, AssertionFailure otherwise.
+ ::testing::AssertionResult runWithConfig(const char* config) {
+ int rcode = -1;
+ // Convert the string configuration into an Element set.
+ ::testing::AssertionResult res = fromJSON(config);
+ if (res != ::testing::AssertionSuccess()) {
+ return res;
+ }
+
+ isc::data::ConstElementPtr answer = configure(config_set_);
+ isc::data::ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer);
+
+ if (rcode) {
+ return (::testing::AssertionFailure(::testing::Message() <<
+ "configure() failed:"
+ << comment));
+ }
+
+ // Must call checkQueueStatus, to cause queue manager to reconfigure
+ // and start.
+ checkQueueStatus();
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ // If queue manager isn't in the RUNNING state, return failure.
+ if (D2QueueMgr::RUNNING != queue_mgr->getMgrState()) {
+ return (::testing::AssertionFailure(::testing::Message() <<
+ "queue manager did not start"));
+ }
+
+ // Good to go.
+ return (::testing::AssertionSuccess());
+ }
+
+ /// @brief Checks if shutdown criteria would be met given a shutdown type.
+ ///
+ /// This method sets the D2Process shutdown type to the given value, and
+ /// calls the canShutdown() method, returning its return value.
+ ///
+ /// @return Returns the boolean result canShutdown.
+ bool checkCanShutdown(ShutdownType shutdown_type) {
+ setShutdownType(shutdown_type);
+ return (canShutdown());
+ }
+};
+
+/// @brief Verifies D2Process construction behavior.
+/// 1. Verifies that constructor fails with an invalid IOService
+/// 2. Verifies that constructor succeeds with a valid IOService
+/// 3. Verifies that all managers are accessible
+TEST(D2Process, construction) {
+ // Verify that the constructor will fail if given an empty
+ // io service.
+ IOServicePtr lcl_io_service;
+ EXPECT_THROW (D2Process("TestProcess", lcl_io_service), DProcessBaseError);
+
+ // Verify that the constructor succeeds with a valid io_service
+ lcl_io_service.reset(new isc::asiolink::IOService());
+ ASSERT_NO_THROW (D2Process("TestProcess", lcl_io_service));
+
+ // Verify that the configuration, queue, and update managers
+ // are all accessible after construction.
+ D2Process d2process("TestProcess", lcl_io_service);
+
+ D2CfgMgrPtr cfg_mgr = d2process.getD2CfgMgr();
+ ASSERT_TRUE(cfg_mgr);
+
+ D2QueueMgrPtr queue_mgr = d2process.getD2QueueMgr();
+ ASSERT_TRUE(queue_mgr);
+
+ const D2UpdateMgrPtr& update_mgr = d2process.getD2UpdateMgr();
+ ASSERT_TRUE(update_mgr);
+}
+
+/// @brief Verifies basic configure method behavior.
+/// This test primarily verifies that upon receipt of a new configuration,
+/// D2Process will reconfigure the queue manager if the configuration is valid,
+/// or leave queue manager unaffected if not. Currently, the queue manager is
+/// only D2 component that must adapt to new configurations. Other components,
+/// such as Transactions will be unaffected as they are transient and use
+/// whatever configuration was in play at the time they were created.
+/// If other components need to provide "dynamic" configuration responses,
+/// those tests would need to be added.
+TEST_F(D2ProcessTest, configure) {
+ // Verify the queue manager is not yet initialized.
+ D2QueueMgrPtr queue_mgr = getD2QueueMgr();
+ ASSERT_TRUE(queue_mgr);
+ ASSERT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr->getMgrState());
+
+ // Verify that reconfigure queue manager flag is false.
+ ASSERT_FALSE(getReconfQueueFlag());
+
+ // Create a valid configuration set from text config.
+ ASSERT_TRUE(fromJSON(valid_d2_config));
+
+ // Invoke configure() with a valid D2 configuration.
+ isc::data::ConstElementPtr answer = configure(config_set_);
+
+ // Verify that configure result is success and reconfigure queue manager
+ // flag is true.
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ ASSERT_TRUE(getReconfQueueFlag());
+
+ // Call checkQueueStatus, to cause queue manager to reconfigure and start.
+ checkQueueStatus();
+
+ // Verify that queue manager is now in the RUNNING state, and flag is false.
+ ASSERT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+ ASSERT_FALSE(getReconfQueueFlag());
+
+ // Create an invalid configuration set from text config.
+ ASSERT_TRUE(fromJSON("{ \"bogus\": 1000 } "));
+
+ // Invoke configure() with the invalid configuration.
+ answer = configure(config_set_);
+
+ // Verify that configure result is failure, the reconfigure flag is
+ // false, and that the queue manager is still running.
+ ASSERT_TRUE(checkAnswer(answer, 1));
+ EXPECT_FALSE(getReconfQueueFlag());
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+}
+
+/// @brief Tests checkQueueStatus() logic for stopping the queue on shutdown
+/// This test manually sets shutdown flag and verifies that queue manager
+/// stop is initiated.
+TEST_F(D2ProcessTest, queueStopOnShutdown) {
+ ASSERT_TRUE(runWithConfig(valid_d2_config));
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ setShutdownFlag(true);
+
+ // Calling checkQueueStatus restart queue manager
+ checkQueueStatus();
+
+ // Verify that the queue manager is stopping.
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Verify that a subsequent call with no events occurring in between,
+ // results in no change to queue manager
+ checkQueueStatus();
+
+ // Verify that the queue manager is still stopping.
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Call runIO so the IO cancel event occurs and verify that queue manager
+ // has stopped.
+ runIO();
+ ASSERT_EQ(D2QueueMgr::STOPPED, queue_mgr->getMgrState());
+}
+
+/// @brief Tests checkQueueStatus() logic for stopping the queue on reconfigure.
+/// This test manually sets queue reconfiguration flag and verifies that queue
+/// manager stop is initiated.
+TEST_F(D2ProcessTest, queueStopOnReconf) {
+ ASSERT_TRUE(runWithConfig(valid_d2_config));
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ // Manually set the reconfigure indicator.
+ setReconfQueueFlag(true);
+
+ // Calling checkQueueStatus should initiate stopping the queue manager.
+ checkQueueStatus();
+
+ // Verify that the queue manager is stopping.
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Call runIO so the IO cancel event occurs and verify that queue manager
+ // has stopped.
+ runIO();
+ ASSERT_EQ(D2QueueMgr::STOPPED, queue_mgr->getMgrState());
+}
+
+
+/// @brief Tests checkQueueStatus() logic for recovering from queue full
+/// This test manually creates a receive queue full condition and then
+/// "drains" the queue until the queue manager resumes listening. This
+/// verifies D2Process's ability to recover from a queue full condition.
+TEST_F(D2ProcessTest, queueFullRecovery) {
+ // Valid test message, contents are unimportant.
+ const char* test_msg =
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}";
+
+ // Start queue manager with known good config.
+ ASSERT_TRUE(runWithConfig(valid_d2_config));
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ // Set the maximum queue size to manageable number.
+ size_t max_queue_size = 5;
+ queue_mgr->setMaxQueueSize(max_queue_size);
+
+ // Manually enqueue max requests.
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(test_msg));
+ for (int i = 0; i < max_queue_size; i++) {
+ // Verify that the request can be added to the queue and queue
+ // size increments accordingly.
+ ASSERT_NO_THROW(queue_mgr->enqueue(ncr));
+ ASSERT_EQ(i+1, queue_mgr->getQueueSize());
+ }
+
+ // Since we are not really receiving, we will simulate QUEUE FULL
+ // detection.
+ queue_mgr->stopListening(D2QueueMgr::STOPPED_QUEUE_FULL);
+ ASSERT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Call runIO so the IO cancel event occurs and verify that queue manager
+ // has stopped.
+ runIO();
+ ASSERT_EQ(D2QueueMgr::STOPPED_QUEUE_FULL, queue_mgr->getMgrState());
+
+ // Dequeue requests one at a time, calling checkQueueStatus after each
+ // dequeue, until we reach the resume threshold. This simulates update
+ // manager consuming jobs. Queue manager should remain stopped during
+ // this loop.
+ int resume_threshold = (max_queue_size * QUEUE_RESTART_PERCENT);
+ while (queue_mgr->getQueueSize() > resume_threshold) {
+ checkQueueStatus();
+ ASSERT_EQ(D2QueueMgr::STOPPED_QUEUE_FULL, queue_mgr->getMgrState());
+ ASSERT_NO_THROW(queue_mgr->dequeue());
+ }
+
+ // Dequeue one more, which brings us under the threshold and call
+ // checkQueueStatus.
+ // Verify that the queue manager is again running.
+ ASSERT_NO_THROW(queue_mgr->dequeue());
+ checkQueueStatus();
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+}
+
+/// @brief Tests checkQueueStatus() logic for queue receive error recovery
+/// This test manually creates a queue receive error condition and tests
+/// verifies that checkQueueStatus reacts properly to recover.
+TEST_F(D2ProcessTest, queueErrorRecovery) {
+ ASSERT_TRUE(runWithConfig(valid_d2_config));
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ // Since we are not really receiving, we have to stage an error.
+ queue_mgr->stopListening(D2QueueMgr::STOPPED_RECV_ERROR);
+ ASSERT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Call runIO so the IO cancel event occurs and verify that queue manager
+ // has stopped.
+ runIO();
+ ASSERT_EQ(D2QueueMgr::STOPPED_RECV_ERROR, queue_mgr->getMgrState());
+
+ // Calling checkQueueStatus should restart queue manager
+ checkQueueStatus();
+
+ // Verify that queue manager is again running.
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+}
+
+/// @brief Verifies queue manager recovery from unusable configuration
+/// This test checks D2Process's gracefully handle a configuration which
+/// while valid is not operationally usable (i.e. IP address is unavailable),
+/// and to subsequently recover given a usable configuration.
+TEST_F(D2ProcessTest, badConfigureRecovery) {
+ D2QueueMgrPtr queue_mgr = getD2QueueMgr();
+ ASSERT_TRUE(queue_mgr);
+
+ // Verify the queue manager is not initialized.
+ EXPECT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr->getMgrState());
+
+ // Invoke configure() with a valid config that contains an unusable IP
+ ASSERT_TRUE(fromJSON(bad_ip_d2_config));
+ isc::data::ConstElementPtr answer = configure(config_set_);
+
+ // Verify that configure result is success and reconfigure queue manager
+ // flag is true.
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ ASSERT_TRUE(getReconfQueueFlag());
+
+ // Call checkQueueStatus to cause queue manager to attempt to reconfigure.
+ checkQueueStatus();
+
+ // Verify that queue manager failed to start, (i.e. is in INITTED state),
+ // and the the reconfigure flag is false.
+ ASSERT_EQ(D2QueueMgr::INITTED, queue_mgr->getMgrState());
+ ASSERT_FALSE(getReconfQueueFlag());
+
+ // Verify we can recover given a valid config with an usable IP address.
+ ASSERT_TRUE(fromJSON(valid_d2_config));
+ answer = configure(config_set_);
+
+ // Verify that configure result is success and reconfigure queue manager
+ // flag is true.
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ ASSERT_TRUE(getReconfQueueFlag());
+
+ // Call checkQueueStatus to cause queue manager to reconfigure and start.
+ checkQueueStatus();
+
+ // Verify that queue manager is now in the RUNNING state, and reconfigure
+ // flag is false.
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+ EXPECT_FALSE(getReconfQueueFlag());
+}
+
+/// @brief Verifies basic command method behavior.
+/// @TODO IF the D2Process is extended to support extra commands this testing
+/// will need to augmented accordingly.
+TEST_F(D2ProcessTest, command) {
+ // Verify that the process will process unsupported command and
+ // return a failure response.
+ int rcode = -1;
+ string args = "{ \"arg1\": 77 } ";
+ isc::data::ElementPtr json = isc::data::Element::fromJSON(args);
+ isc::data::ConstElementPtr answer = command("bogus_command", json);
+ parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_INVALID, rcode);
+}
+
+/// @brief Tests shutdown command argument parsing
+/// The shutdown command supports an optional "type" argument. This test
+/// checks that for valid values, the shutdown() method: sets the shutdown
+/// type to correct value, set the shutdown flag to true, and returns a
+/// success response; and for invalid values: sets the shutdown flag to false
+/// and returns a failure response.
+TEST_F(D2ProcessTest, shutdownArgs) {
+ isc::data::ElementPtr args;
+ isc::data::ConstElementPtr answer;
+ const char* default_args = "{}";
+ const char* normal_args = "{ \"type\" : \"normal\" }";
+ const char* drain_args = "{ \"type\" : \"drain_first\" }";
+ const char* now_args = "{ \"type\" : \"now\" }";
+ const char* bogus_args = "{ \"type\" : \"bogus\" }";
+
+ // Verify defaulting to SD_NORMAL if no argument is given.
+ ASSERT_NO_THROW(args = isc::data::Element::fromJSON(default_args));
+ EXPECT_NO_THROW(answer = shutdown(args));
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ EXPECT_EQ(SD_NORMAL, getShutdownType());
+ EXPECT_TRUE(shouldShutdown());
+
+ // Verify argument value "normal".
+ ASSERT_NO_THROW(args = isc::data::Element::fromJSON(normal_args));
+ EXPECT_NO_THROW(answer = shutdown(args));
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ EXPECT_EQ(SD_NORMAL, getShutdownType());
+ EXPECT_TRUE(shouldShutdown());
+
+ // Verify argument value "drain_first".
+ ASSERT_NO_THROW(args = isc::data::Element::fromJSON(drain_args));
+ EXPECT_NO_THROW(answer = shutdown(args));
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ EXPECT_EQ(SD_DRAIN_FIRST, getShutdownType());
+ EXPECT_TRUE(shouldShutdown());
+
+ // Verify argument value "now".
+ ASSERT_NO_THROW(args = isc::data::Element::fromJSON(now_args));
+ EXPECT_NO_THROW(answer = shutdown(args));
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ EXPECT_EQ(SD_NOW, getShutdownType());
+ EXPECT_TRUE(shouldShutdown());
+
+ // Verify correct handling of an invalid value.
+ ASSERT_NO_THROW(args = isc::data::Element::fromJSON(bogus_args));
+ EXPECT_NO_THROW(answer = shutdown(args));
+ ASSERT_TRUE(checkAnswer(answer, 1));
+ EXPECT_FALSE(shouldShutdown());
+}
+
+/// @brief Tests shutdown criteria logic
+/// D2Process using the method canShutdown() to determine if a shutdown
+/// can be performed given the value of the shutdown flag and the type of
+/// shutdown requested. For each shutdown type certain criteria must be met
+/// before the shutdown is permitted. This method is invoked once each pass
+/// through the main event loop. This test checks the operation of the
+/// canShutdown method. It uses a convenience method, checkCanShutdown(),
+/// which sets the shutdown type to the given value and invokes canShutdown(),
+/// returning its result.
+TEST_F(D2ProcessTest, canShutdown) {
+ ASSERT_TRUE(runWithConfig(valid_d2_config));
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ // Shutdown flag is false. Method should return false for all types.
+ EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
+ EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_FALSE(checkCanShutdown(SD_NOW));
+
+ // Set shutdown flag to true.
+ setShutdownFlag(true);
+
+ // Queue Manager is running, queue is empty, no transactions.
+ // Only SD_NOW should return true.
+ EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
+ EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_TRUE(checkCanShutdown(SD_NOW));
+
+ // Tell queue manager to stop.
+ queue_mgr->stopListening();
+ // Verify that the queue manager is stopping.
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Queue Manager is stopping, queue is empty, no transactions.
+ // Only SD_NOW should return true.
+ EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
+ EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_TRUE(checkCanShutdown(SD_NOW));
+
+ // Allow cancel event to process.
+ ASSERT_NO_THROW(runIO());
+ // Verify that queue manager is stopped.
+ EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr->getMgrState());
+
+ // Queue Manager is stopped, queue is empty, no transactions.
+ // All types should return true.
+ EXPECT_TRUE(checkCanShutdown(SD_NORMAL));
+ EXPECT_TRUE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_TRUE(checkCanShutdown(SD_NOW));
+
+ const char* test_msg =
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"fish.tmark.org\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}";
+
+ // Manually enqueue a request. This lets us test logic with queue
+ // not empty.
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(test_msg));
+ ASSERT_NO_THROW(queue_mgr->enqueue(ncr));
+ ASSERT_EQ(1, queue_mgr->getQueueSize());
+
+ // Queue Manager is stopped. Queue is not empty, no transactions.
+ // SD_DRAIN_FIRST should be false, SD_NORMAL and SD_NOW should be true.
+ EXPECT_TRUE(checkCanShutdown(SD_NORMAL));
+ EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_TRUE(checkCanShutdown(SD_NOW));
+
+ // Now use update manager to dequeue the request and make a transaction.
+ // This lets us verify transaction list not empty logic.
+ const D2UpdateMgrPtr& update_mgr = getD2UpdateMgr();
+ ASSERT_TRUE(update_mgr);
+ ASSERT_NO_THROW(update_mgr->sweep());
+ ASSERT_EQ(0, queue_mgr->getQueueSize());
+ ASSERT_EQ(1, update_mgr->getTransactionCount());
+
+ // Queue Manager is stopped. Queue is empty, one transaction.
+ // Only SD_NOW should be true.
+ EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
+ EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_TRUE(checkCanShutdown(SD_NOW));
+}
+
+
+/// @brief Verifies that an "external" call to shutdown causes the run method
+/// to exit gracefully.
+TEST_F(D2ProcessTest, normalShutdown) {
+ // Use an asiolink IntervalTimer and callback to generate the
+ // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIoService());
+ timer.setup(boost::bind(&D2ProcessTest::genShutdownCallback, this),
+ 2 * 1000);
+
+ // Record start time, and invoke run().
+ ptime start = microsec_clock::universal_time();
+ EXPECT_NO_THROW(run());
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2100);
+}
+
+
+/// @brief Verifies that an "uncaught" exception thrown during event loop
+/// execution is treated as a fatal error.
+TEST_F(D2ProcessTest, fatalErrorShutdown) {
+ // Use an asiolink IntervalTimer and callback to generate the
+ // the exception. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIoService());
+ timer.setup(boost::bind(&D2ProcessTest::genFatalErrorCallback, this),
+ 2 * 1000);
+
+ // Record start time, and invoke run().
+ ptime start = microsec_clock::universal_time();
+ EXPECT_THROW(run(), DProcessBaseError);
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the anomaly occurred
+ // during io callback processing.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2100);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d2_queue_mgr_unittests.cc b/src/bin/d2/tests/d2_queue_mgr_unittests.cc
new file mode 100644
index 0000000..18bf95c
--- /dev/null
+++ b/src/bin/d2/tests/d2_queue_mgr_unittests.cc
@@ -0,0 +1,440 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <asiolink/interval_timer.h>
+#include <d2/d2_queue_mgr.h>
+#include <dhcp_ddns/ncr_udp.h>
+#include <util/time_utilities.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <gtest/gtest.h>
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <vector>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+using namespace isc::d2;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest test messages.
+const char *valid_msgs[] =
+{
+ // Valid Add.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Valid Remove.
+ "{"
+ " \"change_type\" : 1 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Valid Add with IPv6 address
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}"
+};
+
+static const int VALID_MSG_CNT = sizeof(valid_msgs)/sizeof(char*);
+
+const char* TEST_ADDRESS = "127.0.0.1";
+const uint32_t LISTENER_PORT = 5301;
+const uint32_t SENDER_PORT = LISTENER_PORT+1;
+const long TEST_TIMEOUT = 5 * 1000;
+
+/// @brief Tests that construction with max queue size of zero is not allowed.
+TEST(D2QueueMgrBasicTest, construction1) {
+ isc::asiolink::IOService io_service;
+
+ // Verify that constructing with max queue size of zero is not allowed.
+ EXPECT_THROW(D2QueueMgr(io_service, 0), D2QueueMgrError);
+}
+
+/// @brief Tests default construction works.
+TEST(D2QueueMgrBasicTest, construction2) {
+ isc::asiolink::IOService io_service;
+
+ // Verify that valid constructor works.
+ D2QueueMgrPtr queue_mgr;
+ ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service)));
+ // Verify queue max is defaulted correctly.
+ EXPECT_EQ(D2QueueMgr::MAX_QUEUE_DEFAULT, queue_mgr->getMaxQueueSize());
+}
+
+/// @brief Tests construction with custom queue size works properly
+TEST(D2QueueMgrBasicTest, construction3) {
+ isc::asiolink::IOService io_service;
+
+ // Verify that custom queue size constructor works.
+ D2QueueMgrPtr queue_mgr;
+ ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service, 100)));
+ // Verify queue max is the custom value.
+ EXPECT_EQ(100, queue_mgr->getMaxQueueSize());
+}
+
+/// @brief Tests QueueMgr's basic queue functions
+/// This test verifies that:
+/// 1. Following construction queue is empty
+/// 2. Attempting to peek at an empty queue is not allowed
+/// 3. Attempting to dequeue an empty queue is not allowed
+/// 4. Peek returns the first entry on the queue without altering queue content
+/// 5. Dequeue removes the first entry on the queue
+TEST(D2QueueMgrBasicTest, basicQueue) {
+ isc::asiolink::IOService io_service;
+
+ // Construct the manager with max queue size set to number of messages
+ // we'll use.
+ D2QueueMgrPtr queue_mgr;
+ ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service, VALID_MSG_CNT)));
+ ASSERT_EQ(VALID_MSG_CNT, queue_mgr->getMaxQueueSize());
+
+ // Verify queue is empty after construction.
+ EXPECT_EQ(0, queue_mgr->getQueueSize());
+
+ // Verify that peek and dequeue both throw when queue is empty.
+ EXPECT_THROW(queue_mgr->peek(), D2QueueMgrQueueEmpty);
+ EXPECT_THROW(queue_mgr->dequeue(), D2QueueMgrQueueEmpty);
+
+ // Vector to keep track of the NCRs we que.
+ std::vector<NameChangeRequestPtr>ref_msgs;
+ NameChangeRequestPtr ncr;
+
+ // Iterate over the list of requests and add each to the queue.
+ for (int i = 0; i < VALID_MSG_CNT; i++) {
+ // Create the ncr and add to our reference list.
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ref_msgs.push_back(ncr);
+
+ // Verify that the request can be added to the queue and queue
+ // size increments accordingly.
+ EXPECT_NO_THROW(queue_mgr->enqueue(ncr));
+ EXPECT_EQ(i+1, queue_mgr->getQueueSize());
+ }
+
+ // Loop through and verify that the queue contents match the
+ // reference list.
+ for (int i = 0; i < VALID_MSG_CNT; i++) {
+ // Verify that peek on a non-empty queue returns first entry
+ // without altering queue content.
+ EXPECT_NO_THROW(ncr = queue_mgr->peek());
+
+ // Verify the peeked entry is the one it should be.
+ ASSERT_TRUE(ncr);
+ EXPECT_TRUE (*(ref_msgs[i]) == *ncr);
+
+ // Verify that peek did not alter the queue size.
+ EXPECT_EQ(VALID_MSG_CNT - i, queue_mgr->getQueueSize());
+
+ // Verify the dequeueing from non-empty queue works
+ EXPECT_NO_THROW(queue_mgr->dequeue());
+
+ // Verify queue size decrements following dequeue.
+ EXPECT_EQ(VALID_MSG_CNT - (i + 1), queue_mgr->getQueueSize());
+ }
+
+ // Iterate over the list of requests and add each to the queue.
+ for (int i = 0; i < VALID_MSG_CNT; i++) {
+ // Create the ncr and add to our reference list.
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ref_msgs.push_back(ncr);
+ EXPECT_NO_THROW(queue_mgr->enqueue(ncr));
+ }
+
+ // Verify queue count is correct.
+ EXPECT_EQ(VALID_MSG_CNT, queue_mgr->getQueueSize());
+
+ // Verfiy that peekAt returns the correct entry.
+ EXPECT_NO_THROW(ncr = queue_mgr->peekAt(1));
+ EXPECT_TRUE (*(ref_msgs[1]) == *ncr);
+
+ // Verfiy that dequeueAt removes the correct entry.
+ // Removing it, this should shift the queued entries forward by one.
+ EXPECT_NO_THROW(queue_mgr->dequeueAt(1));
+ EXPECT_NO_THROW(ncr = queue_mgr->peekAt(1));
+ EXPECT_TRUE (*(ref_msgs[2]) == *ncr);
+
+ // Verify the peekAt and dequeueAt throw when given indexes beyond the end.
+ EXPECT_THROW(queue_mgr->peekAt(VALID_MSG_CNT + 1), D2QueueMgrInvalidIndex);
+ EXPECT_THROW(queue_mgr->dequeueAt(VALID_MSG_CNT + 1),
+ D2QueueMgrInvalidIndex);
+}
+
+/// @brief Compares two NameChangeRequests for equality.
+bool checkSendVsReceived(NameChangeRequestPtr sent_ncr,
+ NameChangeRequestPtr received_ncr) {
+ return ((sent_ncr && received_ncr) &&
+ (*sent_ncr == *received_ncr));
+}
+
+/// @brief Text fixture that allows testing a listener and sender together
+/// It derives from both the receive and send handler classes and contains
+/// and instance of UDP listener and UDP sender.
+class QueueMgrUDPTest : public virtual ::testing::Test,
+ NameChangeSender::RequestSendHandler {
+public:
+ isc::asiolink::IOService io_service_;
+ NameChangeSenderPtr sender_;
+ isc::asiolink::IntervalTimer test_timer_;
+ D2QueueMgrPtr queue_mgr_;
+
+ NameChangeSender::Result send_result_;
+ std::vector<NameChangeRequestPtr> sent_ncrs_;
+ std::vector<NameChangeRequestPtr> received_ncrs_;
+
+ QueueMgrUDPTest() : io_service_(), test_timer_(io_service_) {
+ isc::asiolink::IOAddress addr(TEST_ADDRESS);
+ // Create our sender instance. Note that reuse_address is true.
+ sender_.reset(new NameChangeUDPSender(addr, SENDER_PORT,
+ addr, LISTENER_PORT,
+ FMT_JSON, *this, 100, true));
+
+ // Set the test timeout to break any running tasks if they hang.
+ test_timer_.setup(boost::bind(&QueueMgrUDPTest::testTimeoutHandler,
+ this),
+ TEST_TIMEOUT);
+ }
+
+ void reset_results() {
+ sent_ncrs_.clear();
+ received_ncrs_.clear();
+ }
+
+ /// @brief Implements the send completion handler.
+ virtual void operator ()(const NameChangeSender::Result result,
+ NameChangeRequestPtr& ncr) {
+ // save the result and the NCR sent.
+ send_result_ = result;
+ sent_ncrs_.push_back(ncr);
+ }
+
+ /// @brief Handler invoked when test timeout is hit.
+ ///
+ /// This callback stops all running (hanging) tasks on IO service.
+ void testTimeoutHandler() {
+ io_service_.stop();
+ FAIL() << "Test timeout hit.";
+ }
+};
+
+/// @brief Tests D2QueueMgr's state model.
+/// This test verifies that:
+/// 1. Upon construction, initial state is NOT_INITTED.
+/// 2. Cannot start listening from while state is NOT_INITTED.
+/// 3. Successful listener initialization transitions from NOT_INITTED
+/// to INITTED.
+/// 4. Attempting to initialize the listener from INITTED state is not
+/// allowed.
+/// 5. Starting listener from INITTED transitions to RUNNING.
+/// 6. Stopping the listener transitions from RUNNING to STOPPED.
+/// 7. Starting listener from STOPPED transitions to RUNNING.
+TEST_F (QueueMgrUDPTest, stateModel) {
+ // Create the queue manager.
+ ASSERT_NO_THROW(queue_mgr_.reset(new D2QueueMgr(io_service_,
+ VALID_MSG_CNT)));
+
+ // Verify that the initial state is NOT_INITTED.
+ EXPECT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr_->getMgrState());
+
+ // Verify that trying to listen before when not initialized fails.
+ EXPECT_THROW(queue_mgr_->startListening(), D2QueueMgrError);
+
+ // Verify that initializing the listener moves us to INITTED state.
+ isc::asiolink::IOAddress addr(TEST_ADDRESS);
+ EXPECT_NO_THROW(queue_mgr_->initUDPListener(addr, LISTENER_PORT,
+ FMT_JSON, true));
+ EXPECT_EQ(D2QueueMgr::INITTED, queue_mgr_->getMgrState());
+
+ // Verify that attempting to initialize the listener, from INITTED
+ // is not allowed.
+ EXPECT_THROW(queue_mgr_->initUDPListener(addr, LISTENER_PORT,
+ FMT_JSON, true),
+ D2QueueMgrError);
+
+ // Verify that we can enter the RUNNING from INITTED by starting the
+ // listener.
+ EXPECT_NO_THROW(queue_mgr_->startListening());
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+ // Verify that we can move from RUNNING to STOPPING by stopping the
+ // listener.
+ EXPECT_NO_THROW(queue_mgr_->stopListening());
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr_->getMgrState());
+
+ // Stopping requires IO cancel, which result in a callback.
+ // So process one event and verify we are STOPPED.
+ io_service_.run_one();
+ EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr_->getMgrState());
+
+ // Verify that we can re-enter the RUNNING from STOPPED by starting the
+ // listener.
+ EXPECT_NO_THROW(queue_mgr_->startListening());
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+ // Verify that we cannot remove the listener in the RUNNING state
+ EXPECT_THROW(queue_mgr_->removeListener(), D2QueueMgrError);
+
+ // Stop the listener.
+ EXPECT_NO_THROW(queue_mgr_->stopListening());
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr_->getMgrState());
+
+ // Stopping requires IO cancel, which result in a callback.
+ // So process one event and verify we are STOPPED.
+ io_service_.run_one();
+ EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr_->getMgrState());
+
+ // Verify that we can remove the listener in the STOPPED state and
+ // end up back in NOT_INITTED.
+ EXPECT_NO_THROW(queue_mgr_->removeListener());
+ EXPECT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr_->getMgrState());
+}
+
+/// @brief Tests D2QueueMgr's ability to manage received requests
+/// This test verifies that:
+/// 1. Requests can be received, queued, and dequeued
+/// 2. Once the queue is full, a subsequent request transitions
+/// manager to STOPPED_QUEUE_FULL state.
+/// 3. Starting listener returns manager to the RUNNING state.
+/// 4. Queue contents are preserved across state transitions.
+/// 5. Clearing the queue via the clearQueue() method works.
+/// 6. Requests can be received and queued normally after the queue
+/// has been emptied.
+/// 7. setQueueMax disallows values of 0 or less than current queue size.
+TEST_F (QueueMgrUDPTest, liveFeed) {
+ NameChangeRequestPtr send_ncr;
+ NameChangeRequestPtr received_ncr;
+
+ // Create the queue manager and start listening..
+ ASSERT_NO_THROW(queue_mgr_.reset(new D2QueueMgr(io_service_,
+ VALID_MSG_CNT)));
+ ASSERT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr_->getMgrState());
+
+ // Verify that setting max queue size to 0 is not allowed.
+ EXPECT_THROW(queue_mgr_->setMaxQueueSize(0), D2QueueMgrError);
+ EXPECT_EQ(VALID_MSG_CNT, queue_mgr_->getMaxQueueSize());
+
+ isc::asiolink::IOAddress addr(TEST_ADDRESS);
+ ASSERT_NO_THROW(queue_mgr_->initUDPListener(addr, LISTENER_PORT,
+ FMT_JSON, true));
+ ASSERT_EQ(D2QueueMgr::INITTED, queue_mgr_->getMgrState());
+
+ ASSERT_NO_THROW(queue_mgr_->startListening());
+ ASSERT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+ // Place the sender into sending state.
+ ASSERT_NO_THROW(sender_->startSending(io_service_));
+ ASSERT_TRUE(sender_->amSending());
+
+ // Iterate over the list of requests sending and receiving
+ // each one. Verify and dequeue as they arrive.
+ for (int i = 0; i < VALID_MSG_CNT; i++) {
+ // Create the ncr and add to our reference list.
+ ASSERT_NO_THROW(send_ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+
+ // running two should do the send then the receive
+ io_service_.run_one();
+ io_service_.run_one();
+
+ // Verify that the request can be added to the queue and queue
+ // size increments accordingly.
+ EXPECT_EQ(1, queue_mgr_->getQueueSize());
+
+ // Verify that peek shows the NCR we just sent
+ EXPECT_NO_THROW(received_ncr = queue_mgr_->peek());
+ EXPECT_TRUE(checkSendVsReceived(send_ncr, received_ncr));
+
+ // Verify that we and dequeue the request.
+ EXPECT_NO_THROW(queue_mgr_->dequeue());
+ EXPECT_EQ(0, queue_mgr_->getQueueSize());
+ }
+
+ // Iterate over the list of requests, sending and receiving
+ // each one. Allow them to accumulate in the queue.
+ for (int i = 0; i < VALID_MSG_CNT; i++) {
+ // Create the ncr and add to our reference list.
+ ASSERT_NO_THROW(send_ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+
+ // running two should do the send then the receive
+ EXPECT_NO_THROW(io_service_.run_one());
+ EXPECT_NO_THROW(io_service_.run_one());
+ EXPECT_EQ(i+1, queue_mgr_->getQueueSize());
+ }
+
+ // Verify that the queue is at max capacity.
+ EXPECT_EQ(queue_mgr_->getMaxQueueSize(), queue_mgr_->getQueueSize());
+
+ // Send another. The send should succeed.
+ ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+ EXPECT_NO_THROW(io_service_.run_one());
+
+ // Now execute the receive which should not throw but should move us
+ // to STOPPED_QUEUE_FULL state.
+ EXPECT_NO_THROW(io_service_.run_one());
+ EXPECT_EQ(D2QueueMgr::STOPPED_QUEUE_FULL, queue_mgr_->getMgrState());
+
+ // Verify queue size did not increase beyond max.
+ EXPECT_EQ(VALID_MSG_CNT, queue_mgr_->getQueueSize());
+
+ // Verify that setting max queue size to a value less than current size of
+ // the queue is not allowed.
+ EXPECT_THROW(queue_mgr_->setMaxQueueSize(VALID_MSG_CNT-1), D2QueueMgrError);
+ EXPECT_EQ(VALID_MSG_CNT, queue_mgr_->getQueueSize());
+
+ // Verify that we can re-enter RUNNING from STOPPED_QUEUE_FULL.
+ EXPECT_NO_THROW(queue_mgr_->startListening());
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+ // Verify that the queue contents were preserved.
+ EXPECT_EQ(queue_mgr_->getMaxQueueSize(), queue_mgr_->getQueueSize());
+
+ // Verify that clearQueue works.
+ EXPECT_NO_THROW(queue_mgr_->clearQueue());
+ EXPECT_EQ(0, queue_mgr_->getQueueSize());
+
+
+ // Verify that we can again receive requests.
+ // Send should be fine.
+ ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+ EXPECT_NO_THROW(io_service_.run_one());
+
+ // Receive should succeed.
+ EXPECT_NO_THROW(io_service_.run_one());
+ EXPECT_EQ(1, queue_mgr_->getQueueSize());
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d2_test.py b/src/bin/d2/tests/d2_test.py
new file mode 100644
index 0000000..7548672
--- /dev/null
+++ b/src/bin/d2/tests/d2_test.py
@@ -0,0 +1,168 @@
+# Copyright (C) 2013 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 init import ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
+
+import unittest
+import sys
+import os
+import signal
+import socket
+from isc.net.addr import IPAddr
+import time
+import isc
+import fcntl
+
+class TestD2Daemon(unittest.TestCase):
+ def setUp(self):
+ # Don't redirect stdout/stderr here as we want to print out things
+ # during the test
+ #
+ # However, we do want to set the logging lock directory to somewhere
+ # to which we can write - use the current working directory. We then
+ # set the appropriate environment variable. os.putenv() may be not
+ # supported on some platforms as suggested in
+ # http://docs.python.org/release/3.2/library/os.html?highlight=putenv#os.environ:
+ # "If the platform supports the putenv() function...". It was checked
+ # that it does not work on Ubuntu. To overcome this problem we access
+ # os.environ directly.
+ lockdir_envvar = "B10_LOCKFILE_DIR_FROM_BUILD"
+ if lockdir_envvar not in os.environ:
+ os.environ[lockdir_envvar] = os.getcwd()
+
+ def tearDown(self):
+ pass
+
+ def readPipe(self, pipe_fd):
+ """
+ Reads bytes from a pipe and returns a character string. If nothing is
+ read, or if there is an error, an empty string is returned.
+
+ pipe_fd - Pipe file descriptor to read
+ """
+ try:
+ data = os.read(pipe_fd, 16384)
+ # Make sure we have a string
+ if (data is None):
+ data = ""
+ else:
+ data = str(data)
+ except OSError:
+ data = ""
+
+ return data
+
+ def runCommand(self, params, wait=1):
+ """
+ This method runs a command and returns a tuple: (returncode, stdout, stderr)
+ """
+ ## @todo: Convert this into generic method and reuse it in dhcp4 and dhcp6
+
+ print("Running command: %s" % (" ".join(params)))
+
+ # redirect stdout to a pipe so we can check that our
+ # process spawning is doing the right thing with stdout
+ self.stdout_old = os.dup(sys.stdout.fileno())
+ self.stdout_pipes = os.pipe()
+ os.dup2(self.stdout_pipes[1], sys.stdout.fileno())
+ os.close(self.stdout_pipes[1])
+
+ # do the same trick for stderr:
+ self.stderr_old = os.dup(sys.stderr.fileno())
+ self.stderr_pipes = os.pipe()
+ os.dup2(self.stderr_pipes[1], sys.stderr.fileno())
+ os.close(self.stderr_pipes[1])
+
+ # note that we use dup2() to restore the original stdout
+ # to the main program ASAP in each test... this prevents
+ # hangs reading from the child process (as the pipe is only
+ # open in the child), and also insures nice pretty output
+
+ pi = ProcessInfo('Test Process', params)
+ pi.spawn()
+ time.sleep(wait)
+ os.dup2(self.stdout_old, sys.stdout.fileno())
+ os.dup2(self.stderr_old, sys.stderr.fileno())
+ self.assertNotEqual(pi.process, None)
+ self.assertTrue(type(pi.pid) is int)
+
+ # Set non-blocking read on pipes. Process may not print anything
+ # on specific output and the we would hang without this.
+ fd = self.stdout_pipes[0]
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ fd = self.stderr_pipes[0]
+ fl = fcntl.fcntl(fd, fcntl.F_GETFL)
+ fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
+
+ # As we don't know how long the subprocess will take to start and
+ # produce output, we'll loop and sleep for 250 ms between each
+ # iteration. To avoid an infinite loop, we'll loop for a maximum
+ # of five seconds: that should be enough.
+ for count in range(20):
+ # Read something from stderr and stdout (these reads don't block).
+ output = self.readPipe(self.stdout_pipes[0])
+ error = self.readPipe(self.stderr_pipes[0])
+
+ # If the process has already exited, or if it has output something,
+ # quit the loop now.
+ if pi.process.poll() is not None or len(error) > 0 or len(output) > 0:
+ break
+
+ # Process still running, try again in 250 ms.
+ time.sleep(0.25)
+
+ # Exited loop, kill the process if it is still running
+ if pi.process.poll() is None:
+ try:
+ pi.process.terminate()
+ except OSError:
+ print("Ignoring failed kill attempt. Process is dead already.")
+
+ # call this to get returncode, process should be dead by now
+ rc = pi.process.wait()
+
+ # Clean up our stdout/stderr munging.
+ os.dup2(self.stdout_old, sys.stdout.fileno())
+ os.close(self.stdout_old)
+ os.close(self.stdout_pipes[0])
+
+ os.dup2(self.stderr_old, sys.stderr.fileno())
+ os.close(self.stderr_old)
+ os.close(self.stderr_pipes[0])
+
+ # Free up resources (file descriptors) from the ProcessInfo object
+ # TODO: For some reason, this gives an error if the process has ended,
+ # although it does cause all descriptors still allocated to the
+ # object to be freed.
+ pi = None
+
+ print ("Process finished, return code=%d, stdout=%d bytes, stderr=%d bytes"
+ % (rc, len(output), len(error)) )
+
+ return (rc, output, error)
+
+ def test_alive(self):
+ print("Note: Simple test to verify that D2 server can be started.")
+ # note that "-s" for stand alone is necessary in order to flush the log output
+ # soon enough to catch it.
+ (returncode, output, error) = self.runCommand(["../b10-dhcp-ddns",
+ "-s", "-v"])
+ output_text = str(output) + str(error)
+ self.assertEqual(output_text.count("DCTL_STARTING"), 1)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/bin/d2/tests/d2_unittests.cc b/src/bin/d2/tests/d2_unittests.cc
new file mode 100644
index 0000000..bfba379
--- /dev/null
+++ b/src/bin/d2/tests/d2_unittests.cc
@@ -0,0 +1,31 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log/logger_support.h>
+#include <d2/d2_log.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+
+ ::testing::InitGoogleTest(&argc, argv);
+
+ // See the documentation of the B10_* environment variables in
+ // src/lib/log/README for info on how to tweak logging
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/bin/d2/tests/d2_update_message_unittests.cc b/src/bin/d2/tests/d2_update_message_unittests.cc
new file mode 100644
index 0000000..28e01c7
--- /dev/null
+++ b/src/bin/d2/tests/d2_update_message_unittests.cc
@@ -0,0 +1,591 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <d2/d2_update_message.h>
+#include <d2/d2_zone.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdataclass.h>
+#include <dns/rdata.h>
+#include <dns/rrttl.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::util;
+
+namespace {
+
+// @brief Test fixture class for testing D2UpdateMessage object.
+class D2UpdateMessageTest : public ::testing::Test {
+public:
+ // @brief Constructor.
+ //
+ // Does nothing.
+ D2UpdateMessageTest() { }
+
+ // @brief Destructor.
+ //
+ // Does nothing.
+ ~D2UpdateMessageTest() { };
+
+ // @brief Return string representation of the name encoded in wire format.
+ //
+ // This function reads the number of bytes specified in the second
+ // argument from the buffer. It doesn't check if buffer has sufficient
+ // length for reading given number of bytes. Caller should verify it
+ // prior to calling this function.
+ //
+ // @param buf input buffer, its internal pointer will be moved to
+ // the position after a name being read from it.
+ // @param name_length length of the name stored in the buffer
+ // @param no_zero_byte if true it indicates that the given buffer does not
+ // comprise the zero byte, which signals end of the name. This is
+ // the case, when dealing with compressed messages which don't have
+ // this byte.
+ //
+ // @return string representation of the name.
+ std::string readNameFromWire(InputBuffer& buf, size_t name_length,
+ const bool no_zero_byte = false) {
+ std::vector<uint8_t> name_data;
+ // Create another InputBuffer which holds only the name in the wire
+ // format.
+ buf.readVector(name_data, name_length);
+ if (no_zero_byte) {
+ ++name_length;
+ name_data.push_back(0);
+ }
+ InputBuffer name_buf(&name_data[0], name_length);
+ // Parse the name and return its textual representation.
+ Name name(name_buf);
+ return (name.toText());
+ }
+};
+
+// This test verifies that DNS Update message ID can be set using
+// setId function.
+TEST_F(D2UpdateMessageTest, setId) {
+ // Message ID is initialized to 0.
+ D2UpdateMessage msg;
+ EXPECT_EQ(0, msg.getId());
+ // Override the default value and verify that it has been set.
+ msg.setId(0x1234);
+ EXPECT_EQ(0x1234, msg.getId());
+}
+
+// This test verifies that the DNS Update message RCODE can be set
+// using setRcode function.
+TEST_F(D2UpdateMessageTest, setRcode) {
+ D2UpdateMessage msg;
+ // Rcode must be explicitly set before it is accessed.
+ msg.setRcode(Rcode::NOERROR());
+ EXPECT_EQ(Rcode::NOERROR().getCode(), msg.getRcode().getCode());
+ // Let's override current value to make sure that getter does
+ // not return fixed value.
+ msg.setRcode(Rcode::NOTIMP());
+ EXPECT_EQ(Rcode::NOTIMP().getCode(), msg.getRcode().getCode());
+}
+
+// This test verifies that the Zone section in the DNS Update message
+// can be set.
+TEST_F(D2UpdateMessageTest, setZone) {
+ D2UpdateMessage msg;
+ // The zone pointer is initialized to NULL.
+ D2ZonePtr zone = msg.getZone();
+ EXPECT_FALSE(zone);
+ // Let's create a new Zone and check that it is returned
+ // via getter.
+ msg.setZone(Name("example.com"), RRClass::ANY());
+ zone = msg.getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::ANY().getCode(), zone->getClass().getCode());
+
+ // Now, let's check that the existing Zone object can be
+ // overriden with a new one.
+ msg.setZone(Name("foo.example.com"), RRClass::NONE());
+ zone = msg.getZone();
+ EXPECT_TRUE(zone);
+ EXPECT_EQ("foo.example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::NONE().getCode(), zone->getClass().getCode());
+}
+
+// This test verifies that the DNS message is properly decoded from the
+// wire format.
+TEST_F(D2UpdateMessageTest, fromWire) {
+ // The following table holds the DNS response in on-wire format.
+ // This message comprises the following sections:
+ // - HEADER
+ // - PREREQUISITE section with one RR
+ // - UPDATE section with 1 RR.
+ // Such a response may be generated by the DNS server as a result
+ // of copying the contents of the REQUEST message sent by DDNS client.
+ const uint8_t bin_msg[] = {
+ // HEADER section starts here (see RFC 2136, section 2).
+ 0x05, 0xAF, // ID=0x05AF
+ 0xA8, 0x6, // QR=1, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x1, // ZOCOUNT=1
+ 0x0, 0x2, // PRCOUNT=2
+ 0x0, 0x1, // UPCOUNT=1
+ 0x0, 0x0, // ADCOUNT=0
+
+ // Zone section starts here. The The first field comprises
+ // the Zone name encoded as a set of labels, each preceded
+ // by a length of the following label. The whole Zone name is
+ // terminated with a NULL char.
+ // For Zone section format see (RFC 2136, section 2.3).
+ 0x7, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example (7 is length)
+ 0x3, 0x63, 0x6F, 0x6D, //.com. (0x3 is a length)
+ 0x0, // NULL character terminates the Zone name.
+ 0x0, 0x6, // ZTYPE='SOA'
+ 0x0, 0x1, // ZCLASS='IN'
+
+ // Prerequisite section starts here. This section comprises two
+ // prerequisites:
+ // - 'Name is not in use'
+ // - 'Name is in use'
+ // See RFC 2136, section 2.4 for the format of Prerequisite section.
+ // Each prerequisite RR starts with its name. It is expressed in the
+ // compressed format as described in RFC 1035, section 4.1.4. The first
+ // label is expressed as in case of non-compressed name. It is preceded
+ // by the length value. The following two bytes are the pointer to the
+ // offset in the message where 'example.com' was used. That is, in the
+ // Zone name at offset 12. Pointer starts with two bits set - they
+ // mark start of the pointer.
+
+ // First prerequisite. NONE class indicates that the update requires
+ // that the name 'foo.example.com' is not use/
+ 0x03, 0x66, 0x6F, 0x6F, // foo.
+ 0xC0, 0x0C, // pointer to example.com.
+ 0x0, 0x1C, // TYPE=AAAA
+ 0x0, 0xFE, // CLASS=NONE
+ 0x0, 0x0, 0x0, 0x0, // TTL=0
+ 0x0, 0x0, // RDLENGTH=0
+
+ // Second prerequisite. ANY class indicates tha the update requires
+ // that the name 'bar.example.com' exists.
+ 0x03, 0x62, 0x61, 0x72, // bar.
+ 0xC0, 0x0C, // pointer to example.com.
+ 0x0, 0x1C, // TYPE=AAAA
+ 0x0, 0xFF, // CLASS=ANY
+ 0x0, 0x0, 0x0, 0x0, // TTL=0
+ 0x0, 0x0, // RDLENGTH=0
+
+ // Update section starts here. The format of this section conforms to
+ // RFC 2136, section 2.5. The name of the RR is again expressed in
+ // compressed format. The two pointer bytes point to the offset in the
+ // message where 'foo.example.com' was used already - 29.
+ 0xC0, 0x1D, // pointer to foo.example.com.
+ 0x0, 0x1C, // TYPE=AAAA
+ 0x0, 0x1, // CLASS=IN
+ 0xAA, 0xBB, 0xCC, 0xDD, // TTL=0xAABBCCDD
+ 0x0, 0x10, // RDLENGTH=16
+ // The following 16 bytes of RDATA hold IPv6 address: 2001:db8:1::1.
+ 0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
+ };
+ InputBuffer buf(bin_msg, sizeof(bin_msg));
+
+ // Create an object to be used to decode the message from the wire format.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ // Decode the message.
+ ASSERT_NO_THROW(msg.fromWire(buf));
+
+ // Check that the message header is valid.
+ EXPECT_EQ(0x05AF, msg.getId());
+ EXPECT_EQ(D2UpdateMessage::RESPONSE, msg.getQRFlag());
+ EXPECT_EQ(Rcode::YXDOMAIN_CODE, msg.getRcode().getCode());
+
+ // The ZOCOUNT must contain exactly one zone. If it does, we should get
+ // the name, class and type of the zone and verify they are valid.
+ ASSERT_EQ(1, msg.getRRCount(D2UpdateMessage::SECTION_ZONE));
+ D2ZonePtr zone = msg.getZone();
+ ASSERT_TRUE(zone);
+ EXPECT_EQ("example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());
+
+ // Check the Prerequisite section. It should contain two records.
+ ASSERT_EQ(2, msg.getRRCount(D2UpdateMessage::SECTION_PREREQUISITE));
+
+ // Proceed to the first prerequisite.
+ RRsetIterator rrset_it =
+ msg.beginSection(D2UpdateMessage::SECTION_PREREQUISITE);
+ RRsetPtr prereq1 = *rrset_it;
+ ASSERT_TRUE(prereq1);
+ // Check record fields.
+ EXPECT_EQ("foo.example.com.", prereq1->getName().toText()); // NAME
+ EXPECT_EQ(RRType::AAAA().getCode(), prereq1->getType().getCode()); // TYPE
+ EXPECT_EQ(RRClass::NONE().getCode(),
+ prereq1->getClass().getCode()); // CLASS
+ EXPECT_EQ(0, prereq1->getTTL().getValue()); // TTL
+ EXPECT_EQ(0, prereq1->getRdataCount()); // RDLENGTH
+
+ // Move to next prerequisite section.
+ ++rrset_it;
+ RRsetPtr prereq2 = *rrset_it;
+ ASSERT_TRUE(prereq2);
+ // Check record fields.
+ EXPECT_EQ("bar.example.com.", prereq2->getName().toText()); // NAME
+ EXPECT_EQ(RRType::AAAA().getCode(), prereq2->getType().getCode()); // TYPE
+ EXPECT_EQ(RRClass::ANY().getCode(), prereq2->getClass().getCode()); // CLASS
+ EXPECT_EQ(0, prereq2->getTTL().getValue()); // TTL
+ EXPECT_EQ(0, prereq2->getRdataCount()); // RDLENGTH
+
+ // Check the Update section. There is only one record, so beginSection()
+ // should return the pointer to this sole record.
+ ASSERT_EQ(1, msg.getRRCount(D2UpdateMessage::SECTION_UPDATE));
+ rrset_it = msg.beginSection(D2UpdateMessage::SECTION_UPDATE);
+ RRsetPtr update = *rrset_it;
+ ASSERT_TRUE(update);
+ // Check the record fields.
+ EXPECT_EQ("foo.example.com.", update->getName().toText()); // NAME
+ EXPECT_EQ(RRType::AAAA().getCode(), update->getType().getCode()); // TYPE
+ EXPECT_EQ(RRClass::IN().getCode(), update->getClass().getCode()); // CLASS
+ EXPECT_EQ(0xAABBCCDD, update->getTTL().getValue()); // TTL
+ // There should be exactly one record holding the IPv6 address.
+ // This record can be accessed using RdataIterator. This record
+ // can be compared with the reference record, holding expected IPv6
+ // address using compare function.
+ ASSERT_EQ(1, update->getRdataCount());
+ RdataIteratorPtr rdata_it = update->getRdataIterator();
+ ASSERT_TRUE(rdata_it);
+ in::AAAA rdata_ref("2001:db8:1::1");
+ EXPECT_EQ(0, rdata_ref.compare(rdata_it->getCurrent()));
+
+ // @todo: at this point we don't test Additional Data records. We may
+ // consider implementing tests for it in the future.
+}
+
+// This test verifies that the fromWire function throws appropriate exception
+// if the message being parsed comprises invalid Opcode (is not a DNS Update).
+TEST_F(D2UpdateMessageTest, fromWireInvalidOpcode) {
+ // This is a binary representation of the DNS message.
+ // It comprises invalid Opcode=3, expected value is 6
+ // (Update).
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0x98, 0x6, // QR=1, Opcode=3, RCODE=YXDOMAIN
+ 0x0, 0x0, // ZOCOUNT=0
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0 // ADCOUNT=0
+ };
+ InputBuffer buf(bin_msg, sizeof(bin_msg));
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary mesasage data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ // When using invalid Opcode, the fromWire function should
+ // throw NotUpdateMessage exception.
+ EXPECT_THROW(msg.fromWire(buf), isc::d2::NotUpdateMessage);
+}
+
+// This test verifies that the fromWire function throws appropriate exception
+// if the message being parsed comprises invalid QR flag. The QR bit is
+// expected to be set to indicate that the message is a RESPONSE.
+TEST_F(D2UpdateMessageTest, fromWireInvalidQRFlag) {
+ // This is a binary representation of the DNS message.
+ // It comprises invalid QR flag = 0.
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0x28, 0x6, // QR=0, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x0, // ZOCOUNT=0
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0 // ADCOUNT=0
+ };
+ InputBuffer buf(bin_msg, sizeof(bin_msg));
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary mesasage data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ // When using invalid QR flag, the fromWire function should
+ // throw InvalidQRFlag exception.
+ EXPECT_THROW(msg.fromWire(buf), isc::d2::InvalidQRFlag);
+}
+
+// This test verifies that the fromWire function throws appropriate exception
+// if the message being parsed comprises more than one (two in this case)
+// Zone records.
+TEST_F(D2UpdateMessageTest, fromWireTooManyZones) {
+ // This is a binary representation of the DNS message. This message
+ // comprises two Zone records.
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0xA8, 0x6, // QR=1, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x2, // ZOCOUNT=2
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0, // ADCOUNT=0
+
+ // Start first Zone record.
+ 0x7, 0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, // example (7 is length)
+ 0x3, 0x63, 0x6F, 0x6D, //.com. (0x3 is a length)
+ 0x0, // NULL character terminates the Zone name.
+ 0x0, 0x6, // ZTYPE='SOA'
+ 0x0, 0x1, // ZCLASS='IN'
+
+ // Start second Zone record. Presence of this record should result
+ // in error when parsing this message.
+ 0x3, 0x63, 0x6F, 0x6D, // com. (0x3 is a length)
+ 0x0, // NULL character terminates the Zone name.
+ 0x0, 0x6, // ZTYPE='SOA'
+ 0x0, 0x1 // ZCLASS='IN'
+ };
+ InputBuffer buf(bin_msg, sizeof(bin_msg));
+
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary mesasage data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ // When parsing a message with more than one Zone record,
+ // exception should be thrown.
+ EXPECT_THROW(msg.fromWire(buf), isc::d2::InvalidZoneSection);
+}
+
+// This test verifies that the wire format of the message is produced
+// in the render mode.
+TEST_F(D2UpdateMessageTest, toWire) {
+ D2UpdateMessage msg;
+ // Set message ID.
+ msg.setId(0x1234);
+ // Rcode to NOERROR.
+ msg.setRcode(Rcode(Rcode::NOERROR_CODE));
+
+ // Set Zone section. This section must comprise exactly
+ // one Zone. toWire function would fail if Zone is not set.
+ msg.setZone(Name("example.com"), RRClass::IN());
+
+ // Set prerequisities.
+
+ // 'Name Is Not In Use' prerequisite (RFC 2136, section 2.4.5)
+ RRsetPtr prereq1(new RRset(Name("foo.example.com"), RRClass::NONE(),
+ RRType::ANY(), RRTTL(0)));
+ msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq1);
+
+ // 'Name is In Use' prerequisite (RFC 2136, section 2.4.4)
+ RRsetPtr prereq2(new RRset(Name("bar.example.com"), RRClass::ANY(),
+ RRType::ANY(), RRTTL(0)));
+ msg.addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq2);
+
+ // Set Update Section.
+
+ // Create RR holding a name being added. This RR is constructed
+ // in conformance to RFC 2136, section 2.5.1.
+ RRsetPtr updaterr1(new RRset(Name("foo.example.com"), RRClass::IN(),
+ RRType::A(), RRTTL(10)));
+ // RR record is of the type A, thus RDATA holds 4 octet Internet
+ // address. This address is 10.10.1.1.
+ char rdata1[] = {
+ 0xA, 0xA , 0x1, 0x1
+ };
+ InputBuffer buf_rdata1(rdata1, 4);
+ updaterr1->addRdata(createRdata(RRType::A(), RRClass::IN(), buf_rdata1,
+ buf_rdata1.getLength()));
+ // Add the RR to the message.
+ msg.addRRset(D2UpdateMessage::SECTION_UPDATE, updaterr1);
+
+ // Render message into the wire format.
+ MessageRenderer renderer;
+ ASSERT_NO_THROW(msg.toWire(renderer));
+
+ // Make sure that created packet is not truncated.
+ ASSERT_EQ(77, renderer.getLength());
+
+ // Create input buffer from the rendered data. InputBuffer
+ // is handy to validate the byte contents of the rendered
+ // message.
+ InputBuffer buf(renderer.getData(), renderer.getLength());
+
+ // Start validating the message header.
+
+ // Verify message ID.
+ EXPECT_EQ(0x1234, buf.readUint16());
+ // The 2-bytes following message ID comprise the following fields:
+ // - QR - 1 bit indicating that it is REQUEST. Should be 0.
+ // - Opcode - 4 bits which should hold value of 5 indicating this is
+ // an Update message. Binary form is "0101".
+ // - Z - These bits are unused for Update Message and should be 0.
+ // - RCODE - Response code, set to NOERROR for REQUEST. It is 0.
+ //8706391835
+ // The binary value is:
+ // 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ // | QR| Opcode | Z | RCODE |
+ // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+ // | 0 | 0 1 0 1 | 0 0 0 0 0 0 0 | 0 0 0 0 |
+ // +---+---+---+-------+---+---+---+---+---+---+---+---+---+---+---+
+ // and the hexadecimal representation is 0x2800.
+ EXPECT_EQ(0x2800, buf.readUint16());
+
+ // ZOCOUNT - holds the number of zones for the update. For Request
+ // message it must be exactly one record (RFC2136, section 2.3).
+ EXPECT_EQ(1, buf.readUint16());
+
+ // PRCOUNT - holds the number of prerequisites. Earlier we have added
+ // two prerequisites. Thus, expect that this conter is 2.
+ EXPECT_EQ(2, buf.readUint16());
+
+ // UPCOUNT - holds the number of RRs in the Update Section. We have
+ // added 1 RR, which adds the name foo.example.com to the Zone.
+ EXPECT_EQ(1, buf.readUint16());
+
+ // ADCOUNT - holds the number of RRs in the Additional Data Section.
+ EXPECT_EQ(0, buf.readUint16());
+
+ // Start validating the Zone section. This section comprises the
+ // following data:
+ // - ZNAME
+ // - ZTYPE
+ // - ZCLASS
+
+ // ZNAME holds 'example.com.' encoded as set of labels. Each label
+ // is preceded by its length. The name is ended with the byte holding
+ // zero value. This yields the total size of the name in wire format
+ // of 13 bytes.
+
+ // The simplest way to convert the name from wire format to a string
+ // is to use dns::Name class. It should be ok to rely on the Name class
+ // to decode the name, because it is unit tested elswhere.
+ std::string zone_name = readNameFromWire(buf, 13);
+ EXPECT_EQ("example.com.", zone_name);
+
+ // ZTYPE of the Zone section must be SOA according to RFC 2136,
+ // section 2.3.
+ EXPECT_EQ(RRType::SOA().getCode(), buf.readUint16());
+
+ // ZCLASS of the Zone section is IN.
+ EXPECT_EQ(RRClass::IN().getCode(), buf.readUint16());
+
+ // Start checks on Prerequisite section. Each prerequisite comprises
+ // the following fields:
+ // - NAME - name of the RR in wire format
+ // - TYPE - two octets with one of the RR TYPE codes
+ // - CLASS - two octets with one of the RR CLASS codes
+ // - TTL - a 32-bit signed integer specifying Time-To-Live
+ // - RDLENGTH - length of the RDATA field
+ // - RDATA - a variable length string of octets containing
+ // resource data.
+ // In case of this message, we expect to have two prerequisite RRs.
+ // Their structure is checked below.
+
+ // First prerequisite should comprise the 'Name is not in use prerequisite'
+ // for 'foo.example.com'.
+
+ // Check the name first. Message renderer is using compression for domain
+ // names as described in RFC 1035, section 4.1.4. The name in this RR is
+ // foo.example.com. The name of the zone is example.com and it has occured
+ // in this message already at offset 12 (the size of the header is 12).
+ // Therefore, name of this RR is encoded as 'foo', followed by a pointer
+ // to offset in this message where the remainder of this name was used.
+ // This pointer has the following format:
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ // | 1 1| OFFSET |
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ // | 1 1| 0 0 0 0 0 0 0 0 0 0 1 1 0 0|
+ // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+ // which has a following hexadecimal representation: 0xC00C
+
+ // Let's read the non-compressed part first - 'foo.'
+ std::string name_prereq1 = readNameFromWire(buf, 4, true);
+ EXPECT_EQ("foo.", name_prereq1);
+ // The remaining two bytes hold the pointer to 'example.com'.
+ EXPECT_EQ(0xC00C, buf.readUint16());
+ // TYPE is ANY
+ EXPECT_EQ(RRType::ANY().getCode(), buf.readUint16());
+ // CLASS is NONE
+ EXPECT_EQ(RRClass::NONE().getCode(), buf.readUint16());
+ // TTL is a 32-but value, expecting 0
+ EXPECT_EQ(0, buf.readUint32());
+ // There is no RDATA, so RDLENGTH is 0
+ EXPECT_EQ(0, buf.readUint16());
+
+ // Start checking second prerequisite.
+
+ std::string name_prereq2 = readNameFromWire(buf, 4, true);
+ EXPECT_EQ("bar.", name_prereq2);
+ // The remaining two bytes hold the pointer to 'example.com'.
+ EXPECT_EQ(0xC00C, buf.readUint16());
+ // TYPE is ANY
+ EXPECT_EQ(RRType::ANY().getCode(), buf.readUint16());
+ // CLASS is ANY
+ EXPECT_EQ(RRClass::ANY().getCode(), buf.readUint16());
+ // TTL is a 32-but value, expecting 0
+ EXPECT_EQ(0, buf.readUint32());
+ // There is no RDATA, so RDLENGTH is 0
+ EXPECT_EQ(0, buf.readUint16());
+
+ // Start checking Update section. This section contains RRset with
+ // one A RR.
+
+ // The name of the RR is 'foo.example.com'. It is encoded in the
+ // compressed format - as a pointer to the name of prerequisite 1.
+ // This name is in offset 0x1D in this message.
+ EXPECT_EQ(0xC01D, buf.readUint16());
+ // TYPE is A
+ EXPECT_EQ(RRType::A().getCode(), buf.readUint16());
+ // CLASS is IN (same as zone class)
+ EXPECT_EQ(RRClass::IN().getCode(), buf.readUint16());
+ // TTL is a 32-but value, set here to 10.
+ EXPECT_EQ(10, buf.readUint32());
+ // For A records, the RDATA comprises the 4-byte Internet address.
+ // So, RDLENGTH is 4.
+ EXPECT_EQ(4, buf.readUint16());
+ // We have stored the following address in RDATA field: 10.10.1.1
+ // (which is 0A 0A 01 01) in hexadecimal format.
+ EXPECT_EQ(0x0A0A0101, buf.readUint32());
+
+ // @todo: consider extending this test to verify Additional Data
+ // section.
+}
+
+// This test verifies that an attempt to call toWire function on the
+// received message will result in an exception.
+TEST_F(D2UpdateMessageTest, toWireInvalidQRFlag) {
+ // This is a binary representation of the DNS message.
+ // This message is valid and should be parsed with no
+ // error.
+ const uint8_t bin_msg[] = {
+ 0x05, 0xAF, // ID=0x05AF
+ 0xA8, 0x6, // QR=1, Opcode=6, RCODE=YXDOMAIN
+ 0x0, 0x0, // ZOCOUNT=0
+ 0x0, 0x0, // PRCOUNT=0
+ 0x0, 0x0, // UPCOUNT=0
+ 0x0, 0x0 // ADCOUNT=0
+ };
+
+ InputBuffer buf(bin_msg, sizeof(bin_msg));
+ // The 'true' argument passed to the constructor turns the
+ // message into the parse mode in which the fromWire function
+ // can be used to decode the binary mesasage data.
+ D2UpdateMessage msg(D2UpdateMessage::INBOUND);
+ ASSERT_NO_THROW(msg.fromWire(buf));
+
+ // The message is parsed. The QR Flag should now indicate that
+ // it is a Response message.
+ ASSERT_EQ(D2UpdateMessage::RESPONSE, msg.getQRFlag());
+
+ // An attempt to call toWire on the Response message should
+ // result in the InvalidQRFlag exception.
+ MessageRenderer renderer;
+ EXPECT_THROW(msg.toWire(renderer), isc::d2::InvalidQRFlag);
+}
+
+} // End of anonymous namespace
diff --git a/src/bin/d2/tests/d2_update_mgr_unittests.cc b/src/bin/d2/tests/d2_update_mgr_unittests.cc
new file mode 100644
index 0000000..0abed5d
--- /dev/null
+++ b/src/bin/d2/tests/d2_update_mgr_unittests.cc
@@ -0,0 +1,443 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <asiolink/interval_timer.h>
+#include <d2/d2_update_mgr.h>
+#include <util/time_utilities.h>
+#include <d_test_stubs.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <gtest/gtest.h>
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <vector>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+using namespace isc::d2;
+
+namespace {
+
+/// @brief Wrapper class for D2UpdateMgr to provide acces non-public methods.
+///
+/// This class faciliates testing by making non-public methods accessible so
+/// they can be invoked directly in test routines.
+class D2UpdateMgrWrapper : public D2UpdateMgr {
+public:
+ /// @brief Constructor
+ ///
+ /// Parameters match those needed by D2UpdateMgr.
+ D2UpdateMgrWrapper(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
+ isc::asiolink::IOService& io_service,
+ const size_t max_transactions = MAX_TRANSACTIONS_DEFAULT)
+ : D2UpdateMgr(queue_mgr, cfg_mgr, io_service, max_transactions) {
+ }
+
+ /// @brief Destructor
+ virtual ~D2UpdateMgrWrapper() {
+ }
+
+ // Expose the protected methods to be tested.
+ using D2UpdateMgr::checkFinishedTransactions;
+ using D2UpdateMgr::pickNextJob;
+ using D2UpdateMgr::makeTransaction;
+};
+
+/// @brief Defines a pointer to a D2UpdateMgr instance.
+typedef boost::shared_ptr<D2UpdateMgrWrapper> D2UpdateMgrWrapperPtr;
+
+/// @brief Test fixture for testing D2UpdateMgr.
+///
+/// Note this class uses D2UpdateMgrWrapper class to exercise non-public
+/// aspects of D2UpdateMgr. D2UpdateMgr depends on both D2QueueMgr and
+/// D2CfgMgr. This fixture provides an instance of each, plus a canned,
+/// valid DHCP_DDNS configuration sufficient to test D2UpdateMgr's basic
+/// functions.
+class D2UpdateMgrTest : public ConfigParseTest {
+public:
+ isc::asiolink::IOService io_service_;
+ D2QueueMgrPtr queue_mgr_;
+ D2CfgMgrPtr cfg_mgr_;
+ //D2UpdateMgrPtr update_mgr_;
+ D2UpdateMgrWrapperPtr update_mgr_;
+ std::vector<NameChangeRequestPtr> canned_ncrs_;
+ size_t canned_count_;
+
+ D2UpdateMgrTest() {
+ queue_mgr_.reset(new D2QueueMgr(io_service_));
+ cfg_mgr_.reset(new D2CfgMgr());
+ update_mgr_.reset(new D2UpdateMgrWrapper(queue_mgr_, cfg_mgr_,
+ io_service_));
+ makeCannedNcrs();
+ makeCannedConfig();
+ }
+
+ ~D2UpdateMgrTest() {
+ }
+
+ /// @brief Creates a list of valid NameChangeRequest.
+ ///
+ /// This method builds a list of NameChangeRequests from a single
+ /// JSON string request. Each request is assigned a unique DHCID.
+ void makeCannedNcrs() {
+ const char* msg_str =
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.org.\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}";
+
+ const char* dhcids[] = { "111111", "222222", "333333", "444444"};
+ canned_count_ = 4;
+ for (int i = 0; i < canned_count_; i++) {
+ dhcp_ddns::NameChangeRequestPtr ncr = NameChangeRequest::
+ fromJSON(msg_str);
+ ncr->setDhcid(dhcids[i]);
+ canned_ncrs_.push_back(ncr);
+ }
+ }
+
+ /// @brief Seeds configuration manager with a valid DHCP_DDNS configuration.
+ void makeCannedConfig() {
+ std::string canned_config_ =
+ "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig_keys\": [] ,"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"two.three.org.\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] },"
+ "{ \"name\": \"org.\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] }, "
+ "] }, "
+ "\"reverse_ddns\" : { "
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"1.168.192.in-addr.arpa.\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] }, "
+ "{ \"name\": \"2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.1\" } "
+ " ] } "
+ "] } }";
+
+ // If this configuration fails to parse most tests will fail.
+ ASSERT_TRUE(fromJSON(canned_config_));
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+ }
+
+};
+
+/// @brief Tests the D2UpdateMgr construction.
+/// This test verifies that:
+/// 1. Construction with invalid queue manager is not allowed
+/// 2. Construction with invalid configuration manager is not allowed
+/// 3. Construction with max transactions of zero is not allowed
+/// 4. Default construction works and max transactions is defaulted properly
+/// 5. Construction with custom max transactions works properly
+TEST(D2UpdateMgr, construction) {
+ isc::asiolink::IOService io_service;
+ D2QueueMgrPtr queue_mgr;
+ D2CfgMgrPtr cfg_mgr;
+ D2UpdateMgrPtr update_mgr;
+
+ // Verify that constrctor fails if given an invalid queue manager.
+ ASSERT_NO_THROW(cfg_mgr.reset(new D2CfgMgr()));
+ EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service),
+ D2UpdateMgrError);
+
+ // Verify that constrctor fails if given an invalid config manager.
+ ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service)));
+ ASSERT_NO_THROW(cfg_mgr.reset());
+ EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service),
+ D2UpdateMgrError);
+
+ ASSERT_NO_THROW(cfg_mgr.reset(new D2CfgMgr()));
+
+ // Verify that max transactions cannot be zero.
+ EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service, 0),
+ D2UpdateMgrError);
+
+ // Verify that given valid values, constructor works.
+ ASSERT_NO_THROW(update_mgr.reset(new D2UpdateMgr(queue_mgr, cfg_mgr,
+ io_service)));
+
+ // Verify that max transactions defaults properly.
+ EXPECT_EQ(D2UpdateMgr::MAX_TRANSACTIONS_DEFAULT,
+ update_mgr->getMaxTransactions());
+
+
+ // Verify that constructor permits custom max transactions.
+ ASSERT_NO_THROW(update_mgr.reset(new D2UpdateMgr(queue_mgr, cfg_mgr,
+ io_service, 100)));
+
+ // Verify that max transactions is correct.
+ EXPECT_EQ(100, update_mgr->getMaxTransactions());
+}
+
+/// @brief Tests the D2UpdateManager's transaction list services
+/// This test verifies that:
+/// 1. A transaction can be added to the list.
+/// 2. Finding a transaction in the list by key works correctly.
+/// 3. Looking for a non-existant transaction works properly.
+/// 4. Attempting to add a transaction for a DHCID already in the list fails.
+/// 5. Removing a transaction by key works properly.
+/// 6. Attempting to remove an non-existant transaction does no harm.
+TEST_F(D2UpdateMgrTest, transactionList) {
+ // Grab a canned request for test purposes.
+ NameChangeRequestPtr& ncr = canned_ncrs_[0];
+ TransactionList::iterator pos;
+
+ // Verify that we can add a transaction.
+ EXPECT_NO_THROW(update_mgr_->makeTransaction(ncr));
+ EXPECT_EQ(1, update_mgr_->getTransactionCount());
+
+ // Verify that we can find a transaction by key.
+ EXPECT_NO_THROW(pos = update_mgr_->findTransaction(ncr->getDhcid()));
+ EXPECT_TRUE(pos != update_mgr_->transactionListEnd());
+
+ // Verify that convenience method has same result.
+ EXPECT_TRUE(update_mgr_->hasTransaction(ncr->getDhcid()));
+
+ // Verify that we will not find a transaction that isn't there.
+ dhcp_ddns::D2Dhcid bogus_id("FFFF");
+ EXPECT_NO_THROW(pos = update_mgr_->findTransaction(bogus_id));
+ EXPECT_TRUE(pos == update_mgr_->transactionListEnd());
+
+ // Verify that convenience method has same result.
+ EXPECT_FALSE(update_mgr_->hasTransaction(bogus_id));
+
+ // Verify that adding a transaction for the same key fails.
+ EXPECT_THROW(update_mgr_->makeTransaction(ncr), D2UpdateMgrError);
+ EXPECT_EQ(1, update_mgr_->getTransactionCount());
+
+ // Verify the we can remove a transaction by key.
+ EXPECT_NO_THROW(update_mgr_->removeTransaction(ncr->getDhcid()));
+ EXPECT_EQ(0, update_mgr_->getTransactionCount());
+
+ // Verify the we can try to remove a non-existant transaction without harm.
+ EXPECT_NO_THROW(update_mgr_->removeTransaction(ncr->getDhcid()));
+}
+
+/// @brief Tests D2UpdateManager's checkFinishedTransactions method.
+/// This test verifies that:
+/// 1. Completed transactions are removed from the transaction list.
+/// 2. Failed transactions are removed from the transaction list.
+/// @todo This test will need to expand if and when checkFinishedTransactions
+/// method expands to do more than remove them from the list.
+TEST_F(D2UpdateMgrTest, checkFinishedTransaction) {
+ // Ensure we have at least 4 canned requests with which to work.
+ ASSERT_TRUE(canned_count_ >= 4);
+
+ // Create a transaction for each canned request.
+ for (int i = 0; i < canned_count_; i++) {
+ EXPECT_NO_THROW(update_mgr_->makeTransaction(canned_ncrs_[i]));
+ }
+ // Verfiy we have that the transaçtion count is correct.
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+
+ // Set two of the transactions to finished states.
+ (canned_ncrs_[1])->setStatus(dhcp_ddns::ST_COMPLETED);
+ (canned_ncrs_[3])->setStatus(dhcp_ddns::ST_FAILED);
+
+ // Verify that invoking checkFinishedTransactions does not throw.
+ EXPECT_NO_THROW(update_mgr_->checkFinishedTransactions());
+
+ // Verify that the list of transactions has decreased by two.
+ EXPECT_EQ(canned_count_ - 2, update_mgr_->getTransactionCount());
+
+ // Vefity that the transaction list is correct.
+ EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[0]->getDhcid()));
+ EXPECT_FALSE(update_mgr_->hasTransaction(canned_ncrs_[1]->getDhcid()));
+ EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[2]->getDhcid()));
+ EXPECT_FALSE(update_mgr_->hasTransaction(canned_ncrs_[3]->getDhcid()));
+}
+
+/// @brief Tests D2UpdateManager's pickNextJob method.
+/// This test verifies that:
+/// 1. pickNextJob will select and make transactions from NCR queue.
+/// 2. Requests are removed from the queue once selected
+/// 3. Requests for DHCIDs with transactions already in progress are not
+/// selected.
+/// 4. Requests with no matching servers are removed from the queue and
+/// discarded.
+TEST_F(D2UpdateMgrTest, pickNextJob) {
+ // Ensure we have at least 4 canned requests with which to work.
+ ASSERT_TRUE(canned_count_ >= 4);
+
+ // Put each transaction on the queue.
+ for (int i = 0; i < canned_count_; i++) {
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i]));
+ }
+
+ // Invoke pickNextJob canned_count_ times which should create a
+ // transaction for each canned ncr.
+ for (int i = 0; i < canned_count_; i++) {
+ EXPECT_NO_THROW(update_mgr_->pickNextJob());
+ EXPECT_EQ(i + 1, update_mgr_->getTransactionCount());
+ EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[i]->getDhcid()));
+ }
+
+ // Verify that the queue has been drained.
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+ // Now verify that a subsequent request for a DCHID for which a
+ // transaction is in progress, is not dequeued.
+ // First add the "subsequent" request.
+ dhcp_ddns::NameChangeRequestPtr
+ subsequent_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[2])));
+ EXPECT_NO_THROW(queue_mgr_->enqueue(subsequent_ncr));
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Verify that invoking pickNextJob:
+ // 1. does not throw
+ // 2. does not make a new transaction
+ // 3. does not dequeu the entry
+ EXPECT_NO_THROW(update_mgr_->pickNextJob());
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Clear out the queue and transaction list.
+ queue_mgr_->clearQueue();
+ update_mgr_->clearTransactionList();
+
+ // Make a forward change NCR with an FQDN that has no forward match.
+ dhcp_ddns::NameChangeRequestPtr
+ bogus_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[0])));
+ bogus_ncr->setForwardChange(true);
+ bogus_ncr->setReverseChange(false);
+ bogus_ncr->setFqdn("bogus.forward.domain.com");
+
+ // Put it on the queue up
+ ASSERT_NO_THROW(queue_mgr_->enqueue(bogus_ncr));
+
+ // Verify that invoking pickNextJob:
+ // 1. does not throw
+ // 2. does not make a new transaction
+ // 3. does dequeue the entry
+ EXPECT_NO_THROW(update_mgr_->pickNextJob());
+ EXPECT_EQ(0, update_mgr_->getTransactionCount());
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+ // Make a reverse change NCR with an FQDN that has no reverse match.
+ bogus_ncr.reset(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[0])));
+ bogus_ncr->setForwardChange(false);
+ bogus_ncr->setReverseChange(true);
+ bogus_ncr->setIpAddress("77.77.77.77");
+
+ // Verify that invoking pickNextJob:
+ // 1. does not throw
+ // 2. does not make a new transaction
+ // 3. does dequeue the entry
+ EXPECT_NO_THROW(update_mgr_->pickNextJob());
+ EXPECT_EQ(0, update_mgr_->getTransactionCount());
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+}
+
+/// @brief Tests D2UpdateManager's sweep method.
+/// Since sweep is primarly a wrapper around chechFinishedTransactions and
+/// pickNextJob, along with checks on maximum transaction limits, it mostly
+/// verifies that these three pieces work togther to move process jobs.
+/// Most of what is tested here is tested above.
+TEST_F(D2UpdateMgrTest, sweep) {
+ // Ensure we have at least 4 canned requests with which to work.
+ ASSERT_TRUE(canned_count_ >= 4);
+
+ // Set max transactions to same as current transaction count.
+ EXPECT_NO_THROW(update_mgr_->setMaxTransactions(canned_count_));
+ EXPECT_EQ(canned_count_, update_mgr_->getMaxTransactions());
+
+ // Put each transaction on the queue.
+ for (int i = 0; i < canned_count_; i++) {
+ EXPECT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i]));
+ }
+
+ // Invoke sweep canned_count_ times which should create a
+ // transaction for each canned ncr.
+ for (int i = 0; i < canned_count_; i++) {
+ EXPECT_NO_THROW(update_mgr_->sweep());
+ EXPECT_EQ(i + 1, update_mgr_->getTransactionCount());
+ EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[i]->getDhcid()));
+ }
+
+ // Verify that the queue has been drained.
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+ // Verify max transactions can't be less than current transaction count.
+ EXPECT_THROW(update_mgr_->setMaxTransactions(1), D2UpdateMgrError);
+
+ // Queue up a request for a DCHID which has a transaction in progress.
+ dhcp_ddns::NameChangeRequestPtr
+ subsequent_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[2])));
+ EXPECT_NO_THROW(queue_mgr_->enqueue(subsequent_ncr));
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Verify that invoking sweep, does not dequeue the job nor make a
+ // transaction for it.
+ EXPECT_NO_THROW(update_mgr_->sweep());
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Mark the transaction complete.
+ (canned_ncrs_[2])->setStatus(dhcp_ddns::ST_COMPLETED);
+
+ // Verify that invoking sweep, cleans up the completed transaction,
+ // dequeues the queued job and adds its transaction to the list.
+ EXPECT_NO_THROW(update_mgr_->sweep());
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+ // Queue up a request from a new DHCID.
+ dhcp_ddns::NameChangeRequestPtr
+ another_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[0])));
+ another_ncr->setDhcid("AABBCCDDEEFF");
+ EXPECT_NO_THROW(queue_mgr_->enqueue(another_ncr));
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Verify that sweep does not dequeue the new request as we are at
+ // transaction count.
+ EXPECT_NO_THROW(update_mgr_->sweep());
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Set max transactions to same as current transaction count.
+ EXPECT_NO_THROW(update_mgr_->setMaxTransactions(canned_count_ + 1));
+
+ // Verify that invoking sweep, dequeues the request and creates
+ // a transaction for it.
+ EXPECT_NO_THROW(update_mgr_->sweep());
+ EXPECT_EQ(canned_count_ + 1, update_mgr_->getTransactionCount());
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+ // Verify that clearing transaction list works.
+ EXPECT_NO_THROW(update_mgr_->clearTransactionList());
+ EXPECT_EQ(0, update_mgr_->getTransactionCount());
+}
+
+}
diff --git a/src/bin/d2/tests/d2_zone_unittests.cc b/src/bin/d2/tests/d2_zone_unittests.cc
new file mode 100644
index 0000000..853cdbe
--- /dev/null
+++ b/src/bin/d2/tests/d2_zone_unittests.cc
@@ -0,0 +1,75 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <d2/d2_zone.h>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::dns;
+
+namespace {
+
+// This test verifies that Zone object is created and its constructor sets
+// appropriate values for its members.
+TEST(D2ZoneTest, constructor) {
+ // Create first object.
+ D2Zone zone1(Name("example.com"), RRClass::ANY());
+ EXPECT_EQ("example.com.", zone1.getName().toText());
+ EXPECT_EQ(RRClass::ANY().getCode(), zone1.getClass().getCode());
+ // Create another object to make sure that constructor doesn't assign
+ // fixed values, but they change when constructor's parameters change.
+ D2Zone zone2(Name("foo.example.com"), RRClass::IN());
+ EXPECT_EQ("foo.example.com.", zone2.getName().toText());
+ EXPECT_EQ(RRClass::IN().getCode(), zone2.getClass().getCode());
+}
+
+// This test verifies that toText() function returns text representation of
+// of the zone in expected format.
+TEST(D2ZoneTest, toText) {
+ // Create first object.
+ D2Zone zone1(Name("example.com"), RRClass::ANY());
+ EXPECT_EQ("example.com. ANY SOA\n", zone1.toText());
+ // Create another object with different parameters to make sure that the
+ // function's output changes accordingly.
+ D2Zone zone2(Name("foo.example.com"), RRClass::IN());
+ EXPECT_EQ("foo.example.com. IN SOA\n", zone2.toText());
+}
+
+// This test verifies that the equality and inequality operators behave as
+// expected.
+TEST(D2ZoneTest, compare) {
+ const Name a("a"), b("b");
+ const RRClass in(RRClass::IN()), any(RRClass::ANY());
+
+ // Equality check
+ EXPECT_TRUE(D2Zone(a, any) == D2Zone(a, any));
+ EXPECT_FALSE(D2Zone(a, any) != D2Zone(a, any));
+
+ // Inequality check, objects differ by class.
+ EXPECT_FALSE(D2Zone(a, any) == D2Zone(a, in));
+ EXPECT_TRUE(D2Zone(a, any) != D2Zone(a, in));
+
+ // Inequality check, objects differ by name.
+ EXPECT_FALSE(D2Zone(a, any) == D2Zone(b, any));
+ EXPECT_TRUE(D2Zone(a, any) != D2Zone(b, any));
+
+ // Inequality check, objects differ by name and class.
+ EXPECT_FALSE(D2Zone(a, any) == D2Zone(b, in));
+ EXPECT_TRUE(D2Zone(a, any) != D2Zone(b, in));
+}
+
+} // End of anonymous namespace
diff --git a/src/bin/d2/tests/d_cfg_mgr_unittests.cc b/src/bin/d2/tests/d_cfg_mgr_unittests.cc
new file mode 100644
index 0000000..512b896
--- /dev/null
+++ b/src/bin/d2/tests/d_cfg_mgr_unittests.cc
@@ -0,0 +1,386 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <config/module_spec.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <d2/d_cfg_mgr.h>
+#include <d_test_stubs.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <config.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::config;
+using namespace isc::d2;
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief Test Class for verifying that configuration context cannot be null
+/// during construction.
+class DCtorTestCfgMgr : public DCfgMgrBase {
+public:
+ /// @brief Constructor - Note that is passes in an empty configuration
+ /// pointer to the base class constructor.
+ DCtorTestCfgMgr() : DCfgMgrBase(DCfgContextBasePtr()) {
+ }
+
+ /// @brief Destructor
+ virtual ~DCtorTestCfgMgr() {
+ }
+
+ /// @brief Dummy implementation as this method is abstract.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& /* element_id */) {
+ return (isc::dhcp::ParserPtr());
+ }
+};
+
+/// @brief Test fixture class for testing DCfgMgrBase class.
+/// It maintains an member instance of DStubCfgMgr and derives from
+/// ConfigParseTest fixture, thus providing methods for converting JSON
+/// strings to configuration element sets, checking parse results, and
+/// accessing the configuration context.
+class DStubCfgMgrTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ DStubCfgMgrTest():cfg_mgr_(new DStubCfgMgr) {
+ }
+
+ /// @brief Destructor
+ ~DStubCfgMgrTest() {
+ }
+
+ /// @brief Convenience method which returns a DStubContextPtr to the
+ /// configuration context.
+ ///
+ /// @return returns a DStubContextPtr.
+ DStubContextPtr getStubContext() {
+ return (boost::dynamic_pointer_cast<DStubContext>
+ (cfg_mgr_->getContext()));
+ }
+
+ /// @brief Configuration manager instance.
+ DStubCfgMgrPtr cfg_mgr_;
+};
+
+///@brief Tests basic construction/destruction of configuration manager.
+/// Verifies that:
+/// 1. Proper construction succeeds.
+/// 2. Configuration context is initialized by construction.
+/// 3. Destruction works properly.
+/// 4. Construction with a null context is not allowed.
+TEST(DCfgMgrBase, construction) {
+ DCfgMgrBasePtr cfg_mgr;
+
+ // Verify that configuration manager constructions without error.
+ ASSERT_NO_THROW(cfg_mgr.reset(new DStubCfgMgr()));
+
+ // Verify that the context can be retrieved and is not null.
+ DCfgContextBasePtr context = cfg_mgr->getContext();
+ EXPECT_TRUE(context);
+
+ // Verify that the manager can be destructed without error.
+ EXPECT_NO_THROW(cfg_mgr.reset());
+
+ // Verify that an attempt to construct a manger with a null context fails.
+ ASSERT_THROW(DCtorTestCfgMgr(), DCfgMgrBaseError);
+}
+
+///@brief Tests fundamental aspects of configuration parsing.
+/// Verifies that:
+/// 1. A correctly formed simple configuration parses without error.
+/// 2. An error building the element is handled.
+/// 3. An error committing the element is handled.
+/// 4. An unknown element error is handled.
+TEST_F(DStubCfgMgrTest, basicParseTest) {
+ // Create a simple configuration.
+ string config = "{ \"test-value\": 1000 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that we can parse a simple configuration.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that an error building the element is caught and returns a
+ // failed parse result.
+ SimFailure::set(SimFailure::ftElementBuild);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Verify that an error committing the element is caught and returns a
+ // failed parse result.
+ SimFailure::set(SimFailure::ftElementCommit);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Verify that an unknown element error is caught and returns a failed
+ // parse result.
+ SimFailure::set(SimFailure::ftElementUnknown);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+}
+
+///@brief Tests ordered and non-ordered element parsing
+/// This test verifies that:
+/// 1. Non-ordered parsing parses elements in the order they are presented
+/// by the configuration set (as-they-come).
+/// 2. A parse order list with too few elements is detected.
+/// 3. Ordered parsing parses the elements in the order specified by the
+/// configuration manager's parse order list.
+/// 4. A parse order list with too many elements is detected.
+TEST_F(DStubCfgMgrTest, parseOrderTest) {
+ // Element ids used for test.
+ std::string charlie("charlie");
+ std::string bravo("bravo");
+ std::string alpha("alpha");
+
+ // Create the test configuration with the elements in "random" order.
+
+ // NOTE that element sets produced by isc::data::Element::fromJSON(),
+ // are in lexical order by element_id. This means that iterating over
+ // such an element set, will present the elements in lexical order. Should
+ // this change, this test will need to be modified accordingly.
+ string config = "{ \"bravo\": 2, "
+ " \"alpha\": 1, "
+ " \"charlie\": 3 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that non-ordered parsing, results in an as-they-come parse order.
+ // Create an expected parse order.
+ // (NOTE that iterating over Element sets produced by fromJSON() will
+ // present the elements in lexical order. Should this change, the expected
+ // order list below would need to be changed accordingly).
+ ElementIdList order_expected;
+ order_expected.push_back(alpha);
+ order_expected.push_back(bravo);
+ order_expected.push_back(charlie);
+
+ // Verify that the manager has an EMPTY parse order list. (Empty list
+ // instructs the manager to parse them as-they-come.)
+ EXPECT_EQ(0, cfg_mgr_->getParseOrder().size());
+
+ // Parse the configuration, verify it parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that the parsed order matches what we expected.
+ EXPECT_TRUE(cfg_mgr_->parsed_order_ == order_expected);
+
+ // Clear the manager's parse order "memory".
+ cfg_mgr_->parsed_order_.clear();
+
+ // Create a parse order list that has too few entries. Verify that
+ // when parsing the test config, it fails.
+ cfg_mgr_->addToParseOrder(charlie);
+ // Verify the parse order list is the size we expect.
+ EXPECT_EQ(1, cfg_mgr_->getParseOrder().size());
+
+ // Verify the configuration fails.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Verify that the configuration parses correctly, when the parse order
+ // is correct. Add the needed entries to the parse order
+ cfg_mgr_->addToParseOrder(bravo);
+ cfg_mgr_->addToParseOrder(alpha);
+
+ // Verify the parse order list is the size we expect.
+ EXPECT_EQ(3, cfg_mgr_->getParseOrder().size());
+
+ // Clear the manager's parse order "memory".
+ cfg_mgr_->parsed_order_.clear();
+
+ // Verify the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that the parsed order is the order we configured.
+ EXPECT_TRUE(cfg_mgr_->getParseOrder() == cfg_mgr_->parsed_order_);
+
+ // Create a parse order list that has too many entries. Verify that
+ // when parsing the test config, it fails.
+ cfg_mgr_->addToParseOrder("delta");
+
+ // Verify the parse order list is the size we expect.
+ EXPECT_EQ(4, cfg_mgr_->getParseOrder().size());
+
+ // Verify the configuration fails.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+}
+
+/// @brief Tests that element ids supported by the base class as well as those
+/// added by the derived class function properly.
+/// This test verifies that:
+/// 1. Boolean parameters can be parsed and retrieved.
+/// 2. Uint32 parameters can be parsed and retrieved.
+/// 3. String parameters can be parsed and retrieved.
+/// 4. Derivation-specific parameters can be parsed and retrieved.
+/// 5. Parsing a second configuration, updates the existing context values
+/// correctly.
+TEST_F(DStubCfgMgrTest, simpleTypesTest) {
+ // Fetch a derivation specific pointer to the context.
+ DStubContextPtr context = getStubContext();
+ ASSERT_TRUE(context);
+
+ // Create a configuration with all of the parameters.
+ string config = "{ \"bool_test\": true , "
+ " \"uint32_test\": 77 , "
+ " \"string_test\": \"hmmm chewy\" , "
+ " \"extra_test\": 430 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+
+ // Verify that the boolean parameter was parsed correctly by retrieving
+ // its value from the context.
+ bool actual_bool = false;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_EQ(true, actual_bool);
+
+ // Verify that the uint32 parameter was parsed correctly by retrieving
+ // its value from the context.
+ uint32_t actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(77, actual_uint32);
+
+ // Verify that the string parameter was parsed correctly by retrieving
+ // its value from the context.
+ std::string actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("hmmm chewy", actual_string);
+
+ // Verify that the "extra" parameter was parsed correctly by retrieving
+ // its value from the context.
+ uint32_t actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(430, actual_extra);
+
+ // Create a configuration which "updates" all of the parameter values.
+ string config2 = "{ \"bool_test\": false , "
+ " \"uint32_test\": 88 , "
+ " \"string_test\": \"ewww yuk!\" , "
+ " \"extra_test\": 11 } ";
+ ASSERT_TRUE(fromJSON(config2));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that the boolean parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_bool = true;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_FALSE(actual_bool);
+
+ // Verify that the uint32 parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(88, actual_uint32);
+
+ // Verify that the string parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("ewww yuk!", actual_string);
+
+ // Verify that the "extra" parameter was updated correctly by retrieving
+ // its value from the context.
+ actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(11, actual_extra);
+}
+
+/// @brief Tests that the configuration context is preserved after failure
+/// during parsing causes a rollback.
+/// 1. Verifies configuration context rollback.
+TEST_F(DStubCfgMgrTest, rollBackTest) {
+ // Fetch a derivation specific pointer to the context.
+ DStubContextPtr context = getStubContext();
+ ASSERT_TRUE(context);
+
+ // Create a configuration with all of the parameters.
+ string config = "{ \"bool_test\": true , "
+ " \"uint32_test\": 77 , "
+ " \"string_test\": \"hmmm chewy\" , "
+ " \"extra_test\": 430 } ";
+ ASSERT_TRUE(fromJSON(config));
+
+ // Verify that the configuration parses without error.
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(0));
+
+ // Verify that all of parameters have the expected values.
+ bool actual_bool = false;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_EQ(true, actual_bool);
+
+ uint32_t actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(77, actual_uint32);
+
+ std::string actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("hmmm chewy", actual_string);
+
+ uint32_t actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(430, actual_extra);
+
+ // Create a configuration which "updates" all of the parameter values
+ // plus one unknown at the end.
+ string config2 = "{ \"bool_test\": false , "
+ " \"uint32_test\": 88 , "
+ " \"string_test\": \"ewww yuk!\" , "
+ " \"extra_test\": 11 , "
+ " \"zeta_unknown\": 33 } ";
+ ASSERT_TRUE(fromJSON(config2));
+
+ // Force a failure on the last element
+ SimFailure::set(SimFailure::ftElementUnknown);
+ answer_ = cfg_mgr_->parseConfig(config_set_);
+ EXPECT_TRUE(checkAnswer(1));
+
+ // Refresh our local pointer.
+ context = getStubContext();
+
+ // Verify that all of parameters have the original values.
+ actual_bool = false;
+ EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+ EXPECT_EQ(true, actual_bool);
+
+ actual_uint32 = 0;
+ EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+ EXPECT_EQ(77, actual_uint32);
+
+ actual_string = "";
+ EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+ EXPECT_EQ("hmmm chewy", actual_string);
+
+ actual_extra = 0;
+ EXPECT_NO_THROW(context->getExtraParam("extra_test", actual_extra));
+ EXPECT_EQ(430, actual_extra);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d_controller_unittests.cc b/src/bin/d2/tests/d_controller_unittests.cc
new file mode 100644
index 0000000..26b0e0e
--- /dev/null
+++ b/src/bin/d2/tests/d_controller_unittests.cc
@@ -0,0 +1,364 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <d_test_stubs.h>
+#include <d2/spec_config.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <config.h>
+#include <sstream>
+
+using namespace boost::posix_time;
+
+namespace isc {
+namespace d2 {
+
+/// @brief Test fixture class for testing DControllerBase class. This class
+/// derives from DControllerTest and wraps a DStubController. DStubController
+/// has been constructed to exercise DControllerBase.
+class DStubControllerTest : public DControllerTest {
+public:
+
+ /// @brief Constructor.
+ /// Note the constructor passes in the static DStubController instance
+ /// method.
+ DStubControllerTest() : DControllerTest (DStubController::instance) {
+ }
+
+ virtual ~DStubControllerTest() {
+ }
+};
+
+/// @brief Basic Controller instantiation testing.
+/// Verfies that the controller singleton gets created and that the
+/// basic derivation from the base class is intact.
+TEST_F(DStubControllerTest, basicInstanceTesting) {
+ // Verify that the singleton exists and it is the correct type.
+ DControllerBasePtr& controller = DControllerTest::getController();
+ ASSERT_TRUE(controller);
+ ASSERT_NO_THROW(boost::dynamic_pointer_cast<DStubController>(controller));
+
+ // Verify that controller's app name is correct.
+ EXPECT_TRUE(checkAppName(DStubController::stub_app_name_));
+
+ // Verify that controller's bin name is correct.
+ EXPECT_TRUE(checkBinName(DStubController::stub_bin_name_));
+
+ // Verify that controller's spec file name is correct.
+ EXPECT_TRUE(checkSpecFileName(D2_SPECFILE_LOCATION));
+
+ // Verify that controller's IOService exists.
+ EXPECT_TRUE(checkIOService());
+
+ // Verify that the Process does NOT exist.
+ EXPECT_FALSE(checkProcess());
+}
+
+/// @brief Tests basic command line processing.
+/// Verifies that:
+/// 1. Standard command line options are supported.
+/// 2. Custom command line options are supported.
+/// 3. Invalid options are detected.
+/// 4. Extraneous command line information is detected.
+TEST_F(DStubControllerTest, commandLineArgs) {
+
+ // Verify that both flags are false initially.
+ EXPECT_TRUE(checkStandAlone(false));
+ EXPECT_TRUE(checkVerbose(false));
+
+ // Verify that standard options can be parsed without error.
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-s"),
+ const_cast<char*>("-v") };
+ int argc = 3;
+ EXPECT_NO_THROW(parseArgs(argc, argv));
+
+ // Verify that flags are now true.
+ EXPECT_TRUE(checkStandAlone(true));
+ EXPECT_TRUE(checkVerbose(true));
+
+ // Verify that the custom command line option is parsed without error.
+ char xopt[3] = "- ";
+ xopt[1] = *DStubController::stub_option_x_;
+ char* argv1[] = { const_cast<char*>("progName"), xopt};
+ argc = 2;
+ EXPECT_NO_THROW (parseArgs(argc, argv1));
+
+ // Verify that an unknown option is detected.
+ char* argv2[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-bs") };
+ argc = 2;
+ EXPECT_THROW (parseArgs(argc, argv2), InvalidUsage);
+
+ // Verify that extraneous information is detected.
+ char* argv3[] = { const_cast<char*>("progName"),
+ const_cast<char*>("extra"),
+ const_cast<char*>("information") };
+ argc = 3;
+ EXPECT_THROW (parseArgs(argc, argv3), InvalidUsage);
+}
+
+/// @brief Tests application process creation and initialization.
+/// Verifies that:
+/// 1. An error during process creation is handled.
+/// 2. A NULL returned by process creation is handled.
+/// 3. An error during process initialization is handled.
+/// 4. Process can be successfully created and initialized.
+TEST_F(DStubControllerTest, initProcessTesting) {
+ // Verify that a failure during process creation is caught.
+ SimFailure::set(SimFailure::ftCreateProcessException);
+ EXPECT_THROW(initProcess(), DControllerBaseError);
+ EXPECT_FALSE(checkProcess());
+
+ // Verify that a NULL returned by process creation is handled.
+ SimFailure::set(SimFailure::ftCreateProcessNull);
+ EXPECT_THROW(initProcess(), DControllerBaseError);
+ EXPECT_FALSE(checkProcess());
+
+ // Re-create controller, verify that we are starting clean
+ resetController();
+ EXPECT_FALSE(checkProcess());
+
+ // Verify that an error during process initialization is handled.
+ SimFailure::set(SimFailure::ftProcessInit);
+ EXPECT_THROW(initProcess(), DProcessBaseError);
+
+ // Re-create controller, verify that we are starting clean
+ resetController();
+ EXPECT_FALSE(checkProcess());
+
+ // Verify that the application process can created and initialized.
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+}
+
+/// @brief Tests launch handling of invalid command line.
+/// This test launches with an invalid command line which should throw
+/// an InvalidUsage.
+TEST_F(DStubControllerTest, launchInvalidUsage) {
+ // Command line to run integrated
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-z") };
+ int argc = 2;
+
+ // Launch the controller in integrated mode.
+ EXPECT_THROW(launch(argc, argv), InvalidUsage);
+}
+
+/// @brief Tests launch handling of failure in application process
+/// initialization. This test launches with a valid command line but with
+/// SimFailure set to fail during process creation. Launch should throw
+/// ProcessInitError.
+TEST_F(DStubControllerTest, launchProcessInitError) {
+ // Command line to run integrated
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-s"),
+ const_cast<char*>("-v") };
+ int argc = 3;
+
+ // Launch the controller in stand alone mode.
+ SimFailure::set(SimFailure::ftCreateProcessException);
+ EXPECT_THROW(launch(argc, argv), ProcessInitError);
+}
+
+/// @brief Tests launch and normal shutdown (stand alone mode).
+/// This creates an interval timer to generate a normal shutdown and then
+/// launches with a valid, stand-alone command line and no simulated errors.
+TEST_F(DStubControllerTest, launchNormalShutdown) {
+ // command line to run standalone
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-s"),
+ const_cast<char*>("-v") };
+ int argc = 3;
+
+ // Use an asiolink IntervalTimer and callback to generate the
+ // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIOService());
+ timer.setup(genShutdownCallback, 2 * 1000);
+
+ // Record start time, and invoke launch().
+ ptime start = microsec_clock::universal_time();
+ EXPECT_NO_THROW(launch(argc, argv));
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2100);
+}
+
+/// @brief Tests launch with an operational error during application execution.
+/// This test creates an interval timer to generate a runtime exception during
+/// the process event loop. It launches wih a valid, stand-alone command line
+/// and no simulated errors. Launch should throw ProcessRunError.
+TEST_F(DStubControllerTest, launchRuntimeError) {
+ // command line to run standalone
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-s"),
+ const_cast<char*>("-v") };
+ int argc = 3;
+
+ // Use an asiolink IntervalTimer and callback to generate the
+ // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIOService());
+ timer.setup(genFatalErrorCallback, 2 * 1000);
+
+ // Record start time, and invoke launch().
+ ptime start = microsec_clock::universal_time();
+ EXPECT_THROW(launch(argc, argv), ProcessRunError);
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2100);
+}
+
+/// @brief Tests launch with a session establishment failure.
+/// This test launches with a valid command line for integrated mode and no.
+/// Attempting to connect to BIND10 should fail, even if BIND10 is running
+/// UNLESS the test is run as root. Launch should throw SessionStartError.
+TEST_F(DStubControllerTest, launchSessionFailure) {
+ // Command line to run integrated
+ char* argv[] = { (char*)"progName" };
+ int argc = 1;
+
+ // Launch the controller in integrated mode.
+ EXPECT_THROW(launch(argc, argv), SessionStartError);
+}
+
+/// @brief Configuration update event testing.
+/// This really tests just the ability of the handlers to invoke the necessary
+/// chain of methods and handle error conditions. Configuration parsing and
+/// retrieval should be tested as part of the d2 configuration management
+/// implementation. Note that this testing calls the configuration update event
+/// callback, configHandler, directly.
+/// This test verifies that:
+/// 1. Configuration will be rejected in integrated mode when there is no
+/// session established. (This is a very contrived situation).
+/// 2. In stand-alone mode a configuration update results in successful
+/// status return.
+/// 3. That an application process error in configuration updating is handled
+/// properly.
+TEST_F(DStubControllerTest, configUpdateTests) {
+ int rcode = -1;
+ isc::data::ConstElementPtr answer;
+
+ // Initialize the application process.
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ // Create a configuration set. Content is arbitrary, just needs to be
+ // valid JSON.
+ std::string config = "{ \"test-value\": 1000 } ";
+ isc::data::ElementPtr config_set = isc::data::Element::fromJSON(config);
+
+ // We are not stand-alone, so configuration should be rejected as there is
+ // no session. This is a pretty contrived situation that shouldn't be
+ // possible other than the handler being called directly (like this does).
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+
+ // Verify that in stand alone we get a successful update result.
+ setStandAlone(true);
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode);
+
+ // Verify that an error in process configure method is handled.
+ SimFailure::set(SimFailure::ftProcessConfigure);
+ answer = DControllerBase::configHandler(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+}
+
+/// @brief Command execution tests.
+/// This really tests just the ability of the handler to invoke the necessary
+/// chain of methods and to handle error conditions. Note that this testing
+/// calls the command callback, commandHandler, directly.
+/// This test verifies that:
+/// 1. That an unrecognized command is detected and returns a status of
+/// d2::COMMAND_INVALID.
+/// 2. Shutdown command is recognized and returns a d2::COMMAND_SUCCESS status.
+/// 3. A valid, custom controller command is recognized a d2::COMMAND_SUCCESS
+/// status.
+/// 4. A valid, custom process command is recognized a d2::COMMAND_SUCCESS
+/// status.
+/// 5. That a valid controller command that fails returns a d2::COMMAND_ERROR.
+/// 6. That a valid process command that fails returns a d2::COMMAND_ERROR.
+TEST_F(DStubControllerTest, executeCommandTests) {
+ int rcode = -1;
+ isc::data::ConstElementPtr answer;
+ isc::data::ElementPtr arg_set;
+
+ // Initialize the application process.
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ // Verify that an unknown command returns an d2::COMMAND_INVALID response.
+ std::string bogus_command("bogus");
+ answer = DControllerBase::commandHandler(bogus_command, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_INVALID, rcode);
+
+ // Verify that shutdown command returns d2::COMMAND_SUCCESS response.
+ answer = DControllerBase::commandHandler(SHUT_DOWN_COMMAND, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_SUCCESS, rcode);
+
+ // Verify that a valid custom controller command returns
+ // d2::COMMAND_SUCCESS response.
+ answer = DControllerBase::commandHandler(DStubController::
+ stub_ctl_command_, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_SUCCESS, rcode);
+
+ // Verify that a valid custom process command returns d2::COMMAND_SUCCESS
+ // response.
+ answer = DControllerBase::commandHandler(DStubProcess::
+ stub_proc_command_, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_SUCCESS, rcode);
+
+ // Verify that a valid custom controller command that fails returns
+ // a d2::COMMAND_ERROR.
+ SimFailure::set(SimFailure::ftControllerCommand);
+ answer = DControllerBase::commandHandler(DStubController::
+ stub_ctl_command_, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_ERROR, rcode);
+
+ // Verify that a valid custom process command that fails returns
+ // a d2::COMMAND_ERROR.
+ SimFailure::set(SimFailure::ftProcessCommand);
+ answer = DControllerBase::commandHandler(DStubProcess::
+ stub_proc_command_, arg_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(COMMAND_ERROR, rcode);
+}
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
diff --git a/src/bin/d2/tests/d_test_stubs.cc b/src/bin/d2/tests/d_test_stubs.cc
new file mode 100644
index 0000000..ab061ef
--- /dev/null
+++ b/src/bin/d2/tests/d_test_stubs.cc
@@ -0,0 +1,319 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <d2/d2_log.h>
+#include <d2/spec_config.h>
+#include <d2/tests/d_test_stubs.h>
+
+using namespace asio;
+
+namespace isc {
+namespace d2 {
+
+const char* valid_d2_config = "{ "
+ "\"interface\" : \"eth1\" , "
+ "\"ip_address\" : \"127.0.0.1\" , "
+ "\"port\" : 5031, "
+ "\"tsig_keys\": ["
+ "{ \"name\": \"d2_key.tmark.org\" , "
+ " \"algorithm\": \"md5\" ,"
+ " \"secret\": \"0123456989\" "
+ "} ],"
+ "\"forward_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \"tmark.org\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"hostname\": \"one.tmark\" } "
+ "] } ] }, "
+ "\"reverse_ddns\" : {"
+ "\"ddns_domains\": [ "
+ "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+ " \"key_name\": \"d2_key.tmark.org\" , "
+ " \"dns_servers\" : [ "
+ " { \"ip_address\": \"127.0.0.101\" , "
+ " \"port\": 100 } ] } "
+ "] } }";
+
+// Initialize the static failure flag.
+SimFailure::FailureType SimFailure::failure_type_ = SimFailure::ftNoFailure;
+
+// Define custom process command supported by DStubProcess.
+const char* DStubProcess::stub_proc_command_("cool_proc_cmd");
+
+DStubProcess::DStubProcess(const char* name, IOServicePtr io_service)
+ : DProcessBase(name, io_service, DCfgMgrBasePtr(new DStubCfgMgr())) {
+};
+
+void
+DStubProcess::init() {
+ if (SimFailure::shouldFailOn(SimFailure::ftProcessInit)) {
+ // Simulates a failure to instantiate the process.
+ isc_throw(DProcessBaseError, "DStubProcess simulated init() failure");
+ }
+};
+
+void
+DStubProcess::run() {
+ // Until shut down or an fatal error occurs, wait for and
+ // execute a single callback. This is a preliminary implementation
+ // that is likely to evolve as development progresses.
+ // To use run(), the "managing" layer must issue an io_service::stop
+ // or the call to run will continue to block, and shutdown will not
+ // occur.
+ IOServicePtr& io_service = getIoService();
+ while (!shouldShutdown()) {
+ try {
+ io_service->run_one();
+ } catch (const std::exception& ex) {
+ isc_throw (DProcessBaseError,
+ std::string("Process run method failed:") + ex.what());
+ }
+ }
+};
+
+isc::data::ConstElementPtr
+DStubProcess::shutdown(isc::data::ConstElementPtr /* args */) {
+ if (SimFailure::shouldFailOn(SimFailure::ftProcessShutdown)) {
+ // Simulates a failure during shutdown process.
+ isc_throw(DProcessBaseError, "DStubProcess simulated shutdown failure");
+ }
+
+ setShutdownFlag(true);
+ stopIOService();
+ return (isc::config::createAnswer(0, "Shutdown inititiated."));
+}
+
+isc::data::ConstElementPtr
+DStubProcess::configure(isc::data::ConstElementPtr /*config_set*/) {
+ if (SimFailure::shouldFailOn(SimFailure::ftProcessConfigure)) {
+ // Simulates a process configure failure.
+ return (isc::config::createAnswer(1,
+ "Simulated process configuration error."));
+ }
+
+ return (isc::config::createAnswer(0, "Configuration accepted."));
+}
+
+isc::data::ConstElementPtr
+DStubProcess::command(const std::string& command,
+ isc::data::ConstElementPtr /* args */) {
+ isc::data::ConstElementPtr answer;
+ if (SimFailure::shouldFailOn(SimFailure::ftProcessCommand)) {
+ // Simulates a process command execution failure.
+ answer = isc::config::createAnswer(COMMAND_ERROR,
+ "SimFailure::ftProcessCommand");
+ } else if (command.compare(stub_proc_command_) == 0) {
+ answer = isc::config::createAnswer(COMMAND_SUCCESS, "Command accepted");
+ } else {
+ answer = isc::config::createAnswer(COMMAND_INVALID,
+ "Unrecognized command:" + command);
+ }
+
+ return (answer);
+}
+
+DStubProcess::~DStubProcess() {
+};
+
+//************************** DStubController *************************
+
+// Define custom controller command supported by DStubController.
+const char* DStubController::stub_ctl_command_("spiffy");
+
+// Define custom command line option command supported by DStubController.
+const char* DStubController::stub_option_x_ = "x";
+
+/// @brief Defines the app name used to construct the controller
+const char* DStubController::stub_app_name_ = "TestService";
+
+/// @brief Defines the bin name used to construct the controller
+const char* DStubController::stub_bin_name_ = "TestBin";
+
+DControllerBasePtr&
+DStubController::instance() {
+ // If the singleton hasn't been created, do it now.
+ if (!getController()) {
+ DControllerBasePtr p(new DStubController());
+ setController(p);
+ }
+
+ return (getController());
+}
+
+DStubController::DStubController()
+ : DControllerBase(stub_app_name_, stub_bin_name_) {
+
+ if (getenv("B10_FROM_BUILD")) {
+ setSpecFileName(std::string(getenv("B10_FROM_BUILD")) +
+ "/src/bin/d2/dhcp-ddns.spec");
+ } else {
+ setSpecFileName(D2_SPECFILE_LOCATION);
+ }
+}
+
+bool
+DStubController::customOption(int option, char* /* optarg */)
+{
+ // Check for the custom option supported by DStubController.
+ if (static_cast<char>(option) == *stub_option_x_) {
+ return (true);
+ }
+
+ return (false);
+}
+
+DProcessBase* DStubController::createProcess() {
+ if (SimFailure::shouldFailOn(SimFailure::ftCreateProcessException)) {
+ // Simulates a failure to instantiate the process due to exception.
+ throw std::runtime_error("SimFailure::ftCreateProcess");
+ }
+
+ if (SimFailure::shouldFailOn(SimFailure::ftCreateProcessNull)) {
+ // Simulates a failure to instantiate the process.
+ return (NULL);
+ }
+
+ // This should be a successful instantiation.
+ return (new DStubProcess(getAppName().c_str(), getIOService()));
+}
+
+isc::data::ConstElementPtr
+DStubController::customControllerCommand(const std::string& command,
+ isc::data::ConstElementPtr /* args */) {
+ isc::data::ConstElementPtr answer;
+ if (SimFailure::shouldFailOn(SimFailure::ftControllerCommand)) {
+ // Simulates command failing to execute.
+ answer = isc::config::createAnswer(COMMAND_ERROR,
+ "SimFailure::ftControllerCommand");
+ } else if (command.compare(stub_ctl_command_) == 0) {
+ answer = isc::config::createAnswer(COMMAND_SUCCESS, "Command accepted");
+ } else {
+ answer = isc::config::createAnswer(COMMAND_INVALID,
+ "Unrecognized command:" + command);
+ }
+
+ return (answer);
+}
+
+const std::string DStubController::getCustomOpts() const {
+ // Return the "list" of custom options supported by DStubController.
+ return (std::string(stub_option_x_));
+}
+
+DStubController::~DStubController() {
+}
+
+// Initialize controller wrapper's static instance getter member.
+DControllerTest::InstanceGetter DControllerTest::instanceGetter_ = NULL;
+
+//************************** TestParser *************************
+
+TestParser::TestParser(const std::string& param_name):param_name_(param_name) {
+}
+
+TestParser::~TestParser(){
+}
+
+void
+TestParser::build(isc::data::ConstElementPtr new_config) {
+ if (SimFailure::shouldFailOn(SimFailure::ftElementBuild)) {
+ // Simulates an error during element data parsing.
+ isc_throw (DCfgMgrBaseError, "Simulated build exception");
+ }
+
+ value_ = new_config;
+}
+
+void
+TestParser::commit() {
+ if (SimFailure::shouldFailOn(SimFailure::ftElementCommit)) {
+ // Simulates an error while committing the parsed element data.
+ throw std::runtime_error("Simulated commit exception");
+ }
+}
+
+//************************** DStubContext *************************
+
+DStubContext::DStubContext(): extra_values_(new isc::dhcp::Uint32Storage()) {
+}
+
+DStubContext::~DStubContext() {
+}
+
+void
+DStubContext::getExtraParam(const std::string& name, uint32_t& value) {
+ value = extra_values_->getParam(name);
+}
+
+isc::dhcp::Uint32StoragePtr
+DStubContext::getExtraStorage() {
+ return (extra_values_);
+}
+
+DCfgContextBasePtr
+DStubContext::clone() {
+ return (DCfgContextBasePtr(new DStubContext(*this)));
+}
+
+DStubContext::DStubContext(const DStubContext& rhs): DCfgContextBase(rhs),
+ extra_values_(new isc::dhcp::Uint32Storage(*(rhs.extra_values_))) {
+}
+
+//************************** DStubCfgMgr *************************
+
+DStubCfgMgr::DStubCfgMgr()
+ : DCfgMgrBase(DCfgContextBasePtr(new DStubContext())) {
+}
+
+DStubCfgMgr::~DStubCfgMgr() {
+}
+
+isc::dhcp::ParserPtr
+DStubCfgMgr::createConfigParser(const std::string& element_id) {
+ isc::dhcp::DhcpConfigParser* parser = NULL;
+ DStubContextPtr context =
+ boost::dynamic_pointer_cast<DStubContext>(getContext());
+
+ if (element_id == "bool_test") {
+ parser = new isc::dhcp::BooleanParser(element_id,
+ context->getBooleanStorage());
+ } else if (element_id == "uint32_test") {
+ parser = new isc::dhcp::Uint32Parser(element_id,
+ context->getUint32Storage());
+ } else if (element_id == "string_test") {
+ parser = new isc::dhcp::StringParser(element_id,
+ context->getStringStorage());
+ } else if (element_id == "extra_test") {
+ parser = new isc::dhcp::Uint32Parser(element_id,
+ context->getExtraStorage());
+ } else {
+ // Fail only if SimFailure dictates we should. This makes it easier
+ // to test parse ordering, by permitting a wide range of element ids
+ // to "succeed" without specifically supporting them.
+ if (SimFailure::shouldFailOn(SimFailure::ftElementUnknown)) {
+ isc_throw(DCfgMgrBaseError, "Configuration parameter not supported: "
+ << element_id);
+ }
+
+ parsed_order_.push_back(element_id);
+ parser = new TestParser(element_id);
+ }
+
+ return (isc::dhcp::ParserPtr(parser));
+}
+
+
+}; // namespace isc::d2
+}; // namespace isc
diff --git a/src/bin/d2/tests/d_test_stubs.h b/src/bin/d2/tests/d_test_stubs.h
new file mode 100644
index 0000000..1313687
--- /dev/null
+++ b/src/bin/d2/tests/d_test_stubs.h
@@ -0,0 +1,667 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 D_TEST_STUBS_H
+#define D_TEST_STUBS_H
+
+#include <asiolink/asiolink.h>
+#include <cc/data.h>
+#include <cc/session.h>
+#include <config/ccsession.h>
+
+#include <d2/d_controller.h>
+#include <d2/d_cfg_mgr.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Provides a valid DHCP-DDNS configuration for testing basic
+/// parsing fundamentals.
+extern const char* valid_d2_config;
+
+
+/// @brief Class is used to set a globally accessible value that indicates
+/// a specific type of failure to simulate. Test derivations of base classes
+/// can exercise error handling code paths by testing for specific SimFailure
+/// values at the appropriate places and then causing the error to "occur".
+/// The class consists of an enumerated set of failures, and static methods
+/// for getting, setting, and testing the current value.
+class SimFailure {
+public:
+ enum FailureType {
+ ftUnknown = -1,
+ ftNoFailure = 0,
+ ftCreateProcessException,
+ ftCreateProcessNull,
+ ftProcessInit,
+ ftProcessConfigure,
+ ftControllerCommand,
+ ftProcessCommand,
+ ftProcessShutdown,
+ ftElementBuild,
+ ftElementCommit,
+ ftElementUnknown
+ };
+
+ /// @brief Sets the SimFailure value to the given value.
+ ///
+ /// @param value is the new value to assign to the global value.
+ static void set(enum FailureType value) {
+ failure_type_ = value;
+ }
+
+ /// @brief Gets the current global SimFailure value
+ ///
+ /// @return returns the current SimFailure value
+ static enum FailureType get() {
+ return (failure_type_);
+ }
+
+ /// @brief One-shot test of the SimFailure value. If the global
+ /// SimFailure value is equal to the given value, clear the global
+ /// value and return true. This makes it convenient for code to
+ /// test and react without having to explicitly clear the global
+ /// value.
+ ///
+ /// @param value is the value against which the global value is
+ /// to be compared.
+ ///
+ /// @return returns true if current SimFailure value matches the
+ /// given value.
+ static bool shouldFailOn(enum FailureType value) {
+ if (failure_type_ == value) {
+ clear();
+ return (true);
+ }
+
+ return (false);
+ }
+
+ /// @brief Resets the failure type to none.
+ static void clear() {
+ failure_type_ = ftNoFailure;
+ }
+
+ /// @brief Static value for holding the failure type to simulate.
+ static enum FailureType failure_type_;
+};
+
+/// @brief Test Derivation of the DProcessBase class.
+///
+/// This class is used primarily to server as a test process class for testing
+/// DControllerBase. It provides minimal, but sufficient implementation to
+/// test the majority of DControllerBase functionality.
+class DStubProcess : public DProcessBase {
+public:
+
+ /// @brief Static constant that defines a custom process command string.
+ static const char* stub_proc_command_;
+
+ /// @brief Constructor
+ ///
+ /// @param name name is a text label for the process. Generally used
+ /// in log statements, but otherwise arbitrary.
+ /// @param io_service is the io_service used by the caller for
+ /// asynchronous event handling.
+ ///
+ /// @throw DProcessBaseError is io_service is NULL.
+ DStubProcess(const char* name, IOServicePtr io_service);
+
+ /// @brief Invoked after process instantiation to perform initialization.
+ /// This implementation supports simulating an error initializing the
+ /// process by throwing a DProcessBaseError if SimFailure is set to
+ /// ftProcessInit.
+ virtual void init();
+
+ /// @brief Implements the process's event loop.
+ /// This implementation is quite basic, surrounding calls to
+ /// io_service->runOne() with a test of the shutdown flag. Once invoked,
+ /// the method will continue until the process itself is exiting due to a
+ /// request to shutdown or some anomaly forces an exit.
+ /// @return returns 0 upon a successful, "normal" termination, non-zero to
+ /// indicate an abnormal termination.
+ virtual void run();
+
+ /// @brief Implements the process shutdown procedure.
+ ///
+ /// This sets the instance shutdown flag monitored by run() and stops
+ /// the IO service.
+ virtual isc::data::ConstElementPtr shutdown(isc::data::ConstElementPtr);
+
+ /// @brief Processes the given configuration.
+ ///
+ /// This implementation fails if SimFailure is set to ftProcessConfigure.
+ /// Otherwise it will complete successfully. It does not check the content
+ /// of the inbound configuration.
+ ///
+ /// @param config_set a new configuration (JSON) for the process
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr configure(isc::data::ConstElementPtr
+ config_set);
+
+ /// @brief Executes the given command.
+ ///
+ /// This implementation will recognizes one "custom" process command,
+ /// stub_proc_command_. It will fail if SimFailure is set to
+ /// ftProcessCommand.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command.
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value and a string explanation of the outcome.
+ /// The status value is:
+ /// COMMAND_SUCCESS if the command is recognized and executes successfully.
+ /// COMMAND_ERROR if the command is recognized but fails to execute.
+ /// COMMAND_INVALID if the command is not recognized.
+ virtual isc::data::ConstElementPtr command(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ // @brief Destructor
+ virtual ~DStubProcess();
+};
+
+
+/// @brief Test Derivation of the DControllerBase class.
+///
+/// DControllerBase is an abstract class and therefore requires a derivation
+/// for testing. It allows testing the majority of the base class code
+/// without polluting production derivations (e.g. D2Process). It uses
+/// DStubProcess as its application process class. It is a full enough
+/// implementation to support running both stand alone and integrated.
+/// Obviously BIND10 connectivity is not available under unit tests, so
+/// testing here is limited to "failures" to communicate with BIND10.
+class DStubController : public DControllerBase {
+public:
+ /// @brief Static singleton instance method. This method returns the
+ /// base class singleton instance member. It instantiates the singleton
+ /// and sets the base class instance member upon first invocation.
+ ///
+ /// @return returns a pointer reference to the singleton instance.
+ static DControllerBasePtr& instance();
+
+ /// @brief Defines a custom controller command string. This is a
+ /// custom command supported by DStubController.
+ static const char* stub_ctl_command_;
+
+ /// @brief Defines a custom command line option supported by
+ /// DStubController.
+ static const char* stub_option_x_;
+
+ /// @brief Defines the app name used to construct the controller
+ static const char* stub_app_name_;
+
+ /// @brief Defines the executable name used to construct the controller
+ static const char* stub_bin_name_;
+
+protected:
+ /// @brief Handles additional command line options that are supported
+ /// by DStubController. This implementation supports an option "-x".
+ ///
+ /// @param option is the option "character" from the command line, without
+ /// any prefixing hyphen(s)
+ /// @optarg optarg is the argument value (if one) associated with the option
+ ///
+ /// @return returns true if the option is "x", otherwise ti returns false.
+ virtual bool customOption(int option, char *optarg);
+
+ /// @brief Instantiates an instance of DStubProcess.
+ ///
+ /// This implementation will fail if SimFailure is set to
+ /// ftCreateProcessException OR ftCreateProcessNull.
+ ///
+ /// @return returns a pointer to the new process instance (DProcessBase*)
+ /// or NULL if SimFailure is set to ftCreateProcessNull.
+ /// @throw throws std::runtime_error if SimFailure is set to
+ /// ftCreateProcessException.
+ virtual DProcessBase* createProcess();
+
+ /// @brief Executes custom controller commands are supported by
+ /// DStubController. This implementation supports one custom controller
+ /// command, stub_ctl_command_. It will fail if SimFailure is set
+ /// to ftControllerCommand.
+ ///
+ /// @param command is a string label representing the command to execute.
+ /// @param args is a set of arguments (if any) required for the given
+ /// command.
+ /// @return an Element that contains the results of command composed
+ /// of an integer status value and a string explanation of the outcome.
+ /// The status value is:
+ /// COMMAND_SUCCESS if the command is recognized and executes successfully.
+ /// COMMAND_ERROR if the command is recognized but fails to execute.
+ /// COMMAND_INVALID if the command is not recognized.
+ virtual isc::data::ConstElementPtr customControllerCommand(
+ const std::string& command, isc::data::ConstElementPtr args);
+
+ /// @brief Provides a string of the additional command line options
+ /// supported by DStubController. DStubController supports one
+ /// addition option, stub_option_x_.
+ ///
+ /// @return returns a string containing the option letters.
+ virtual const std::string getCustomOpts() const;
+
+private:
+ /// @brief Constructor is private to protect singleton integrity.
+ DStubController();
+
+public:
+ virtual ~DStubController();
+};
+
+/// @brief Abstract Test fixture class that wraps a DControllerBase. This class
+/// is a friend class of DControllerBase which allows it access to class
+/// content to facilitate testing. It provides numerous wrapper methods for
+/// the protected and private methods and member of the base class.
+class DControllerTest : public ::testing::Test {
+public:
+
+ /// @brief Defines a function pointer for controller singleton fetchers.
+ typedef DControllerBasePtr& (*InstanceGetter)();
+
+ /// @brief Static storage of the controller class's singleton fetcher.
+ /// We need this this statically available for callbacks.
+ static InstanceGetter instanceGetter_;
+
+ /// @brief Constructor
+ ///
+ /// @param instance_getter is a function pointer to the static instance
+ /// method of the DControllerBase derivation under test.
+ DControllerTest(InstanceGetter instance_getter) {
+ // Set the static fetcher member, then invoke it via getController.
+ // This ensures the singleton is instantiated.
+ instanceGetter_ = instance_getter;
+ getController();
+ }
+
+ /// @brief Destructor
+ /// Note the controller singleton is destroyed. This is essential to ensure
+ /// a clean start between tests.
+ virtual ~DControllerTest() {
+ getController().reset();
+ }
+
+ /// @brief Convenience method that destructs and then recreates the
+ /// controller singleton under test. This is handy for tests within
+ /// tests.
+ void resetController() {
+ getController().reset();
+ getController();
+ }
+
+ /// @brief Static method which returns the instance of the controller
+ /// under test.
+ /// @return returns a reference to the controller instance.
+ static DControllerBasePtr& getController() {
+ return ((*instanceGetter_)());
+ }
+
+ /// @brief Returns true if the Controller's app name matches the
+ /// given value.
+ ///
+ /// @param should_be is the value to compare against.
+ ///
+ /// @return returns true if the values are equal.
+ bool checkAppName(const std::string& should_be) {
+ return (getController()->getAppName().compare(should_be) == 0);
+ }
+
+ /// @brief Returns true if the Controller's service name matches the
+ /// given value.
+ ///
+ /// @param should_be is the value to compare against.
+ ///
+ /// @return returns true if the values are equal.
+ bool checkBinName(const std::string& should_be) {
+ return (getController()->getBinName().compare(should_be) == 0);
+ }
+
+ /// @brief Returns true if the Controller's spec file name matches the
+ /// given value.
+ ///
+ /// @param should_be is the value to compare against.
+ ///
+ /// @return returns true if the values are equal.
+ bool checkSpecFileName(const std::string& should_be) {
+ return (getController()->getSpecFileName().compare(should_be) == 0);
+ }
+
+ /// @brief Tests the existence of the Controller's application process.
+ ///
+ /// @return returns true if the process instance exists.
+ bool checkProcess() {
+ return (getController()->process_);
+ }
+
+ /// @brief Tests the existence of the Controller's IOService.
+ ///
+ /// @return returns true if the IOService exists.
+ bool checkIOService() {
+ return (getController()->io_service_);
+ }
+
+ /// @brief Gets the Controller's IOService.
+ ///
+ /// @return returns a reference to the IOService
+ IOServicePtr& getIOService() {
+ return (getController()->io_service_);
+ }
+
+ /// @brief Compares stand alone flag with the given value.
+ ///
+ /// @param value
+ ///
+ /// @return returns true if the stand alone flag is equal to the given
+ /// value.
+ bool checkStandAlone(bool value) {
+ return (getController()->isStandAlone() == value);
+ }
+
+ /// @brief Sets the controller's stand alone flag to the given value.
+ ///
+ /// @param value is the new value to assign.
+ ///
+ void setStandAlone(bool value) {
+ getController()->setStandAlone(value);
+ }
+
+ /// @brief Compares verbose flag with the given value.
+ ///
+ /// @param value
+ ///
+ /// @return returns true if the verbose flag is equal to the given value.
+ bool checkVerbose(bool value) {
+ return (getController()->isVerbose() == value);
+ }
+
+ /// @Wrapper to invoke the Controller's parseArgs method. Please refer to
+ /// DControllerBase::parseArgs for details.
+ void parseArgs(int argc, char* argv[]) {
+ getController()->parseArgs(argc, argv);
+ }
+
+ /// @Wrapper to invoke the Controller's init method. Please refer to
+ /// DControllerBase::init for details.
+ void initProcess() {
+ getController()->initProcess();
+ }
+
+ /// @Wrapper to invoke the Controller's establishSession method. Please
+ /// refer to DControllerBase::establishSession for details.
+ void establishSession() {
+ getController()->establishSession();
+ }
+
+ /// @Wrapper to invoke the Controller's launch method. Please refer to
+ /// DControllerBase::launch for details.
+ void launch(int argc, char* argv[]) {
+ optind = 1;
+ getController()->launch(argc, argv, true);
+ }
+
+ /// @Wrapper to invoke the Controller's disconnectSession method. Please
+ /// refer to DControllerBase::disconnectSession for details.
+ void disconnectSession() {
+ getController()->disconnectSession();
+ }
+
+ /// @Wrapper to invoke the Controller's updateConfig method. Please
+ /// refer to DControllerBase::updateConfig for details.
+ isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr
+ new_config) {
+ return (getController()->updateConfig(new_config));
+ }
+
+ /// @Wrapper to invoke the Controller's executeCommand method. Please
+ /// refer to DControllerBase::executeCommand for details.
+ isc::data::ConstElementPtr executeCommand(const std::string& command,
+ isc::data::ConstElementPtr args){
+ return (getController()->executeCommand(command, args));
+ }
+
+ /// @brief Callback that will generate shutdown command via the
+ /// command callback function.
+ static void genShutdownCallback() {
+ isc::data::ElementPtr arg_set;
+ DControllerBase::commandHandler(SHUT_DOWN_COMMAND, arg_set);
+ }
+
+ /// @brief Callback that throws an exception.
+ static void genFatalErrorCallback() {
+ isc_throw (DProcessBaseError, "simulated fatal error");
+ }
+};
+
+/// @brief Simple parser derivation for testing the basics of configuration
+/// parsing.
+class TestParser : public isc::dhcp::DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @param param_name name of the parsed parameter
+ TestParser(const std::string& param_name);
+
+ /// @brief Destructor
+ virtual ~TestParser();
+
+ /// @brief Builds parameter value.
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @param new_config pointer to the new configuration
+ /// @throw throws DCfgMgrBaseError if the SimFailure is set to
+ /// ftElementBuild. This allows for the simulation of an
+ /// exception during the build portion of parsing an element.
+ virtual void build(isc::data::ConstElementPtr new_config);
+
+ /// @brief Commits the parsed value to storage.
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @throw throws DCfgMgrBaseError if SimFailure is set to ftElementCommit.
+ /// This allows for the simulation of an exception during the commit
+ /// portion of parsing an element.
+ virtual void commit();
+
+private:
+ /// name of the parsed parameter
+ std::string param_name_;
+
+ /// pointer to the parsed value of the parameter
+ isc::data::ConstElementPtr value_;
+};
+
+/// @brief Test Derivation of the DCfgContextBase class.
+///
+/// This class is used to test basic functionality of configuration context.
+/// It adds an additional storage container "extra values" to mimic an
+/// application extension of configuration storage. This permits testing that
+/// both the base class content as well as the application content is
+/// correctly copied during cloning. This is vital to configuration backup
+/// and rollback during configuration parsing.
+class DStubContext : public DCfgContextBase {
+public:
+
+ /// @brief Constructor
+ DStubContext();
+
+ /// @brief Destructor
+ virtual ~DStubContext();
+
+ /// @brief Fetches the value for a given "extra" configuration parameter
+ /// from the context.
+ ///
+ /// @param name is the name of the parameter to retrieve.
+ /// @param value is an output parameter in which to return the retrieved
+ /// value.
+ /// @throw throws DhcpConfigError if the context does not contain the
+ /// parameter.
+ void getExtraParam(const std::string& name, uint32_t& value);
+
+ /// @brief Fetches the extra storage.
+ ///
+ /// @return returns a pointer to the extra storage.
+ isc::dhcp::Uint32StoragePtr getExtraStorage();
+
+ /// @brief Creates a clone of a DStubContext.
+ ///
+ /// @return returns a pointer to the new clone.
+ virtual DCfgContextBasePtr clone();
+
+protected:
+ /// @brief Copy constructor
+ DStubContext(const DStubContext& rhs);
+
+private:
+ /// @brief Private assignment operator, not implemented.
+ DStubContext& operator=(const DStubContext& rhs);
+
+ /// @brief Extra storage for uint32 parameters.
+ isc::dhcp::Uint32StoragePtr extra_values_;
+};
+
+/// @brief Defines a pointer to DStubContext.
+typedef boost::shared_ptr<DStubContext> DStubContextPtr;
+
+/// @brief Test Derivation of the DCfgMgrBase class.
+///
+/// This class is used to test basic functionality of configuration management.
+/// It supports the following configuration elements:
+///
+/// "bool_test" - Boolean element, tests parsing and committing a boolean
+/// configuration parameter.
+/// "uint32_test" - Uint32 element, tests parsing and committing a uint32_t
+/// configuration parameter.
+/// "string_test" - String element, tests parsing and committing a string
+/// configuration parameter.
+/// "extra_test" - "Extra" element, tests parsing and committing an extra
+/// configuration parameter. (This is used to demonstrate
+/// derivation's addition of storage to configuration context.
+///
+/// It also keeps track of the element ids that are parsed in the order they
+/// are parsed. This is used to test ordered and non-ordered parsing.
+class DStubCfgMgr : public DCfgMgrBase {
+public:
+ /// @brief Constructor
+ DStubCfgMgr();
+
+ /// @brief Destructor
+ virtual ~DStubCfgMgr();
+
+ /// @brief Given an element_id returns an instance of the appropriate
+ /// parser. It supports the element ids as described in the class brief.
+ ///
+ /// @param element_id is the string name of the element as it will appear
+ /// in the configuration set.
+ ///
+ /// @return returns a ParserPtr to the parser instance.
+ /// @throw throws DCfgMgrBaseError if SimFailure is ftElementUnknown.
+ virtual isc::dhcp::ParserPtr
+ createConfigParser(const std::string& element_id);
+
+ /// @brief A list for remembering the element ids in the order they were
+ /// parsed.
+ ElementIdList parsed_order_;
+};
+
+/// @brief Defines a pointer to DStubCfgMgr.
+typedef boost::shared_ptr<DStubCfgMgr> DStubCfgMgrPtr;
+
+/// @brief Test fixture base class for any fixtures which test parsing.
+/// It provides methods for converting JSON strings to configuration element
+/// sets and checking parse results
+class ConfigParseTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ ConfigParseTest(){
+ }
+
+ /// @brief Destructor
+ ~ConfigParseTest() {
+ }
+
+ /// @brief Converts a given JSON string into an Element set and stores the
+ /// result the member variable, config_set_.
+ ///
+ /// @param json_text contains the configuration text in JSON format to
+ /// convert.
+ /// @return returns AssertionSuccess if there were no parsing errors,
+ /// AssertionFailure otherwise.
+ ::testing::AssertionResult fromJSON(const std::string& json_text) {
+ try {
+ config_set_ = isc::data::Element::fromJSON(json_text);
+ } catch (const isc::Exception &ex) {
+ return (::testing::AssertionFailure(::testing::Message() <<
+ "JSON text failed to parse:"
+ << ex.what()));
+ }
+
+ return (::testing::AssertionSuccess());
+ }
+
+ /// @brief Compares the status in the parse result stored in member
+ /// variable answer_ to a given value.
+ ///
+ /// @param should_be is an integer against which to compare the status.
+ ///
+ /// @return returns AssertionSuccess if there were no parsing errors,
+ /// AssertionFailure otherwise.
+ ::testing::AssertionResult checkAnswer(int should_be) {
+ return (checkAnswer(answer_, should_be));
+ }
+
+ /// @brief Compares the status in the given parse result to a given value.
+ ///
+ /// @param answer Element set containing an integer response and string
+ /// comment.
+ /// @param should_be is an integer against which to compare the status.
+ ///
+ /// @return returns AssertionSuccess if there were no parsing errors,
+ /// AssertionFailure otherwise.
+ ::testing::AssertionResult checkAnswer(isc::data::ConstElementPtr answer,
+ int should_be) {
+ int rcode = 0;
+ isc::data::ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer);
+ if (rcode == should_be) {
+ return (testing::AssertionSuccess());
+ }
+
+ return (::testing::AssertionFailure(::testing::Message() <<
+ "checkAnswer rcode:" << rcode
+ << " comment: " << *comment));
+ }
+
+ /// @brief Configuration set being tested.
+ isc::data::ElementPtr config_set_;
+
+ /// @brief Results of most recent element parsing.
+ isc::data::ConstElementPtr answer_;
+};
+
+/// @brief Defines a small but valid DHCP-DDNS compliant configuration for
+/// testing configuration parsing fundamentals.
+extern const char* valid_d2_config;
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif
diff --git a/src/bin/d2/tests/dns_client_unittests.cc b/src/bin/d2/tests/dns_client_unittests.cc
new file mode 100644
index 0000000..9105ab8
--- /dev/null
+++ b/src/bin/d2/tests/dns_client_unittests.cc
@@ -0,0 +1,408 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <d2/dns_client.h>
+#include <asiodns/io_fetch.h>
+#include <asiodns/logger.h>
+#include <asiolink/interval_timer.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/tsig.h>
+#include <asio/ip/udp.hpp>
+#include <asio/socket_base.hpp>
+#include <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::asiodns;
+using namespace isc::d2;
+
+using namespace isc;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace asio;
+using namespace asio::ip;
+
+namespace {
+
+const char* TEST_ADDRESS = "127.0.0.1";
+const uint16_t TEST_PORT = 5301;
+const size_t MAX_SIZE = 1024;
+const long TEST_TIMEOUT = 5 * 1000;
+
+// @brief Test Fixture class.
+//
+// This test fixture class implements DNSClient::Callback so as it can be
+// installed as a completion callback for tests it implements. This callback
+// is called when a DDNS transaction (send and receive) completes. This allows
+// for the callback function to directly access class members. In particular,
+// the callback function can access IOService on which run() was called and
+// call stop() on it.
+//
+// Many of the tests defined here schedule execution of certain tasks and block
+// until tasks are completed or a timeout is hit. However, if timeout is not
+// properly handled a task may be hanging for a long time. In order to prevent
+// it, the asiolink::IntervalTimer is used to break a running test if test
+// timeout is hit. This will result in test failure.
+class DNSClientTest : public virtual ::testing::Test, DNSClient::Callback {
+public:
+ IOService service_;
+ D2UpdateMessagePtr response_;
+ DNSClient::Status status_;
+ uint8_t receive_buffer_[MAX_SIZE];
+ DNSClientPtr dns_client_;
+ bool corrupt_response_;
+ bool expect_response_;
+ asiolink::IntervalTimer test_timer_;
+
+ // @brief Constructor.
+ //
+ // This constructor overrides the default logging level of asiodns logger to
+ // prevent it from emitting debug messages from IOFetch class. Such an error
+ // message can be emitted if timeout occurs when DNSClient class is
+ // waiting for a response. Some of the tests are checking DNSClient behavior
+ // in case when response from the server is not received. Tests output would
+ // become messy if such errors were logged.
+ DNSClientTest()
+ : service_(),
+ status_(DNSClient::SUCCESS),
+ corrupt_response_(false),
+ expect_response_(true),
+ test_timer_(service_) {
+ asiodns::logger.setSeverity(isc::log::INFO);
+ response_.reset(new D2UpdateMessage(D2UpdateMessage::INBOUND));
+ dns_client_.reset(new DNSClient(response_, this));
+
+ // Set the test timeout to break any running tasks if they hang.
+ test_timer_.setup(boost::bind(&DNSClientTest::testTimeoutHandler, this),
+ TEST_TIMEOUT);
+ }
+
+ // @brief Destructor.
+ //
+ // Sets the asiodns logging level back to DEBUG.
+ virtual ~DNSClientTest() {
+ asiodns::logger.setSeverity(isc::log::DEBUG);
+ };
+
+ // @brief Exchange completion callback.
+ //
+ // This callback is called when the exchange with the DNS server is
+ // complete or an error occurred. This includes the occurrence of a timeout.
+ //
+ // @param status A status code returned by DNSClient.
+ virtual void operator()(DNSClient::Status status) {
+ status_ = status;
+ service_.stop();
+
+ if (expect_response_) {
+ if (!corrupt_response_) {
+ // We should have received a response.
+ EXPECT_EQ(DNSClient::SUCCESS, status_);
+
+ ASSERT_TRUE(response_);
+ EXPECT_EQ(D2UpdateMessage::RESPONSE, response_->getQRFlag());
+ ASSERT_EQ(1,
+ response_->getRRCount(D2UpdateMessage::SECTION_ZONE));
+ D2ZonePtr zone = response_->getZone();
+ ASSERT_TRUE(zone);
+ EXPECT_EQ("example.com.", zone->getName().toText());
+ EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());
+
+ } else {
+ EXPECT_EQ(DNSClient::INVALID_RESPONSE, status_);
+
+ }
+ // If we don't expect a response, the status should indicate a timeout.
+ } else {
+ EXPECT_EQ(DNSClient::TIMEOUT, status_);
+
+ }
+ }
+
+ // @brief Handler invoked when test timeout is hit.
+ //
+ // This callback stops all running (hanging) tasks on IO service.
+ void testTimeoutHandler() {
+ service_.stop();
+ FAIL() << "Test timeout hit.";
+ }
+
+ // @brief Handler invoked when test request is received.
+ //
+ // This callback handler is installed when performing async read on a
+ // socket to emulate reception of the DNS Update request by a server.
+ // As a result, this handler will send an appropriate DNS Update response
+ // message back to the address from which the request has come.
+ //
+ // @param socket A pointer to a socket used to receive a query and send a
+ // response.
+ // @param remote A pointer to an object which specifies the host (address
+ // and port) from which a request has come.
+ // @param receive_length A length (in bytes) of the received data.
+ // @param corrupt_response A bool value which indicates that the server's
+ // response should be invalid (true) or valid (false)
+ void udpReceiveHandler(udp::socket* socket, udp::endpoint* remote,
+ size_t receive_length, const bool corrupt_response) {
+ // The easiest way to create a response message is to copy the entire
+ // request.
+ OutputBuffer response_buf(receive_length);
+ response_buf.writeData(receive_buffer_, receive_length);
+ // If a response is to be valid, we have to modify it slightly. If not,
+ // we leave it as is.
+ if (!corrupt_response) {
+ // For a valid response the QR bit must be set. This bit
+ // differentiates both types of messages. Note that the 3rd byte of
+ // the message header comprises this bit in the front followed by
+ // the message code and reserved zeros. Therefore, this byte
+ // has the following value:
+ // 10101000,
+ // where a leading bit is a QR flag. The hexadecimal value is 0xA8.
+ // Write it at message offset 2.
+ response_buf.writeUint8At(0xA8, 2);
+ }
+ // A response message is now ready to send. Send it!
+ socket->send_to(asio::buffer(response_buf.getData(),
+ response_buf.getLength()),
+ *remote);
+ }
+
+ // This test verifies that when invalid response placeholder object is
+ // passed to a constructor, constructor throws the appropriate exception.
+ // It also verifies that the constructor will not throw if the supplied
+ // callback object is NULL.
+ void runConstructorTest() {
+ D2UpdateMessagePtr null_response;
+ EXPECT_THROW(DNSClient(null_response, this, DNSClient::UDP),
+ isc::BadValue);
+ EXPECT_NO_THROW(DNSClient(response_, NULL, DNSClient::UDP));
+
+ // The TCP Transport is not supported right now. So, we return exception
+ // if caller specified TCP as a preferred protocol. This test will be
+ // removed once TCP is supported.
+ EXPECT_THROW(DNSClient(response_, NULL, DNSClient::TCP),
+ isc::NotImplemented);
+ }
+
+ // This test verifies that it accepted timeout values belong to the range of
+ // <0, DNSClient::getMaxTimeout()>.
+ void runInvalidTimeoutTest() {
+
+ expect_response_ = false;
+
+ // Create outgoing message. Simply set the required message fields:
+ // error code and Zone section. This is enough to create on-wire format
+ // of this message and send it.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ // Start with a valid timeout equal to maximal allowed. This way we will
+ // ensure that doUpdate doesn't throw an exception for valid timeouts.
+ unsigned int timeout = DNSClient::getMaxTimeout();
+ EXPECT_NO_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
+ TEST_PORT, message, timeout));
+
+ // Cross the limit and expect that exception is thrown this time.
+ timeout = DNSClient::getMaxTimeout() + 1;
+ EXPECT_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
+ TEST_PORT, message, timeout),
+ isc::BadValue);
+ }
+
+ // This test verifies that isc::NotImplemented exception is thrown when
+ // attempt to send DNS Update message with TSIG is attempted.
+ void runTSIGTest() {
+ // Create outgoing message. Simply set the required message fields:
+ // error code and Zone section. This is enough to create on-wire format
+ // of this message and send it.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ const int timeout = 0;
+ // Try to send DNS Update with TSIG key. Currently TSIG is not supported
+ // and therefore we expect an exception.
+ TSIGKey tsig_key("key.example:MSG6Ng==");
+ EXPECT_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
+ TEST_PORT, message, timeout,
+ tsig_key),
+ isc::NotImplemented);
+ }
+
+ // This test verifies the DNSClient behavior when a server does not respond
+ // do the DNS Update message. In such case, the callback function is
+ // expected to be called and the TIME_OUT error code should be returned.
+ void runSendNoReceiveTest() {
+ // We expect no response from a server.
+ expect_response_ = false;
+
+ // Create outgoing message. Simply set the required message fields:
+ // error code and Zone section. This is enough to create on-wire format
+ // of this message and send it.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ // Set the response wait time to 0 so as our test is not hanging. This
+ // should cause instant timeout.
+ const int timeout = 0;
+ // The doUpdate() function starts asynchronous message exchange with DNS
+ // server. When message exchange is done or timeout occurs, the
+ // completion callback will be triggered. The doUpdate function returns
+ // immediately.
+ EXPECT_NO_THROW(dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS),
+ TEST_PORT, message, timeout));
+
+ // This starts the execution of tasks posted to IOService. run() blocks
+ // until stop() is called in the completion callback function.
+ service_.run();
+
+ }
+
+ // This test verifies that DNSClient can send DNS Update and receive a
+ // corresponding response from a server.
+ void runSendReceiveTest(const bool corrupt_response,
+ const bool two_sends) {
+ corrupt_response_ = corrupt_response;
+
+ // Create a request DNS Update message.
+ D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
+ ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
+ ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
+
+ // In order to perform the full test, when the client sends the request
+ // and receives a response from the server, we have to emulate the
+ // server's response in the test. A request will be sent via loopback
+ // interface to 127.0.0.1 and known test port. Response must be sent
+ // to 127.0.0.1 and a source port which has been used to send the
+ // request. A new socket is created, specifically to handle sending
+ // responses. The reuse address option is set so as both sockets can
+ // use the same address. This new socket is bound to the test address
+ // and port, where requests will be sent.
+ udp::socket udp_socket(service_.get_io_service(), asio::ip::udp::v4());
+ udp_socket.set_option(socket_base::reuse_address(true));
+ udp_socket.bind(udp::endpoint(address::from_string(TEST_ADDRESS),
+ TEST_PORT));
+ // Once socket is created, we can post an IO request to receive some
+ // a packet from this socket. This is asynchronous operation and
+ // nothing is received until another IO request to send a query is
+ // posted and the run() is invoked on this IO. A callback function is
+ // attached to this asynchronous read. This callback function requires
+ // that a socket object used to receive the request is passed to it,
+ // because the same socket will be used by the callback function to send
+ // a response. Also, the remote object is passed to the callback,
+ // because it holds a source address and port where request originated.
+ // Callback function will send a response to this address and port.
+ // The last parameter holds a length of the received request. It is
+ // required to construct a response.
+ udp::endpoint remote;
+ udp_socket.async_receive_from(asio::buffer(receive_buffer_,
+ sizeof(receive_buffer_)),
+ remote,
+ boost::bind(&DNSClientTest::udpReceiveHandler,
+ this, &udp_socket, &remote, _2,
+ corrupt_response));
+
+ // The socket is now ready to receive the data. Let's post some request
+ // message then.
+ const int timeout = 5;
+ dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
+ message, timeout);
+
+ // It is possible to request that two packets are sent concurrently.
+ if (two_sends) {
+ dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
+ message, timeout);
+
+ }
+
+ // Kick of the message exchange by actually running the scheduled
+ // "send" and "receive" operations.
+ service_.run();
+
+ udp_socket.close();
+
+ }
+};
+
+// Verify that the DNSClient object can be created if provided parameters are
+// valid. Constructor should throw exceptions when parameters are invalid.
+TEST_F(DNSClientTest, constructor) {
+ runConstructorTest();
+}
+
+// This test verifies that the maximal allowed timeout value is maximal int
+// value.
+TEST_F(DNSClientTest, getMaxTimeout) {
+ EXPECT_EQ(std::numeric_limits<int>::max(), DNSClient::getMaxTimeout());
+}
+
+// Verify that timeout is reported when no response is received from DNS.
+TEST_F(DNSClientTest, timeout) {
+ runSendNoReceiveTest();
+}
+
+// Verify that exception is thrown when invalid (too high) timeout value is
+// specified for asynchronous DNS Update.
+TEST_F(DNSClientTest, invalidTimeout) {
+ runInvalidTimeoutTest();
+}
+
+// Verify that exception is thrown when an attempt to send DNS Update with TSIG
+// is made. This test will be removed/changed once TSIG support is added.
+TEST_F(DNSClientTest, runTSIGTest) {
+ runTSIGTest();
+}
+
+// Verify that the DNSClient receives the response from DNS and the received
+// buffer can be decoded as DNS Update Response.
+TEST_F(DNSClientTest, sendReceive) {
+ // false means that server response is not corrupted.
+ runSendReceiveTest(false, false);
+}
+
+// Verify that the DNSClient reports an error when the response is received from
+// a DNS and this response is corrupted.
+TEST_F(DNSClientTest, sendReceiveCurrupted) {
+ // true means that server's response is corrupted.
+ runSendReceiveTest(true, false);
+}
+
+// Verify that it is possible to use the same DNSClient instance to
+// perform the following sequence of message exchanges:
+// 1. send
+// 2. receive
+// 3. send
+// 4. receive
+TEST_F(DNSClientTest, sendReceiveTwice) {
+ runSendReceiveTest(false, false);
+ runSendReceiveTest(false, false);
+}
+
+// Verify that it is possible to use the DNSClient instance to perform the
+// following sequence of message exchanges:
+// 1. send
+// 2. send
+// 3. receive
+// 4. receive
+TEST_F(DNSClientTest, concurrentSendReceive) {
+ runSendReceiveTest(false, true);
+}
+
+} // End of anonymous namespace
diff --git a/src/bin/d2/tests/test_data_files_config.h.in b/src/bin/d2/tests/test_data_files_config.h.in
new file mode 100644
index 0000000..6064d3d
--- /dev/null
+++ b/src/bin/d2/tests/test_data_files_config.h.in
@@ -0,0 +1,17 @@
+// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @brief Path to D2 source dir so tests against the dhcp-ddns.spec file
+/// can find it reliably.
+#define D2_SRC_DIR "@abs_top_srcdir@/src/bin/d2"
diff --git a/src/bin/dbutil/b10-dbutil.xml b/src/bin/dbutil/b10-dbutil.xml
index c93d060..f32301f 100644
--- a/src/bin/dbutil/b10-dbutil.xml
+++ b/src/bin/dbutil/b10-dbutil.xml
@@ -68,7 +68,7 @@
</para>
<para>
- <command>b10-dbutil</command> operates in one of two modesr: check mode
+ <command>b10-dbutil</command> operates in one of two modes: check mode
or upgrade mode.
</para>
diff --git a/src/bin/dbutil/dbutil.py.in b/src/bin/dbutil/dbutil.py.in
index 7a1469c..292a0ba 100755
--- a/src/bin/dbutil/dbutil.py.in
+++ b/src/bin/dbutil/dbutil.py.in
@@ -16,7 +16,7 @@
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""
- at file Dabase Utilities
+ at file Database Utilities
This file holds the "dbutil" program, a general utility program for doing
management of the BIND 10 database. There are two modes of operation:
diff --git a/src/bin/dbutil/run_dbutil.sh.in b/src/bin/dbutil/run_dbutil.sh.in
index 8ec5668..cdbaf15 100755
--- a/src/bin/dbutil/run_dbutil.sh.in
+++ b/src/bin/dbutil/run_dbutil.sh.in
@@ -30,7 +30,7 @@ export PYTHONPATH
# required by loadable python modules.
SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
if test $SET_ENV_LIBRARY_PATH = yes; then
- @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/threads/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
export @ENV_LIBRARY_PATH@
fi
diff --git a/src/bin/dbutil/tests/Makefile.am b/src/bin/dbutil/tests/Makefile.am
index aaa57cc..1030c63 100644
--- a/src/bin/dbutil/tests/Makefile.am
+++ b/src/bin/dbutil/tests/Makefile.am
@@ -5,5 +5,11 @@ SUBDIRS = . testdata
noinst_SCRIPTS = dbutil_test.sh
check-local:
+if HAVE_SQLITE3_PROGRAM
B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
$(SHELL) $(abs_builddir)/dbutil_test.sh
+else
+ @echo ""
+ @echo " **** The sqlite3 program is required to run dbutil tests **** "
+ @echo ""
+endif
diff --git a/src/bin/dbutil/tests/dbutil_test.sh.in b/src/bin/dbutil/tests/dbutil_test.sh.in
index 4bc9f85..e11c01b 100755
--- a/src/bin/dbutil/tests/dbutil_test.sh.in
+++ b/src/bin/dbutil/tests/dbutil_test.sh.in
@@ -132,7 +132,7 @@ check_no_backup() {
# .schema command, with spaces removed and upper converted to lowercase.
#
# The database is copied before the schema is taken (and removed after)
-# as SQLite3 assummes a writeable database, which may not be the case if
+# as SQLite3 assumes a writeable database, which may not be the case if
# getting the schema from a reference copy.
#
# @param $1 Database for which the schema is required
@@ -140,7 +140,24 @@ get_schema() {
db1=@abs_builddir@/dbutil_test_schema_$$
copy_file $1 $db1
+ # The purpose of the following sed command is to join multi-line SQL
+ # statements to form single-line SQL statements.
+ #
+ # The sed command is explained as follows:
+ # ':a' creates a new label "a"
+ # 'N' appends the next line to the pattern space
+ # '$!ba' if it's not the last line, branch to "a"
+ #
+ # The above makes sed loop over the entire sqlite3 output. At this
+ # point, the pattern space contain all lines in the sqlite3 output.
+ #
+ # 's/,[\ ]*\n/, /g' then substitutes lines trailing with comma
+ # followed by zero or more spaces and the newline character, with
+ # just a comma and a single space.
+
db_schema=`sqlite3 $db1 '.schema' | \
+ sed -e ':a' -e 'N' -e '$!ba' -e 's/,[\ ]*\n/, /g' | \
+ sort | \
awk '{line = line $0} END {print line}' | \
sed -e 's/ //g' | \
tr [:upper:] [:lower:]`
@@ -161,7 +178,7 @@ get_schema() {
# @param $2 Expected backup file
upgrade_ok_test() {
copy_file $1 $tempfile
- ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
+ @SHELL@ ../run_dbutil.sh --upgrade --noconfirm $tempfile
if [ $? -eq 0 ]
then
# Compare schema with the reference
@@ -199,7 +216,7 @@ upgrade_ok_test() {
# @param $2 Expected backup file
upgrade_fail_test() {
copy_file $1 $tempfile
- ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
+ @SHELL@ ../run_dbutil.sh --upgrade --noconfirm $tempfile
failzero $?
check_backup $1 $backupfile
}
@@ -222,7 +239,7 @@ record_count_test() {
records_count=`sqlite3 $tempfile 'select count(*) from records'`
zones_count=`sqlite3 $tempfile 'select count(*) from zones'`
- ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
+ @SHELL@ ../run_dbutil.sh --upgrade --noconfirm $tempfile
if [ $? -ne 0 ]
then
# Reason for failure should already have been output
@@ -268,12 +285,12 @@ record_count_test() {
# @param $2 Expected version string
check_version() {
copy_file $1 $verfile
- ${SHELL} ../run_dbutil.sh --check $verfile
+ @SHELL@ ../run_dbutil.sh --check $verfile
if [ $? -gt 2 ]
then
fail "version check failed on database $1; return code $?"
else
- ${SHELL} ../run_dbutil.sh --check $verfile 2>&1 | grep "$2" > /dev/null
+ @SHELL@ ../run_dbutil.sh --check $verfile 2>&1 | grep "$2" > /dev/null
if [ $? -ne 0 ]
then
fail "database $1 not at expected version $2 (output: $?)"
@@ -293,7 +310,7 @@ check_version() {
# @param $2 Backup file
check_version_fail() {
copy_file $1 $verfile
- ${SHELL} ../run_dbutil.sh --check $verfile
+ @SHELL@ ../run_dbutil.sh --check $verfile
failzero $?
check_no_backup $tempfile $backupfile
}
@@ -310,12 +327,12 @@ sec=0
# Test: check that the utility fails if the database does not exist
sec=`expr $sec + 1`
echo $sec".1. Non-existent database - check"
-${SHELL} ../run_dbutil.sh --check $tempfile
+ at SHELL@ ../run_dbutil.sh --check $tempfile
failzero $?
check_no_backup $tempfile $backupfile
echo $sec".2. Non-existent database - upgrade"
-${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
+ at SHELL@ ../run_dbutil.sh --upgrade --noconfirm $tempfile
failzero $?
check_no_backup $tempfile $backupfile
rm -f $tempfile $backupfile
@@ -330,7 +347,7 @@ rm -f $tempfile $backupfile
echo $sec".2. Database is an empty file - upgrade"
touch $tempfile
-${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
+ at SHELL@ ../run_dbutil.sh --upgrade --noconfirm $tempfile
failzero $?
# A backup is performed before anything else, so the backup should exist.
check_backup $tempfile $backupfile
@@ -344,7 +361,7 @@ rm -f $tempfile $backupfile
echo $sec".2. Database is not an SQLite file - upgrade"
echo "This is not an sqlite3 database" > $tempfile
-${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
+ at SHELL@ ../run_dbutil.sh --upgrade --noconfirm $tempfile
failzero $?
# ...and as before, a backup should have been created
check_backup $tempfile $backupfile
@@ -459,31 +476,31 @@ rm -f $tempfile $backupfile ${backupfile}-1 ${backupfile}-2
sec=`expr $sec + 1`
echo $sec".1 Command-line errors"
copy_file $testdata/old_v1.sqlite3 $tempfile
-${SHELL} ../run_dbutil.sh $tempfile
+ at SHELL@ ../run_dbutil.sh $tempfile
failzero $?
-${SHELL} ../run_dbutil.sh --upgrade --check $tempfile
+ at SHELL@ ../run_dbutil.sh --upgrade --check $tempfile
failzero $?
-${SHELL} ../run_dbutil.sh --noconfirm --check $tempfile
+ at SHELL@ ../run_dbutil.sh --noconfirm --check $tempfile
failzero $?
-${SHELL} ../run_dbutil.sh --check
+ at SHELL@ ../run_dbutil.sh --check
failzero $?
-${SHELL} ../run_dbutil.sh --upgrade --noconfirm
+ at SHELL@ ../run_dbutil.sh --upgrade --noconfirm
failzero $?
-${SHELL} ../run_dbutil.sh --check $tempfile $backupfile
+ at SHELL@ ../run_dbutil.sh --check $tempfile $backupfile
failzero $?
-${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile $backupfile
+ at SHELL@ ../run_dbutil.sh --upgrade --noconfirm $tempfile $backupfile
failzero $?
rm -f $tempfile $backupfile
echo $sec".2 verbose flag"
copy_file $testdata/old_v1.sqlite3 $tempfile
-${SHELL} ../run_dbutil.sh --upgrade --noconfirm --verbose $tempfile
+ at SHELL@ ../run_dbutil.sh --upgrade --noconfirm --verbose $tempfile
passzero $?
rm -f $tempfile $backupfile
echo $sec".3 Interactive prompt - yes"
copy_file $testdata/old_v1.sqlite3 $tempfile
-${SHELL} ../run_dbutil.sh --upgrade $tempfile << .
+ at SHELL@ ../run_dbutil.sh --upgrade $tempfile << .
Yes
.
passzero $?
@@ -492,7 +509,7 @@ rm -f $tempfile $backupfile
echo $sec".4 Interactive prompt - no"
copy_file $testdata/old_v1.sqlite3 $tempfile
-${SHELL} ../run_dbutil.sh --upgrade $tempfile << .
+ at SHELL@ ../run_dbutil.sh --upgrade $tempfile << .
no
.
passzero $?
@@ -502,7 +519,7 @@ rm -f $tempfile $backupfile
echo $sec".5 quiet flag"
copy_file $testdata/old_v1.sqlite3 $tempfile
-${SHELL} ../run_dbutil.sh --check --quiet $tempfile 2>&1 | grep .
+ at SHELL@ ../run_dbutil.sh --check --quiet $tempfile 2>&1 | grep .
failzero $?
rm -f $tempfile $backupfile
diff --git a/src/bin/ddns/ddns.py.in b/src/bin/ddns/ddns.py.in
index d382495..47f67ca 100755
--- a/src/bin/ddns/ddns.py.in
+++ b/src/bin/ddns/ddns.py.in
@@ -236,7 +236,7 @@ class DDNSServer:
'''Exception for internal errors in an update session.
This exception is expected to be caught within the server class,
- only used for controling the code flow.
+ only used for controlling the code flow.
'''
pass
@@ -354,7 +354,7 @@ class DDNSServer:
zname = Name(zone_spec['name'])
# class has the default value in case it's unspecified.
# ideally this should be merged within the config module, but
- # the current implementation doesn't esnure that, so we need to
+ # the current implementation doesn't ensure that, so we need to
# subsitute it ourselves.
if 'class' in zone_spec:
zclass = RRClass(zone_spec['class'])
@@ -510,8 +510,8 @@ class DDNSServer:
'''Send DDNS response to the client.
Right now, this is a straightforward subroutine of handle_request(),
- but is intended to be extended evetually so that it can handle more
- comlicated operations for TCP (which requires asynchronous write).
+ but is intended to be extended eventually so that it can handle more
+ complicated operations for TCP (which requires asynchronous write).
Further, when we support multiple requests over a single TCP
connection, this method may even be shared by multiple methods.
diff --git a/src/bin/ddns/ddns_messages.mes b/src/bin/ddns/ddns_messages.mes
index 7d440d9..c537ae4 100644
--- a/src/bin/ddns/ddns_messages.mes
+++ b/src/bin/ddns/ddns_messages.mes
@@ -141,7 +141,7 @@ logged.
% DDNS_RESPONSE_TCP_SOCKET_SEND_FAILED failed to complete sending update response to %1 over TCP
b10-ddns had tried to send an update response over TCP, and it hadn't
-been completed at that time, and a followup attempt to complete the
+been completed at that time, and a follow-up attempt to complete the
send operation failed due to some network I/O error. While a network
error can happen any time, this event is quite unexpected for two
reasons. First, since the size of a response to an update request
diff --git a/src/bin/ddns/tests/Makefile.am b/src/bin/ddns/tests/Makefile.am
index 5c824d4..bf72353 100644
--- a/src/bin/ddns/tests/Makefile.am
+++ b/src/bin/ddns/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/ddns/tests/ddns_test.py b/src/bin/ddns/tests/ddns_test.py
index e7d2099..4cf31be 100755
--- a/src/bin/ddns/tests/ddns_test.py
+++ b/src/bin/ddns/tests/ddns_test.py
@@ -40,7 +40,7 @@ READ_ZONE_DB_FILE = TESTDATA_PATH + "rwtest.sqlite3" # original, to be copied
TEST_ZONE_NAME = Name('example.org')
TEST_ZONE_NAME_STR = TEST_ZONE_NAME.to_text()
UPDATE_RRTYPE = RRType.SOA
-TEST_QID = 5353 # arbitrary chosen
+TEST_QID = 5353 # arbitrarily chosen
TEST_RRCLASS = RRClass.IN
TEST_RRCLASS_STR = TEST_RRCLASS.to_text()
TEST_SERVER6 = ('2001:db8::53', 53, 0, 0)
@@ -53,7 +53,7 @@ TEST_ACL_CONTEXT = isc.acl.dns.RequestContext(
socket.IPPROTO_UDP, socket.AI_NUMERICHOST)[0][4])
# TSIG key for tests when needed. The key name is TEST_ZONE_NAME.
TEST_TSIG_KEY = TSIGKey("example.org:SFuWd/q99SzF8Yzd1QbB9g==")
-# TSIG keyring that contanins the test key
+# TSIG keyring that contains the test key
TEST_TSIG_KEYRING = TSIGKeyRing()
TEST_TSIG_KEYRING.add(TEST_TSIG_KEY)
# Another TSIG key not in the keyring, making verification fail
@@ -450,7 +450,7 @@ class TestDDNSServer(unittest.TestCase):
self.assertEqual(1, isc.config.parse_answer(answer)[0])
self.assertEqual({}, self.ddns_server._zone_config)
- # the first zone cofig is valid, but not the second. the first one
+ # the first zone config is valid, but not the second. the first one
# shouldn't be installed.
bad_config = { 'zones': [ { 'origin': TEST_ZONE_NAME_STR,
'class': TEST_RRCLASS_STR,
@@ -856,7 +856,7 @@ class TestDDNSServer(unittest.TestCase):
def test_select_multi_tcp(self):
'''Test continuation of sending a TCP response, multiple sockets.'''
# Check if the implementation still works with multiple outstanding
- # TCP contexts. We use three (arbitray choice), of which two will be
+ # TCP contexts. We use three (arbitrary choice), of which two will be
# writable after select and complete the send.
tcp_socks = []
for i in range(0, 3):
diff --git a/src/bin/dhcp4/Makefile.am b/src/bin/dhcp4/Makefile.am
index b3818c7..80b7fc3 100644
--- a/src/bin/dhcp4/Makefile.am
+++ b/src/bin/dhcp4/Makefile.am
@@ -63,6 +63,7 @@ b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+b10_dhcp4_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
b10_dhcp4dir = $(pkgdatadir)
b10_dhcp4_DATA = dhcp4.spec
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index d8a586b..98393c1 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -13,19 +13,21 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <config/ccsession.h>
-#include <dhcp4/config_parser.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option_definition.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcp4/config_parser.h>
#include <dhcpsrv/dbaccess_parser.h>
-#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/dhcp_parsers.h>
#include <dhcpsrv/option_space_container.h>
#include <util/encode/hex.h>
#include <util/strutil.h>
+
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
+
#include <limits>
#include <iostream>
#include <vector>
@@ -39,1415 +41,213 @@ using namespace isc::asiolink;
namespace {
-// Forward declarations of some of the parser classes.
-// They are used to define pointer types for these classes.
-class BooleanParser;
-class StringParser;
-class Uint32Parser;
-
-// Pointers to various parser objects.
-typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
-typedef boost::shared_ptr<StringParser> StringParserPtr;
-typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
-
-/// @brief a factory method that will create a parser for a given element name
-typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
-
-/// @brief a collection of factories that creates parsers for specified element names
-typedef std::map<std::string, ParserFactory*> FactoryMap;
-
-/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
-typedef std::map<std::string, uint32_t> Uint32Storage;
-
-/// @brief a collection of elements that store string values
-typedef std::map<std::string, std::string> StringStorage;
-
-/// @brief Storage for parsed boolean values.
-typedef std::map<string, bool> BooleanStorage;
-
-/// @brief Storage for option definitions.
-typedef OptionSpaceContainer<OptionDefContainer,
- OptionDefinitionPtr> OptionDefStorage;
-
-/// @brief a collection of pools
-///
-/// That type is used as intermediate storage, when pools are parsed, but there is
-/// no subnet object created yet to store them.
-typedef std::vector<Pool4Ptr> PoolStorage;
-
-/// Collection of containers holding option spaces. Each container within
-/// a particular option space holds so-called option descriptors.
-typedef OptionSpaceContainer<Subnet::OptionContainer,
- Subnet::OptionDescriptor> OptionStorage;
-
-/// @brief Global uint32 parameters that will be used as defaults.
-Uint32Storage uint32_defaults;
-
-/// @brief global string parameters that will be used as defaults.
-StringStorage string_defaults;
-
-/// @brief Global storage for options that will be used as defaults.
-OptionStorage option_defaults;
-
-/// @brief Global storage for option definitions.
-OptionDefStorage option_def_intermediate;
-
-/// @brief a dummy configuration parser
-///
-/// It is a debugging parser. It does not configure anything,
-/// will accept any configuration and will just print it out
-/// on commit. Useful for debugging existing configurations and
-/// adding new ones.
-class DebugParser : public DhcpConfigParser {
-public:
-
- /// @brief Constructor
- ///
- /// See @ref DhcpConfigParser class for details.
- ///
- /// @param param_name name of the parsed parameter
- DebugParser(const std::string& param_name)
- :param_name_(param_name) {
- }
-
- /// @brief builds parameter value
- ///
- /// See @ref DhcpConfigParser class for details.
- ///
- /// @param new_config pointer to the new configuration
- virtual void build(ConstElementPtr new_config) {
- std::cout << "Build for token: [" << param_name_ << "] = ["
- << value_->str() << "]" << std::endl;
- value_ = new_config;
- }
-
- /// @brief pretends to apply the configuration
- ///
- /// This is a method required by base class. It pretends to apply the
- /// configuration, but in fact it only prints the parameter out.
- ///
- /// See @ref DhcpConfigParser class for details.
- virtual void commit() {
- // Debug message. The whole DebugParser class is used only for parser
- // debugging, and is not used in production code. It is very convenient
- // to keep it around. Please do not turn this cout into logger calls.
- std::cout << "Commit for token: [" << param_name_ << "] = ["
- << value_->str() << "]" << std::endl;
- }
-
- /// @brief factory that constructs DebugParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
- return (new DebugParser(param_name));
- }
-
-private:
- /// name of the parsed parameter
- std::string param_name_;
-
- /// pointer to the actual value of the parameter
- ConstElementPtr value_;
-};
-
-/// @brief A boolean value parser.
-///
-/// This parser handles configuration values of the boolean type.
-/// Parsed values are stored in a provided storage. If no storage
-/// is provided then the build function throws an exception.
-class BooleanParser : public DhcpConfigParser {
-public:
- /// @brief Constructor.
- ///
- /// @param param_name name of the parameter.
- BooleanParser(const std::string& param_name)
- : storage_(NULL),
- param_name_(param_name),
- value_(false) {
- // Empty parameter name is invalid.
- if (param_name_.empty()) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- }
-
- /// @brief Parse a boolean value.
- ///
- /// @param value a value to be parsed.
- ///
- /// @throw isc::InvalidOperation if a storage has not been set
- /// prior to calling this function
- /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
- /// name is empty.
- virtual void build(ConstElementPtr value) {
- if (storage_ == NULL) {
- isc_throw(isc::InvalidOperation, "parser logic error:"
- << " storage for the " << param_name_
- << " value has not been set");
- } else if (param_name_.empty()) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- // The Config Manager checks if user specified a
- // valid value for a boolean parameter: True or False.
- // It is then ok to assume that if str() does not return
- // 'true' the value is 'false'.
- value_ = (value->str() == "true") ? true : false;
- }
-
- /// @brief Put a parsed value to the storage.
- virtual void commit() {
- if (storage_ != NULL && !param_name_.empty()) {
- (*storage_)[param_name_] = value_;
- }
- }
-
- /// @brief Create an instance of the boolean parser.
- ///
- /// @param param_name name of the parameter for which the
- /// parser is created.
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new BooleanParser(param_name));
- }
-
- /// @brief Set the storage for parsed value.
- ///
- /// This function must be called prior to calling build.
- ///
- /// @param storage a pointer to the storage where parsed data
- /// is to be stored.
- void setStorage(BooleanStorage* storage) {
- storage_ = storage;
- }
-
-private:
- /// Pointer to the storage where parsed value is stored.
- BooleanStorage* storage_;
- /// Name of the parameter which value is parsed with this parser.
- std::string param_name_;
- /// Parsed value.
- bool value_;
-};
-
-/// @brief Configuration parser for uint32 parameters
-///
-/// This class is a generic parser that is able to handle any uint32 integer
-/// type. By default it stores the value in external global container
-/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
-/// in subnet config), it can be pointed to a different storage, using
-/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, @ref DhcpConfigParser.
-///
-/// For overview of usability of this generic purpose parser, see
-/// @ref dhcpv4ConfigInherit page.
-class Uint32Parser : public DhcpConfigParser {
-public:
-
- /// @brief constructor for Uint32Parser
- /// @param param_name name of the configuration parameter being parsed
- Uint32Parser(const std::string& param_name)
- : storage_(&uint32_defaults),
- param_name_(param_name) {
- // Empty parameter name is invalid.
- if (param_name_.empty()) {
- isc_throw(DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- }
-
- /// @brief Parses configuration configuration parameter as uint32_t.
- ///
- /// @param value pointer to the content of parsed values
- /// @throw BadValue if supplied value could not be base to uint32_t
- /// or the parameter name is empty.
- virtual void build(ConstElementPtr value) {
- if (param_name_.empty()) {
- isc_throw(DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
-
- int64_t check;
- string x = value->str();
- try {
- check = boost::lexical_cast<int64_t>(x);
- } catch (const boost::bad_lexical_cast &) {
- isc_throw(BadValue, "Failed to parse value " << value->str()
- << " as unsigned 32-bit integer.");
- }
- if (check > std::numeric_limits<uint32_t>::max()) {
- isc_throw(BadValue, "Value " << value->str() << "is too large"
- << " for unsigned 32-bit integer.");
- }
- if (check < 0) {
- isc_throw(BadValue, "Value " << value->str() << "is negative."
- << " Only 0 or larger are allowed for unsigned 32-bit integer.");
- }
-
- // value is small enough to fit
- value_ = static_cast<uint32_t>(check);
- }
-
- /// @brief Stores the parsed uint32_t value in a storage.
- virtual void commit() {
- if (storage_ != NULL && !param_name_.empty()) {
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
- }
- }
-
- /// @brief factory that constructs Uint32Parser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new Uint32Parser(param_name));
- }
-
- /// @brief sets storage for value of this parameter
- ///
- /// See @ref dhcpv4ConfigInherit for details.
- ///
- /// @param storage pointer to the storage container
- void setStorage(Uint32Storage* storage) {
- storage_ = storage;
- }
-
-private:
- /// pointer to the storage, where parsed value will be stored
- Uint32Storage* storage_;
-
- /// name of the parameter to be parsed
- std::string param_name_;
-
- /// the actual parsed value
- uint32_t value_;
-};
-
-/// @brief Configuration parser for string parameters
-///
-/// This class is a generic parser that is able to handle any string
-/// parameter. By default it stores the value in external global container
-/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
-/// in subnet config), it can be pointed to a different storage, using
-/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, @ref DhcpConfigParser.
-///
-/// For overview of usability of this generic purpose parser, see
-/// @ref dhcpv4ConfigInherit page.
-class StringParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor for StringParser
- /// @param param_name name of the configuration parameter being parsed
- StringParser(const std::string& param_name)
- :storage_(&string_defaults), param_name_(param_name) {
- // Empty parameter name is invalid.
- if (param_name_.empty()) {
- isc_throw(DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- }
-
- /// @brief parses parameter value
- ///
- /// Parses configuration entry and stores it in storage. See
- /// @ref setStorage() for details.
- ///
- /// @param value pointer to the content of parsed values
- virtual void build(ConstElementPtr value) {
- value_ = value->str();
- boost::erase_all(value_, "\"");
- }
-
- /// @brief Stores the parsed value in a storage.
- virtual void commit() {
- if (storage_ != NULL && !param_name_.empty()) {
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
- }
- }
-
- /// @brief factory that constructs StringParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new StringParser(param_name));
- }
-
- /// @brief sets storage for value of this parameter
- ///
- /// See \ref dhcpv4ConfigInherit for details.
- ///
- /// @param storage pointer to the storage container
- void setStorage(StringStorage* storage) {
- storage_ = storage;
- }
-
-private:
- /// pointer to the storage, where parsed value will be stored
- StringStorage* storage_;
-
- /// name of the parameter to be parsed
- std::string param_name_;
-
- /// the actual parsed value
- std::string value_;
-};
-
-
-/// @brief parser for interface list definition
-///
-/// This parser handles Dhcp4/interface entry.
-/// It contains a list of network interfaces that the server listens on.
-/// In particular, it can contain an entry called "all" or "any" that
-/// designates all interfaces.
-///
-/// It is useful for parsing Dhcp4/interface parameter.
-class InterfaceListConfigParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor
- ///
- /// As this is a dedicated parser, it must be used to parse
- /// "interface" parameter only. All other types will throw exception.
- ///
- /// @param param_name name of the configuration parameter being parsed
- /// @throw BadValue if supplied parameter name is not "interface"
- InterfaceListConfigParser(const std::string& param_name) {
- if (param_name != "interface") {
- isc_throw(BadValue, "Internal error. Interface configuration "
- "parser called for the wrong parameter: " << param_name);
- }
- }
-
- /// @brief parses parameters value
- ///
- /// Parses configuration entry (list of parameters) and adds each element
- /// to the interfaces list.
- ///
- /// @param value pointer to the content of parsed values
- virtual void build(ConstElementPtr value) {
- BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
- interfaces_.push_back(iface->str());
- }
- }
-
- /// @brief commits interfaces list configuration
- virtual void commit() {
- /// @todo: Implement per interface listening. Currently always listening
- /// on all interfaces.
- }
-
- /// @brief factory that constructs InterfaceListConfigParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new InterfaceListConfigParser(param_name));
- }
-
-private:
- /// contains list of network interfaces
- vector<string> interfaces_;
-};
-
-/// @brief parser for pool definition
-///
-/// This parser handles pool definitions, i.e. a list of entries of one
-/// of two syntaxes: min-max and prefix/len. Pool4 objects are created
-/// and stored in chosen PoolStorage container.
-///
-/// As there are no default values for pool, setStorage() must be called
-/// before build(). Otherwise exception will be thrown.
-///
-/// It is useful for parsing Dhcp4/subnet4[X]/pool parameters.
-class PoolParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor.
- PoolParser(const std::string& /*param_name*/)
- :pools_(NULL) {
- // ignore parameter name, it is always Dhcp4/subnet4[X]/pool
- }
-
- /// @brief parses the actual list
- ///
- /// This method parses the actual list of interfaces.
- /// No validation is done at this stage, everything is interpreted as
- /// interface name.
- /// @param pools_list list of pools defined for a subnet
- /// @throw InvalidOperation if storage was not specified (setStorage() not called)
- /// @throw DhcpConfigError when pool parsing fails
- void build(ConstElementPtr pools_list) {
- // setStorage() should have been called before build
- if (!pools_) {
- isc_throw(InvalidOperation, "Parser logic error. No pool storage set,"
- " but pool parser asked to parse pools");
- }
-
- BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) {
-
- // That should be a single pool representation. It should contain
- // text is form prefix/len or first - last. Note that spaces
- // are allowed
- string txt = text_pool->stringValue();
-
- // first let's remove any whitespaces
- boost::erase_all(txt, " "); // space
- boost::erase_all(txt, "\t"); // tabulation
-
- // Is this prefix/len notation?
- size_t pos = txt.find("/");
- if (pos != string::npos) {
- IOAddress addr("::");
- uint8_t len = 0;
- try {
- addr = IOAddress(txt.substr(0, pos));
-
- // start with the first character after /
- string prefix_len = txt.substr(pos + 1);
-
- // It is lexical cast to int and then downcast to uint8_t.
- // Direct cast to uint8_t (which is really an unsigned char)
- // will result in interpreting the first digit as output
- // value and throwing exception if length is written on two
- // digits (because there are extra characters left over).
-
- // No checks for values over 128. Range correctness will
- // be checked in Pool4 constructor.
- len = boost::lexical_cast<int>(prefix_len);
- } catch (...) {
- isc_throw(DhcpConfigError, "Failed to parse pool "
- "definition: " << text_pool->stringValue());
- }
-
- Pool4Ptr pool(new Pool4(addr, len));
- local_pools_.push_back(pool);
- continue;
- }
-
- // Is this min-max notation?
- pos = txt.find("-");
- if (pos != string::npos) {
- // using min-max notation
- IOAddress min(txt.substr(0,pos));
- IOAddress max(txt.substr(pos + 1));
-
- Pool4Ptr pool(new Pool4(min, max));
-
- local_pools_.push_back(pool);
- continue;
- }
-
- isc_throw(DhcpConfigError, "Failed to parse pool definition:"
- << text_pool->stringValue() <<
- ". Does not contain - (for min-max) nor / (prefix/len)");
- }
- }
-
- /// @brief sets storage for value of this parameter
- ///
- /// See \ref dhcpv4ConfigInherit for details.
- ///
- /// @param storage pointer to the storage container
- void setStorage(PoolStorage* storage) {
- pools_ = storage;
- }
-
- /// @brief Stores the parsed values in a storage provided
- /// by an upper level parser.
- virtual void commit() {
- if (pools_) {
- // local_pools_ holds the values produced by the build function.
- // At this point parsing should have completed successfuly so
- // we can append new data to the supplied storage.
- pools_->insert(pools_->end(), local_pools_.begin(),
- local_pools_.end());
- }
- }
-
- /// @brief factory that constructs PoolParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new PoolParser(param_name));
- }
-
-private:
- /// @brief pointer to the actual Pools storage
- ///
- /// That is typically a storage somewhere in Subnet parser
- /// (an upper level parser).
- PoolStorage* pools_;
- /// A temporary storage for pools configuration. It is a
- /// storage where pools are stored by build function.
- PoolStorage local_pools_;
-};
-
-/// @brief Parser for option data value.
+/// @brief Parser for DHCP4 option data value.
///
/// This parser parses configuration entries that specify value of
-/// a single option. These entries include option name, option code
-/// and data carried by the option. The option data can be specified
-/// in one of the two available formats: binary value represented as
-/// a string of hexadecimal digits or a list of comma separated values.
-/// The format being used is controlled by csv-format configuration
-/// parameter. When setting this value to True, the latter format is
-/// used. The subsequent values in the CSV format apply to relevant
-/// option data fields in the configured option. For example the
-/// configuration: "data" : "192.168.2.0, 56, hello world" can be
-/// used to set values for the option comprising IPv4 address,
-/// integer and string data field. Note that order matters. If the
-/// order of values does not match the order of data fields within
-/// an option the configuration will not be accepted. If parsing
-/// is successful then an instance of an option is created and
-/// added to the storage provided by the calling class.
-class OptionDataParser : public DhcpConfigParser {
+/// a single option specific to DHCP4. It provides the DHCP4-specific
+/// implementation of the abstract class OptionDataParser.
+class Dhcp4OptionDataParser : public OptionDataParser {
public:
-
/// @brief Constructor.
///
- /// Class constructor.
- OptionDataParser(const std::string&)
- : options_(NULL),
- // initialize option to NULL ptr
- option_descriptor_(false) { }
-
- /// @brief Parses the single option data.
- ///
- /// This method parses the data of a single option from the configuration.
- /// The option data includes option name, option code and data being
- /// carried by this option. Eventually it creates the instance of the
- /// option.
- ///
- /// @warning setStorage must be called with valid storage pointer prior
- /// to calling this method.
- ///
- /// @param option_data_entries collection of entries that define value
- /// for a particular option.
- /// @throw DhcpConfigError if invalid parameter specified in
- /// the configuration.
- /// @throw isc::InvalidOperation if failed to set storage prior to
- /// calling build.
- virtual void build(ConstElementPtr option_data_entries) {
- if (options_ == NULL) {
- isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
- "parsing option data.");
- }
- BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
- ParserPtr parser;
- if (param.first == "name" || param.first == "data" ||
- param.first == "space") {
- boost::shared_ptr<StringParser>
- name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
- if (name_parser) {
- name_parser->setStorage(&string_values_);
- parser = name_parser;
- }
- } else if (param.first == "code") {
- boost::shared_ptr<Uint32Parser>
- code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(param.first)));
- if (code_parser) {
- code_parser->setStorage(&uint32_values_);
- parser = code_parser;
- }
- } else if (param.first == "csv-format") {
- boost::shared_ptr<BooleanParser>
- value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
- if (value_parser) {
- value_parser->setStorage(&boolean_values_);
- parser = value_parser;
- }
- } else {
- isc_throw(DhcpConfigError,
- "Parser error: option-data parameter not supported: "
- << param.first);
- }
- parser->build(param.second);
- // Before we can create an option we need to get the data from
- // the child parsers. The only way to do it is to invoke commit
- // on them so as they store the values in appropriate storages
- // that this class provided to them. Note that this will not
- // modify values stored in the global storages so the configuration
- // will remain consistent even parsing fails somewhere further on.
- parser->commit();
- }
- // Try to create the option instance.
- createOption();
- }
-
- /// @brief Commits option value.
- ///
- /// This function adds a new option to the storage or replaces an existing option
- /// with the same code.
- ///
- /// @throw isc::InvalidOperation if failed to set pointer to storage or failed
- /// to call build() prior to commit. If that happens data in the storage
- /// remain un-modified.
- virtual void commit() {
- if (options_ == NULL) {
- isc_throw(isc::InvalidOperation, "parser logic error: storage must be set before "
- "committing option data.");
- } else if (!option_descriptor_.option) {
- // Before we can commit the new option should be configured. If it is not
- // than somebody must have called commit() before build().
- isc_throw(isc::InvalidOperation, "parser logic error: no option has been configured and"
- " thus there is nothing to commit. Has build() been called?");
- }
- uint16_t opt_type = option_descriptor_.option->getType();
- Subnet::OptionContainerPtr options = options_->getItems(option_space_);
- // The getItems() should never return NULL pointer. If there are no
- // options configured for the particular option space a pointer
- // to an empty container should be returned.
- assert(options);
- Subnet::OptionContainerTypeIndex& idx = options->get<1>();
- // Try to find options with the particular option code in the main
- // storage. If found, remove these options because they will be
- // replaced with new one.
- Subnet::OptionContainerTypeRange range =
- idx.equal_range(opt_type);
- if (std::distance(range.first, range.second) > 0) {
- idx.erase(range.first, range.second);
- }
- // Append new option to the main storage.
- options_->addItem(option_descriptor_, option_space_);
- }
-
- /// @brief Set storage for the parser.
- ///
- /// Sets storage for the parser. This storage points to the
- /// vector of options and is used by multiple instances of
- /// OptionDataParser. Each instance creates exactly one object
- /// of dhcp::Option or derived type and appends it to this
- /// storage.
- ///
- /// @param storage pointer to the options storage
- void setStorage(OptionStorage* storage) {
- options_ = storage;
- }
-
-private:
-
- /// @brief Create option instance.
- ///
- /// Creates an instance of an option and adds it to the provided
- /// options storage. If the option data parsed by \ref build function
- /// are invalid or insufficient this function emits an exception.
- ///
- /// @warning this function does not check if options_ storage pointer
- /// is intitialized but this check is not needed here because it is done
- /// in the \ref build function.
- ///
- /// @throw DhcpConfigError if parameters provided in the configuration
- /// are invalid.
- void createOption() {
- // Option code is held in the uint32_t storage but is supposed to
- // be uint16_t value. We need to check that value in the configuration
- // does not exceed range of uint8_t and is not zero.
- uint32_t option_code = getParam<uint32_t>("code", uint32_values_);
- if (option_code == 0) {
- isc_throw(DhcpConfigError, "option code must not be zero."
- << " Option code '0' is reserved in DHCPv4.");
- } else if (option_code > std::numeric_limits<uint8_t>::max()) {
- isc_throw(DhcpConfigError, "invalid option code '" << option_code
- << "', it must not exceed '"
- << std::numeric_limits<uint8_t>::max() << "'");
- }
- // Check that the option name has been specified, is non-empty and does not
- // contain spaces.
- std::string option_name = getParam<std::string>("name", string_values_);
- if (option_name.empty()) {
- isc_throw(DhcpConfigError, "name of the option with code '"
- << option_code << "' is empty");
- } else if (option_name.find(" ") != std::string::npos) {
- isc_throw(DhcpConfigError, "invalid option name '" << option_name
- << "', space character is not allowed");
- }
-
- std::string option_space = getParam<std::string>("space", string_values_);
- if (!OptionSpace::validateName(option_space)) {
- isc_throw(DhcpConfigError, "invalid option space name '"
- << option_space << "' specified for option '"
- << option_name << "' (code '" << option_code
- << "')");
- }
-
+ /// @param dummy first param, option names are always "Dhcp4/option-data[n]"
+ /// @param options is the option storage in which to store the parsed option
+ /// upon "commit".
+ /// @param global_context is a pointer to the global context which
+ /// stores global scope parameters, options, option defintions.
+ Dhcp4OptionDataParser(const std::string&,
+ OptionStoragePtr options, ParserContextPtr global_context)
+ :OptionDataParser("", options, global_context) {
+ }
+
+ /// @brief static factory method for instantiating Dhcp4OptionDataParsers
+ ///
+ /// @param param_name name of the parameter to be parsed.
+ /// @param options storage where the parameter value is to be stored.
+ /// @param global_context is a pointer to the global context which
+ /// stores global scope parameters, options, option defintions.
+ /// @return returns a pointer to a new OptionDataParser. Caller is
+ /// is responsible for deleting it when it is no longer needed.
+ static OptionDataParser* factory(const std::string& param_name,
+ OptionStoragePtr options, ParserContextPtr global_context) {
+ return (new Dhcp4OptionDataParser(param_name, options, global_context));
+ }
+
+protected:
+ /// @brief Finds an option definition within the server's option space
+ ///
+ /// Given an option space and an option code, find the correpsonding
+ /// option defintion within the server's option defintion storage.
+ ///
+ /// @param option_space name of the parameter option space
+ /// @param option_code numeric value of the parameter to find
+ /// @return OptionDefintionPtr of the option defintion or an
+ /// empty OptionDefinitionPtr if not found.
+ /// @throw DhcpConfigError if the option space requested is not valid
+ /// for this server.
+ virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
+ std::string& option_space, uint32_t option_code) {
OptionDefinitionPtr def;
if (option_space == "dhcp4" &&
LibDHCP::isStandardOption(Option::V4, option_code)) {
def = LibDHCP::getOptionDef(Option::V4, option_code);
-
} else if (option_space == "dhcp6") {
isc_throw(DhcpConfigError, "'dhcp6' option space name is reserved"
- << " for DHCPv6 server");
- } else {
- // If we are not dealing with a standard option then we
- // need to search for its definition among user-configured
- // options. They are expected to be in the global storage
- // already.
- OptionDefContainerPtr defs = option_def_intermediate.getItems(option_space);
- // The getItems() should never return the NULL pointer. If there are
- // no option definitions for the particular option space a pointer
- // to an empty container should be returned.
- assert(defs);
- const OptionDefContainerTypeIndex& idx = defs->get<1>();
- OptionDefContainerTypeRange range = idx.equal_range(option_code);
- if (std::distance(range.first, range.second) > 0) {
- def = *range.first;
- }
- if (!def) {
- isc_throw(DhcpConfigError, "definition for the option '"
- << option_space << "." << option_name
- << "' having code '" << option_code
- << "' does not exist");
- }
-
- }
-
- // Get option data from the configuration database ('data' field).
- const std::string option_data = getParam<std::string>("data", string_values_);
- const bool csv_format = getParam<bool>("csv-format", boolean_values_);
-
- // Transform string of hexadecimal digits into binary format.
- std::vector<uint8_t> binary;
- std::vector<std::string> data_tokens;
-
- if (csv_format) {
- // If the option data is specified as a string of comma
- // separated values then we need to split this string into
- // individual values - each value will be used to initialize
- // one data field of an option.
- data_tokens = isc::util::str::tokens(option_data, ",");
- } else {
- // Otherwise, the option data is specified as a string of
- // hexadecimal digits that we have to turn into binary format.
- try {
- util::encode::decodeHex(option_data, binary);
- } catch (...) {
- isc_throw(DhcpConfigError, "option data is not a valid"
- << " string of hexadecimal digits: " << option_data);
- }
+ << " for DHCPv6 server");
}
- OptionPtr option;
- if (!def) {
- if (csv_format) {
- isc_throw(DhcpConfigError, "the CSV option data format can be"
- " used to specify values for an option that has a"
- " definition. The option with code " << option_code
- << " does not have a definition.");
- }
-
- // @todo We have a limited set of option definitions intiialized at the moment.
- // In the future we want to initialize option definitions for all options.
- // Consequently an error will be issued if an option definition does not exist
- // for a particular option code. For now it is ok to create generic option
- // if definition does not exist.
- OptionPtr option(new Option(Option::V4, static_cast<uint16_t>(option_code),
- binary));
- // The created option is stored in option_descriptor_ class member until the
- // commit stage when it is inserted into the main storage. If an option with the
- // same code exists in main storage already the old option is replaced.
- option_descriptor_.option = option;
- option_descriptor_.persistent = false;
- } else {
-
- // Option name should match the definition. The option name
- // may seem to be redundant but in the future we may want
- // to reference options and definitions using their names
- // and/or option codes so keeping the option name in the
- // definition of option value makes sense.
- if (def->getName() != option_name) {
- isc_throw(DhcpConfigError, "specified option name '"
- << option_name << "' does not match the "
- << "option definition: '" << option_space
- << "." << def->getName() << "'");
- }
-
- // Option definition has been found so let's use it to create
- // an instance of our option.
- try {
- OptionPtr option = csv_format ?
- def->optionFactory(Option::V4, option_code, data_tokens) :
- def->optionFactory(Option::V4, option_code, binary);
- Subnet::OptionDescriptor desc(option, false);
- option_descriptor_.option = option;
- option_descriptor_.persistent = false;
- } catch (const isc::Exception& ex) {
- isc_throw(DhcpConfigError, "option data does not match"
- << " option definition (space: " << option_space
- << ", code: " << option_code << "): "
- << ex.what());
- }
-
- }
- // All went good, so we can set the option space name.
- option_space_ = option_space;
+ return (def);
}
-
- /// Storage for uint32 values (e.g. option code).
- Uint32Storage uint32_values_;
- /// Storage for string values (e.g. option name or data).
- StringStorage string_values_;
- /// Storage for boolean values.
- BooleanStorage boolean_values_;
- /// Pointer to options storage. This storage is provided by
- /// the calling class and is shared by all OptionDataParser objects.
- OptionStorage* options_;
- /// Option descriptor holds newly configured option.
- Subnet::OptionDescriptor option_descriptor_;
- /// Option space name where the option belongs to.
- std::string option_space_;
};
-/// @brief Parser for option data values within a subnet.
+/// @brief Parser for IPv4 pool definitions.
+///
+/// This is the IPv4 derivation of the PoolParser class and handles pool
+/// definitions, i.e. a list of entries of one of two syntaxes: min-max and
+/// prefix/len for IPv4 pools. Pool4 objects are created and stored in chosen
+/// PoolStorage container.
///
-/// This parser iterates over all entries that define options
-/// data for a particular subnet and creates a collection of options.
-/// If parsing is successful, all these options are added to the Subnet
-/// object.
-class OptionDataListParser : public DhcpConfigParser {
+/// It is useful for parsing Dhcp4/subnet4[X]/pool parameters.
+class Pool4Parser : public PoolParser {
public:
/// @brief Constructor.
///
- /// Unless otherwise specified, parsed options will be stored in
- /// a global option container (option_default). That storage location
- /// is overriden on a subnet basis.
- OptionDataListParser(const std::string&)
- : options_(&option_defaults), local_options_() { }
-
- /// @brief Parses entries that define options' data for a subnet.
- ///
- /// This method iterates over all entries that define option data
- /// for options within a single subnet and creates options' instances.
- ///
- /// @param option_data_list pointer to a list of options' data sets.
- /// @throw DhcpConfigError if option parsing failed.
- void build(ConstElementPtr option_data_list) {
- BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
- boost::shared_ptr<OptionDataParser> parser(new OptionDataParser("option-data"));
- // options_ member will hold instances of all options thus
- // each OptionDataParser takes it as a storage.
- parser->setStorage(&local_options_);
- // Build the instance of a single option.
- parser->build(option_value);
- // Store a parser as it will be used to commit.
- parsers_.push_back(parser);
- }
- }
-
- /// @brief Set storage for option instances.
- ///
- /// @param storage pointer to options storage.
- void setStorage(OptionStorage* storage) {
- options_ = storage;
+ /// @param param_name name of the parameter. Note, it is passed through
+ /// but unused, parameter is currently always "Dhcp4/subnet4[X]/pool"
+ /// @param pools storage container in which to store the parsed pool
+ /// upon "commit"
+ Pool4Parser(const std::string& param_name, PoolStoragePtr pools)
+ :PoolParser(param_name, pools) {
}
-
- /// @brief Commit all option values.
+protected:
+ /// @brief Creates a Pool4 object given a IPv4 prefix and the prefix length.
///
- /// This function invokes commit for all option values.
- void commit() {
- BOOST_FOREACH(ParserPtr parser, parsers_) {
- parser->commit();
- }
- // Parsing was successful and we have all configured
- // options in local storage. We can now replace old values
- // with new values.
- std::swap(local_options_, *options_);
+ /// @param addr is the IPv4 prefix of the pool.
+ /// @param len is the prefix length.
+ /// @param ignored dummy parameter to provide symmetry between the
+ /// PoolParser derivations. The V6 derivation requires a third value.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (IOAddress &addr, uint32_t len, int32_t) {
+ return (PoolPtr(new Pool4(addr, len)));
}
- /// @brief Create DhcpDataListParser object
- ///
- /// @param param_name param name.
+ /// @brief Creates a Pool4 object given starting and ending IPv4 addresses.
///
- /// @return DhcpConfigParser object.
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new OptionDataListParser(param_name));
+ /// @param min is the first IPv4 address in the pool.
+ /// @param max is the last IPv4 address in the pool.
+ /// @param ignored dummy parameter to provide symmetry between the
+ /// PoolParser derivations. The V6 derivation requires a third value.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (IOAddress &min, IOAddress &max, int32_t) {
+ return (PoolPtr(new Pool4(min, max)));
}
-
- /// Pointer to options instances storage.
- OptionStorage* options_;
- /// Intermediate option storage. This storage is used by
- /// lower level parsers to add new options. Values held
- /// in this storage are assigned to main storage (options_)
- /// if overall parsing was successful.
- OptionStorage local_options_;
- /// Collection of parsers;
- ParserCollection parsers_;
};
-/// @brief Parser for a single option definition.
+/// @brief This class parses a single IPv4 subnet.
///
-/// This parser creates an instance of a single option definition.
-class OptionDefParser : public DhcpConfigParser {
+/// This is the IPv4 derivation of the SubnetConfigParser class and it parses
+/// the whole subnet definition. It creates parsersfor received configuration
+/// parameters as needed.
+class Subnet4ConfigParser : public SubnetConfigParser {
public:
-
- /// @brief Constructor.
- ///
- /// This constructor sets the pointer to the option definitions
- /// storage to NULL. It must be set to point to the actual storage
- /// before \ref build is called.
- OptionDefParser(const std::string&)
- : storage_(NULL) {
- }
-
- /// @brief Parses an entry that describes single option definition.
- ///
- /// @param option_def a configuration entry to be parsed.
+ /// @brief Constructor
///
- /// @throw DhcpConfigError if parsing was unsuccessful.
- void build(ConstElementPtr option_def) {
- if (storage_ == NULL) {
- isc_throw(DhcpConfigError, "parser logic error: storage must be set"
- " before parsing option definition data");
- }
- // Parse the elements that make up the option definition.
- BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
- std::string entry(param.first);
- ParserPtr parser;
- if (entry == "name" || entry == "type" ||
- entry == "record-types" || entry == "space" ||
- entry == "encapsulate") {
- StringParserPtr
- str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
- if (str_parser) {
- str_parser->setStorage(&string_values_);
- parser = str_parser;
- }
- } else if (entry == "code") {
- Uint32ParserPtr
- code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(entry)));
- if (code_parser) {
- code_parser->setStorage(&uint32_values_);
- parser = code_parser;
- }
- } else if (entry == "array") {
- BooleanParserPtr
- array_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(entry)));
- if (array_parser) {
- array_parser->setStorage(&boolean_values_);
- parser = array_parser;
- }
- } else {
- isc_throw(DhcpConfigError, "invalid parameter: " << entry);
- }
-
- parser->build(param.second);
- parser->commit();
- }
-
- // Create an instance of option definition.
- createOptionDef();
-
- // Get all items we collected so far for the particular option space.
- OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
- // Check if there are any items with option code the same as the
- // one specified for the definition we are now creating.
- const OptionDefContainerTypeIndex& idx = defs->get<1>();
- const OptionDefContainerTypeRange& range =
- idx.equal_range(option_definition_->getCode());
- // If there are any items with this option code already we need
- // to issue an error because we don't allow duplicates for
- // option definitions within an option space.
- if (std::distance(range.first, range.second) > 0) {
- isc_throw(DhcpConfigError, "duplicated option definition for"
- << " code '" << option_definition_->getCode() << "'");
- }
+ /// @param ignored first parameter
+ /// stores global scope parameters, options, option defintions.
+ Subnet4ConfigParser(const std::string&)
+ :SubnetConfigParser("", globalContext()) {
}
- /// @brief Stores the parsed option definition in a storage.
+ /// @brief Adds the created subnet to a server's configuration.
+ /// @throw throws Unexpected if dynamic cast fails.
void commit() {
- if (storage_ && option_definition_ &&
- OptionSpace::validateName(option_space_name_)) {
- storage_->addItem(option_definition_, option_space_name_);
- }
- }
-
- /// @brief Sets a pointer to the data store.
- ///
- /// The newly created instance of an option definition will be
- /// added to the data store given by the argument.
- ///
- /// @param storage pointer to the data store where the option definition
- /// will be added to.
- void setStorage(OptionDefStorage* storage) {
- storage_ = storage;
- }
-
-private:
-
- /// @brief Create option definition from the parsed parameters.
- void createOptionDef() {
- // Get the option space name and validate it.
- std::string space = getParam<std::string>("space", string_values_);
- if (!OptionSpace::validateName(space)) {
- isc_throw(DhcpConfigError, "invalid option space name '"
- << space << "'");
- }
-
- // Get other parameters that are needed to create the
- // option definition.
- std::string name = getParam<std::string>("name", string_values_);
- uint32_t code = getParam<uint32_t>("code", uint32_values_);
- std::string type = getParam<std::string>("type", string_values_);
- bool array_type = getParam<bool>("array", boolean_values_);
- std::string encapsulates = getParam<std::string>("encapsulate",
- string_values_);
-
- // Create option definition.
- OptionDefinitionPtr def;
- // We need to check if user has set encapsulated option space
- // name. If so, different constructor will be used.
- if (!encapsulates.empty()) {
- // Arrays can't be used together with sub-options.
- if (array_type) {
- isc_throw(DhcpConfigError, "option '" << space << "."
- << "name" << "', comprising an array of data"
- << " fields may not encapsulate any option space");
-
- } else if (encapsulates == space) {
- isc_throw(DhcpConfigError, "option must not encapsulate"
- << " an option space it belongs to: '"
- << space << "." << name << "' is set to"
- << " encapsulate '" << space << "'");
-
- } else {
- def.reset(new OptionDefinition(name, code, type,
- encapsulates.c_str()));
- }
-
- } else {
- def.reset(new OptionDefinition(name, code, type, array_type));
-
- }
- // The record-types field may carry a list of comma separated names
- // of data types that form a record.
- std::string record_types = getParam<std::string>("record-types",
- string_values_);
- // Split the list of record types into tokens.
- std::vector<std::string> record_tokens =
- isc::util::str::tokens(record_types, ",");
- // Iterate over each token and add a record type into
- // option definition.
- BOOST_FOREACH(std::string record_type, record_tokens) {
- try {
- boost::trim(record_type);
- if (!record_type.empty()) {
- def->addRecordField(record_type);
- }
- } catch (const Exception& ex) {
- isc_throw(DhcpConfigError, "invalid record type values"
- << " specified for the option definition: "
- << ex.what());
+ if (subnet_) {
+ Subnet4Ptr sub4ptr = boost::dynamic_pointer_cast<Subnet4>(subnet_);
+ if (!sub4ptr) {
+ // If we hit this, it is a programming error.
+ isc_throw(Unexpected,
+ "Invalid cast in Subnet4ConfigParser::commit");
}
- }
- // Check the option definition parameters are valid.
- try {
- def->validate();
- } catch (const isc::Exception& ex) {
- isc_throw(DhcpConfigError, "invalid option definition"
- << " parameters: " << ex.what());
+ isc::dhcp::CfgMgr::instance().addSubnet4(sub4ptr);
}
- // Option definition has been created successfully.
- option_space_name_ = space;
- option_definition_ = def;
}
- /// Instance of option definition being created by this parser.
- OptionDefinitionPtr option_definition_;
- /// Name of the space the option definition belongs to.
- std::string option_space_name_;
-
- /// Pointer to a storage where the option definition will be
- /// added when \ref commit is called.
- OptionDefStorage* storage_;
-
- /// Storage for boolean values.
- BooleanStorage boolean_values_;
- /// Storage for string values.
- StringStorage string_values_;
- /// Storage for uint32 values.
- Uint32Storage uint32_values_;
-};
-
-/// @brief Parser for a list of option definitions.
-///
-/// This parser iterates over all configuration entries that define
-/// option definitions and creates instances of these definitions.
-/// If the parsing is successful, the collection of created definitions
-/// is put into the provided storage.
-class OptionDefListParser : DhcpConfigParser {
-public:
-
- /// @brief Constructor.
- ///
- /// This constructor initializes the pointer to option definitions
- /// storage to NULL value. This pointer has to be set to point to
- /// the actual storage before the \ref build function is called.
- OptionDefListParser(const std::string&) {
- }
+protected:
- /// @brief Parse configuration entries.
+ /// @brief Creates parsers for entries in subnet definition.
///
- /// This function parses configuration entries and creates instances
- /// of option definitions.
+ /// @param config_id name of the entry
///
- /// @param option_def_list pointer to an element that holds entries
- /// that define option definitions.
- /// @throw DhcpConfigError if configuration parsing fails.
- void build(ConstElementPtr option_def_list) {
- // Clear existing items in the global storage.
- // We are going to replace all of them.
- option_def_intermediate.clearItems();
-
- if (!option_def_list) {
- isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
- << " option definitions is NULL");
- }
-
- BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
- boost::shared_ptr<OptionDefParser>
- parser(new OptionDefParser("single-option-def"));
- parser->setStorage(&option_def_intermediate);
- parser->build(option_def);
- parser->commit();
- }
- }
-
- /// @brief Stores option definitions in the CfgMgr.
- void commit() {
-
- CfgMgr& cfg_mgr = CfgMgr::instance();
-
- cfg_mgr.deleteOptionDefs();
-
- // We need to move option definitions from the temporary
- // storage to the global storage.
- std::list<std::string> space_names =
- option_def_intermediate.getOptionSpaceNames();
- BOOST_FOREACH(std::string space_name, space_names) {
-
- BOOST_FOREACH(OptionDefinitionPtr def,
- *option_def_intermediate.getItems(space_name)) {
- // All option definitions should be initialized to non-NULL
- // values. The validation is expected to be made by the
- // OptionDefParser when creating an option definition.
- assert(def);
- cfg_mgr.addOptionDef(def, space_name);
- }
+ /// @return parser object for specified entry name. Note the caller is
+ /// responsible for deleting the parser created.
+ /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
+ /// for unknown config element
+ DhcpConfigParser* createSubnetConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ if ((config_id.compare("valid-lifetime") == 0) ||
+ (config_id.compare("renew-timer") == 0) ||
+ (config_id.compare("rebind-timer") == 0)) {
+ parser = new Uint32Parser(config_id, uint32_values_);
+ } else if ((config_id.compare("subnet") == 0) ||
+ (config_id.compare("interface") == 0)) {
+ parser = new StringParser(config_id, string_values_);
+ } else if (config_id.compare("pool") == 0) {
+ parser = new Pool4Parser(config_id, pools_);
+ } else if (config_id.compare("option-data") == 0) {
+ parser = new OptionDataListParser(config_id, options_,
+ global_context_,
+ Dhcp4OptionDataParser::factory);
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: Subnet4 parameter not supported: " << config_id);
}
- }
- /// @brief Create an OptionDefListParser object.
- ///
- /// @param param_name configuration entry holding option definitions.
- ///
- /// @return OptionDefListParser object.
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new OptionDefListParser(param_name));
+ return (parser);
}
-};
-
-/// @brief this class parses a single subnet
-///
-/// This class parses the whole subnet definition. It creates parsers
-/// for received configuration parameters as needed.
-class Subnet4ConfigParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor
- Subnet4ConfigParser(const std::string& ) {
- // The parameter should always be "subnet", but we don't check here
- // against it in case someone wants to reuse this parser somewhere.
- }
-
- /// @brief parses parameter value
- ///
- /// @param subnet pointer to the content of subnet definition
- void build(ConstElementPtr subnet) {
-
- BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
- ParserPtr parser(createSubnet4ConfigParser(param.first));
- // The actual type of the parser is unknown here. We have to discover
- // the parser type here to invoke the corresponding setStorage function
- // on it. We discover parser type by trying to cast the parser to various
- // parser types and checking which one was successful. For this one
- // a setStorage and build methods are invoked.
-
- // Try uint32 type parser.
- if (!buildParser<Uint32Parser, Uint32Storage >(parser, uint32_values_,
- param.second) &&
- // Try string type parser.
- !buildParser<StringParser, StringStorage >(parser, string_values_,
- param.second) &&
- // Try pool parser.
- !buildParser<PoolParser, PoolStorage >(parser, pools_,
- param.second) &&
- // Try option data parser.
- !buildParser<OptionDataListParser, OptionStorage >(parser, options_,
- param.second)) {
- // Appropriate parsers are created in the createSubnet6ConfigParser
- // and they should be limited to those that we check here for. Thus,
- // if we fail to find a matching parser here it is a programming error.
- isc_throw(DhcpConfigError, "failed to find suitable parser");
- }
- }
- // In order to create new subnet we need to get the data out
- // of the child parsers first. The only way to do it is to
- // invoke commit on them because it will make them write
- // parsed data into storages we have supplied.
- // Note that triggering commits on child parsers does not
- // affect global data because we supplied pointers to storages
- // local to this object. Thus, even if this method fails
- // later on, the configuration remains consistent.
- BOOST_FOREACH(ParserPtr parser, parsers_) {
- parser->commit();
- }
-
- // Create a subnet.
- createSubnet();
- }
- /// @brief commits received configuration.
+ /// @brief Determines if the given option space name and code describe
+ /// a standard option for the DCHP4 server.
///
- /// This method does most of the configuration. Many other parsers are just
- /// storing the values that are actually consumed here. Pool definitions
- /// created in other parsers are used here and added to newly created Subnet4
- /// objects. Subnet4 are then added to DHCP CfgMgr.
- /// @throw DhcpConfigError if there are any issues encountered during commit
- void commit() {
- if (subnet_) {
- CfgMgr::instance().addSubnet4(subnet_);
- }
+ /// @param option_space is the name of the option space to consider
+ /// @param code is the numeric option code to consider
+ /// @return returns true if the space and code are part of the server's
+ /// standard options.
+ bool isServerStdOption(std::string option_space, uint32_t code) {
+ return ((option_space.compare("dhcp4") == 0)
+ && LibDHCP::isStandardOption(Option::V4, code));
}
-private:
-
- /// @brief Set storage for a parser and invoke build.
- ///
- /// This helper method casts the provided parser pointer to the specified
- /// type. If the cast is successful it sets the corresponding storage for
- /// this parser, invokes build on it and saves the parser.
- ///
- /// @tparam T parser type to which parser argument should be cast.
- /// @tparam Y storage type for the specified parser type.
- /// @param parser parser on which build must be invoked.
- /// @param storage reference to a storage that will be set for a parser.
- /// @param subnet subnet element read from the configuration and being parsed.
- /// @return true if parser pointer was successfully cast to specialized
- /// parser type provided as Y.
- template<typename T, typename Y>
- bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
- // We need to cast to T in order to set storage for the parser.
- boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
- // It is common that this cast is not successful because we try to cast to all
- // supported parser types as we don't know the type of a parser in advance.
- if (cast_parser) {
- // Cast, successful so we go ahead with setting storage and actual parse.
- cast_parser->setStorage(&storage);
- parser->build(subnet);
- parsers_.push_back(parser);
- // We indicate that cast was successful so as the calling function
- // may skip attempts to cast to other parser types and proceed to
- // next element.
- return (true);
- }
- // It was not successful. Indicate that another parser type
- // should be tried.
- return (false);
+ /// @brief Returns the option definition for a given option code from
+ /// the DHCP4 server's standard set of options.
+ /// @param code is the numeric option code of the desired option definition.
+ /// @return returns a pointer the option definition
+ OptionDefinitionPtr getServerStdOptionDefinition (uint32_t code) {
+ return (LibDHCP::getOptionDef(Option::V4, code));
}
- /// @brief Append sub-options to an option.
+ /// @brief Issues a DHCP4 server specific warning regarding duplicate subnet
+ /// options.
///
- /// @param option_space a name of the encapsulated option space.
- /// @param option option instance to append sub-options to.
- void appendSubOptions(const std::string& option_space, OptionPtr& option) {
- // Only non-NULL options are stored in option container.
- // If this option pointer is NULL this is a serious error.
- assert(option);
-
- OptionDefinitionPtr def;
- if (option_space == "dhcp4" &&
- LibDHCP::isStandardOption(Option::V4, option->getType())) {
- def = LibDHCP::getOptionDef(Option::V4, option->getType());
- // Definitions for some of the standard options hasn't been
- // implemented so it is ok to leave here.
- if (!def) {
- return;
- }
- } else {
- const OptionDefContainerPtr defs =
- option_def_intermediate.getItems(option_space);
- const OptionDefContainerTypeIndex& idx = defs->get<1>();
- const OptionDefContainerTypeRange& range =
- idx.equal_range(option->getType());
- // There is no definition so we have to leave.
- if (std::distance(range.first, range.second) == 0) {
- return;
- }
-
- def = *range.first;
-
- // If the definition exists, it must be non-NULL.
- // Otherwise it is a programming error.
- assert(def);
- }
-
- // We need to get option definition for the particular option space
- // and code. This definition holds the information whether our
- // option encapsulates any option space.
- // Get the encapsulated option space name.
- std::string encapsulated_space = def->getEncapsulatedSpace();
- // If option space name is empty it means that our option does not
- // encapsulate any option space (does not include sub-options).
- if (!encapsulated_space.empty()) {
- // Get the sub-options that belong to the encapsulated
- // option space.
- const Subnet::OptionContainerPtr sub_opts =
- option_defaults.getItems(encapsulated_space);
- // Append sub-options to the option.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, *sub_opts) {
- if (desc.option) {
- option->addOption(desc.option);
- }
- }
- }
+ /// @param code is the numeric option code of the duplicate option
+ /// @param addr is the subnet address
+ /// @todo a means to know the correct logger and perhaps a common
+ /// message would allow this method to be emitted by the base class.
+ virtual void duplicate_option_warning(uint32_t code,
+ isc::asiolink::IOAddress& addr) {
+ LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
+ .arg(code).arg(addr.toText());
}
- /// @brief Create a new subnet using a data from child parsers.
+ /// @brief Instantiates the IPv4 Subnet based on a given IPv4 address
+ /// and prefix length.
///
- /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
- void createSubnet() {
- StringStorage::const_iterator it = string_values_.find("subnet");
- if (it == string_values_.end()) {
- isc_throw(DhcpConfigError,
- "Mandatory subnet definition in subnet missing");
- }
- // Remove any spaces or tabs.
- string subnet_txt = it->second;
- boost::erase_all(subnet_txt, " ");
- boost::erase_all(subnet_txt, "\t");
-
- // The subnet format is prefix/len. We are going to extract
- // the prefix portion of a subnet string to create IOAddress
- // object from it. IOAddress will be passed to the Subnet's
- // constructor later on. In order to extract the prefix we
- // need to get all characters preceding "/".
- size_t pos = subnet_txt.find("/");
- if (pos == string::npos) {
- isc_throw(DhcpConfigError,
- "Invalid subnet syntax (prefix/len expected):" << it->second);
- }
-
- // Try to create the address object. It also validates that
- // the address syntax is ok.
- IOAddress addr(subnet_txt.substr(0, pos));
- uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
-
+ /// @param addr is IPv4 address of the subnet.
+ /// @param len is the prefix length
+ void initSubnet(isc::asiolink::IOAddress addr, uint8_t len) {
// Get all 'time' parameters using inheritance.
// If the subnet-specific value is defined then use it, else
// use the global value. The global value must always be
@@ -1465,154 +265,10 @@ private:
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str());
subnet_.reset(new Subnet4(addr, len, t1, t2, valid));
-
- for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
- subnet_->addPool(*it);
- }
-
- // We are going to move configured options to the Subnet object.
- // Configured options reside in the container where options
- // are grouped by space names. Thus we need to get all space names
- // and iterate over all options that belong to them.
- std::list<std::string> space_names = options_.getOptionSpaceNames();
- BOOST_FOREACH(std::string option_space, space_names) {
- // Get all options within a particular option space.
- BOOST_FOREACH(Subnet::OptionDescriptor desc,
- *options_.getItems(option_space)) {
- // The pointer should be non-NULL. The validation is expected
- // to be performed by the OptionDataParser before adding an
- // option descriptor to the container.
- assert(desc.option);
- // We want to check whether an option with the particular
- // option code has been already added. If so, we want
- // to issue a warning.
- Subnet::OptionDescriptor existing_desc =
- subnet_->getOptionDescriptor("option_space",
- desc.option->getType());
- if (existing_desc.option) {
- LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
- .arg(desc.option->getType()).arg(addr.toText());
- }
- // Add sub-options (if any).
- appendSubOptions(option_space, desc.option);
- // In any case, we add the option to the subnet.
- subnet_->addOption(desc.option, false, option_space);
- }
- }
-
- // Check all global options and add them to the subnet object if
- // they have been configured in the global scope. If they have been
- // configured in the subnet scope we don't add global option because
- // the one configured in the subnet scope always takes precedence.
- space_names = option_defaults.getOptionSpaceNames();
- BOOST_FOREACH(std::string option_space, space_names) {
- // Get all global options for the particular option space.
- BOOST_FOREACH(Subnet::OptionDescriptor desc,
- *option_defaults.getItems(option_space)) {
- // The pointer should be non-NULL. The validation is expected
- // to be performed by the OptionDataParser before adding an
- // option descriptor to the container.
- assert(desc.option);
- // Check if the particular option has been already added.
- // This would mean that it has been configured in the
- // subnet scope. Since option values configured in the
- // subnet scope take precedence over globally configured
- // values we don't add option from the global storage
- // if there is one already.
- Subnet::OptionDescriptor existing_desc =
- subnet_->getOptionDescriptor(option_space, desc.option->getType());
- if (!existing_desc.option) {
- // Add sub-options (if any).
- appendSubOptions(option_space, desc.option);
-
- subnet_->addOption(desc.option, false, option_space);
- }
- }
- }
}
-
- /// @brief creates parsers for entries in subnet definition
- ///
- /// @todo Add subnet-specific things here (e.g. subnet-specific options)
- ///
- /// @param config_id name od the entry
- /// @return parser object for specified entry name
- /// @throw NotImplemented if trying to create a parser for unknown config element
- DhcpConfigParser* createSubnet4ConfigParser(const std::string& config_id) {
- FactoryMap factories;
-
- factories["valid-lifetime"] = Uint32Parser::factory;
- factories["renew-timer"] = Uint32Parser::factory;
- factories["rebind-timer"] = Uint32Parser::factory;
- factories["subnet"] = StringParser::factory;
- factories["pool"] = PoolParser::factory;
- factories["option-data"] = OptionDataListParser::factory;
-
- FactoryMap::iterator f = factories.find(config_id);
- if (f == factories.end()) {
- // Used for debugging only.
- // return new DebugParser(config_id);
-
- isc_throw(NotImplemented,
- "parser error: Subnet4 parameter not supported: "
- << config_id);
- }
- return (f->second(config_id));
- }
-
- /// @brief Returns value for a given parameter (after using inheritance)
- ///
- /// This method implements inheritance. For a given parameter name, it first
- /// checks if there is a global value for it and overwrites it with specific
- /// value if such value was defined in subnet.
- ///
- /// @param name name of the parameter
- /// @return triplet with the parameter name
- /// @throw DhcpConfigError when requested parameter is not present
- Triplet<uint32_t> getParam(const std::string& name) {
- uint32_t value = 0;
- bool found = false;
- Uint32Storage::iterator global = uint32_defaults.find(name);
- if (global != uint32_defaults.end()) {
- value = global->second;
- found = true;
- }
-
- Uint32Storage::iterator local = uint32_values_.find(name);
- if (local != uint32_values_.end()) {
- value = local->second;
- found = true;
- }
-
- if (found) {
- return (Triplet<uint32_t>(value));
- } else {
- isc_throw(DhcpConfigError, "Mandatory parameter " << name
- << " missing (no global default and no subnet-"
- << "specific value)");
- }
- }
-
- /// storage for subnet-specific uint32 values
- Uint32Storage uint32_values_;
-
- /// storage for subnet-specific integer values
- StringStorage string_values_;
-
- /// storage for pools belonging to this subnet
- PoolStorage pools_;
-
- /// storage for options belonging to this subnet
- OptionStorage options_;
-
- /// parsers are stored here
- ParserCollection parsers_;
-
- /// @brief Pointer to the created subnet object.
- isc::dhcp::Subnet4Ptr subnet_;
};
-/// @brief this class parses list of subnets
+/// @brief this class parses list of DHCP4 subnets
///
/// This is a wrapper parser that handles the whole list of Subnet4
/// definitions. It iterates over all entries and creates Subnet4ConfigParser
@@ -1622,8 +278,9 @@ public:
/// @brief constructor
///
+ /// @param dummy first argument, always ignored. All parsers accept a
+ /// string parameter "name" as their first argument.
Subnets4ListConfigParser(const std::string&) {
- /// parameter name is ignored
}
/// @brief parses contents of the list
@@ -1633,22 +290,17 @@ public:
///
/// @param subnets_list pointer to a list of IPv4 subnets
void build(ConstElementPtr subnets_list) {
-
- // No need to define FactoryMap here. There's only one type
- // used: Subnet4ConfigParser
-
BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
ParserPtr parser(new Subnet4ConfigParser("subnet"));
parser->build(subnet);
subnets_.push_back(parser);
}
-
}
/// @brief commits subnets definitions.
///
- /// Iterates over all Subnet4 parsers. Each parser contains definitions
- /// of a single subnet and its parameters and commits each subnet separately.
+ /// Iterates over all Subnet4 parsers. Each parser contains definitions of
+ /// a single subnet and its parameters and commits each subnet separately.
void commit() {
// @todo: Implement more subtle reconfiguration than toss
// the old one and replace with the new one.
@@ -1686,44 +338,56 @@ namespace dhcp {
///
/// @param config_id pointer to received global configuration entry
/// @return parser for specified global DHCPv4 parameter
-/// @throw NotImplemented if trying to create a parser for unknown config element
+/// @throw NotImplemented if trying to create a parser for unknown
+/// config element
DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
- FactoryMap factories;
-
- factories["valid-lifetime"] = Uint32Parser::factory;
- factories["renew-timer"] = Uint32Parser::factory;
- factories["rebind-timer"] = Uint32Parser::factory;
- factories["interface"] = InterfaceListConfigParser::factory;
- factories["subnet4"] = Subnets4ListConfigParser::factory;
- factories["option-data"] = OptionDataListParser::factory;
- factories["option-def"] = OptionDefListParser::factory;
- factories["version"] = StringParser::factory;
- factories["lease-database"] = DbAccessParser::factory;
-
- FactoryMap::iterator f = factories.find(config_id);
- if (f == factories.end()) {
- // Used for debugging only.
- // return new DebugParser(config_id);
-
+ DhcpConfigParser* parser = NULL;
+ if ((config_id.compare("valid-lifetime") == 0) ||
+ (config_id.compare("renew-timer") == 0) ||
+ (config_id.compare("rebind-timer") == 0)) {
+ parser = new Uint32Parser(config_id,
+ globalContext()->uint32_values_);
+ } else if (config_id.compare("interfaces") == 0) {
+ parser = new InterfaceListConfigParser(config_id);
+ } else if (config_id.compare("subnet4") == 0) {
+ parser = new Subnets4ListConfigParser(config_id);
+ } else if (config_id.compare("option-data") == 0) {
+ parser = new OptionDataListParser(config_id,
+ globalContext()->options_,
+ globalContext(),
+ Dhcp4OptionDataParser::factory);
+ } else if (config_id.compare("option-def") == 0) {
+ parser = new OptionDefListParser(config_id,
+ globalContext()->option_defs_);
+ } else if (config_id.compare("version") == 0) {
+ parser = new StringParser(config_id,
+ globalContext()->string_values_);
+ } else if (config_id.compare("lease-database") == 0) {
+ parser = new DbAccessParser(config_id);
+ } else if (config_id.compare("hooks-libraries") == 0) {
+ parser = new HooksLibrariesParser(config_id);
+ } else {
isc_throw(NotImplemented,
- "Parser error: Global configuration parameter not supported: "
- << config_id);
+ "Parser error: Global configuration parameter not supported: "
+ << config_id);
}
- return (f->second(config_id));
+
+ return (parser);
}
isc::data::ConstElementPtr
-configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
+configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
if (!config_set) {
ConstElementPtr answer = isc::config::createAnswer(1,
string("Can't parse NULL config"));
return (answer);
}
- /// @todo: append most essential info here (like "2 new subnets configured")
+ /// @todo: Append most essential info here (like "2 new subnets configured")
string config_details;
- LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START).arg(config_set->str());
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND,
+ DHCP4_CONFIG_START).arg(config_set->str());
// Some of the values specified in the configuration depend on
// other values. Typically, the values in the subnet4 structure
@@ -1735,6 +399,12 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
ParserCollection independent_parsers;
ParserPtr subnet_parser;
ParserPtr option_parser;
+ ParserPtr iface_parser;
+
+ // Some of the parsers alter the state of the system in a way that can't
+ // easily be undone. (Or alter it in a way such that undoing the change has
+ // the same risk of failure as doing the change.)
+ ParserPtr hooks_parser_;
// The subnet parsers implement data inheritance by directly
// accessing global storage. For this reason the global data
@@ -1743,14 +413,11 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
// parsing operation fails after the global storage has been
// modified. We need to preserve the original global data here
// so as we can rollback changes when an error occurs.
- Uint32Storage uint32_local(uint32_defaults);
- StringStorage string_local(string_defaults);
- OptionStorage option_local(option_defaults);
- OptionDefStorage option_def_local(option_def_intermediate);
+ ParserContext original_context(*globalContext());
- // answer will hold the result.
+ // Answer will hold the result.
ConstElementPtr answer;
- // rollback informs whether error occured and original data
+ // Rollback informs whether error occured and original data
// have to be restored to global storages.
bool rollback = false;
// config_pair holds the details of the current parser when iterating over
@@ -1760,17 +427,26 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
try {
// Make parsers grouping.
const std::map<std::string, ConstElementPtr>& values_map =
- config_set->mapValue();
+ config_set->mapValue();
BOOST_FOREACH(config_pair, values_map) {
ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PARSER_CREATED)
.arg(config_pair.first);
if (config_pair.first == "subnet4") {
subnet_parser = parser;
-
} else if (config_pair.first == "option-data") {
option_parser = parser;
-
+ } else if (config_pair.first == "interfaces") {
+ // The interface parser is independent from any other
+ // parser and can be run here before any other parsers.
+ iface_parser = parser;
+ parser->build(config_pair.second);
+ } else if (config_pair.first == "hooks-libraries") {
+ // Executing commit will alter currently-loaded hooks
+ // libraries. Check if the supplied libraries are valid,
+ // but defer the commit until everything else has committed.
+ hooks_parser_ = parser;
+ parser->build(config_pair.second);
} else {
// Those parsers should be started before other
// parsers so we can call build straight away.
@@ -1808,7 +484,7 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
rollback = true;
} catch (...) {
- // for things like bad_cast in boost::lexical_cast
+ // For things like bad_cast in boost::lexical_cast
LOG_ERROR(dhcp4_logger, DHCP4_PARSER_EXCEPTION).arg(config_pair.first);
answer = isc::config::createAnswer(1,
string("Configuration parsing failed"));
@@ -1826,29 +502,35 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
if (subnet_parser) {
subnet_parser->commit();
}
+
+ if (iface_parser) {
+ iface_parser->commit();
+ }
+
+ // This occurs last as if it succeeds, there is no easy way
+ // revert it. As a result, the failure to commit a subsequent
+ // change causes problems when trying to roll back.
+ if (hooks_parser_) {
+ hooks_parser_->commit();
+ }
}
catch (const isc::Exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
answer = isc::config::createAnswer(2,
string("Configuration commit failed: ") + ex.what());
rollback = true;
-
} catch (...) {
- // for things like bad_cast in boost::lexical_cast
+ // For things like bad_cast in boost::lexical_cast
LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION);
answer = isc::config::createAnswer(2,
string("Configuration commit failed"));
rollback = true;
-
}
}
// Rollback changes as the configuration parsing failed.
if (rollback) {
- std::swap(uint32_defaults, uint32_local);
- std::swap(string_defaults, string_local);
- std::swap(option_defaults, option_local);
- std::swap(option_def_intermediate, option_def_local);
+ globalContext().reset(new ParserContext(original_context));
return (answer);
}
@@ -1859,9 +541,13 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
return (answer);
}
-const std::map<std::string, uint32_t>& getUint32Defaults() {
- return (uint32_defaults);
+ParserContextPtr& globalContext() {
+ static ParserContextPtr global_context_ptr(new ParserContext(Option::V4));
+ return (global_context_ptr);
}
+
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
+
diff --git a/src/bin/dhcp4/config_parser.h b/src/bin/dhcp4/config_parser.h
index 4f1ea32..3af9911 100644
--- a/src/bin/dhcp4/config_parser.h
+++ b/src/bin/dhcp4/config_parser.h
@@ -12,8 +12,10 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <exceptions/exceptions.h>
#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/dhcp_parsers.h>
+
#include <stdint.h>
#include <string>
@@ -28,7 +30,8 @@ namespace dhcp {
class Dhcpv4Srv;
-/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration values.
+/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration
+/// values.
///
/// This function parses configuration information stored in @c config_set
/// and configures the @c server by applying the configuration to it.
@@ -41,9 +44,9 @@ class Dhcpv4Srv;
/// (such as malformed configuration or invalid configuration parameter),
/// this function returns appropriate error code.
///
-/// This function is called every time a new configuration is received. The extra
-/// parameter is a reference to DHCPv4 server component. It is currently not used
-/// and CfgMgr::instance() is accessed instead.
+/// This function is called every time a new configuration is received. The
+/// extra parameter is a reference to DHCPv4 server component. It is currently
+/// not used and CfgMgr::instance() is accessed instead.
///
/// This method does not throw. It catches all exceptions and returns them as
/// reconfiguration statuses. It may return the following response codes:
@@ -58,15 +61,10 @@ isc::data::ConstElementPtr
configureDhcp4Server(Dhcpv4Srv&,
isc::data::ConstElementPtr config_set);
-
-/// @brief Returns the global uint32_t values storage.
-///
-/// This function must be only used by unit tests that need
-/// to access uint32_t global storage to verify that the
-/// Uint32Parser works as expected.
+/// @brief Returns the global context
///
-/// @return a reference to a global uint32 values storage.
-const std::map<std::string, uint32_t>& getUint32Defaults();
+/// @return a reference to the global context
+ParserContextPtr& globalContext();
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index 8ac49d3..43c08c9 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -19,24 +19,28 @@
#include <cc/session.h>
#include <config/ccsession.h>
#include <dhcp/iface_mgr.h>
-#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcp4/config_parser.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/spec_config.h>
-#include <dhcp4/config_parser.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <exceptions/exceptions.h>
+#include <hooks/hooks_manager.h>
#include <util/buffer.h>
-#include <cassert>
-#include <iostream>
#include <cassert>
#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
using namespace isc::asiolink;
using namespace isc::cc;
using namespace isc::config;
using namespace isc::data;
using namespace isc::dhcp;
+using namespace isc::hooks;
using namespace isc::log;
using namespace isc::util;
using namespace std;
@@ -101,7 +105,27 @@ ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
}
// Configure the server.
- return (configureDhcp4Server(*server_, merged_config));
+ ConstElementPtr answer = configureDhcp4Server(*server_, merged_config);
+
+ // Check that configuration was successful. If not, do not reopen sockets.
+ int rcode = 0;
+ parseAnswer(rcode, answer);
+ if (rcode != 0) {
+ return (answer);
+ }
+
+ // Configuration may change active interfaces. Therefore, we have to reopen
+ // sockets according to new configuration. This operation is not exception
+ // safe and we really don't want to emit exceptions to the callback caller.
+ // Instead, catch an exception and create appropriate answer.
+ try {
+ server_->openActiveSockets(server_->getPort(), server_->useBroadcast());
+ } catch (std::exception& ex) {
+ std::ostringstream err;
+ err << "failed to open sockets after server reconfiguration: " << ex.what();
+ answer = isc::config::createAnswer(1, err.str());
+ }
+ return (answer);
}
ConstElementPtr
@@ -121,6 +145,21 @@ ControlledDhcpv4Srv::dhcp4CommandHandler(const string& command, ConstElementPtr
ConstElementPtr answer = isc::config::createAnswer(0,
"Shutting down.");
return (answer);
+
+ } else if (command == "libreload") {
+ // TODO delete any stored CalloutHandles referring to the old libraries
+ // Get list of currently loaded libraries and reload them.
+ vector<string> loaded = HooksManager::getLibraryNames();
+ bool status = HooksManager::loadLibraries(loaded);
+ if (!status) {
+ LOG_ERROR(dhcp4_logger, DHCP4_HOOKS_LIBS_RELOAD_FAIL);
+ ConstElementPtr answer = isc::config::createAnswer(1,
+ "Failed to reload hooks libraries.");
+ return (answer);
+ }
+ ConstElementPtr answer = isc::config::createAnswer(0,
+ "Hooks libraries successfully reloaded.");
+ return (answer);
}
ConstElementPtr answer = isc::config::createAnswer(1,
@@ -156,7 +195,7 @@ void ControlledDhcpv4Srv::establishSession() {
// Dumy configuration handler is internally invoked by the
// constructor and on success the constructor updates
// the current session with the configuration that had been
- // commited in the previous session. If we did not install
+ // committed in the previous session. If we did not install
// the dummy handler, the previous configuration would have
// been lost.
config_session_ = new ModuleCCSession(specfile, *cc_session_,
@@ -172,8 +211,13 @@ void ControlledDhcpv4Srv::establishSession() {
try {
configureDhcp4Server(*this, config_session_->getFullConfig());
+ // Configuration may disable or enable interfaces so we have to
+ // reopen sockets according to new configuration.
+ openActiveSockets(getPort(), useBroadcast());
+
} catch (const DhcpConfigError& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
+
}
/// Integrate the asynchronous I/O model of BIND 10 configuration
@@ -228,6 +272,5 @@ ControlledDhcpv4Srv::execDhcpv4ServerCommand(const std::string& command_id,
}
}
-
};
};
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h
index ac15c44..526d987 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.h
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -97,7 +97,7 @@ protected:
/// @brief A dummy configuration handler that always returns success.
///
/// This configuration handler does not perform configuration
- /// parsing and always returns success. A dummy hanlder should
+ /// parsing and always returns success. A dummy handler should
/// be installed using \ref isc::config::ModuleCCSession ctor
/// to get the initial configuration. This initial configuration
/// comprises values for only those elements that were modified
@@ -130,13 +130,14 @@ protected:
/// when there is a new command or configuration sent over msgq.
static void sessionReader(void);
+
/// @brief IOService object, used for all ASIO operations.
isc::asiolink::IOService io_service_;
/// @brief Helper session object that represents raw connection to msgq.
isc::cc::Session* cc_session_;
- /// @brief Session that receives configuation and commands
+ /// @brief Session that receives configuration and commands
isc::config::ModuleCCSession* config_session_;
};
diff --git a/src/bin/dhcp4/dhcp4.dox b/src/bin/dhcp4/dhcp4.dox
index c189c8a..bd490fb 100644
--- a/src/bin/dhcp4/dhcp4.dox
+++ b/src/bin/dhcp4/dhcp4.dox
@@ -33,7 +33,7 @@ assigned is not implemented in IfaceMgr yet.
DHCPv4 server component is now integrated with BIND10 message queue.
The integration is performed by establishSession() and disconnectSession()
-functions in isc::dhcp::ControlledDhcpv4Srv class. main() method deifined
+functions in isc::dhcp::ControlledDhcpv4Srv class. main() method defined
in the src/bin/dhcp4/main.cc file instantiates isc::dhcp::ControlledDhcpv4Srv
class that establishes connection with msgq and install necessary handlers
for receiving commands and configuration updates. It is derived from
@@ -79,4 +79,8 @@ See \ref dhcpv6ConfigParser.
Configuration inheritance in DHCPv4 follows exactly the same logic as its DHCPv6
counterpart. See \ref dhcpv6ConfigInherit.
+ at section dhcpv4Other Other DHCPv4 topics
+
+ For hooks API support in DHCPv4, see @ref dhcpv4Hooks.
+
*/
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index 59d727e..b979d45 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -3,16 +3,30 @@
"module_name": "Dhcp4",
"module_description": "DHCPv4 server daemon",
"config_data": [
- { "item_name": "interface",
+ {
+ "item_name": "hooks-libraries",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "hooks-library",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }
+ },
+
+ { "item_name": "interfaces",
"item_type": "list",
"item_optional": false,
- "item_default": [ "all" ],
+ "item_default": [ "*" ],
"list_item_spec":
{
"item_name": "interface_name",
"item_type": "string",
"item_optional": false,
- "item_default": "all"
+ "item_default": "*"
}
} ,
@@ -272,7 +286,14 @@
"item_optional": true
}
]
+ },
+
+ {
+ "command_name": "libreload",
+ "command_description": "Reloads the current hooks libraries.",
+ "command_args": []
}
+
]
}
}
diff --git a/src/bin/dhcp4/dhcp4_hooks.dox b/src/bin/dhcp4/dhcp4_hooks.dox
new file mode 100644
index 0000000..ebe2d89
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4_hooks.dox
@@ -0,0 +1,216 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/**
+ @page dhcpv4Hooks The Hooks API for the DHCPv4 Server
+
+ @section dhcpv4HooksIntroduction Introduction
+ BIND10 features an API (the "Hooks" API) that allows user-written code to
+ be integrated into BIND 10 and called at specific points in its processing.
+ An overview of the API and a tutorial for writing such code can be found in
+ the @ref hooksdgDevelopersGuide. Information for BIND 10 maintainers can be
+ found in the @ref hooksComponentDeveloperGuide.
+
+ This manual is more specialised and is aimed at developers of hook
+ code for the DHCPv4 server. It describes each hook point, what the callouts
+ attached to the hook are able to do, and the arguments passed to the
+ callouts. Each entry in this manual has the following information:
+
+ - Name of the hook point.
+ - Arguments for the callout. As well as the argument name and data type, the
+ information includes the direction, which can be one of:
+ - @b in - the server passes values to the callout but ignored any data
+ returned.
+ - @b out - the callout is expected to set this value.
+ - <b>in/out</b> - the server passes a value to the callout and uses whatever
+ value the callout sends back. Note that the callout may choose not to
+ do any modification, in which case the server will use whatever value
+ it sent to the callout.
+ - Description of the hook. This explains where in the processing the hook
+ is located, the possible actions a callout attached to this hook could take,
+ and a description of the data passed to the callouts.
+ - Skip flag action: the action taken by the server if a callout chooses to set
+ the "skip" flag.
+
+ at section dhcpv4HooksHookPoints Hooks in the DHCPv4 Server
+
+The following list is ordered by appearance of specific hook points during
+packet processing. Hook points that are not specific to packet processing
+(e.g. lease expiration) will be added to the end of this list.
+
+ @subsection dhcpv4HooksBuffer4Receive buffer4_receive
+
+ - @b Arguments:
+ - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed when an incoming DHCPv4
+ buffer is received, before its content is parsed. The sole argument
+ - query4 - contains a pointer to an isc::dhcp::Pkt4 object that
+ contains raw information regarding incoming packet, including its
+ source and destination addresses, interface over which it was
+ received, and a raw buffer stored in data_ field. None of the
+ packet fields (op_, hlen_, chaddr_, etc.) are set yet. Callouts
+ installed on this hook point can modify the incoming buffer. The
+ server will parse the buffer afterwards.
+
+ - <b>Skip flag action</b>: If any callout sets the skip flag, the server will
+ skip the buffer parsing. In such case there is an expectation that
+ the callout will parse the buffer and create option objects (see
+ isc::dhcp::Pkt4::addOption()). Otherwise the server will find out
+ that some mandatory options are missing (e.g. DHCP Message Type) and
+ will drop the packet. If you want to have the capability to drop
+ a message, it is better to use skip flag in pkt4_receive callout.
+
+ @subsection dhcpv4HooksPkt4Receive pkt4_receive
+
+ - @b Arguments:
+ - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed when an incoming DHCPv4
+ packet is received and its content has been parsed. The sole
+ argument - query4 - contains a pointer to an isc::dhcp::Pkt4 object
+ that contains all information regarding incoming packet, including
+ its source and destination addresses, interface over which it was
+ received, a list of all options present within and relay
+ information. All fields of the Pkt4 object can be modified at this
+ time, except data_. (By the time this hook is reached, the contents
+ of the data_ field has been already parsed and stored in other
+ fields. Therefore, the modification in the data_ field has no
+ effect.)
+
+ - <b>Skip flag action</b>: If any callout sets the skip flag, the server will
+ drop the packet and start processing the next one. The reason for the drop
+ will be logged if logging is set to the appropriate debug level.
+
+ at subsection dhcpv4HooksSubnet4Select subnet4_select
+
+ - @b Arguments:
+ - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in/out</b>
+ - name: @b subnet4, type: isc::dhcp::Subnet4Ptr, direction: <b>in/out</b>
+ - name: @b subnet4collection, type: const isc::dhcp::Subnet4Collection *, direction: <b>in</b>
+
+ - @b Description: this callout is executed when a subnet is being
+ selected for the incoming packet. All parameters and addresses
+ will be assigned from that subnet. A callout can select a
+ different subnet if it wishes so, the list of all subnets currently
+ configured being provided as 'subnet4collection'. The list itself must
+ not be modified.
+
+ - <b>Skip flag action</b>: If any callout installed on 'subnet4_select'
+ sets the skip flag, the server will not select any subnet. Packet processing
+ will continue, but will be severely limited (i.e. only global options
+ will be assigned).
+
+ at subsection dhcpv4HooksLeaseSelect lease4_select
+
+ - @b Arguments:
+ - name: @b subnet4, type: isc::dhcp::Subnet4Ptr, direction: <b>in</b>
+ - name: @b fake_allocation, type: bool, direction: <b>in</b>
+ - name: @b lease4, type: isc::dhcp::Lease4Ptr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed after the server engine
+ has selected a lease for client's request but before the lease has
+ been inserted into the database. Any modifications made to the
+ isc::dhcp::Lease4 object will be stored in the lease's record in
+ the database. The callout should sanity check all modifications as
+ the server will use that data as is with no further checking.\n\n
+ The server processes lease requests for DISCOVER and REQUEST in a
+ very similar way. The only major difference is that for DISCOVER
+ the lease is just selected, but not inserted into the database. It
+ is possible to distinguish between DISCOVER and REQUEST by checking
+ value of the fake_allocation flag: a value of true indicates that the
+ lease won't be inserted into the database (DISCOVER), a value of
+ false indicates that it will (REQUEST).
+
+ - <b>Skip flag action</b>: If any callout installed on 'lease4_select'
+ sets the skip flag, the server will not assign any lease. Packet
+ processing will continue, but client will not get an address.
+
+ at subsection dhcpv4HooksLeaseRenew lease4_renew
+
+ - @b Arguments:
+ - name: @b subnet4, type: isc::dhcp::Subnet4Ptr, direction: <b>in</b>
+ - name: @b clientid, type: isc::dhcp::ClientId, direction: <b>in</b>
+ - name: @b hwaddr, type: isc::dhcp::HWAddr, direction: <b>in</b>
+ - name: @b lease4, type: isc::dhcp::Lease4Ptr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed when the server engine
+ is about to renew a lease, as a result of receiving REQUEST/Renewing
+ packet. The lease4 argument points to Lease4 object that contains
+ the updated values. Callout can modify those values. Care should be taken
+ as the server will attempt to update the lease in the database without
+ any additional checks.
+
+ - <b>Skip flag action</b>: If any callout installed on 'lease4_renew'
+ sets the skip flag, the server will not update the lease and will
+ use old values instead.
+
+ at subsection dhcpv4HooksLeaseRelease lease4_release
+
+ - @b Arguments:
+ - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in</b>
+ - name: @b lease4, type: isc::dhcp::Lease4Ptr, direction: <b>in</b>
+
+ - @b Description: this callout is executed when the server engine
+ is about to release a lease, as a result of receiving RELEASE packet.
+ The lease4 argument points to Lease4 object that contains the lease to
+ be released. It doesn't make sense to modify it at this time.
+
+ - <b>Skip flag action</b>: If any callout installed on 'lease4_release'
+ sets the skip flag, the server will not delete the lease. It will be
+ kept in the database and will go through the regular expiration/reuse
+ process.
+
+ at subsection dhcpv4HooksPkt4Send pkt4_send
+
+ - @b Arguments:
+ - name: @b response4, type: isc::dhcp::Pkt4Ptr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed when server's response
+ is about to be sent back to the client. The sole argument - response4 -
+ contains a pointer to an isc::dhcp::Pkt4 object that contains the
+ packet, with source and destination addresses set, interface over which
+ it will be sent, and a list of all options and relay information. All fields
+ of the Pkt4 object can be modified at this time, except buffer_out_.
+ (This is scratch space used for constructing the packet after all
+ pkt4_send callouts are complete, so any changes to that field will
+ be overwritten.)
+
+ - <b>Skip flag action</b>: if any callout sets the skip flag, the server
+ will not construct raw buffer. The expectation is that if the callout
+ set skip flag, it is responsible for constructing raw form on its own.
+ Otherwise the output packet will be sent with zero length.
+
+ at subsection dhcpv4HooksBuffer4Send buffer4_send
+
+ - @b Arguments:
+ - name: @b response4, type: isc::dhcp::Pkt4Ptr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed when server's response
+ is about to be sent back to the client. The sole argument - response4 -
+ contains a pointer to an isc::dhcp::Pkt4 object that contains the
+ packet, with source and destination addresses set, interface over which
+ it will be sent, and a list of all options and relay information. The raw
+ on-wire form is already prepared in buffer_out_ (see isc::dhcp::Pkt4::getBuffer())
+ It doesn't make any sense to modify packet fields or options content
+ at this time, because they were already used to construct on-wire buffer.
+
+ - <b>Skip flag action</b>: if any callout sets the skip flag, the server
+ will drop this response packet. However, the original request packet
+ from a client was processed, so server's state was most likely changed
+ (e.g. lease was allocated). Setting this flag merely stops the change
+ being communicated to the client.
+
+
+*/
diff --git a/src/bin/dhcp4/dhcp4_log.h b/src/bin/dhcp4/dhcp4_log.h
index 07d009a..54dbb40 100644
--- a/src/bin/dhcp4/dhcp4_log.h
+++ b/src/bin/dhcp4/dhcp4_log.h
@@ -38,6 +38,9 @@ const int DBG_DHCP4_COMMAND = DBGLVL_COMMAND;
// Trace basic operations within the code.
const int DBG_DHCP4_BASIC = DBGLVL_TRACE_BASIC;
+// Trace hook related operations
+const int DBG_DHCP4_HOOKS = DBGLVL_TRACE_BASIC;
+
// Trace detailed operations, including errors raised when processing invalid
// packets. (These are not logged at severities of WARN or higher for fear
// that a set of deliberately invalid packets set to the server could overwhelm
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index 2f49ac8..fca75bf 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -14,6 +14,11 @@
$NAMESPACE isc::dhcp
+% DHCP4_ACTIVATE_INTERFACE activating interface %1
+This message is printed when DHCPv4 server enabled an interface to be used
+to receive DHCPv4 traffic. IPv4 socket on this interface will be opened once
+Interface Manager starts up procedure of opening sockets.
+
% DHCP4_CCSESSION_STARTED control channel session started on socket %1
A debug message issued during startup after the IPv4 DHCP server has
successfully established a session with the BIND 10 control channel.
@@ -37,7 +42,7 @@ This critical error message indicates that the initial DHCPv4
configuration has failed. The server will start, but nothing will be
served until the configuration has been corrected.
-% DHCP4_CONFIG_NEW_SUBNET A new subnet has been added to configuration: %1
+% DHCP4_CONFIG_NEW_SUBNET a new subnet has been added to configuration: %1
This is an informational message reporting that the configuration has
been extended to include the specified IPv4 subnet.
@@ -60,6 +65,53 @@ This informational message is printed every time DHCPv4 server is started
and gives both the type and name of the database being used to store
lease and other information.
+% DHCP4_DEACTIVATE_INTERFACE deactivate interface %1
+This message is printed when DHCPv4 server disables an interface from being
+used to receive DHCPv4 traffic. Sockets on this interface will not be opened
+by the Interface Manager until interface is enabled.
+
+% DHCP4_HOOK_BUFFER_RCVD_SKIP received DHCPv4 buffer was dropped because a callout set the skip flag.
+This debug message is printed when a callout installed on buffer4_receive
+hook point set the skip flag. For this particular hook point, the
+setting of the flag by a callout instructs the server to drop the packet.
+
+% DHCP4_HOOK_BUFFER_SEND_SKIP prepared DHCPv4 response was dropped because a callout set the skip flag.
+This debug message is printed when a callout installed on buffer4_send
+hook point set the skip flag. For this particular hook point, the
+setting of the flag by a callout instructs the server to drop the packet.
+Server completed all the processing (e.g. may have assigned, updated
+or released leases), but the response will not be send to the client.
+
+% DHCP4_HOOK_LEASE4_RELEASE_SKIP DHCPv4 lease was not released because a callout set the skip flag.
+This debug message is printed when a callout installed on lease4_release
+hook point set the skip flag. For this particular hook point, the
+setting of the flag by a callout instructs the server to not release
+a lease.
+
+% DHCP4_HOOK_PACKET_RCVD_SKIP received DHCPv4 packet was dropped, because a callout set the skip flag.
+This debug message is printed when a callout installed on the pkt4_receive
+hook point sets the skip flag. For this particular hook point, the
+setting of the flag instructs the server to drop the packet.
+
+% DHCP4_HOOK_PACKET_SEND_SKIP prepared DHCPv6 response was not sent, because a callout set skip flag.
+This debug message is printed when a callout installed on the pkt4_send
+hook point sets the skip flag. For this particular hook point, the setting
+of the flag instructs the server to drop the packet. This means that
+the client will not get any response, even though the server processed
+client's request and acted on it (e.g. possibly allocated a lease).
+
+% DHCP4_HOOK_SUBNET4_SELECT_SKIP no subnet was selected, because a callout set skip flag.
+This debug message is printed when a callout installed on the
+subnet4_select hook point sets the skip flag. For this particular hook
+point, the setting of the flag instructs the server not to choose a
+subnet, an action that severely limits further processing; the server
+will be only able to offer global options - no addresses will be assigned.
+
+% DHCP4_HOOKS_LIBS_RELOAD_FAIL reload of hooks libraries failed
+A "libreload" command was issued to reload the hooks libraries but for
+some reason the reload failed. Other error messages issued from the
+hooks framework will indicate the nature of the problem.
+
% DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
This debug message indicates that the server successfully advertised
a lease. It is up to the client to choose one server out of othe advertised
@@ -74,7 +126,7 @@ many possible reasons for such a failure.
% DHCP4_LEASE_ALLOC lease %1 has been allocated for client-id %2, hwaddr %3
This debug message indicates that the server successfully granted a lease
in response to client's REQUEST message. This is a normal behavior and
-incicates successful operation.
+indicates successful operation.
% DHCP4_LEASE_ALLOC_FAIL failed to grant a lease for client-id %1, hwaddr %2
This message indicates that the server failed to grant a lease to the
@@ -82,6 +134,11 @@ specified client after receiving a REQUEST message from it. There are many
possible reasons for such a failure. Additional messages will indicate the
reason.
+% DHCP4_NO_SOCKETS_OPEN no interface configured to listen to DHCP traffic
+This warning message is issued when current server configuration specifies
+no interfaces that server should listen on, or specified interfaces are not
+configured to receive the traffic.
+
% DHCP4_NOT_RUNNING IPv4 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
IPv4 DHCP server but it is not running.
@@ -99,6 +156,10 @@ This is a general catch-all message indicating that the processing of a
received packet failed. The reason is given in the message. The server
will not send a response but will instead ignore the packet.
+% DHCP4_PACKET_DROP_NO_TYPE packet received on interface %1 dropped, because of missing msg-type option
+This is a debug message informing that incoming DHCPv4 packet did not
+have mandatory DHCP message type option and thus was dropped.
+
% DHCP4_PACKET_RECEIVED %1 (type %2) packet received on interface %3
A debug message noting that the server has received the specified type of
packet on the specified interface. Note that a packet marked as UNKNOWN
@@ -115,11 +176,6 @@ This error is output if the IPv4 DHCP server fails to send an assembled
DHCP message to a client. The reason for the error is included in the
message.
-% DHCP4_PACK_FAIL failed to assemble response correctly
-This error is output if the server failed to assemble the data to be
-returned to the client into a valid packet. The cause is most likely
-to be a programming error: please raise a bug report.
-
% DHCP4_PARSER_COMMIT_EXCEPTION parser failed to commit changes
On receipt of message containing details to a change of the IPv4 DHCP
server configuration, a set of parsers were successfully created, but one
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 261e213..bf4f76b 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -14,22 +14,25 @@
#include <asiolink/io_address.h>
#include <dhcp/dhcp4.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/pkt4.h>
-#include <dhcp/duid.h>
-#include <dhcp/hwaddr.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/dhcp4_srv.h>
-#include <dhcpsrv/utils.h>
+#include <dhcpsrv/addr_utilities.h>
+#include <dhcpsrv/callout_handle_store.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/utils.h>
-#include <dhcpsrv/addr_utilities.h>
+#include <dhcpsrv/utils.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
#include <boost/algorithm/string/erase.hpp>
@@ -39,9 +42,36 @@
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
+using namespace isc::hooks;
using namespace isc::log;
using namespace std;
+/// Structure that holds registered hook indexes
+struct Dhcp4Hooks {
+ int hook_index_buffer4_receive_;///< index for "buffer4_receive" hook point
+ int hook_index_pkt4_receive_; ///< index for "pkt4_receive" hook point
+ int hook_index_subnet4_select_; ///< index for "subnet4_select" hook point
+ int hook_index_lease4_release_; ///< index for "lease4_release" hook point
+ int hook_index_pkt4_send_; ///< index for "pkt4_send" hook point
+ int hook_index_buffer4_send_; ///< index for "buffer4_send" hook point
+
+ /// Constructor that registers hook points for DHCPv4 engine
+ Dhcp4Hooks() {
+ hook_index_buffer4_receive_= HooksManager::registerHook("buffer4_receive");
+ hook_index_pkt4_receive_ = HooksManager::registerHook("pkt4_receive");
+ hook_index_subnet4_select_ = HooksManager::registerHook("subnet4_select");
+ hook_index_pkt4_send_ = HooksManager::registerHook("pkt4_send");
+ hook_index_lease4_release_ = HooksManager::registerHook("lease4_release");
+ hook_index_buffer4_send_ = HooksManager::registerHook("buffer4_send");
+ }
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+Dhcp4Hooks Hooks;
+
namespace isc {
namespace dhcp {
@@ -57,17 +87,27 @@ static const char* SERVER_ID_FILE = "b10-dhcp4-serverid";
// These are hardcoded parameters. Currently this is a skeleton server that only
// grants those options and a single, fixed, hardcoded lease.
-Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
+Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
+ const bool direct_response_desired)
+: serverid_(), shutdown_(true), alloc_engine_(), port_(port),
+ use_bcast_(use_bcast), hook_index_pkt4_receive_(-1),
+ hook_index_subnet4_select_(-1), hook_index_pkt4_send_(-1) {
+
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
try {
// First call to instance() will create IfaceMgr (it's a singleton)
- // it may throw something if things go wrong
- IfaceMgr::instance();
+ // it may throw something if things go wrong.
+ // The 'true' value of the call to setMatchingPacketFilter imposes
+ // that IfaceMgr will try to use the mechanism to respond directly
+ // to the client which doesn't have address assigned. This capability
+ // may be lacking on some OSes, so there is no guarantee that server
+ // will be able to respond directly.
+ IfaceMgr::instance().setMatchingPacketFilter(direct_response_desired);
if (port) {
// open sockets only if port is non-zero. Port 0 is used
// for non-socket related testing.
- IfaceMgr::instance().openSockets4(port);
+ IfaceMgr::instance().openSockets4(port_, use_bcast_);
}
string srvid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_ID_FILE);
@@ -97,6 +137,13 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
// Instantiate allocation engine
alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
+ // Register hook points
+ hook_index_pkt4_receive_ = Hooks.hook_index_pkt4_receive_;
+ hook_index_subnet4_select_ = Hooks.hook_index_subnet4_select_;
+ hook_index_pkt4_send_ = Hooks.hook_index_pkt4_send_;
+
+ /// @todo call loadLibraries() when handling configuration changes
+
} catch (const std::exception &e) {
LOG_ERROR(dhcp4_logger, DHCP4_SRV_CONSTRUCT_ERROR).arg(e.what());
shutdown_ = true;
@@ -116,117 +163,272 @@ Dhcpv4Srv::shutdown() {
shutdown_ = true;
}
+Pkt4Ptr
+Dhcpv4Srv::receivePacket(int timeout) {
+ return (IfaceMgr::instance().receive4(timeout));
+}
+
+void
+Dhcpv4Srv::sendPacket(const Pkt4Ptr& packet) {
+ IfaceMgr::instance().send(packet);
+}
+
bool
Dhcpv4Srv::run() {
while (!shutdown_) {
/// @todo: calculate actual timeout once we have lease database
- int timeout = 1000;
+ //cppcheck-suppress variableScope This is temporary anyway
+ const int timeout = 1000;
// client's message and server's response
Pkt4Ptr query;
Pkt4Ptr rsp;
try {
- query = IfaceMgr::instance().receive4(timeout);
+ query = receivePacket(timeout);
} catch (const std::exception& e) {
LOG_ERROR(dhcp4_logger, DHCP4_PACKET_RECEIVE_FAIL).arg(e.what());
}
- if (query) {
+ // Timeout may be reached or signal received, which breaks select()
+ // with no reception ocurred
+ if (!query) {
+ continue;
+ }
+
+ bool skip_unpack = false;
+
+ // The packet has just been received so contains the uninterpreted wire
+ // data; execute callouts registered for buffer4_receive.
+ if (HooksManager::getHooksManager()
+ .calloutsPresent(Hooks.hook_index_buffer4_receive_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Delete previously set arguments
+ callout_handle->deleteAllArguments();
+
+ // Pass incoming packet as argument
+ callout_handle->setArgument("query4", query);
+
+ // Call callouts
+ HooksManager::callCallouts(Hooks.hook_index_buffer4_receive_,
+ *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to parse the packet, so skip at this
+ // stage means that callouts did the parsing already, so server
+ // should skip parsing.
+ if (callout_handle->getSkip()) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_BUFFER_RCVD_SKIP);
+ skip_unpack = true;
+ }
+
+ callout_handle->getArgument("query4", query);
+ }
+
+ // Unpack the packet information unless the buffer4_receive callouts
+ // indicated they did it
+ if (!skip_unpack) {
try {
query->unpack();
-
} catch (const std::exception& e) {
// Failed to parse the packet.
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL,
DHCP4_PACKET_PARSE_FAIL).arg(e.what());
continue;
}
- LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_RECEIVED)
- .arg(serverReceivedPacketName(query->getType()))
- .arg(query->getType())
- .arg(query->getIface());
- LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_QUERY_DATA)
- .arg(query->toText());
+ }
- try {
- switch (query->getType()) {
- case DHCPDISCOVER:
- rsp = processDiscover(query);
- break;
-
- case DHCPREQUEST:
- rsp = processRequest(query);
- break;
-
- case DHCPRELEASE:
- processRelease(query);
- break;
-
- case DHCPDECLINE:
- processDecline(query);
- break;
-
- case DHCPINFORM:
- processInform(query);
- break;
-
- default:
- // Only action is to output a message if debug is enabled,
- // and that is covered by the debug statement before the
- // "switch" statement.
- ;
- }
- } catch (const isc::Exception& e) {
-
- // Catch-all exception (at least for ones based on the isc
- // Exception class, which covers more or less all that
- // are explicitly raised in the BIND 10 code). Just log
- // the problem and ignore the packet. (The problem is logged
- // as a debug message because debug is disabled by default -
- // it prevents a DDOS attack based on the sending of problem
- // packets.)
- if (dhcp4_logger.isDebugEnabled(DBG_DHCP4_BASIC)) {
- std::string source = "unknown";
- HWAddrPtr hwptr = query->getHWAddr();
- if (hwptr) {
- source = hwptr->toText();
- }
- LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC,
- DHCP4_PACKET_PROCESS_FAIL)
- .arg(source).arg(e.what());
- }
+ // When receiving a packet without message type option, getType() will
+ // throw. Let's set type to -1 as default error indicator.
+ int type = -1;
+ try {
+ type = query->getType();
+ } catch (...) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_DROP_NO_TYPE)
+ .arg(query->getIface());
+ continue;
+ }
+
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_RECEIVED)
+ .arg(serverReceivedPacketName(type))
+ .arg(type)
+ .arg(query->getIface());
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_QUERY_DATA)
+ .arg(type)
+ .arg(query->toText());
+
+ // Let's execute all callouts registered for pkt4_receive
+ if (HooksManager::calloutsPresent(hook_index_pkt4_receive_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Delete previously set arguments
+ callout_handle->deleteAllArguments();
+
+ // Pass incoming packet as argument
+ callout_handle->setArgument("query4", query);
+
+ // Call callouts
+ HooksManager::callCallouts(hook_index_pkt4_receive_,
+ *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to process the packet, so skip at this
+ // stage means drop.
+ if (callout_handle->getSkip()) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_PACKET_RCVD_SKIP);
+ continue;
}
- if (rsp) {
- if (rsp->getRemoteAddr().toText() == "0.0.0.0") {
- rsp->setRemoteAddr(query->getRemoteAddr());
- }
- if (!rsp->getHops()) {
- rsp->setRemotePort(DHCP4_CLIENT_PORT);
- } else {
- rsp->setRemotePort(DHCP4_SERVER_PORT);
+ callout_handle->getArgument("query4", query);
+ }
+
+ try {
+ switch (query->getType()) {
+ case DHCPDISCOVER:
+ rsp = processDiscover(query);
+ break;
+
+ case DHCPREQUEST:
+ // Note that REQUEST is used for many things in DHCPv4: for
+ // requesting new leases, renewing existing ones and even
+ // for rebinding.
+ rsp = processRequest(query);
+ break;
+
+ case DHCPRELEASE:
+ processRelease(query);
+ break;
+
+ case DHCPDECLINE:
+ processDecline(query);
+ break;
+
+ case DHCPINFORM:
+ processInform(query);
+ break;
+
+ default:
+ // Only action is to output a message if debug is enabled,
+ // and that is covered by the debug statement before the
+ // "switch" statement.
+ ;
+ }
+ } catch (const isc::Exception& e) {
+
+ // Catch-all exception (at least for ones based on the isc
+ // Exception class, which covers more or less all that
+ // are explicitly raised in the BIND 10 code). Just log
+ // the problem and ignore the packet. (The problem is logged
+ // as a debug message because debug is disabled by default -
+ // it prevents a DDOS attack based on the sending of problem
+ // packets.)
+ if (dhcp4_logger.isDebugEnabled(DBG_DHCP4_BASIC)) {
+ std::string source = "unknown";
+ HWAddrPtr hwptr = query->getHWAddr();
+ if (hwptr) {
+ source = hwptr->toText();
}
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC,
+ DHCP4_PACKET_PROCESS_FAIL)
+ .arg(source).arg(e.what());
+ }
+ }
+
+ if (!rsp) {
+ continue;
+ }
+
+ adjustRemoteAddr(query, rsp);
+
+ if (!rsp->getHops()) {
+ rsp->setRemotePort(DHCP4_CLIENT_PORT);
+ } else {
+ rsp->setRemotePort(DHCP4_SERVER_PORT);
+ }
+
+ rsp->setLocalAddr(query->getLocalAddr());
+ rsp->setLocalPort(DHCP4_SERVER_PORT);
+ rsp->setIface(query->getIface());
+ rsp->setIndex(query->getIndex());
+
+ // Specifies if server should do the packing
+ bool skip_pack = false;
+
+ // Execute all callouts registered for pkt4_send
+ if (HooksManager::calloutsPresent(hook_index_pkt4_send_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Delete all previous arguments
+ callout_handle->deleteAllArguments();
+
+ // Clear skip flag if it was set in previous callouts
+ callout_handle->setSkip(false);
+
+ // Set our response
+ callout_handle->setArgument("response4", rsp);
+
+ // Call all installed callouts
+ HooksManager::callCallouts(hook_index_pkt4_send_,
+ *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to send the packet, so skip at this
+ // stage means "drop response".
+ if (callout_handle->getSkip()) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_PACKET_SEND_SKIP);
+ skip_pack = true;
+ }
+ }
- rsp->setLocalAddr(query->getLocalAddr());
- rsp->setLocalPort(DHCP4_SERVER_PORT);
- rsp->setIface(query->getIface());
- rsp->setIndex(query->getIndex());
-
- LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA,
- DHCP4_RESPONSE_DATA)
- .arg(rsp->getType()).arg(rsp->toText());
-
- if (rsp->pack()) {
- try {
- IfaceMgr::instance().send(rsp);
- } catch (const std::exception& e) {
- LOG_ERROR(dhcp4_logger, DHCP4_PACKET_SEND_FAIL).arg(e.what());
- }
- } else {
- LOG_ERROR(dhcp4_logger, DHCP4_PACK_FAIL);
+ if (!skip_pack) {
+ try {
+ rsp->pack();
+ } catch (const std::exception& e) {
+ LOG_ERROR(dhcp4_logger, DHCP4_PACKET_SEND_FAIL)
+ .arg(e.what());
+ }
+ }
+
+ try {
+ // Now all fields and options are constructed into output wire buffer.
+ // Option objects modification does not make sense anymore. Hooks
+ // can only manipulate wire buffer at this stage.
+ // Let's execute all callouts registered for buffer4_send
+ if (HooksManager::getHooksManager()
+ .calloutsPresent(Hooks.hook_index_buffer4_send_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Delete previously set arguments
+ callout_handle->deleteAllArguments();
+
+ // Pass incoming packet as argument
+ callout_handle->setArgument("response4", rsp);
+
+ // Call callouts
+ HooksManager::callCallouts(Hooks.hook_index_buffer4_send_,
+ *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to parse the packet, so skip at this
+ // stage means drop.
+ if (callout_handle->getSkip()) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS,
+ DHCP4_HOOK_BUFFER_SEND_SKIP);
+ continue;
}
+
+ callout_handle->getArgument("response4", rsp);
}
+
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA,
+ DHCP4_RESPONSE_DATA)
+ .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());
+
+ sendPacket(rsp);
+ } catch (const std::exception& e) {
+ LOG_ERROR(dhcp4_logger, DHCP4_PACKET_SEND_FAIL)
+ .arg(e.what());
}
}
@@ -287,9 +489,9 @@ Dhcpv4Srv::generateServerID() {
continue;
}
- const IfaceMgr::AddressCollection addrs = iface->getAddresses();
+ const Iface::AddressCollection addrs = iface->getAddresses();
- for (IfaceMgr::AddressCollection::const_iterator addr = addrs.begin();
+ for (Iface::AddressCollection::const_iterator addr = addrs.begin();
addr != addrs.end(); ++addr) {
if (addr->getFamily() != AF_INET) {
continue;
@@ -317,7 +519,7 @@ Dhcpv4Srv::writeServerID(const std::string& file_name) {
return (true);
}
-string
+string
Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
if (!srvid) {
isc_throw(BadValue, "NULL pointer passed to srvidToString()");
@@ -343,7 +545,7 @@ Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
answer->setIndex(question->getIndex());
answer->setCiaddr(question->getCiaddr());
- answer->setSiaddr(IOAddress("0.0.0.0")); // explictly set this to 0
+ answer->setSiaddr(IOAddress("0.0.0.0")); // explicitly set this to 0
answer->setHops(question->getHops());
// copy MAC address
@@ -352,19 +554,27 @@ Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
// relay address
answer->setGiaddr(question->getGiaddr());
- if (question->getGiaddr().toText() != "0.0.0.0") {
- // relayed traffic
- answer->setRemoteAddr(question->getGiaddr());
- } else {
- // direct traffic
- answer->setRemoteAddr(question->getRemoteAddr());
- }
-
// Let's copy client-id to response. See RFC6842.
OptionPtr client_id = question->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
if (client_id) {
answer->addOption(client_id);
}
+
+ // If src/dest HW addresses are used by the packet filtering class
+ // we need to copy them as well. There is a need to check that the
+ // address being set is not-NULL because an attempt to set the NULL
+ // HW would result in exception. If these values are not set, the
+ // the default HW addresses (zeroed) should be generated by the
+ // packet filtering class when creating Ethernet header for
+ // outgoing packet.
+ HWAddrPtr src_hw_addr = question->getLocalHWAddr();
+ if (src_hw_addr) {
+ answer->setLocalHWAddr(src_hw_addr);
+ }
+ HWAddrPtr dst_hw_addr = question->getRemoteHWAddr();
+ if (dst_hw_addr) {
+ answer->setRemoteHWAddr(dst_hw_addr);
+ }
}
void
@@ -499,12 +709,19 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
// allocation.
bool fake_allocation = (question->getType() == DHCPDISCOVER);
+ CalloutHandlePtr callout_handle = getCalloutHandle(question);
+
// Use allocation engine to pick a lease for this client. Allocation engine
// will try to honour the hint, but it is just a hint - some other address
// may be used instead. If fake_allocation is set to false, the lease will
// be inserted into the LeaseMgr as well.
+ // @todo pass the actual FQDN data.
+ Lease4Ptr old_lease;
Lease4Ptr lease = alloc_engine_->allocateAddress4(subnet, client_id, hwaddr,
- hint, fake_allocation);
+ hint, false, false, "",
+ fake_allocation,
+ callout_handle,
+ old_lease);
if (lease) {
// We have a lease! Let's set it in the packet and send it back to
@@ -551,6 +768,60 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
}
}
+void
+Dhcpv4Srv::adjustRemoteAddr(const Pkt4Ptr& question, Pkt4Ptr& msg) {
+ // Let's create static objects representing zeroed and broadcast
+ // addresses. We will use them further in this function to test
+ // other addresses against them. Since they are static, they will
+ // be created only once.
+ static const IOAddress zero_addr("0.0.0.0");
+ static const IOAddress bcast_addr("255.255.255.255");
+
+ // If received relayed message, server responds to the relay address.
+ if (question->getGiaddr() != zero_addr) {
+ msg->setRemoteAddr(question->getGiaddr());
+
+ // If giaddr is 0 but client set ciaddr, server should unicast the
+ // response to ciaddr.
+ } else if (question->getCiaddr() != zero_addr) {
+ msg->setRemoteAddr(question->getCiaddr());
+
+ // We can't unicast the response to the client when sending NAK,
+ // because we haven't allocated address for him. Therefore,
+ // NAK is broadcast.
+ } else if (msg->getType() == DHCPNAK) {
+ msg->setRemoteAddr(bcast_addr);
+
+ // If yiaddr is set it means that we have created a lease for a client.
+ } else if (msg->getYiaddr() != zero_addr) {
+ // If the broadcast bit is set in the flags field, we have to
+ // send the response to broadcast address. Client may have requested it
+ // because it doesn't support reception of messages on the interface
+ // which doesn't have an address assigned. The other case when response
+ // must be broadcasted is when our server does not support responding
+ // directly to a client without address assigned.
+ const bool bcast_flag = ((question->getFlags() & Pkt4::FLAG_BROADCAST_MASK) != 0);
+ if (!IfaceMgr::instance().isDirectResponseSupported() || bcast_flag) {
+ msg->setRemoteAddr(bcast_addr);
+
+ // Client cleared the broadcast bit and we support direct responses
+ // so we should unicast the response to a newly allocated address -
+ // yiaddr.
+ } else {
+ msg->setRemoteAddr(msg->getYiaddr());
+
+ }
+
+ // In most cases, we should have the remote address found already. If we
+ // found ourselves at this point, the rational thing to do is to respond
+ // to the address we got the query from.
+ } else {
+ msg->setRemoteAddr(question->getRemoteAddr());
+
+ }
+}
+
+
OptionPtr
Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
uint32_t netmask = getNetmask4(subnet->get().second);
@@ -563,6 +834,9 @@ Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
Pkt4Ptr
Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
+
+ sanityCheck(discover, FORBIDDEN);
+
Pkt4Ptr offer = Pkt4Ptr
(new Pkt4(DHCPOFFER, discover->getTransid()));
@@ -582,6 +856,10 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
Pkt4Ptr
Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
+
+ /// @todo Uncomment this (see ticket #3116)
+ // sanityCheck(request, MANDATORY);
+
Pkt4Ptr ack = Pkt4Ptr
(new Pkt4(DHCPACK, request->getTransid()));
@@ -589,6 +867,9 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
appendDefaultOptions(ack, DHCPACK);
appendRequestedOptions(request, ack);
+ // Note that we treat REQUEST message uniformly, regardless if this is a
+ // first request (requesting for new address), renewing existing address
+ // or even rebinding.
assignLease(request, ack);
// There are a few basic options that we always want to
@@ -602,6 +883,9 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
void
Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
+ /// @todo Uncomment this (see ticket #3116)
+ // sanityCheck(release, MANDATORY);
+
// Try to find client-id
ClientIdPtr client_id;
OptionPtr opt = release->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
@@ -643,21 +927,53 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
return;
}
- // Ok, hw and client-id match - let's release the lease.
- if (LeaseMgrFactory::instance().deleteLease(lease->addr_)) {
+ bool skip = false;
- // Release successful - we're done here
- LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE)
- .arg(lease->addr_.toText())
- .arg(client_id ? client_id->toText() : "(no client-id)")
- .arg(release->getHWAddr()->toText());
- } else {
+ // Execute all callouts registered for lease4_release
+ if (HooksManager::getHooksManager()
+ .calloutsPresent(Hooks.hook_index_lease4_release_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(release);
+
+ // Delete all previous arguments
+ callout_handle->deleteAllArguments();
+
+ // Pass the original packet
+ callout_handle->setArgument("query4", release);
+
+ // Pass the lease to be updated
+ callout_handle->setArgument("lease4", lease);
+
+ // Call all installed callouts
+ HooksManager::callCallouts(Hooks.hook_index_lease4_release_,
+ *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to send the packet, so skip at this
+ // stage means "drop response".
+ if (callout_handle->getSkip()) {
+ skip = true;
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS,
+ DHCP4_HOOK_LEASE4_RELEASE_SKIP);
+ }
+ }
- // Release failed -
- LOG_ERROR(dhcp4_logger, DHCP4_RELEASE_FAIL)
- .arg(lease->addr_.toText())
+ // Ok, hw and client-id match - let's release the lease.
+ if (!skip) {
+ bool success = LeaseMgrFactory::instance().deleteLease(lease->addr_);
+
+ if (success) {
+ // Release successful - we're done here
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE)
+ .arg(lease->addr_.toText())
+ .arg(client_id ? client_id->toText() : "(no client-id)")
+ .arg(release->getHWAddr()->toText());
+ } else {
+ // Release failed -
+ LOG_ERROR(dhcp4_logger, DHCP4_RELEASE_FAIL)
+ .arg(lease->addr_.toText())
.arg(client_id ? client_id->toText() : "(no client-id)")
- .arg(release->getHWAddr()->toText());
+ .arg(release->getHWAddr()->toText());
+ }
}
} catch (const isc::Exception& ex) {
// Rethrow the exception with a bit more data.
@@ -670,12 +986,13 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
void
Dhcpv4Srv::processDecline(Pkt4Ptr& /* decline */) {
- /// TODO: Implement this.
+ /// @todo Implement this (also see ticket #3116)
}
Pkt4Ptr
Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
- /// TODO: Currently implemented echo mode. Implement this for real
+
+ /// @todo Implement this for real. (also see ticket #3116)
return (inform);
}
@@ -713,17 +1030,53 @@ Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
Subnet4Ptr
Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
+ Subnet4Ptr subnet;
// Is this relayed message?
IOAddress relay = question->getGiaddr();
- if (relay.toText() == "0.0.0.0") {
+ static const IOAddress notset("0.0.0.0");
+ if (relay != notset) {
// Yes: Use relay address to select subnet
- return (CfgMgr::instance().getSubnet4(relay));
+ subnet = CfgMgr::instance().getSubnet4(relay);
} else {
// No: Use client's address to select subnet
- return (CfgMgr::instance().getSubnet4(question->getRemoteAddr()));
+ subnet = CfgMgr::instance().getSubnet4(question->getRemoteAddr());
+ }
+
+ /// @todo Implement getSubnet4(interface-name)
+
+ // Let's execute all callouts registered for subnet4_select
+ if (HooksManager::calloutsPresent(hook_index_subnet4_select_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(question);
+
+ // We're reusing callout_handle from previous calls
+ callout_handle->deleteAllArguments();
+
+ // Set new arguments
+ callout_handle->setArgument("query4", question);
+ callout_handle->setArgument("subnet4", subnet);
+ callout_handle->setArgument("subnet4collection",
+ CfgMgr::instance().getSubnets4());
+
+ // Call user (and server-side) callouts
+ HooksManager::callCallouts(hook_index_subnet4_select_,
+ *callout_handle);
+
+ // Callouts decided to skip this step. This means that no subnet will be
+ // selected. Packet processing will continue, but it will be severly
+ // limited (i.e. only global options will be assigned)
+ if (callout_handle->getSkip()) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS,
+ DHCP4_HOOK_SUBNET4_SELECT_SKIP);
+ return (Subnet4Ptr());
+ }
+
+ // Use whatever subnet was specified by the callout
+ callout_handle->getArgument("subnet4", subnet);
}
+
+ return (subnet);
}
void
@@ -749,7 +1102,67 @@ Dhcpv4Srv::sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid) {
// do nothing here
;
}
+
+ // If there is HWAddress set and it is non-empty, then we're good
+ if (pkt->getHWAddr() && !pkt->getHWAddr()->hwaddr_.empty()) {
+ return;
+ }
+
+ // There has to be something to uniquely identify the client:
+ // either non-zero MAC address or client-id option present (or both)
+ OptionPtr client_id = pkt->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+
+ // If there's no client-id (or a useless one is provided, i.e. 0 length)
+ if (!client_id || client_id->len() == client_id->getHeaderLen()) {
+ isc_throw(RFCViolation, "Missing or useless client-id and no HW address "
+ " provided in message "
+ << serverReceivedPacketName(pkt->getType()));
+ }
+}
+
+void
+Dhcpv4Srv::openActiveSockets(const uint16_t port,
+ const bool use_bcast) {
+ IfaceMgr::instance().closeSockets();
+
+ // Get the reference to the collection of interfaces. This reference should
+ // be valid as long as the program is run because IfaceMgr is a singleton.
+ // Therefore we can safely iterate over instances of all interfaces and
+ // modify their flags. Here we modify flags which indicate whether socket
+ // should be open for a particular interface or not.
+ const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+ for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
+ iface != ifaces.end(); ++iface) {
+ Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
+ if (iface_ptr == NULL) {
+ isc_throw(isc::Unexpected, "Interface Manager returned NULL"
+ << " instance of the interface when DHCPv4 server was"
+ << " trying to reopen sockets after reconfiguration");
+ }
+ if (CfgMgr::instance().isActiveIface(iface->getName())) {
+ iface_ptr->inactive4_ = false;
+ LOG_INFO(dhcp4_logger, DHCP4_ACTIVATE_INTERFACE)
+ .arg(iface->getFullName());
+
+ } else {
+ // For deactivating interface, it should be sufficient to log it
+ // on the debug level because it is more useful to know what
+ // interface is activated which is logged on the info level.
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC,
+ DHCP4_DEACTIVATE_INTERFACE).arg(iface->getName());
+ iface_ptr->inactive4_ = true;
+
+ }
+ }
+ // Let's reopen active sockets. openSockets4 will check internally whether
+ // sockets are marked active or inactive.
+ // @todo Optimization: we should not reopen all sockets but rather select
+ // those that have been affected by the new configuration.
+ if (!IfaceMgr::instance().openSockets4(port, use_bcast)) {
+ LOG_WARN(dhcp4_logger, DHCP4_NO_SOCKETS_OPEN);
+ }
}
+
} // namespace dhcp
} // namespace isc
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index 1c988b1..f53f2a7 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -20,6 +20,7 @@
#include <dhcp/option.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/alloc_engine.h>
+#include <hooks/callout_handle.h>
#include <boost/noncopyable.hpp>
@@ -45,7 +46,7 @@ namespace dhcp {
/// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
class Dhcpv4Srv : public boost::noncopyable {
- public:
+public:
/// @brief defines if certain option may, must or must not appear
typedef enum {
@@ -61,16 +62,25 @@ class Dhcpv4Srv : public boost::noncopyable {
/// network interaction. Will instantiate lease manager, and load
/// old or create new DUID. It is possible to specify alternate
/// port on which DHCPv4 server will listen on. That is mostly useful
- /// for testing purposes.
+ /// for testing purposes. The Last two arguments of the constructor
+ /// should be left at default values for normal server operation.
+ /// They should be set to 'false' when creating an instance of this
+ /// class for unit testing because features they enable require
+ /// root privileges.
///
/// @param port specifies port number to listen on
/// @param dbconfig Lease manager configuration string. The default
/// of the "memfile" manager is used for testing.
+ /// @param use_bcast configure sockets to support broadcast messages.
+ /// @param direct_response_desired specifies if it is desired to
+ /// use direct V4 traffic.
Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT,
- const char* dbconfig = "type=memfile");
+ const char* dbconfig = "type=memfile",
+ const bool use_bcast = true,
+ const bool direct_response_desired = true);
/// @brief Destructor. Used during DHCPv4 service shutdown.
- ~Dhcpv4Srv();
+ virtual ~Dhcpv4Srv();
/// @brief Main server processing loop.
///
@@ -104,6 +114,46 @@ class Dhcpv4Srv : public boost::noncopyable {
/// be freed by the caller.
static const char* serverReceivedPacketName(uint8_t type);
+ ///
+ /// @name Public accessors returning values required to (re)open sockets.
+ ///
+ /// These accessors must be public because sockets are reopened from the
+ /// static configuration callback handler. This callback handler invokes
+ /// @c ControlledDhcpv4Srv::openActiveSockets which requires parameters
+ /// which has to be retrieved from the @c ControlledDhcpv4Srv object.
+ /// They are retrieved using these public functions
+ //@{
+ ///
+ /// @brief Get UDP port on which server should listen.
+ ///
+ /// Typically, server listens on UDP port number 67. Other ports are used
+ /// for testing purposes only.
+ ///
+ /// @return UDP port on which server should listen.
+ uint16_t getPort() const {
+ return (port_);
+ }
+
+ /// @brief Return bool value indicating that broadcast flags should be set
+ /// on sockets.
+ ///
+ /// @return A bool value indicating that broadcast should be used (if true).
+ bool useBroadcast() const {
+ return (use_bcast_);
+ }
+ //@}
+
+ /// @brief Open sockets which are marked as active in @c CfgMgr.
+ ///
+ /// This function reopens sockets according to the current settings in the
+ /// Configuration Manager. It holds the list of the interfaces which server
+ /// should listen on. This function will open sockets on these interfaces
+ /// only. This function is not exception safe.
+ ///
+ /// @param port UDP port on which server should listen.
+ /// @param use_bcast should broadcast flags be set on the sockets.
+ static void openActiveSockets(const uint16_t port, const bool use_bcast);
+
protected:
/// @brief verifies if specified packet meets RFC requirements
@@ -216,7 +266,24 @@ protected:
/// @param msg_type specifies message type
void appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type);
- /// @brief Returns server-intentifier option
+ /// @brief Sets remote addresses for outgoing packet.
+ ///
+ /// This method sets the local and remote addresses on outgoing packet.
+ /// The addresses being set depend on the following conditions:
+ /// - has incoming packet been relayed,
+ /// - is direct response to a client without address supported,
+ /// - type of the outgoing packet,
+ /// - broadcast flag set in the incoming packet.
+ ///
+ /// @warning This method does not check whether provided packet pointers
+ /// are valid. Make sure that pointers are correct before calling this
+ /// function.
+ ///
+ /// @param question instance of a packet received by a server.
+ /// @param [out] msg response packet which addresses are to be adjusted.
+ void adjustRemoteAddr(const Pkt4Ptr& question, Pkt4Ptr& msg);
+
+ /// @brief Returns server-identifier option
///
/// @return server-id option
OptionPtr getServerID() { return serverid_; }
@@ -270,7 +337,19 @@ protected:
/// initiate server shutdown procedure.
volatile bool shutdown_;
- private:
+ /// @brief dummy wrapper around IfaceMgr::receive4
+ ///
+ /// This method is useful for testing purposes, where its replacement
+ /// simulates reception of a packet. For that purpose it is protected.
+ virtual Pkt4Ptr receivePacket(int timeout);
+
+ /// @brief dummy wrapper around IfaceMgr::send()
+ ///
+ /// This method is useful for testing purposes, where its replacement
+ /// simulates transmission of a packet. For that purpose it is protected.
+ virtual void sendPacket(const Pkt4Ptr& pkt);
+
+private:
/// @brief Constructs netmask option based on subnet4
/// @param subnet subnet for which the netmask will be calculated
@@ -284,6 +363,13 @@ protected:
/// during normal operation (e.g. to use different allocators)
boost::shared_ptr<AllocEngine> alloc_engine_;
+ uint16_t port_; ///< UDP port number on which server listens.
+ bool use_bcast_; ///< Should broadcast be enabled on sockets (if true).
+
+ /// Indexes for registered hook points
+ int hook_index_pkt4_receive_;
+ int hook_index_subnet4_select_;
+ int hook_index_pkt4_send_;
};
}; // namespace isc::dhcp
diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am
index 73bf00b..fc7eabf 100644
--- a/src/bin/dhcp4/tests/Makefile.am
+++ b/src/bin/dhcp4/tests/Makefile.am
@@ -8,7 +8,7 @@ EXTRA_DIST = $(PYTESTS)
# about python not being able to load liblog library.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
@@ -32,6 +32,7 @@ AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/tests\"
AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
+CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
AM_CXXFLAGS = $(B10_CXXFLAGS)
if USE_CLANGPP
@@ -48,6 +49,16 @@ TESTS_ENVIRONMENT = \
TESTS =
if HAVE_GTEST
+# Build shared libraries for testing.
+lib_LTLIBRARIES = libco1.la libco2.la
+
+libco1_la_SOURCES = callout_library_1.cc callout_library_common.h
+libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco1_la_CPPFLAGS = $(AM_CPPFLAGS)
+
+libco2_la_SOURCES = callout_library_2.cc callout_library_common.h
+libco2_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco2_la_CPPFLAGS = $(AM_CPPFLAGS)
TESTS += dhcp4_unittests
@@ -58,7 +69,9 @@ dhcp4_unittests_SOURCES += dhcp4_unittests.cc
dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
dhcp4_unittests_SOURCES += config_parser_unittest.cc
+dhcp4_unittests_SOURCES += marker_file.cc
nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc
+nodist_dhcp4_unittests_SOURCES += marker_file.h test_libraries.h
dhcp4_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
@@ -71,6 +84,7 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/dhcp4/tests/callout_library_1.cc b/src/bin/dhcp4/tests/callout_library_1.cc
new file mode 100644
index 0000000..471bb6f
--- /dev/null
+++ b/src/bin/dhcp4/tests/callout_library_1.cc
@@ -0,0 +1,22 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Marker file callout library
+///
+/// This is the source of a test library for the DHCP parser and configuration
+/// tests. See callout_common.cc for details.
+
+static const int LIBRARY_NUMBER = 1;
+#include "callout_library_common.h"
diff --git a/src/bin/dhcp4/tests/callout_library_2.cc b/src/bin/dhcp4/tests/callout_library_2.cc
new file mode 100644
index 0000000..b0b4637
--- /dev/null
+++ b/src/bin/dhcp4/tests/callout_library_2.cc
@@ -0,0 +1,22 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Marker file callout library
+///
+/// This is the source of a test library for the DHCP parser and configuration
+/// tests. See callout_common.cc for details.
+
+static const int LIBRARY_NUMBER = 2;
+#include "callout_library_common.h"
diff --git a/src/bin/dhcp4/tests/callout_library_common.h b/src/bin/dhcp4/tests/callout_library_common.h
new file mode 100644
index 0000000..cbabcda
--- /dev/null
+++ b/src/bin/dhcp4/tests/callout_library_common.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Marker file callout library
+///
+/// This is the source of a test library for the DHCP parser and configuration
+/// tests.
+///
+/// To check that they libraries are loaded and unloaded correctly, the load
+/// and unload functions in this library maintain two marker files - the load
+/// marker file and the unload marker file. The functions append a single
+/// line to the file, creating the file if need be. In this way, the test code
+/// can determine whether the load/unload functions have been run and, if so,
+/// in what order.
+///
+/// This file is the common library file for the tests. It will not compile
+/// by itself - it is included into each callout library which specifies the
+/// missing constant LIBRARY_NUMBER before the inclusion.
+
+#include <hooks/hooks.h>
+#include "marker_file.h"
+
+#include <fstream>
+
+using namespace isc::hooks;
+using namespace std;
+
+extern "C" {
+
+/// @brief Append digit to marker file
+///
+/// If the marker file does not exist, create it. Then append the single
+/// digit (given by the constant LIBRARY_NUMBER) defined earlier to it and
+/// close the file.
+///
+/// @param name Name of the file to open
+///
+/// @return 0 on success, non-zero on error.
+int
+appendDigit(const char* name) {
+ // Open the file and check if successful.
+ fstream file(name, fstream::out | fstream::app);
+ if (!file.good()) {
+ return (1);
+ }
+
+ // Add the library number to it and close.
+ file << LIBRARY_NUMBER;
+ file.close();
+
+ return (0);
+}
+
+// Framework functions
+int
+version() {
+ return (BIND10_HOOKS_VERSION);
+}
+
+int
+load(LibraryHandle&) {
+ return (appendDigit(LOAD_MARKER_FILE));
+}
+
+int
+unload() {
+ return (appendDigit(UNLOAD_MARKER_FILE));
+}
+
+};
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index 9b3be51..7c01145 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -25,18 +25,27 @@
#include <dhcp/option_int.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/cfgmgr.h>
+#include <hooks/hooks_manager.h>
+
+#include "marker_file.h"
+#include "test_libraries.h"
+
#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+
#include <iostream>
#include <fstream>
#include <sstream>
#include <limits.h>
-using namespace std;
using namespace isc;
-using namespace isc::dhcp;
using namespace isc::asiolink;
-using namespace isc::data;
using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::hooks;
+using namespace std;
namespace {
@@ -47,20 +56,30 @@ public:
// Open port 0 means to not do anything at all. We don't want to
// deal with sockets here, just check if configuration handling
// is sane.
- srv_ = new Dhcpv4Srv(0);
+ srv_.reset(new Dhcpv4Srv(0));
+ CfgMgr::instance().deleteActiveIfaces();
+ }
+
+ // Check that no hooks libraries are loaded. This is a pre-condition for
+ // a number of tests, so is checked in one place. As this uses an
+ // ASSERT call - and it is not clear from the documentation that Gtest
+ // predicates can be used in a constructor - the check is placed in SetUp.
+ void SetUp() {
+ std::vector<std::string> libraries = HooksManager::getLibraryNames();
+ ASSERT_TRUE(libraries.empty());
}
// Checks if global parameter of name have expected_value
void checkGlobalUint32(string name, uint32_t expected_value) {
- const std::map<std::string, uint32_t>& uint32_defaults = getUint32Defaults();
- std::map<std::string, uint32_t>::const_iterator it =
- uint32_defaults.find(name);
- if (it == uint32_defaults.end()) {
+ const Uint32StoragePtr uint32_defaults =
+ globalContext()->uint32_values_;
+ try {
+ uint32_t actual_value = uint32_defaults->getParam(name);
+ EXPECT_EQ(expected_value, actual_value);
+ } catch (DhcpConfigError) {
ADD_FAILURE() << "Expected uint32 with name " << name
<< " not found";
- return;
}
- EXPECT_EQ(expected_value, it->second);
}
// Checks if the result of DHCP server configuration has
@@ -74,7 +93,10 @@ public:
~Dhcp4ParserTest() {
resetConfiguration();
- delete srv_;
+
+ // ... and delete the hooks library marker files if present
+ unlink(LOAD_MARKER_FILE);
+ unlink(UNLOAD_MARKER_FILE);
};
/// @brief Create the simple configuration with single option.
@@ -83,7 +105,7 @@ public:
/// option value. These parameters are: "name", "code", "data",
/// "csv-format" and "space".
///
- /// @param param_value string holiding option parameter value to be
+ /// @param param_value string holding option parameter value to be
/// injected into the configuration string.
/// @param parameter name of the parameter to be configured with
/// param value.
@@ -136,7 +158,7 @@ public:
/// describing an option.
std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
std::ostringstream stream;
- stream << "{ \"interface\": [ \"all\" ],"
+ stream << "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
@@ -233,56 +255,81 @@ public:
expected_data_len));
}
- /// @brief Reset configuration database.
+ /// @brief Parse and Execute configuration
///
- /// This function resets configuration data base by
- /// removing all subnets and option-data. Reset must
- /// be performed after each test to make sure that
- /// contents of the database do not affect result of
- /// subsequent tests.
- void resetConfiguration() {
+ /// Parses a configuration and executes a configuration of the server.
+ /// If the operation fails, the current test will register a failure.
+ ///
+ /// @param config Configuration to parse
+ /// @param operation Operation being performed. In the case of an error,
+ /// the error text will include the string "unable to <operation>.".
+ ///
+ /// @return true if the configuration succeeded, false if not. In the
+ /// latter case, a failure will have been added to the current test.
+ bool
+ executeConfiguration(const std::string& config, const char* operation) {
ConstElementPtr status;
-
- string config = "{ \"interface\": [ \"all\" ],"
- "\"rebind-timer\": 2000, "
- "\"renew-timer\": 1000, "
- "\"valid-lifetime\": 4000, "
- "\"subnet4\": [ ], "
- "\"option-def\": [ ], "
- "\"option-data\": [ ] }";
-
try {
ElementPtr json = Element::fromJSON(config);
status = configureDhcp4Server(*srv_, json);
} catch (const std::exception& ex) {
- FAIL() << "Fatal error: unable to reset configuration database"
- << " after the test. The following configuration was used"
- << " to reset database: " << std::endl
+ ADD_FAILURE() << "Unable to " << operation << ". "
+ << "The following configuration was used: " << std::endl
<< config << std::endl
<< " and the following error message was returned:"
<< ex.what() << std::endl;
+ return (false);
}
- // status object must not be NULL
+ // The status object must not be NULL
if (!status) {
- FAIL() << "Fatal error: unable to reset configuration database"
- << " after the test. Configuration function returned"
- << " NULL pointer" << std::endl;
+ ADD_FAILURE() << "Unable to " << operation << ". "
+ << "The configuration function returned a null pointer.";
+ return (false);
}
+ // Store the answer if we need it.
+
+ // Returned value should be 0 (configuration success)
comment_ = parseAnswer(rcode_, status);
- // returned value should be 0 (configuration success)
if (rcode_ != 0) {
- FAIL() << "Fatal error: unable to reset configuration database"
- << " after the test. Configuration function returned"
- << " error code " << rcode_ << std::endl;
+ string reason = "";
+ if (comment_) {
+ reason = string(" (") + comment_->stringValue() + string(")");
+ }
+ ADD_FAILURE() << "Unable to " << operation << ". "
+ << "The configuration function returned error code "
+ << rcode_ << reason;
+ return (false);
}
+
+ return (true);
+ }
+
+ /// @brief Reset configuration database.
+ ///
+ /// This function resets configuration data base by
+ /// removing all subnets and option-data. Reset must
+ /// be performed after each test to make sure that
+ /// contents of the database do not affect result of
+ /// subsequent tests.
+ void resetConfiguration() {
+ string config = "{ \"interfaces\": [ \"*\" ],"
+ "\"hooks-libraries\": [ ], "
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet4\": [ ], "
+ "\"option-def\": [ ], "
+ "\"option-data\": [ ] }";
+ static_cast<void>(executeConfiguration(config,
+ "reset configuration database"));
}
- Dhcpv4Srv* srv_;
- int rcode_;
- ConstElementPtr comment_;
+ boost::scoped_ptr<Dhcpv4Srv> srv_; // DHCP4 server under test
+ int rcode_; // Return code from element parsing
+ ConstElementPtr comment_; // Reason for parse fail
};
// Goal of this test is a verification if a very simple config update
@@ -320,7 +367,7 @@ TEST_F(Dhcp4ParserTest, emptySubnet) {
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
- Element::fromJSON("{ \"interface\": [ \"all\" ],"
+ Element::fromJSON("{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ ], "
@@ -340,7 +387,7 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
ConstElementPtr status;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
@@ -370,7 +417,7 @@ TEST_F(Dhcp4ParserTest, subnetLocal) {
ConstElementPtr status;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
@@ -401,7 +448,7 @@ TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
ConstElementPtr status;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
@@ -425,7 +472,7 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
ConstElementPtr status;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
@@ -487,7 +534,7 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
EXPECT_TRUE(def->getEncapsulatedSpace().empty());
}
-// The goal of this test is to check whether an option definiiton
+// The goal of this test is to check whether an option definition
// that defines an option carrying a record of data fields can
// be created.
TEST_F(Dhcp4ParserTest, optionDefRecord) {
@@ -947,7 +994,7 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
// configuration does not include options configuration.
TEST_F(Dhcp4ParserTest, optionDataDefaults) {
ConstElementPtr x;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1020,7 +1067,7 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
// The definition is not required for the option that
// belongs to the 'dhcp4' option space as it is the
// standard option.
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1060,7 +1107,7 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
ASSERT_TRUE(status);
checkResult(status, 0);
- // Options should be now availabe for the subnet.
+ // Options should be now available for the subnet.
Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
ASSERT_TRUE(subnet);
// Try to get the option from the space dhcp4.
@@ -1098,7 +1145,7 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
// at the very end (when all other parameters are configured).
// Starting stage 1. Configure sub-options and their definitions.
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1147,7 +1194,7 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
// the configuration from the stage 2 is repeated because BIND
// configuration manager sends whole configuration for the lists
// where at least one element is being modified or added.
- config = "{ \"interface\": [ \"all\" ],"
+ config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1243,7 +1290,7 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
// option setting.
TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
ConstElementPtr x;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"option-data\": [ {"
@@ -1315,7 +1362,7 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
// for multiple subnets.
TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
ConstElementPtr x;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
@@ -1430,7 +1477,7 @@ TEST_F(Dhcp4ParserTest, optionDataInvalidChar) {
testInvalidOptionParam("01020R", "data");
}
-// Verify that option data containins '0x' prefix is rejected
+// Verify that option data containing '0x' prefix is rejected
// by the configuration.
TEST_F(Dhcp4ParserTest, optionDataUnexpectedPrefix) {
// Option code 0 is reserved and should not be accepted
@@ -1530,7 +1577,7 @@ TEST_F(Dhcp4ParserTest, stdOptionData) {
boost::shared_ptr<Option4AddrLst> option_addrs =
boost::dynamic_pointer_cast<Option4AddrLst>(option);
// If cast is unsuccessful than option returned was of a
- // differnt type than Option6IA. This is wrong.
+ // different type than Option6IA. This is wrong.
ASSERT_TRUE(option_addrs);
// Get addresses from the option.
@@ -1595,7 +1642,7 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
// In the first stahe we create definitions of suboptions
// that we will add to the base option.
// Let's create some dummy options: foo and foo2.
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1648,7 +1695,7 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
// We add our dummy options to this option space and thus
// they should be included as sub-options in the 'vendor-opts'
// option.
- config = "{ \"interface\": [ \"all\" ],"
+ config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1747,5 +1794,210 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
EXPECT_FALSE(desc.option->getOption(3));
}
+// Tests of the hooks libraries configuration. All tests have the pre-
+// condition (checked in the test fixture's SetUp() method) that no hooks
+// libraries are loaded at the start of the tests.
+
+// Helper function to return a configuration containing an arbitrary number
+// of hooks libraries.
+std::string
+buildHooksLibrariesConfig(const std::vector<std::string>& libraries) {
+ const string quote("\"");
+
+ // Create the first part of the configuration string.
+ string config =
+ "{ \"interfaces\": [ \"*\" ],"
+ "\"hooks-libraries\": [";
+
+ // Append the libraries (separated by commas if needed)
+ for (int i = 0; i < libraries.size(); ++i) {
+ if (i > 0) {
+ config += string(", ");
+ }
+ config += (quote + libraries[i] + quote);
+ }
-};
+ // Append the remainder of the configuration.
+ config += string(
+ "],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 56,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 56,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 56,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}");
+
+ return (config);
+}
+
+// Convenience function for creating hooks library configuration with one or
+// two character string constants.
+std::string
+buildHooksLibrariesConfig(const char* library1 = NULL,
+ const char* library2 = NULL) {
+ std::vector<std::string> libraries;
+ if (library1 != NULL) {
+ libraries.push_back(string(library1));
+ if (library2 != NULL) {
+ libraries.push_back(string(library2));
+ }
+ }
+ return (buildHooksLibrariesConfig(libraries));
+}
+
+
+// The goal of this test is to verify the configuration of hooks libraries if
+// none are specified.
+TEST_F(Dhcp4ParserTest, NoHooksLibraries) {
+ // Parse a configuration containing no names.
+ string config = buildHooksLibrariesConfig();
+ if (!executeConfiguration(config,
+ "set configuration with no hooks libraries")) {
+ FAIL() << "Unable to execute configuration";
+
+ } else {
+ // No libraries should be loaded at the end of the test.
+ std::vector<std::string> libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(libraries.empty());
+ }
+}
+
+// Verify parsing fails with one library that will fail validation.
+TEST_F(Dhcp4ParserTest, InvalidLibrary) {
+ // Parse a configuration containing a failing library.
+ string config = buildHooksLibrariesConfig(NOT_PRESENT_LIBRARY);
+
+ ConstElementPtr status;
+ ElementPtr json = Element::fromJSON(config);
+ ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // The status object must not be NULL
+ ASSERT_TRUE(status);
+
+ // Returned value should not be 0
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_NE(0, rcode_);
+}
+
+// Verify the configuration of hooks libraries with two being specified.
+TEST_F(Dhcp4ParserTest, LibrariesSpecified) {
+ // Marker files should not be present.
+ EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Set up the configuration with two libraries and load them.
+ string config = buildHooksLibrariesConfig(CALLOUT_LIBRARY_1,
+ CALLOUT_LIBRARY_2);
+ ASSERT_TRUE(executeConfiguration(config,
+ "load two valid libraries"));
+
+ // Expect two libraries to be loaded in the correct order (load marker file
+ // is present, no unload marker file).
+ std::vector<std::string> libraries = HooksManager::getLibraryNames();
+ ASSERT_EQ(2, libraries.size());
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Unload the libraries. The load file should not have changed, but
+ // the unload one should indicate the unload() functions have been run.
+ config = buildHooksLibrariesConfig();
+ ASSERT_TRUE(executeConfiguration(config, "unloading libraries"));
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+ EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "21"));
+
+ // Expect the hooks system to say that none are loaded.
+ libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(libraries.empty());
+
+}
+
+// This test verifies that it is possible to select subset of interfaces
+// on which server should listen.
+TEST_F(Dhcp4ParserTest, selectedInterfaces) {
+ ConstElementPtr x;
+ string config = "{ \"interfaces\": [ \"eth0\", \"eth1\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ ConstElementPtr status;
+
+ // Make sure the config manager is clean and there is no hanging
+ // interface configuration.
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+
+ // Apply configuration.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // eth0 and eth1 were explicitly selected. eth2 was not.
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
+ EXPECT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+}
+
+// This test verifies that it is possible to configure the server in such a way
+// that it listens on all interfaces.
+TEST_F(Dhcp4ParserTest, allInterfaces) {
+ ConstElementPtr x;
+ // This configuration specifies two interfaces on which server should listen
+ // but it also includes asterisk. The asterisk switches server into the
+ // mode when it listens on all interfaces regardless of what interface names
+ // were specified in the "interfaces" parameter.
+ string config = "{ \"interfaces\": [ \"eth0\", \"*\", \"eth1\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ ConstElementPtr status;
+
+ // Make sure there is no old configuration.
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+
+ // Apply configuration.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // All interfaces should be now active.
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2"));
+}
+
+
+
+}
diff --git a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
index fd4e90e..e6b500b 100644
--- a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -17,7 +17,12 @@
#include <config/ccsession.h>
#include <dhcp/dhcp4.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <hooks/hooks_manager.h>
+#include "marker_file.h"
+#include "test_libraries.h"
+
+#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <fstream>
@@ -25,18 +30,21 @@
#include <sstream>
#include <arpa/inet.h>
+#include <unistd.h>
using namespace std;
using namespace isc;
-using namespace isc::dhcp;
using namespace isc::asiolink;
-using namespace isc::data;
using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::hooks;
namespace {
class NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
- // "naked" DHCPv4 server, exposes internal fields
+ // "Naked" DHCPv4 server, exposes internal fields
public:
NakedControlledDhcpv4Srv():ControlledDhcpv4Srv(DHCP4_SERVER_PORT + 10000) { }
};
@@ -44,29 +52,44 @@ public:
class CtrlDhcpv4SrvTest : public ::testing::Test {
public:
CtrlDhcpv4SrvTest() {
+ reset();
}
~CtrlDhcpv4SrvTest() {
+ reset();
};
+
+ /// @brief Reset hooks data
+ ///
+ /// Resets the data for the hooks-related portion of the test by ensuring
+ /// that no libraries are loaded and that any marker files are deleted.
+ void reset() {
+ // Unload any previously-loaded libraries.
+ HooksManager::unloadLibraries();
+
+ // Get rid of any marker files.
+ static_cast<void>(unlink(LOAD_MARKER_FILE));
+ static_cast<void>(unlink(UNLOAD_MARKER_FILE));
+ }
};
TEST_F(CtrlDhcpv4SrvTest, commands) {
- ControlledDhcpv4Srv* srv = NULL;
- ASSERT_NO_THROW({
- srv = new ControlledDhcpv4Srv(DHCP4_SERVER_PORT + 10000);
- });
+ boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv4Srv(DHCP4_SERVER_PORT + 10000))
+ );
- // use empty parameters list
+ // Use empty parameters list
ElementPtr params(new isc::data::MapElement());
int rcode = -1;
- // case 1: send bogus command
+ // Case 1: send bogus command
ConstElementPtr result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("blah", params);
ConstElementPtr comment = parseAnswer(rcode, result);
EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
- // case 2: send shutdown command without any parameters
+ // Case 2: send shutdown command without any parameters
result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
comment = parseAnswer(rcode, result);
EXPECT_EQ(0, rcode); // expect success
@@ -75,13 +98,54 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
ConstElementPtr x(new isc::data::IntElement(pid));
params->set("pid", x);
- // case 3: send shutdown command with 1 parameter: pid
+ // Case 3: send shutdown command with 1 parameter: pid
result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
comment = parseAnswer(rcode, result);
EXPECT_EQ(0, rcode); // expect success
+}
+
+// Check that the "libreload" command will reload libraries
+TEST_F(CtrlDhcpv4SrvTest, libreload) {
+ // Ensure no marker files to start with.
+ ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Load two libraries
+ std::vector<std::string> libraries;
+ libraries.push_back(CALLOUT_LIBRARY_1);
+ libraries.push_back(CALLOUT_LIBRARY_2);
+ HooksManager::loadLibraries(libraries);
+
+ // Check they are loaded.
+ std::vector<std::string> loaded_libraries =
+ HooksManager::getLibraryNames();
+ ASSERT_TRUE(libraries == loaded_libraries);
+
+ // ... which also included checking that the marker file created by the
+ // load functions exists and holds the correct value (of "12" - the
+ // first library appends "1" to the file, the second appends "2"). Also
+ // check that the unload marker file does not yet exist.
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Now execute the "libreload" command. This should cause the libraries
+ // to unload and to reload.
+
+ // Use empty parameters list
+ ElementPtr params(new isc::data::MapElement());
+ int rcode = -1;
+
+ ConstElementPtr result =
+ ControlledDhcpv4Srv::execDhcpv4ServerCommand("libreload", params);
+ ConstElementPtr comment = parseAnswer(rcode, result);
+ EXPECT_EQ(0, rcode); // Expect success
- delete srv;
+ // Check that the libraries have unloaded and reloaded. The libraries are
+ // unloaded in the reverse order to which they are loaded. When they load,
+ // they should append information to the loading marker file.
+ EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "21"));
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "1212"));
}
-} // end of anonymous namespace
+} // End of anonymous namespace
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index c938155..1b68747 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -16,18 +16,28 @@
#include <sstream>
#include <asiolink/io_address.h>
+#include <config/ccsession.h>
#include <dhcp/dhcp4.h>
+#include <dhcp/iface_mgr.h>
#include <dhcp/option.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_int_array.h>
+#include <dhcp/pkt_filter.h>
+#include <dhcp/pkt_filter_inet.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
+#include <dhcp4/config_parser.h>
+#include <hooks/server_hooks.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/utils.h>
#include <gtest/gtest.h>
+#include <hooks/server_hooks.h>
+#include <hooks/hooks_manager.h>
+
+#include <boost/scoped_ptr.hpp>
#include <fstream>
#include <iostream>
@@ -37,15 +47,95 @@
using namespace std;
using namespace isc;
using namespace isc::dhcp;
+using namespace isc::data;
using namespace isc::asiolink;
+using namespace isc::hooks;
namespace {
class NakedDhcpv4Srv: public Dhcpv4Srv {
// "Naked" DHCPv4 server, exposes internal fields
public:
- NakedDhcpv4Srv(uint16_t port = 0):Dhcpv4Srv(port) { }
+ /// @brief Constructor.
+ ///
+ /// This constructor disables default modes of operation used by the
+ /// Dhcpv4Srv class:
+ /// - Send/receive broadcast messages through sockets on interfaces
+ /// which support broadcast traffic.
+ /// - Direct DHCPv4 traffic - communication with clients which do not
+ /// have IP address assigned yet.
+ ///
+ /// Enabling these modes requires root privilges so they must be
+ /// disabled for unit testing.
+ ///
+ /// Note, that disabling broadcast options on sockets does not impact
+ /// the operation of these tests because they use local loopback
+ /// interface which doesn't have broadcast capability anyway. It rather
+ /// prevents setting broadcast options on other (broadcast capable)
+ /// sockets which are opened on other interfaces in Dhcpv4Srv constructor.
+ ///
+ /// The Direct DHCPv4 Traffic capability can be disabled here because
+ /// it is tested with PktFilterLPFTest unittest. The tests which belong
+ /// to PktFilterLPFTest can be enabled on demand when root privileges can
+ /// be guaranteed.
+ ///
+ /// @param port port number to listen on; the default value 0 indicates
+ /// that sockets should not be opened.
+ NakedDhcpv4Srv(uint16_t port = 0)
+ : Dhcpv4Srv(port, "type=memfile", false, false) {
+ }
+
+ /// @brief fakes packet reception
+ /// @param timeout ignored
+ ///
+ /// The method receives all packets queued in receive queue, one after
+ /// another. Once the queue is empty, it initiates the shutdown procedure.
+ ///
+ /// See fake_received_ field for description
+ virtual Pkt4Ptr receivePacket(int /*timeout*/) {
+
+ // If there is anything prepared as fake incoming traffic, use it
+ if (!fake_received_.empty()) {
+ Pkt4Ptr pkt = fake_received_.front();
+ fake_received_.pop_front();
+ return (pkt);
+ }
+
+ // If not, just trigger shutdown and return immediately
+ shutdown();
+ return (Pkt4Ptr());
+ }
+
+ /// @brief fake packet sending
+ ///
+ /// Pretend to send a packet, but instead just store it in fake_send_ list
+ /// where test can later inspect server's response.
+ virtual void sendPacket(const Pkt4Ptr& pkt) {
+ fake_sent_.push_back(pkt);
+ }
+
+ /// @brief adds a packet to fake receive queue
+ ///
+ /// See fake_received_ field for description
+ void fakeReceive(const Pkt4Ptr& pkt) {
+ fake_received_.push_back(pkt);
+ }
+
+ virtual ~NakedDhcpv4Srv() {
+ }
+
+ /// @brief packets we pretend to receive
+ ///
+ /// Instead of setting up sockets on interfaces that change between OSes, it
+ /// is much easier to fake packet reception. This is a list of packets that
+ /// we pretend to have received. You can schedule new packets to be received
+ /// using fakeReceive() and NakedDhcpv4Srv::receivePacket() methods.
+ list<Pkt4Ptr> fake_received_;
+
+ list<Pkt4Ptr> fake_sent_;
+
+ using Dhcpv4Srv::adjustRemoteAddr;
using Dhcpv4Srv::processDiscover;
using Dhcpv4Srv::processRequest;
using Dhcpv4Srv::processRelease;
@@ -61,6 +151,41 @@ public:
static const char* SRVID_FILE = "server-id-test.txt";
+/// @brief Dummy Packet Filtering class.
+///
+/// This class reports capability to respond directly to the client which
+/// doesn't have address configured yet.
+///
+/// All packet and socket handling functions do nothing because they are not
+/// used in unit tests.
+class PktFilterTest : public PktFilter {
+public:
+
+ /// @brief Reports 'direct response' capability.
+ ///
+ /// @return always true.
+ virtual bool isDirectResponseSupported() const {
+ return (true);
+ }
+
+ /// Does nothing.
+ virtual int openSocket(const Iface&, const IOAddress&, const uint16_t,
+ const bool, const bool) {
+ return (0);
+ }
+
+ /// Does nothing.
+ virtual Pkt4Ptr receive(const Iface&, const SocketInfo&) {
+ return Pkt4Ptr();
+ }
+
+ /// Does nothing.
+ virtual int send(const Iface&, uint16_t, const Pkt4Ptr&) {
+ return (0);
+ }
+
+};
+
class Dhcpv4SrvTest : public ::testing::Test {
public:
@@ -68,7 +193,9 @@ public:
///
/// Initializes common objects used in many tests.
/// Also sets up initial configuration in CfgMgr.
- Dhcpv4SrvTest() {
+ Dhcpv4SrvTest() :
+ rcode_(-1)
+ {
subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1000,
2000, 3000));
pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
@@ -84,6 +211,19 @@ public:
// it's ok if that fails. There should not be such a file anyway
unlink(SRVID_FILE);
+
+ const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+ // There must be some interface detected
+ if (ifaces.empty()) {
+ // We can't use ASSERT in constructor
+ ADD_FAILURE() << "No interfaces detected.";
+ }
+
+ valid_iface_ = ifaces.begin()->getName();
+ }
+
+ virtual ~Dhcpv4SrvTest() {
}
/// @brief Add 'Parameter Request List' option to the packet.
@@ -116,10 +256,10 @@ public:
/// @brief Configures options being requested in the PRL option.
///
- /// The lpr-servers option is NOT configured here altough it is
+ /// The lpr-servers option is NOT configured here although it is
/// added to the 'Parameter Request List' option in the
/// \ref addPrlOption. When requested option is not configured
- /// the server should not return it in its rensponse. The goal
+ /// the server should not return it in its response. The goal
/// of not configuring the requested option is to verify that
/// the server will not return it.
void configureRequestedOptions() {
@@ -132,8 +272,7 @@ public:
// domain-name
OptionDefinition def("domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE);
- boost::shared_ptr<OptionCustom>
- option_domain_name(new OptionCustom(def, Option::V4));
+ OptionCustomPtr option_domain_name(new OptionCustom(def, Option::V4));
option_domain_name->writeFqdn("example.com");
subnet_->addOption(option_domain_name, false, "dhcp4");
@@ -152,8 +291,7 @@ public:
/// @brief checks that the response matches request
/// @param q query (client's message)
/// @param a answer (server's message)
- void messageCheck(const boost::shared_ptr<Pkt4>& q,
- const boost::shared_ptr<Pkt4>& a) {
+ void messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a) {
ASSERT_TRUE(q);
ASSERT_TRUE(a);
@@ -161,6 +299,11 @@ public:
EXPECT_EQ(q->getIface(), a->getIface());
EXPECT_EQ(q->getIndex(), a->getIndex());
EXPECT_EQ(q->getGiaddr(), a->getGiaddr());
+ // When processing an incoming packet the remote address
+ // is copied as a src address, and the source address is
+ // copied as a remote address to the response.
+ EXPECT_TRUE(q->getLocalHWAddr() == a->getLocalHWAddr());
+ EXPECT_TRUE(q->getRemoteHWAddr() == a->getRemoteHWAddr());
// Check that bare minimum of required options are there.
// We don't check options requested by a client. Those
@@ -170,6 +313,8 @@ public:
EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER));
EXPECT_TRUE(a->getOption(DHO_DHCP_LEASE_TIME));
EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK));
+ EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME));
+ EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME_SERVERS));
// Check that something is offered
EXPECT_TRUE(a->getYiaddr().toText() != "0.0.0.0");
@@ -345,12 +490,139 @@ public:
EXPECT_TRUE(expected_clientid->getData() == opt->getData());
}
- ~Dhcpv4SrvTest() {
+ /// @brief Tests if Discover or Request message is processed correctly
+ ///
+ /// @param msg_type DHCPDISCOVER or DHCPREQUEST
+ void testDiscoverRequest(const uint8_t msg_type) {
+ // Create an instance of the tested class.
+ boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
+
+ // Initialize the source HW address.
+ vector<uint8_t> mac(6);
+ for (int i = 0; i < 6; ++i) {
+ mac[i] = i * 10;
+ }
+ // Initialized the destination HW address.
+ vector<uint8_t> dst_mac(6);
+ for (int i = 0; i < 6; ++i) {
+ dst_mac[i] = i * 20;
+ }
+ // Create a DHCP message. It will be used to simulate the
+ // incoming message.
+ boost::shared_ptr<Pkt4> req(new Pkt4(msg_type, 1234));
+ // Create a response message. It will hold a reponse packet.
+ // Initially, set it to NULL.
+ boost::shared_ptr<Pkt4> rsp;
+ // Set the name of the interface on which packet is received.
+ req->setIface("eth0");
+ // Set the interface index. It is just a dummy value and will
+ // not be interpreted.
+ req->setIndex(17);
+ // Set the target HW address. This value is normally used to
+ // construct the data link layer header.
+ req->setRemoteHWAddr(1, 6, dst_mac);
+ // Set the HW address. This value is set on DHCP level (in chaddr).
+ req->setHWAddr(1, 6, mac);
+ // Set local HW address. It is used to construct the data link layer
+ // header.
+ req->setLocalHWAddr(1, 6, mac);
+ // Set target IP address.
+ req->setRemoteAddr(IOAddress("192.0.2.55"));
+ // Set relay address.
+ req->setGiaddr(IOAddress("192.0.2.10"));
+
+ // We are going to test that certain options are returned
+ // in the response message when requested using 'Parameter
+ // Request List' option. Let's configure those options that
+ // are returned when requested.
+ configureRequestedOptions();
+
+ if (msg_type == DHCPDISCOVER) {
+ ASSERT_NO_THROW(
+ rsp = srv->processDiscover(req);
+ );
+
+ // Should return OFFER
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPOFFER, rsp->getType());
+
+ } else {
+ ASSERT_NO_THROW(
+ rsp = srv->processRequest(req);
+ );
+
+ // Should return ACK
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPACK, rsp->getType());
+
+ }
+
+ messageCheck(req, rsp);
+
+ // We did not request any options so these should not be present
+ // in the RSP.
+ EXPECT_FALSE(rsp->getOption(DHO_LOG_SERVERS));
+ EXPECT_FALSE(rsp->getOption(DHO_COOKIE_SERVERS));
+ EXPECT_FALSE(rsp->getOption(DHO_LPR_SERVERS));
+
+ // Repeat the test but request some options.
+ // Add 'Parameter Request List' option.
+ addPrlOption(req);
+
+ if (msg_type == DHCPDISCOVER) {
+ ASSERT_NO_THROW(
+ rsp = srv->processDiscover(req);
+ );
+
+ // Should return non-NULL packet.
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPOFFER, rsp->getType());
+
+ } else {
+ ASSERT_NO_THROW(
+ rsp = srv->processRequest(req);
+ );
+
+ // Should return non-NULL packet.
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(DHCPACK, rsp->getType());
+
+ }
+
+ // Check that the requested options are returned.
+ optionsCheck(rsp);
+
+ }
+
+ /// @brief This function cleans up after the test.
+ virtual void TearDown() {
+
CfgMgr::instance().deleteSubnets4();
// Let's clean up if there is such a file.
unlink(SRVID_FILE);
- };
+
+ // Close all open sockets.
+ IfaceMgr::instance().closeSockets();
+
+ // Some unit tests override the default packet filtering class, used
+ // by the IfaceMgr. The dummy class, called PktFilterTest, reports the
+ // capability to directly respond to the clients without IP address
+ // assigned. This capability is not supported by the default packet
+ // filtering class: PktFilterInet. Therefore setting the dummy class
+ // allows to test scenarios, when server responds to the broadcast address
+ // on client's request, despite having support for direct response.
+ // The following call restores the use of original packet filtering class
+ // after the test.
+ try {
+ IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterInet()));
+
+ } catch (const Exception& ex) {
+ FAIL() << "Failed to restore the default (PktFilterInet) packet filtering"
+ << " class after the test. Exception has been caught: "
+ << ex.what();
+ }
+ }
/// @brief A subnet used in most tests
Subnet4Ptr subnet_;
@@ -360,6 +632,13 @@ public:
/// @brief A client-id used in most tests
ClientIdPtr client_id_;
+
+ int rcode_;
+
+ ConstElementPtr comment_;
+
+ // Name of a valid network interface
+ string valid_iface_;
};
// Sanity check. Verifies that both Dhcpv4Srv and its derived
@@ -367,278 +646,272 @@ public:
TEST_F(Dhcpv4SrvTest, basic) {
// Check that the base class can be instantiated
- Dhcpv4Srv* srv = NULL;
- ASSERT_NO_THROW({
- srv = new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000);
- });
- delete srv;
+ boost::scoped_ptr<Dhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000, "type=memfile",
+ false, false)));
+ srv.reset();
+ // We have to close open sockets because further in this test we will
+ // call the Dhcpv4Srv constructor again. This constructor will try to
+ // set the appropriate packet filter class for IfaceMgr. This requires
+ // that all sockets are closed.
+ IfaceMgr::instance().closeSockets();
// Check that the derived class can be instantiated
- NakedDhcpv4Srv* naked_srv = NULL;
- ASSERT_NO_THROW({
- naked_srv = new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000);
- });
+ boost::scoped_ptr<NakedDhcpv4Srv> naked_srv;
+ ASSERT_NO_THROW(
+ naked_srv.reset(new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000)));
EXPECT_TRUE(naked_srv->getServerID());
- delete naked_srv;
+ // Close sockets again for the next test.
+ IfaceMgr::instance().closeSockets();
- ASSERT_NO_THROW({
- naked_srv = new NakedDhcpv4Srv(0);
- });
+ ASSERT_NO_THROW(naked_srv.reset(new NakedDhcpv4Srv(0)));
EXPECT_TRUE(naked_srv->getServerID());
-
- delete naked_srv;
}
-// Verifies that received DISCOVER can be processed correctly,
-// that the OFFER message generated in response is valid and
-// contains necessary options.
-//
-// Note: this test focuses on the packet correctness. There
-// are other tests that verify correctness of the allocation
-// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
-// and DiscoverInvalidHint.
-TEST_F(Dhcpv4SrvTest, processDiscover) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv(0);
- vector<uint8_t> mac(6);
- for (int i = 0; i < 6; i++) {
- mac[i] = 255 - i;
- }
-
- boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDISCOVER, 1234));
- boost::shared_ptr<Pkt4> offer;
-
- pkt->setIface("eth0");
- pkt->setIndex(17);
- pkt->setHWAddr(1, 6, mac);
- pkt->setRemoteAddr(IOAddress("192.0.2.56"));
- pkt->setGiaddr(IOAddress("192.0.2.67"));
-
- // Let's make it a relayed message
- pkt->setHops(3);
- pkt->setRemotePort(DHCP4_SERVER_PORT);
-
- // We are going to test that certain options are returned
- // (or not returned) in the OFFER message when requested
- // using 'Parameter Request List' option. Let's configure
- // those options that are returned when requested.
- configureRequestedOptions();
-
- // Should not throw
- EXPECT_NO_THROW(
- offer = srv->processDiscover(pkt);
- );
-
- // Should return something
- ASSERT_TRUE(offer);
-
- EXPECT_EQ(DHCPOFFER, offer->getType());
-
- // This is relayed message. It should be sent back to relay address.
- EXPECT_EQ(pkt->getGiaddr(), offer->getRemoteAddr());
-
- messageCheck(pkt, offer);
-
- // There are some options that are always present in the
- // message, even if not requested.
- EXPECT_TRUE(offer->getOption(DHO_DOMAIN_NAME));
- EXPECT_TRUE(offer->getOption(DHO_DOMAIN_NAME_SERVERS));
-
- // We did not request any options so they should not be present
- // in the OFFER.
- EXPECT_FALSE(offer->getOption(DHO_LOG_SERVERS));
- EXPECT_FALSE(offer->getOption(DHO_COOKIE_SERVERS));
- EXPECT_FALSE(offer->getOption(DHO_LPR_SERVERS));
-
- // Add 'Parameter Request List' option.
- addPrlOption(pkt);
-
- // Now repeat the test but request some options.
- EXPECT_NO_THROW(
- offer = srv->processDiscover(pkt);
- );
-
- // Should return something
- ASSERT_TRUE(offer);
-
- EXPECT_EQ(DHCPOFFER, offer->getType());
-
- // This is relayed message. It should be sent back to relay address.
- EXPECT_EQ(pkt->getGiaddr(), offer->getRemoteAddr());
+// This test verifies that the destination address of the response
+// message is set to giaddr, when giaddr is set to non-zero address
+// in the received message.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressRelay) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
+
+ // Create the instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
+ // Set the giaddr to non-zero address as if it was relayed.
+ req->setGiaddr(IOAddress("192.0.2.1"));
+ // Set ciaddr to zero. This simulates the client which applies
+ // for the new lease.
+ req->setCiaddr(IOAddress("0.0.0.0"));
+ // Clear broadcast flag.
+ req->setFlags(0x0000);
+
+ // Create a response packet. Assume that the new lease have
+ // been created and new address allocated. This address is
+ // stored in yiaddr field.
+ boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+ resp->setYiaddr(IOAddress("192.0.2.100"));
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+ // This function never throws.
+ ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+ // Now the destination address should be relay's address.
+ EXPECT_EQ("192.0.2.1", resp->getRemoteAddr().toText());
+
+ // Let's do another test and set other fields: ciaddr and
+ // flags. By doing it, we want to make sure that the relay
+ // address will take precedence.
+ req->setGiaddr(IOAddress("192.0.2.50"));
+ req->setCiaddr(IOAddress("192.0.2.11"));
+ req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
+
+ resp->setYiaddr(IOAddress("192.0.2.100"));
+ // Clear remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+ ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+ // Response should be sent back to the relay address.
+ EXPECT_EQ("192.0.2.50", resp->getRemoteAddr().toText());
+}
- messageCheck(pkt, offer);
+// This test verifies that the destination address of the response message
+// is set to ciaddr when giaddr is set to zero and the ciaddr is set to
+// non-zero address in the received message. This is the case when the
+// client is in Renew or Rebind state.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressRenewRebind) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
- // Check that the requested options are returned.
- optionsCheck(offer);
+ // Create instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
- // Now repeat the test for directly sent message
- pkt->setHops(0);
- pkt->setGiaddr(IOAddress("0.0.0.0"));
- pkt->setRemotePort(DHCP4_CLIENT_PORT);
+ // Clear giaddr to simulate direct packet.
+ req->setGiaddr(IOAddress("0.0.0.0"));
+ // Set ciaddr to non-zero address. The response should be sent to this
+ // address as the client is in renewing or rebinding state (it is fully
+ // configured).
+ req->setCiaddr(IOAddress("192.0.2.15"));
+ // Let's configure broadcast flag. It should be ignored because
+ // we are responding directly to the client having an address
+ // and trying to extend his lease. Broadcast flag is only used
+ // when new lease is acquired and server must make a decision
+ // whether to unicast the response to the acquired address or
+ // broadcast it.
+ req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
+
+ // Create a response.
+ boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+ // Let's extend the lease for the client in such a way that
+ // it will actually get different address. The response
+ // should not be sent to this address but rather to ciaddr
+ // as client still have ciaddr configured.
+ resp->setYiaddr(IOAddress("192.0.2.13"));
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+ ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+ // Check that server responds to ciaddr
+ EXPECT_EQ("192.0.2.15", resp->getRemoteAddr().toText());
+}
- EXPECT_NO_THROW(
- offer = srv->processDiscover(pkt);
- );
+// This test verifies that the destination address of the response message
+// is set correctly when giaddr and ciaddr is zeroed in the received message
+// and the new lease is acquired. The lease address is carried in the
+// response message in the yiaddr field. In this case destination address
+// of the response should be set to yiaddr if server supports direct responses
+// to the client which doesn't have an address yet or broadcast if the server
+// doesn't support direct responses.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressSelect) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
- // Should return something
- ASSERT_TRUE(offer);
+ // Create instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
- EXPECT_EQ(DHCPOFFER, offer->getType());
+ // Clear giaddr to simulate direct packet.
+ req->setGiaddr(IOAddress("0.0.0.0"));
+ // Clear client address as it hasn't got any address configured yet.
+ req->setCiaddr(IOAddress("0.0.0.0"));
+
+ // Let's clear the broadcast flag.
+ req->setFlags(0);
+
+ // Create a response.
+ boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+ // Assign some new address for this client.
+ resp->setYiaddr(IOAddress("192.0.2.13"));
+
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+ // When running unit tests, the IfaceMgr is using the default Packet
+ // Filtering class, PktFilterInet. This class does not support direct
+ // responses to clients without address assigned. When giaddr and ciaddr
+ // are zero and client has just got new lease, the assigned address is
+ // carried in yiaddr. In order to send this address to the client,
+ // server must broadcast its response.
+ ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+ // Check that the response is sent to broadcast address as the
+ // server doesn't have capability to respond directly.
+ EXPECT_EQ("255.255.255.255", resp->getRemoteAddr().toText());
+
+ // We also want to test the case when the server has capability to
+ // respond directly to the client which is not configured. Server
+ // makes decision whether it responds directly or broadcast its
+ // response based on the capability reported by IfaceMgr. In order
+ // to set this capability we have to provide a dummy Packet Filter
+ // class which would report the support for direct responses.
+ // This class is called PktFilterTest.
+ IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterTest()));
+
+ // Now we expect that the server will send its response to the
+ // address assigned for the client.
+ ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+ EXPECT_EQ("192.0.2.13", resp->getRemoteAddr().toText());
+}
- // This is direct message. It should be sent back to origin, not
- // to relay.
- EXPECT_EQ(pkt->getRemoteAddr(), offer->getRemoteAddr());
+// This test verifies that the destination address of the response message
+// is set to broadcast address when client set broadcast flag in its
+// query. Client sets this flag to indicate that it can't receive direct
+// responses from the server when it doesn't have its interface configured.
+// Server must respect broadcast flag.
+TEST_F(Dhcpv4SrvTest, adjustRemoteAddressBroadcast) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
- messageCheck(pkt, offer);
+ // Create instance of the incoming packet.
+ boost::shared_ptr<Pkt4> req(new Pkt4(DHCPDISCOVER, 1234));
- // Check that the requested options are returned.
- optionsCheck(offer);
+ // Clear giaddr to simulate direct packet.
+ req->setGiaddr(IOAddress("0.0.0.0"));
+ // Clear client address as it hasn't got any address configured yet.
+ req->setCiaddr(IOAddress("0.0.0.0"));
+
+ // Let's set the broadcast flag.
+ req->setFlags(Pkt4::FLAG_BROADCAST_MASK);
+
+ // Create a response.
+ boost::shared_ptr<Pkt4> resp(new Pkt4(DHCPOFFER, 1234));
+ // Assign some new address for this client.
+ resp->setYiaddr(IOAddress("192.0.2.13"));
+
+ // Clear the remote address.
+ resp->setRemoteAddr(IOAddress("0.0.0.0"));
+
+ // When running unit tests, the IfaceMgr is using the default Packet
+ // Filtering class, PktFilterInet. This class does not support direct
+ // responses to the clients without address assigned. If giaddr and
+ // ciaddr are zero and client has just got the new lease, the assigned
+ // address is carried in yiaddr. In order to send this address to the
+ // client, server must send the response to the broadcast address when
+ // direct response is not supported. This conflicts with the purpose
+ // of this test which is supposed to verify that responses are sent
+ // to broadcast address only, when broadcast flag is set. Therefore,
+ // in order to simulate that direct responses are supported we have
+ // to replace the default packet filtering class with a dummy class
+ // which reports direct response capability.
+ IfaceMgr::instance().setPacketFilter(PktFilterPtr(new PktFilterTest()));
+
+ ASSERT_NO_THROW(srv->adjustRemoteAddr(req, resp));
+
+ // Server must repond to broadcast address when client desired that
+ // by setting the broadcast flag in its request.
+ EXPECT_EQ("255.255.255.255", resp->getRemoteAddr().toText());
+}
- delete srv;
+// Verifies that DISCOVER message can be processed correctly,
+// that the OFFER message generated in response is valid and
+// contains necessary options.
+//
+// Note: this test focuses on the packet correctness. There
+// are other tests that verify correctness of the allocation
+// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
+// and DiscoverInvalidHint.
+TEST_F(Dhcpv4SrvTest, processDiscover) {
+ testDiscoverRequest(DHCPDISCOVER);
}
-// Verifies that received REQUEST can be processed correctly,
-// that the ACK message generated in response is valid and
+// Verifies that REQUEST message can be processed correctly,
+// that the OFFER message generated in response is valid and
// contains necessary options.
//
// Note: this test focuses on the packet correctness. There
// are other tests that verify correctness of the allocation
-// engine. See RequestBasic.
+// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
+// and DiscoverInvalidHint.
TEST_F(Dhcpv4SrvTest, processRequest) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv(0);
- vector<uint8_t> mac(6);
- for (int i = 0; i < 6; i++) {
- mac[i] = i*10;
- }
-
- boost::shared_ptr<Pkt4> req(new Pkt4(DHCPREQUEST, 1234));
- boost::shared_ptr<Pkt4> ack;
-
- req->setIface("eth0");
- req->setIndex(17);
- req->setHWAddr(1, 6, mac);
- req->setRemoteAddr(IOAddress("192.0.2.56"));
- req->setGiaddr(IOAddress("192.0.2.67"));
-
- // We are going to test that certain options are returned
- // in the ACK message when requested using 'Parameter
- // Request List' option. Let's configure those options that
- // are returned when requested.
- configureRequestedOptions();
-
- // Should not throw
- ASSERT_NO_THROW(
- ack = srv->processRequest(req);
- );
-
- // Should return something
- ASSERT_TRUE(ack);
-
- EXPECT_EQ(DHCPACK, ack->getType());
-
- // This is relayed message. It should be sent back to relay address.
- EXPECT_EQ(req->getGiaddr(), ack->getRemoteAddr());
-
- messageCheck(req, ack);
-
- // There are some options that are always present in the
- // message, even if not requested.
- EXPECT_TRUE(ack->getOption(DHO_DOMAIN_NAME));
- EXPECT_TRUE(ack->getOption(DHO_DOMAIN_NAME_SERVERS));
-
- // We did not request any options so these should not be present
- // in the ACK.
- EXPECT_FALSE(ack->getOption(DHO_LOG_SERVERS));
- EXPECT_FALSE(ack->getOption(DHO_COOKIE_SERVERS));
- EXPECT_FALSE(ack->getOption(DHO_LPR_SERVERS));
-
- // Add 'Parameter Request List' option.
- addPrlOption(req);
-
- // Repeat the test but request some options.
- ASSERT_NO_THROW(
- ack = srv->processRequest(req);
- );
-
- // Should return something
- ASSERT_TRUE(ack);
-
- EXPECT_EQ(DHCPACK, ack->getType());
-
- // This is relayed message. It should be sent back to relay address.
- EXPECT_EQ(req->getGiaddr(), ack->getRemoteAddr());
-
- // Check that the requested options are returned.
- optionsCheck(ack);
-
- // Now repeat the test for directly sent message
- req->setHops(0);
- req->setGiaddr(IOAddress("0.0.0.0"));
- req->setRemotePort(DHCP4_CLIENT_PORT);
-
- EXPECT_NO_THROW(
- ack = srv->processDiscover(req);
- );
-
- // Should return something
- ASSERT_TRUE(ack);
-
- EXPECT_EQ(DHCPOFFER, ack->getType());
-
- // This is direct message. It should be sent back to origin, not
- // to relay.
- EXPECT_EQ(ack->getRemoteAddr(), req->getRemoteAddr());
-
- messageCheck(req, ack);
-
- // Check that the requested options are returned.
- optionsCheck(ack);
-
- delete srv;
+ testDiscoverRequest(DHCPREQUEST);
}
TEST_F(Dhcpv4SrvTest, processRelease) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
-
- boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPRELEASE, 1234));
+ NakedDhcpv4Srv srv;
+ Pkt4Ptr pkt(new Pkt4(DHCPRELEASE, 1234));
// Should not throw
- EXPECT_NO_THROW(
- srv->processRelease(pkt);
- );
-
- delete srv;
+ EXPECT_NO_THROW(srv.processRelease(pkt));
}
TEST_F(Dhcpv4SrvTest, processDecline) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
-
- boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDECLINE, 1234));
+ NakedDhcpv4Srv srv;
+ Pkt4Ptr pkt(new Pkt4(DHCPDECLINE, 1234));
// Should not throw
- EXPECT_NO_THROW(
- srv->processDecline(pkt);
- );
-
- delete srv;
+ EXPECT_NO_THROW(srv.processDecline(pkt));
}
TEST_F(Dhcpv4SrvTest, processInform) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
-
- boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPINFORM, 1234));
+ NakedDhcpv4Srv srv;
+ Pkt4Ptr pkt(new Pkt4(DHCPINFORM, 1234));
// Should not throw
- EXPECT_NO_THROW(
- srv->processInform(pkt);
- );
+ EXPECT_NO_THROW(srv.processInform(pkt));
// Should return something
- EXPECT_TRUE(srv->processInform(pkt));
+ EXPECT_TRUE(srv.processInform(pkt));
// @todo Implement more reasonable tests before starting
// work on processSomething() method.
-
- delete srv;
}
TEST_F(Dhcpv4SrvTest, serverReceivedPacketName) {
@@ -769,6 +1042,7 @@ TEST_F(Dhcpv4SrvTest, DiscoverNoClientId) {
Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
dis->setRemoteAddr(IOAddress("192.0.2.1"));
dis->setYiaddr(hint);
+ dis->setHWAddr(generateHWAddr(6));
// Pass it to the server and get an offer
Pkt4Ptr offer = srv->processDiscover(dis);
@@ -1130,8 +1404,9 @@ TEST_F(Dhcpv4SrvTest, sanityCheck) {
ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ pkt->setHWAddr(generateHWAddr(6));
- // Client-id is optional for information-request, so
+ // Server-id is optional for information-request, so
EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::OPTIONAL));
// Empty packet, no server-id
@@ -1145,6 +1420,11 @@ TEST_F(Dhcpv4SrvTest, sanityCheck) {
// Server-id is forbidden, but present => exception
EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::FORBIDDEN),
RFCViolation);
+
+ // There's no client-id and no HWADDR. Server needs something to
+ // identify the client
+ pkt->setHWAddr(generateHWAddr(0));
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::MANDATORY), RFCViolation);
}
// This test verifies that incoming (positive) RELEASE can be handled properly.
@@ -1340,4 +1620,1384 @@ TEST_F(Dhcpv4SrvTest, ServerID) {
EXPECT_EQ(srvid_text, text);
}
+/// @todo Implement tests for subnetSelect See tests in dhcp6_srv_unittest.cc:
+/// selectSubnetAddr, selectSubnetIface, selectSubnetRelayLinkaddr,
+/// selectSubnetRelayInterfaceId. Note that the concept of interface-id is not
+/// present in the DHCPv4, so not everything is applicable directly.
+/// See ticket #3057
+
+// Checks if hooks are registered properly.
+TEST_F(Dhcpv4SrvTest, Hooks) {
+ NakedDhcpv4Srv srv(0);
+
+ // check if appropriate hooks are registered
+ int hook_index_buffer4_receive = -1;
+ int hook_index_pkt4_receive = -1;
+ int hook_index_select_subnet = -1;
+ int hook_index_lease4_release = -1;
+ int hook_index_pkt4_send = -1;
+ int hook_index_buffer4_send = -1;
+
+ // check if appropriate indexes are set
+ EXPECT_NO_THROW(hook_index_buffer4_receive = ServerHooks::getServerHooks()
+ .getIndex("buffer4_receive"));
+ EXPECT_NO_THROW(hook_index_pkt4_receive = ServerHooks::getServerHooks()
+ .getIndex("pkt4_receive"));
+ EXPECT_NO_THROW(hook_index_select_subnet = ServerHooks::getServerHooks()
+ .getIndex("subnet4_select"));
+ EXPECT_NO_THROW(hook_index_lease4_release = ServerHooks::getServerHooks()
+ .getIndex("lease4_release"));
+ EXPECT_NO_THROW(hook_index_pkt4_send = ServerHooks::getServerHooks()
+ .getIndex("pkt4_send"));
+ EXPECT_NO_THROW(hook_index_buffer4_send = ServerHooks::getServerHooks()
+ .getIndex("buffer4_send"));
+
+ EXPECT_TRUE(hook_index_buffer4_receive > 0);
+ EXPECT_TRUE(hook_index_pkt4_receive > 0);
+ EXPECT_TRUE(hook_index_select_subnet > 0);
+ EXPECT_TRUE(hook_index_lease4_release > 0);
+ EXPECT_TRUE(hook_index_pkt4_send > 0);
+ EXPECT_TRUE(hook_index_buffer4_send > 0);
+}
+
+ // a dummy MAC address
+ const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5};
+
+ // A dummy MAC address, padded with 0s
+ const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ // Let's use some creative test content here (128 chars + \0)
+ const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur "
+ "adipiscing elit. Proin mollis placerat metus, at "
+ "lacinia orci ornare vitae. Mauris amet.";
+
+ // Yet another type of test content (64 chars + \0)
+ const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
+ "adipiscing elit posuere.";
+
+/// @brief a class dedicated to Hooks testing in DHCPv4 server
+///
+/// This class has a number of static members, because each non-static
+/// method has implicit 'this' parameter, so it does not match callout
+/// signature and couldn't be registered. Furthermore, static methods
+/// can't modify non-static members (for obvious reasons), so many
+/// fields are declared static. It is still better to keep them as
+/// one class rather than unrelated collection of global objects.
+class HooksDhcpv4SrvTest : public Dhcpv4SrvTest {
+
+public:
+
+ /// @brief creates Dhcpv4Srv and prepares buffers for callouts
+ HooksDhcpv4SrvTest() {
+
+ // Allocate new DHCPv6 Server
+ srv_ = new NakedDhcpv4Srv(0);
+
+ // clear static buffers
+ resetCalloutBuffers();
+ }
+
+ /// @brief destructor (deletes Dhcpv4Srv)
+ virtual ~HooksDhcpv4SrvTest() {
+
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_receive");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("buffer4_send");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("pkt4_receive");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("pkt4_send");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("subnet4_select");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_renew");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("lease4_release");
+
+ delete srv_;
+ }
+
+ /// @brief creates an option with specified option code
+ ///
+ /// This method is static, because it is used from callouts
+ /// that do not have a pointer to HooksDhcpv4SSrvTest object
+ ///
+ /// @param option_code code of option to be created
+ ///
+ /// @return pointer to create option object
+ static OptionPtr createOption(uint16_t option_code) {
+
+ char payload[] = {
+ 0xa, 0xb, 0xc, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14
+ };
+
+ OptionBuffer tmp(payload, payload + sizeof(payload));
+ return OptionPtr(new Option(Option::V4, option_code, tmp));
+ }
+
+ /// @brief Generates test packet.
+ ///
+ /// Allocates and generates on-wire buffer that represents test packet, with all
+ /// fixed fields set to non-zero values. Content is not always reasonable.
+ ///
+ /// See generateTestPacket1() function that returns exactly the same packet as
+ /// Pkt4 object.
+ ///
+ /// @return pointer to allocated Pkt4 object
+ // Returns a vector containing a DHCPv4 packet header.
+ Pkt4Ptr
+ generateSimpleDiscover() {
+
+ // That is only part of the header. It contains all "short" fields,
+ // larger fields are constructed separately.
+ uint8_t hdr[] = {
+ 1, 6, 6, 13, // op, htype, hlen, hops,
+ 0x12, 0x34, 0x56, 0x78, // transaction-id
+ 0, 42, 0x80, 0x00, // 42 secs, BROADCAST flags
+ 192, 0, 2, 1, // ciaddr
+ 1, 2, 3, 4, // yiaddr
+ 192, 0, 2, 255, // siaddr
+ 255, 255, 255, 255, // giaddr
+ };
+
+ // Initialize the vector with the header fields defined above.
+ vector<uint8_t> buf(hdr, hdr + sizeof(hdr));
+
+ // Append the large header fields.
+ copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN, back_inserter(buf));
+ copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN, back_inserter(buf));
+ copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN, back_inserter(buf));
+
+ // Should now have all the header, so check. The "static_cast" is used
+ // to get round an odd bug whereby the linker appears not to find the
+ // definition of DHCPV4_PKT_HDR_LEN if it appears within an EXPECT_EQ().
+ EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), buf.size());
+
+ // Add magic cookie
+ buf.push_back(0x63);
+ buf.push_back(0x82);
+ buf.push_back(0x53);
+ buf.push_back(0x63);
+
+ // Add message type DISCOVER
+ buf.push_back(static_cast<uint8_t>(DHO_DHCP_MESSAGE_TYPE));
+ buf.push_back(1); // length (just one byte)
+ buf.push_back(static_cast<uint8_t>(DHCPDISCOVER));
+
+ return (Pkt4Ptr(new Pkt4(&buf[0], buf.size())));
+ }
+
+ /// Test callback that stores received callout name and pkt4 value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer4_receive_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("buffer4_receive");
+
+ callout_handle.getArgument("query4", callback_pkt4_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ /// Test callback that changes hwaddr value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer4_receive_change_hwaddr(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("query4", pkt);
+
+ // If there is at least one option with data
+ if (pkt->data_.size() >= Pkt4::DHCPV4_PKT_HDR_LEN) {
+ // Offset of the first byte of the CHWADDR field. Let's the first
+ // byte to some new value that we could later check
+ pkt->data_[28] = 0xff;
+ }
+
+ // Carry on as usual
+ return buffer4_receive_callout(callout_handle);
+ }
+
+ /// Test callback that deletes MAC address
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer4_receive_delete_hwaddr(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("query4", pkt);
+
+ pkt->data_[2] = 0; // offset 2 is hlen, let's set it to zero
+ memset(&pkt->data_[28], 0, Pkt4::MAX_CHADDR_LEN); // Clear CHADDR content
+
+ // carry on as usual
+ return buffer4_receive_callout(callout_handle);
+ }
+
+ /// Test callback that sets skip flag
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer4_receive_skip(CalloutHandle& callout_handle) {
+
+ callout_handle.setSkip(true);
+
+ // Carry on as usual
+ return buffer4_receive_callout(callout_handle);
+ }
+
+ /// test callback that stores received callout name and pkt4 value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_receive_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("pkt4_receive");
+
+ callout_handle.getArgument("query4", callback_pkt4_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ /// test callback that changes client-id value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_receive_change_clientid(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("query4", pkt);
+
+ // get rid of the old client-id
+ pkt->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
+
+ // add a new option
+ pkt->addOption(createOption(DHO_DHCP_CLIENT_IDENTIFIER));
+
+ // carry on as usual
+ return pkt4_receive_callout(callout_handle);
+ }
+
+ /// test callback that deletes client-id
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_receive_delete_clientid(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("query4", pkt);
+
+ // get rid of the old client-id (and no HWADDR)
+ vector<uint8_t> mac;
+ pkt->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ pkt->setHWAddr(1, 0, mac); // HWtype 1, hwardware len = 0
+
+ // carry on as usual
+ return pkt4_receive_callout(callout_handle);
+ }
+
+ /// test callback that sets skip flag
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_receive_skip(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("query4", pkt);
+
+ callout_handle.setSkip(true);
+
+ // carry on as usual
+ return pkt4_receive_callout(callout_handle);
+ }
+
+ /// Test callback that stores received callout name and pkt4 value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_send_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("pkt4_send");
+
+ callout_handle.getArgument("response4", callback_pkt4_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ // Test callback that changes server-id
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_send_change_serverid(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("response4", pkt);
+
+ // get rid of the old server-id
+ pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER);
+
+ // add a new option
+ pkt->addOption(createOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+ // carry on as usual
+ return pkt4_send_callout(callout_handle);
+ }
+
+ /// test callback that deletes server-id
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_send_delete_serverid(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("response4", pkt);
+
+ // get rid of the old client-id
+ pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER);
+
+ // carry on as usual
+ return pkt4_send_callout(callout_handle);
+ }
+
+ /// Test callback that sets skip flag
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt4_send_skip(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("response4", pkt);
+
+ callout_handle.setSkip(true);
+
+ // carry on as usual
+ return pkt4_send_callout(callout_handle);
+ }
+
+ /// Test callback that stores received callout name and pkt4 value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer4_send_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("buffer4_send");
+
+ callout_handle.getArgument("response4", callback_pkt4_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ /// Test callback changes the output buffer to a hardcoded value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer4_send_change_callout(CalloutHandle& callout_handle) {
+
+ Pkt4Ptr pkt;
+ callout_handle.getArgument("response4", pkt);
+
+ // modify buffer to set a diffferent payload
+ pkt->getBuffer().clear();
+ pkt->getBuffer().writeData(dummyFile, sizeof(dummyFile));
+
+ return (0);
+ }
+
+ /// Test callback that stores received callout name and pkt4 value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ skip_callout(CalloutHandle& callout_handle) {
+
+ callout_handle.setSkip(true);
+
+ return (0);
+ }
+
+ /// Test callback that stores received callout name and subnet4 values
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ subnet4_select_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("subnet4_select");
+
+ callout_handle.getArgument("query4", callback_pkt4_);
+ callout_handle.getArgument("subnet4", callback_subnet4_);
+ callout_handle.getArgument("subnet4collection", callback_subnet4collection_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ /// Test callback that picks the other subnet if possible.
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ subnet4_select_different_subnet_callout(CalloutHandle& callout_handle) {
+
+ // Call the basic calllout to record all passed values
+ subnet4_select_callout(callout_handle);
+
+ const Subnet4Collection* subnets;
+ Subnet4Ptr subnet;
+ callout_handle.getArgument("subnet4", subnet);
+ callout_handle.getArgument("subnet4collection", subnets);
+
+ // Let's change to a different subnet
+ if (subnets->size() > 1) {
+ subnet = (*subnets)[1]; // Let's pick the other subnet
+ callout_handle.setArgument("subnet4", subnet);
+ }
+
+ return (0);
+ }
+
+ /// Test callback that stores received callout name passed parameters
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease4_release_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease4_release");
+
+ callout_handle.getArgument("query4", callback_pkt4_);
+ callout_handle.getArgument("lease4", callback_lease4_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ /// Test callback that stores received callout name and subnet4 values
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease4_renew_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease4_renew");
+
+ callout_handle.getArgument("subnet4", callback_subnet4_);
+ callout_handle.getArgument("lease4", callback_lease4_);
+ callout_handle.getArgument("hwaddr", callback_hwaddr_);
+ callout_handle.getArgument("clientid", callback_clientid_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+
+ /// resets buffers used to store data received by callouts
+ void resetCalloutBuffers() {
+ callback_name_ = string("");
+ callback_pkt4_.reset();
+ callback_lease4_.reset();
+ callback_hwaddr_.reset();
+ callback_clientid_.reset();
+ callback_subnet4_.reset();
+ callback_subnet4collection_ = NULL;
+ callback_argument_names_.clear();
+ }
+
+ /// pointer to Dhcpv4Srv that is used in tests
+ NakedDhcpv4Srv* srv_;
+
+ // The following fields are used in testing pkt4_receive_callout
+
+ /// String name of the received callout
+ static string callback_name_;
+
+ /// Pkt4 structure returned in the callout
+ static Pkt4Ptr callback_pkt4_;
+
+ /// Lease4 structure returned in the callout
+ static Lease4Ptr callback_lease4_;
+
+ /// Hardware address returned in the callout
+ static HWAddrPtr callback_hwaddr_;
+
+ /// Client-id returned in the callout
+ static ClientIdPtr callback_clientid_;
+
+ /// Pointer to a subnet received by callout
+ static Subnet4Ptr callback_subnet4_;
+
+ /// A list of all available subnets (received by callout)
+ static const Subnet4Collection* callback_subnet4collection_;
+
+ /// A list of all received arguments
+ static vector<string> callback_argument_names_;
+};
+
+// The following fields are used in testing pkt4_receive_callout.
+// See fields description in the class for details
+string HooksDhcpv4SrvTest::callback_name_;
+Pkt4Ptr HooksDhcpv4SrvTest::callback_pkt4_;
+Subnet4Ptr HooksDhcpv4SrvTest::callback_subnet4_;
+HWAddrPtr HooksDhcpv4SrvTest::callback_hwaddr_;
+ClientIdPtr HooksDhcpv4SrvTest::callback_clientid_;
+Lease4Ptr HooksDhcpv4SrvTest::callback_lease4_;
+const Subnet4Collection* HooksDhcpv4SrvTest::callback_subnet4collection_;
+vector<string> HooksDhcpv4SrvTest::callback_argument_names_;
+
+// Checks if callouts installed on pkt4_receive are indeed called and the
+// all necessary parameters are passed.
+//
+// Note that the test name does not follow test naming convention,
+// but the proper hook name is "buffer4_receive".
+TEST_F(HooksDhcpv4SrvTest, Buffer4ReceiveSimple) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer4_receive", buffer4_receive_callout));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr dis = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(dis);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer4_receive callback.
+ srv_->run();
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("buffer4_receive", callback_name_);
+
+ // Check that pkt4 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_pkt4_.get() == dis.get());
+
+ // Check that all expected parameters are there
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back(string("query4"));
+
+ EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+}
+
+// Checks if callouts installed on buffer4_receive is able to change
+// the values and the parameters are indeed used by the server.
+TEST_F(HooksDhcpv4SrvTest, buffer4RreceiveValueChange) {
+
+ // Install callback that modifies MAC addr of incoming packet
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer4_receive", buffer4_receive_change_hwaddr));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr discover = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(discover);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer4_receive callback.
+ srv_->run();
+
+ // Check that the server did send a reposonce
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // Make sure that we received a response
+ Pkt4Ptr offer = srv_->fake_sent_.front();
+ ASSERT_TRUE(offer);
+
+ // Get client-id...
+ HWAddrPtr hwaddr = offer->getHWAddr();
+
+ ASSERT_TRUE(hwaddr); // basic sanity check. HWAddr is always present
+
+ // ... and check if it is the modified value
+ ASSERT_FALSE(hwaddr->hwaddr_.empty()); // there must be a MAC address
+ EXPECT_EQ(0xff, hwaddr->hwaddr_[0]); // check that its first byte was modified
+}
+
+// Checks if callouts installed on buffer4_receive is able to set skip flag that
+// will cause the server to not parse the packet. Even though the packet is valid,
+// the server should eventually drop it, because there won't be mandatory options
+// (or rather option objects) in it.
+TEST_F(HooksDhcpv4SrvTest, buffer4ReceiveSkip) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer4_receive", buffer4_receive_skip));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr discover = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(discover);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the server dropped the packet and did not produce any response
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+}
+
+// Checks if callouts installed on pkt4_receive are indeed called and the
+// all necessary parameters are passed.
+//
+// Note that the test name does not follow test naming convention,
+// but the proper hook name is "pkt4_receive".
+TEST_F(HooksDhcpv4SrvTest, pkt4ReceiveSimple) {
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_receive", pkt4_receive_callout));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // check that the callback called is indeed the one we installed
+ EXPECT_EQ("pkt4_receive", callback_name_);
+
+ // check that pkt4 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_pkt4_.get() == sol.get());
+
+ // Check that all expected parameters are there
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back(string("query4"));
+
+ EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+}
+
+// Checks if callouts installed on pkt4_received is able to change
+// the values and the parameters are indeed used by the server.
+TEST_F(HooksDhcpv4SrvTest, valueChange_pkt4_receive) {
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_receive", pkt4_receive_change_clientid));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // check that the server did send a reposonce
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // Make sure that we received a response
+ Pkt4Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Get client-id...
+ OptionPtr clientid = adv->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+
+ // ... and check if it is the modified value
+ OptionPtr expected = createOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ EXPECT_TRUE(clientid->equal(expected));
+}
+
+// Checks if callouts installed on pkt4_received is able to delete
+// existing options and that change impacts server processing (mandatory
+// client-id option is deleted, so the packet is expected to be dropped)
+TEST_F(HooksDhcpv4SrvTest, pkt4ReceiveDeleteClientId) {
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_receive", pkt4_receive_delete_clientid));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // Check that the server dropped the packet and did not send a response
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+}
+
+// Checks if callouts installed on pkt4_received is able to set skip flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv4SrvTest, pkt4ReceiveSkip) {
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_receive", pkt4_receive_skip));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // check that the server dropped the packet and did not produce any response
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+}
+
+
+// Checks if callouts installed on pkt4_send are indeed called and the
+// all necessary parameters are passed.
+TEST_F(HooksDhcpv4SrvTest, pkt4SendSimple) {
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_send", pkt4_send_callout));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("pkt4_send", callback_name_);
+
+ // Check that there is one packet sent
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+ Pkt4Ptr adv = srv_->fake_sent_.front();
+
+ // Check that pkt4 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_pkt4_.get() == adv.get());
+
+ // Check that all expected parameters are there
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back(string("response4"));
+ EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+}
+
+// Checks if callouts installed on pkt4_send is able to change
+// the values and the packet sent contains those changes
+TEST_F(HooksDhcpv4SrvTest, pkt4SendValueChange) {
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_send", pkt4_send_change_serverid));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // check that the server did send a reposonce
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // Make sure that we received a response
+ Pkt4Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Get client-id...
+ OptionPtr clientid = adv->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+
+ // ... and check if it is the modified value
+ OptionPtr expected = createOption(DHO_DHCP_SERVER_IDENTIFIER);
+ EXPECT_TRUE(clientid->equal(expected));
+}
+
+// Checks if callouts installed on pkt4_send is able to delete
+// existing options and that server applies those changes. In particular,
+// we are trying to send a packet without server-id. The packet should
+// be sent
+TEST_F(HooksDhcpv4SrvTest, pkt4SendDeleteServerId) {
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_send", pkt4_send_delete_serverid));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // Check that the server indeed sent a malformed ADVERTISE
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // Get that ADVERTISE
+ Pkt4Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Make sure that it does not have server-id
+ EXPECT_FALSE(adv->getOption(DHO_DHCP_SERVER_IDENTIFIER));
+}
+
+// Checks if callouts installed on pkt4_skip is able to set skip flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv4SrvTest, skip_pkt4_send) {
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt4_send", pkt4_send_skip));
+
+ // Let's create a simple REQUEST
+ Pkt4Ptr sol = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_send callback.
+ srv_->run();
+
+ // Check that the server sent the message
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // Get the first packet and check that it has zero length (i.e. the server
+ // did not do packing on its own)
+ Pkt4Ptr sent = srv_->fake_sent_.front();
+ EXPECT_EQ(0, sent->getBuffer().getLength());
+}
+
+// Checks if callouts installed on buffer4_send are indeed called and the
+// all necessary parameters are passed.
+TEST_F(HooksDhcpv4SrvTest, buffer4SendSimple) {
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer4_send", buffer4_send_callout));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr discover = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(discover);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("buffer4_send", callback_name_);
+
+ // Check that there is one packet sent
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+ Pkt4Ptr adv = srv_->fake_sent_.front();
+
+ // Check that pkt4 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_pkt4_.get() == adv.get());
+
+ // Check that all expected parameters are there
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back(string("response4"));
+ EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+}
+
+// Checks if callouts installed on buffer4_send are indeed called and that
+// the output buffer can be changed.
+TEST_F(HooksDhcpv4SrvTest, buffer4Send) {
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer4_send", buffer4_send_change_callout));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr discover = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(discover);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // Check that there is one packet sent
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+ Pkt4Ptr adv = srv_->fake_sent_.front();
+
+ // The callout is supposed to fill the output buffer with dummyFile content
+ ASSERT_EQ(sizeof(dummyFile), adv->getBuffer().getLength());
+ EXPECT_EQ(0, memcmp(adv->getBuffer().getData(), dummyFile, sizeof(dummyFile)));
+}
+
+// Checks if callouts installed on buffer4_send can set skip flag and that flag
+// causes the packet to not be sent
+TEST_F(HooksDhcpv4SrvTest, buffer4SendSkip) {
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer4_send", skip_callout));
+
+ // Let's create a simple DISCOVER
+ Pkt4Ptr discover = generateSimpleDiscover();
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(discover);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt4_receive callback.
+ srv_->run();
+
+ // Check that there is no packet sent.
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+}
+
+
+// This test checks if subnet4_select callout is triggered and reports
+// valid parameters
+TEST_F(HooksDhcpv4SrvTest, subnet4SelectSimple) {
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "subnet4_select", subnet4_select_callout));
+
+ // Configure 2 subnets, both directly reachable over local interface
+ // (let's not complicate the matter with relays)
+ string config = "{ \"interfaces\": [ \"*\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.0/25\" ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"" + valid_iface_ + "\" "
+ " }, {"
+ " \"pool\": [ \"192.0.3.0/25\" ],"
+ " \"subnet\": \"192.0.3.0/24\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ // Prepare discover packet. Server should select first subnet for it
+ Pkt4Ptr sol = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ sol->setRemoteAddr(IOAddress("192.0.2.1"));
+ sol->setIface(valid_iface_);
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ Pkt4Ptr adv = srv_->processDiscover(sol);
+
+ // check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("subnet4_select", callback_name_);
+
+ // Check that pkt4 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_pkt4_.get() == sol.get());
+
+ const Subnet4Collection* exp_subnets = CfgMgr::instance().getSubnets4();
+
+ // The server is supposed to pick the first subnet, because of matching
+ // interface. Check that the value is reported properly.
+ ASSERT_TRUE(callback_subnet4_);
+ EXPECT_EQ(exp_subnets->front().get(), callback_subnet4_.get());
+
+ // Server is supposed to report two subnets
+ ASSERT_EQ(exp_subnets->size(), callback_subnet4collection_->size());
+
+ // Compare that the available subnets are reported as expected
+ EXPECT_TRUE((*exp_subnets)[0].get() == (*callback_subnet4collection_)[0].get());
+ EXPECT_TRUE((*exp_subnets)[1].get() == (*callback_subnet4collection_)[1].get());
+}
+
+// This test checks if callout installed on subnet4_select hook point can pick
+// a different subnet.
+TEST_F(HooksDhcpv4SrvTest, subnet4SelectChange) {
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "subnet4_select", subnet4_select_different_subnet_callout));
+
+ // Configure 2 subnets, both directly reachable over local interface
+ // (let's not complicate the matter with relays)
+ string config = "{ \"interfaces\": [ \"*\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.0/25\" ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"interface\": \"" + valid_iface_ + "\" "
+ " }, {"
+ " \"pool\": [ \"192.0.3.0/25\" ],"
+ " \"subnet\": \"192.0.3.0/24\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ // Prepare discover packet. Server should select first subnet for it
+ Pkt4Ptr sol = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ sol->setRemoteAddr(IOAddress("192.0.2.1"));
+ sol->setIface(valid_iface_);
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ Pkt4Ptr adv = srv_->processDiscover(sol);
+
+ // check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // The response should have an address from second pool, so let's check it
+ IOAddress addr = adv->getYiaddr();
+ EXPECT_NE("0.0.0.0", addr.toText());
+
+ // Get all subnets and use second subnet for verification
+ const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
+ ASSERT_EQ(2, subnets->size());
+
+ // Advertised address must belong to the second pool (in subnet's range,
+ // in dynamic pool)
+ EXPECT_TRUE((*subnets)[1]->inRange(addr));
+ EXPECT_TRUE((*subnets)[1]->inPool(addr));
+}
+
+// This test verifies that incoming (positive) REQUEST/Renewing can be handled
+// properly and that callout installed on lease4_renew is triggered with
+// expected parameters.
+TEST_F(HooksDhcpv4SrvTest, lease4RenewSimple) {
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_t1 = 50;
+ const uint32_t temp_t2 = 75;
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_renew", lease4_renew_callout));
+
+ // Generate client-id also sets client_id_ member
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // let's create a lease and put it in the LeaseMgr
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2, sizeof(hwaddr2),
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_t1, temp_t2, temp_timestamp,
+ subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RENEW
+ Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+ req->setRemoteAddr(IOAddress(addr));
+ req->setYiaddr(addr);
+ req->setCiaddr(addr); // client's address
+
+ req->addOption(clientid);
+ req->addOption(srv_->getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt4Ptr ack = srv_->processRequest(req);
+
+ // Check if we get response at all
+ checkResponse(ack, DHCPACK, 1234);
+
+ // Check that the lease is really in the database
+ l = checkLease(ack, clientid, req->getHWAddr(), addr);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt were really updated
+ EXPECT_EQ(l->t1_, subnet_->getT1());
+ EXPECT_EQ(l->t2_, subnet_->getT2());
+ EXPECT_EQ(l->valid_lft_, subnet_->getValid());
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease4_renew", callback_name_);
+
+ // Check that hwaddr parameter is passed properly
+ ASSERT_TRUE(callback_hwaddr_);
+ EXPECT_TRUE(*callback_hwaddr_ == *req->getHWAddr());
+
+ // Check that the subnet is passed properly
+ ASSERT_TRUE(callback_subnet4_);
+ EXPECT_EQ(callback_subnet4_->toText(), subnet_->toText());
+
+ ASSERT_TRUE(callback_clientid_);
+ ASSERT_TRUE(client_id_);
+ EXPECT_TRUE(*client_id_ == *callback_clientid_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("subnet4");
+ expected_argument_names.push_back("clientid");
+ expected_argument_names.push_back("hwaddr");
+ expected_argument_names.push_back("lease4");
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+}
+
+// This test verifies that a callout installed on lease4_renew can trigger
+// the server to not renew a lease.
+TEST_F(HooksDhcpv4SrvTest, lease4RenewSkip) {
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_t1 = 50;
+ const uint32_t temp_t2 = 75;
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_renew", skip_callout));
+
+ // Generate client-id also sets client_id_ member
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // let's create a lease and put it in the LeaseMgr
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2, sizeof(hwaddr2),
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_t1, temp_t2, temp_timestamp,
+ subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt really set.
+ // Constructed lease looks as if it was assigned 10 seconds ago
+ // EXPECT_EQ(l->t1_, temp_t1);
+ // EXPECT_EQ(l->t2_, temp_t2);
+ EXPECT_EQ(l->valid_lft_, temp_valid);
+ EXPECT_EQ(l->cltt_, temp_timestamp);
+
+ // Let's create a RENEW
+ Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+ req->setRemoteAddr(IOAddress(addr));
+ req->setYiaddr(addr);
+ req->setCiaddr(addr); // client's address
+
+ req->addOption(clientid);
+ req->addOption(srv_->getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt4Ptr ack = srv_->processRequest(req);
+ ASSERT_TRUE(ack);
+
+ // Check that the lease is really in the database
+ l = checkLease(ack, clientid, req->getHWAddr(), addr);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, valid and cltt were NOT updated
+ EXPECT_EQ(temp_t1, l->t1_);
+ EXPECT_EQ(temp_t2, l->t2_);
+ EXPECT_EQ(temp_valid, l->valid_lft_);
+ EXPECT_EQ(temp_timestamp, l->cltt_);
+
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+}
+
+// This test verifies that valid RELEASE triggers lease4_release callouts
+TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSimple) {
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_t1 = 50;
+ const uint32_t temp_t2 = 75;
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_release", lease4_release_callout));
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(addr, mac_addr, sizeof(mac_addr),
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_t1, temp_t2, temp_timestamp,
+ subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ // Generate client-id also duid_
+ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+ rel->setRemoteAddr(addr);
+ rel->setYiaddr(addr);
+ rel->addOption(clientid);
+ rel->addOption(srv_->getServerID());
+ rel->setHWAddr(hw);
+
+ // Pass it to the server and hope for a REPLY
+ // Note: this is no response to RELEASE in DHCPv4
+ EXPECT_NO_THROW(srv_->processRelease(rel));
+
+ // The lease should be gone from LeaseMgr
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ EXPECT_FALSE(l);
+
+ // Try to get the lease by hardware address
+ // @todo: Uncomment this once trac2592 is implemented
+ // Lease4Collection leases = LeaseMgrFactory::instance().getLease4(hw->hwaddr_);
+ // EXPECT_EQ(leases.size(), 0);
+
+ // Try to get it by hw/subnet_id combination
+ l = LeaseMgrFactory::instance().getLease4(hw->hwaddr_, subnet_->getID());
+ EXPECT_FALSE(l);
+
+ // Try by client-id
+ // @todo: Uncomment this once trac2592 is implemented
+ //Lease4Collection leases = LeaseMgrFactory::instance().getLease4(*client_id_);
+ //EXPECT_EQ(leases.size(), 0);
+
+ // Try by client-id/subnet-id
+ l = LeaseMgrFactory::instance().getLease4(*client_id_, subnet_->getID());
+ EXPECT_FALSE(l);
+
+ // Ok, the lease is *really* not there.
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease4_release", callback_name_);
+
+ // Check that pkt4 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_pkt4_.get() == rel.get());
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query4");
+ expected_argument_names.push_back("lease4");
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+}
+
+// This test verifies that skip flag returned by a callout installed on the
+// lease4_release hook point will keep the lease
+TEST_F(HooksDhcpv4SrvTest, lease4ReleaseSkip) {
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_t1 = 50;
+ const uint32_t temp_t2 = 75;
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_release", skip_callout));
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(addr, mac_addr, sizeof(mac_addr),
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_t1, temp_t2, temp_timestamp,
+ subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ // Generate client-id also duid_
+ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+ rel->setRemoteAddr(addr);
+ rel->setYiaddr(addr);
+ rel->addOption(clientid);
+ rel->addOption(srv_->getServerID());
+ rel->setHWAddr(hw);
+
+ // Pass it to the server and hope for a REPLY
+ // Note: this is no response to RELEASE in DHCPv4
+ EXPECT_NO_THROW(srv_->processRelease(rel));
+
+ // The lease should be still there
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ EXPECT_TRUE(l);
+
+ // Try by client-id/subnet-id
+ l = LeaseMgrFactory::instance().getLease4(*client_id_, subnet_->getID());
+ EXPECT_TRUE(l);
+
+ // Try to get the lease by hardware address
+ // @todo: Uncomment this once trac2592 is implemented
+ // Lease4Collection leases = LeaseMgrFactory::instance().getLease4(hw->hwaddr_);
+ // EXPECT_EQ(leases.size(), 1);
+
+ // Try by client-id
+ // @todo: Uncomment this once trac2592 is implemented
+ //Lease4Collection leases = LeaseMgrFactory::instance().getLease4(*client_id_);
+ //EXPECT_EQ(leases.size(), 1);
+}
+
} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/marker_file.cc b/src/bin/dhcp4/tests/marker_file.cc
new file mode 100644
index 0000000..d1c4aba
--- /dev/null
+++ b/src/bin/dhcp4/tests/marker_file.cc
@@ -0,0 +1,66 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 "marker_file.h"
+
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+using namespace std;
+
+// Check the marker file.
+
+bool
+checkMarkerFile(const char* name, const char* expected) {
+ // Open the file for input
+ fstream file(name, fstream::in);
+
+ // Is it open?
+ if (!file.is_open()) {
+ ADD_FAILURE() << "Unable to open " << name << ". It was expected "
+ << "to be present and to contain the string '"
+ << expected << "'";
+ return (false);
+ }
+
+ // OK, is open, so read the data and see what we have. Compare it
+ // against what is expected.
+ string content;
+ getline(file, content);
+
+ string expected_str(expected);
+ EXPECT_EQ(expected_str, content) << "Marker file " << name
+ << "did not contain the expected data";
+ file.close();
+
+ return (expected_str == content);
+}
+
+// Check if the marker file exists - this is a wrapper for "access(2)" and
+// really tests if the file exists and is accessible
+
+bool
+checkMarkerFileExists(const char* name) {
+ return (access(name, F_OK) == 0);
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/bin/dhcp4/tests/marker_file.h.in b/src/bin/dhcp4/tests/marker_file.h.in
new file mode 100644
index 0000000..52fc006
--- /dev/null
+++ b/src/bin/dhcp4/tests/marker_file.h.in
@@ -0,0 +1,69 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 MARKER_FILE_H
+#define MARKER_FILE_H
+
+/// @file
+/// Define a marker file that is used in tests to prove that an "unload"
+/// function has been called
+
+namespace {
+const char* const LOAD_MARKER_FILE = "@abs_builddir@/load_marker.txt";
+const char* const UNLOAD_MARKER_FILE = "@abs_builddir@/unload_marker.txt";
+}
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Check marker file
+///
+/// This function is used in some of the DHCP server tests.
+///
+/// Marker files are used by the load/unload functions in the hooks
+/// libraries in these tests to signal whether they have been loaded or
+/// unloaded. The file (if present) contains a single line holding
+/// a set of characters.
+///
+/// This convenience function checks the file to see if the characters
+/// are those expected.
+///
+/// @param name Name of the marker file.
+/// @param expected Characters expected. If a marker file is present,
+/// it is expected to contain characters.
+///
+/// @return true if all tests pass, false if not (in which case a failure
+/// will have been logged).
+bool
+checkMarkerFile(const char* name, const char* expected);
+
+/// @brief Check marker file exists
+///
+/// This function is used in some of the DHCP server tests.
+///
+/// Checkes that the specified file does NOT exist.
+///
+/// @param name Name of the marker file.
+///
+/// @return true if file exists, false if not.
+bool
+checkMarkerFileExists(const char* name);
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // MARKER_FILE_H
+
diff --git a/src/bin/dhcp4/tests/test_libraries.h.in b/src/bin/dhcp4/tests/test_libraries.h.in
new file mode 100644
index 0000000..8b03dc2
--- /dev/null
+++ b/src/bin/dhcp4/tests/test_libraries.h.in
@@ -0,0 +1,51 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TEST_LIBRARIES_H
+#define TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+
+// Take care of differences in DLL naming between operating systems.
+
+#ifdef OS_OSX
+#define DLL_SUFFIX ".dylib"
+
+#else
+#define DLL_SUFFIX ".so"
+
+#endif
+
+
+// Names of the libraries used in these tests. These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// shared library.
+
+// Library with load/unload functions creating marker files to check their
+// operation.
+const char* const CALLOUT_LIBRARY_1 = "@abs_builddir@/.libs/libco1"
+ DLL_SUFFIX;
+const char* const CALLOUT_LIBRARY_2 = "@abs_builddir@/.libs/libco2"
+ DLL_SUFFIX;
+
+// Name of a library which is not present.
+const char* const NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere"
+ DLL_SUFFIX;
+} // anonymous namespace
+
+
+#endif // TEST_LIBRARIES_H
diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am
index 3b07510..9b7972b 100644
--- a/src/bin/dhcp6/Makefile.am
+++ b/src/bin/dhcp6/Makefile.am
@@ -61,10 +61,12 @@ b10_dhcp6_LDADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
b10_dhcp6dir = $(pkgdatadir)
b10_dhcp6_DATA = dhcp6.spec
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 76ed228..63bda52 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -22,6 +22,7 @@
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/dbaccess_parser.h>
#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/dhcp_parsers.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/triplet.h>
@@ -49,1432 +50,227 @@ using namespace isc::asiolink;
namespace {
-// Forward declarations of some of the parser classes.
-// They are used to define pointer types for these classes.
-class BooleanParser;
-class StringParser;
-class Uint32Parser;
-
// Pointers to various parser objects.
typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
typedef boost::shared_ptr<StringParser> StringParserPtr;
typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
-/// @brief Factory method that will create a parser for a given element name
-typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
-
-/// @brief Collection of factories that create parsers for specified element names
-typedef std::map<std::string, ParserFactory*> FactoryMap;
-
-/// @brief Storage for parsed boolean values.
-typedef std::map<string, bool> BooleanStorage;
-
-/// @brief Collection of elements that store uint32 values (e.g. renew-timer = 900).
-typedef std::map<string, uint32_t> Uint32Storage;
-
-/// @brief Collection of elements that store string values.
-typedef std::map<string, string> StringStorage;
-
-/// @brief Storage for option definitions.
-typedef OptionSpaceContainer<OptionDefContainer,
- OptionDefinitionPtr> OptionDefStorage;
-
-/// @brief Collection of address pools.
-///
-/// This type is used as intermediate storage, when pools are parsed, but there is
-/// no subnet object created yet to store them.
-typedef std::vector<isc::dhcp::Pool6Ptr> PoolStorage;
-
-/// Collection of containers holding option spaces. Each container within
-/// a particular option space holds so-called option descriptors.
-typedef OptionSpaceContainer<Subnet::OptionContainer,
- Subnet::OptionDescriptor> OptionStorage;
-
-/// @brief Global uint32 parameters that will be used as defaults.
-Uint32Storage uint32_defaults;
-
-/// @brief global string parameters that will be used as defaults.
-StringStorage string_defaults;
-
-/// @brief Global storage for options that will be used as defaults.
-OptionStorage option_defaults;
-
-/// @brief Global storage for option definitions.
-OptionDefStorage option_def_intermediate;
-
-/// @brief a dummy configuration parser
-///
-/// This is a debugging parser. It does not configure anything,
-/// will accept any configuration and will just print it out
-/// on commit. Useful for debugging existing configurations and
-/// adding new ones.
-class DebugParser : public DhcpConfigParser {
-public:
-
- /// @brief Constructor
- ///
- /// See @ref DhcpConfigParser class for details.
- ///
- /// @param param_name name of the parsed parameter
- DebugParser(const std::string& param_name)
- :param_name_(param_name) {
- }
-
- /// @brief builds parameter value
- ///
- /// See @ref DhcpConfigParser class for details.
- ///
- /// @param new_config pointer to the new configuration
- virtual void build(ConstElementPtr new_config) {
- std::cout << "Build for token: [" << param_name_ << "] = ["
- << value_->str() << "]" << std::endl;
- value_ = new_config;
- }
-
- /// @brief Pretends to apply the configuration.
- ///
- /// This is a method required by the base class. It pretends to apply the
- /// configuration, but in fact it only prints the parameter out.
- ///
- /// See @ref DhcpConfigParser class for details.
- virtual void commit() {
- // Debug message. The whole DebugParser class is used only for parser
- // debugging, and is not used in production code. It is very convenient
- // to keep it around. Please do not turn this cout into logger calls.
- std::cout << "Commit for token: [" << param_name_ << "] = ["
- << value_->str() << "]" << std::endl;
- }
-
- /// @brief factory that constructs DebugParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new DebugParser(param_name));
- }
-
-private:
- /// name of the parsed parameter
- std::string param_name_;
-
- /// pointer to the actual value of the parameter
- ConstElementPtr value_;
-};
-
-
-/// @brief A boolean value parser.
-///
-/// This parser handles configuration values of the boolean type.
-/// Parsed values are stored in a provided storage. If no storage
-/// is provided then the build function throws an exception.
-class BooleanParser : public DhcpConfigParser {
-public:
- /// @brief Constructor.
- ///
- /// @param param_name name of the parameter.
- BooleanParser(const std::string& param_name)
- : storage_(NULL),
- param_name_(param_name),
- value_(false) {
- // Empty parameter name is invalid.
- if (param_name_.empty()) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- }
-
- /// @brief Parse a boolean value.
- ///
- /// @param value a value to be parsed.
- ///
- /// @throw isc::InvalidOperation if a storage has not been set
- /// prior to calling this function
- /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
- /// name is empty.
- virtual void build(ConstElementPtr value) {
- if (storage_ == NULL) {
- isc_throw(isc::InvalidOperation, "parser logic error:"
- << " storage for the " << param_name_
- << " value has not been set");
- } else if (param_name_.empty()) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- // The Config Manager checks if user specified a
- // valid value for a boolean parameter: True or False.
- // It is then ok to assume that if str() does not return
- // 'true' the value is 'false'.
- value_ = (value->str() == "true") ? true : false;
- }
-
- /// @brief Put a parsed value to the storage.
- virtual void commit() {
- if (storage_ != NULL && !param_name_.empty()) {
- (*storage_)[param_name_] = value_;
- }
- }
-
- /// @brief Create an instance of the boolean parser.
- ///
- /// @param param_name name of the parameter for which the
- /// parser is created.
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new BooleanParser(param_name));
- }
-
- /// @brief Set the storage for parsed value.
- ///
- /// This function must be called prior to calling build.
- ///
- /// @param storage a pointer to the storage where parsed data
- /// is to be stored.
- void setStorage(BooleanStorage* storage) {
- storage_ = storage;
- }
-
-private:
- /// Pointer to the storage where parsed value is stored.
- BooleanStorage* storage_;
- /// Name of the parameter which value is parsed with this parser.
- std::string param_name_;
- /// Parsed value.
- bool value_;
-};
-
-/// @brief Configuration parser for uint32 parameters
-///
-/// This class is a generic parser that is able to handle any uint32 integer
-/// type. By default it stores the value in external global container
-/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
-/// in subnet config), it can be pointed to a different storage, using
-/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, @ref DhcpConfigParser.
-///
-/// For overview of usability of this generic purpose parser, see
-/// @ref dhcpv6ConfigInherit page.
-///
-/// @todo this class should be turned into the template class which
-/// will handle all uintX_types of data (see ticket #2415).
-class Uint32Parser : public DhcpConfigParser {
-public:
-
- /// @brief constructor for Uint32Parser
- ///
- /// @param param_name name of the configuration parameter being parsed
- Uint32Parser(const std::string& param_name)
- : storage_(&uint32_defaults),
- param_name_(param_name) {
- // Empty parameter name is invalid.
- if (param_name_.empty()) {
- isc_throw(DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- }
-
- /// @brief Parses configuration configuration parameter as uint32_t.
- ///
- /// @param value pointer to the content of parsed values
- /// @throw isc::dhcp::DhcpConfigError if failed to parse
- /// the configuration parameter as uint32_t value.
- virtual void build(ConstElementPtr value) {
- if (param_name_.empty()) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
-
- bool parse_error = false;
- // Cast the provided value to int64 value to check.
- int64_t int64value = 0;
- try {
- // Parsing the value as a int64 value allows to
- // check if the provided value is within the range
- // of uint32_t (is not negative or greater than
- // maximal uint32_t value).
- int64value = boost::lexical_cast<int64_t>(value->str());
- } catch (const boost::bad_lexical_cast&) {
- parse_error = true;
- }
- if (!parse_error) {
- // Check that the value is not out of bounds.
- if ((int64value < 0) ||
- (int64value > std::numeric_limits<uint32_t>::max())) {
- parse_error = true;
- } else {
- // A value is not out of bounds so let's cast it to
- // the uint32_t type.
- value_ = static_cast<uint32_t>(int64value);
- }
-
- }
- // Invalid value provided.
- if (parse_error) {
- isc_throw(isc::dhcp::DhcpConfigError, "Failed to parse value " << value->str()
- << " as unsigned 32-bit integer.");
- }
- }
-
- /// @brief Stores the parsed uint32_t value in a storage.
- virtual void commit() {
- if (storage_ != NULL) {
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
- }
- }
-
- /// @brief Factory that constructs Uint32Parser objects.
- ///
- /// @param param_name name of the parameter to be parsed.
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new Uint32Parser(param_name));
- }
-
- /// @brief Sets storage for value of this parameter.
- ///
- /// See @ref dhcpv6ConfigInherit for details.
- ///
- /// @param storage pointer to the storage container.
- void setStorage(Uint32Storage* storage) {
- storage_ = storage;
- }
-
-private:
- /// pointer to the storage, where parsed value will be stored
- Uint32Storage* storage_;
- /// name of the parameter to be parsed
- std::string param_name_;
- /// the actual parsed value
- uint32_t value_;
-};
-
-/// @brief Configuration parser for string parameters
-///
-/// This class is a generic parser that is able to handle any string
-/// parameter. By default it stores the value in an external global container
-/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
-/// in subnet config), it can be pointed to a different storage, using the
-/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, @ref DhcpConfigParser.
-///
-/// For overview of usability of this generic purpose parser, see
-/// @ref dhcpv6ConfigInherit page.
-class StringParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor for StringParser
- ///
- /// @param param_name name of the configuration parameter being parsed
- StringParser(const std::string& param_name)
- : storage_(&string_defaults),
- param_name_(param_name) {
- // Empty parameter name is invalid.
- if (param_name_.empty()) {
- isc_throw(DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- }
-
- /// @brief parses parameter value
- ///
- /// Parses configuration parameter's value as string.
- ///
- /// @param value pointer to the content of parsed values
- /// @throws DhcpConfigError if the parsed parameter's name is empty.
- virtual void build(ConstElementPtr value) {
- if (param_name_.empty()) {
- isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
- << "empty parameter name provided");
- }
- value_ = value->str();
- boost::erase_all(value_, "\"");
- }
-
- /// @brief Stores the parsed value in a storage.
- virtual void commit() {
- if (storage_ != NULL && !param_name_.empty()) {
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
- }
- }
-
- /// @brief Factory that constructs StringParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new StringParser(param_name));
- }
-
- /// @brief Sets storage for value of this parameter.
- ///
- /// See @ref dhcpv6ConfigInherit for details.
- ///
- /// @param storage pointer to the storage container
- void setStorage(StringStorage* storage) {
- storage_ = storage;
- }
-
-private:
- /// Pointer to the storage, where parsed value will be stored
- StringStorage* storage_;
- /// Name of the parameter to be parsed
- std::string param_name_;
- /// The actual parsed value
- std::string value_;
-};
-
-/// @brief parser for interface list definition
-///
-/// This parser handles Dhcp6/interface entry.
-/// It contains a list of network interfaces that the server listens on.
-/// In particular, it can contain an entry called "all" or "any" that
-/// designates all interfaces.
-///
-/// It is useful for parsing Dhcp6/interface parameter.
-class InterfaceListConfigParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor
- ///
- /// As this is a dedicated parser, it must be used to parse
- /// "interface" parameter only. All other types will throw exception.
- ///
- /// @param param_name name of the configuration parameter being parsed
- /// @throw BadValue if supplied parameter name is not "interface"
- InterfaceListConfigParser(const std::string& param_name) {
- if (param_name != "interface") {
- isc_throw(isc::BadValue, "Internal error. Interface configuration "
- "parser called for the wrong parameter: " << param_name);
- }
- }
-
- /// @brief parses parameters value
- ///
- /// Parses configuration entry (list of parameters) and stores it in
- /// storage.
- ///
- /// @param value pointer to the content of parsed values
- virtual void build(ConstElementPtr value) {
- BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
- interfaces_.push_back(iface->str());
- }
- }
-
- /// @brief commits interfaces list configuration
- virtual void commit() {
- /// @todo: Implement per interface listening. Currently always listening
- /// on all interfaces.
- }
-
- /// @brief factory that constructs InterfaceListConfigParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new InterfaceListConfigParser(param_name));
- }
-
-private:
- /// contains list of network interfaces
- vector<string> interfaces_;
-};
-
-/// @brief parser for pool definition
-///
-/// This parser handles pool definitions, i.e. a list of entries of one
-/// of two syntaxes: min-max and prefix/len. Pool6 objects are created
-/// and stored in chosen PoolStorage container.
-///
-/// As there are no default values for pool, setStorage() must be called
-/// before build(). Otherwise an exception will be thrown.
-///
-/// It is useful for parsing Dhcp6/subnet6[X]/pool parameters.
-class PoolParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor.
- PoolParser(const std::string& /*param_name*/)
- : pools_(NULL) {
- // ignore parameter name, it is always Dhcp6/subnet6[X]/pool
- }
-
- /// @brief parses the actual list
- ///
- /// This method parses the actual list of interfaces.
- /// No validation is done at this stage, everything is interpreted as
- /// interface name.
- /// @param pools_list list of pools defined for a subnet
- /// @throw isc::InvalidOperation if storage was not specified
- /// (setStorage() not called)
- void build(ConstElementPtr pools_list) {
-
- // setStorage() should have been called before build
- if (!pools_) {
- isc_throw(isc::InvalidOperation, "parser logic error: no pool storage set,"
- " but pool parser asked to parse pools");
- }
-
- BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) {
-
- // That should be a single pool representation. It should contain
- // text in the form prefix/len or first - last. Note that spaces
- // are allowed
- string txt = text_pool->stringValue();
-
- // first let's remove any whitespaces
- boost::erase_all(txt, " "); // space
- boost::erase_all(txt, "\t"); // tabulation
-
- // Is this prefix/len notation?
- size_t pos = txt.find("/");
- if (pos != string::npos) {
- IOAddress addr("::");
- uint8_t len = 0;
- try {
- addr = IOAddress(txt.substr(0, pos));
-
- // start with the first character after /
- string prefix_len = txt.substr(pos + 1);
-
- // It is lexically cast to int and then downcast to uint8_t.
- // Direct cast to uint8_t (which is really an unsigned char)
- // will result in interpreting the first digit as output
- // value and throwing exception if length is written on two
- // digits (because there are extra characters left over).
-
- // No checks for values over 128. Range correctness will
- // be checked in Pool6 constructor.
- len = boost::lexical_cast<int>(prefix_len);
- } catch (...) {
- isc_throw(DhcpConfigError, "failed to parse pool "
- "definition: " << text_pool->stringValue());
- }
-
- Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, addr, len));
- local_pools_.push_back(pool);
- continue;
- }
-
- // Is this min-max notation?
- pos = txt.find("-");
- if (pos != string::npos) {
- // using min-max notation
- IOAddress min(txt.substr(0, pos));
- IOAddress max(txt.substr(pos + 1));
-
- Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, min, max));
-
- local_pools_.push_back(pool);
- continue;
- }
-
- isc_throw(DhcpConfigError, "failed to parse pool definition:"
- << text_pool->stringValue() <<
- ". Does not contain - (for min-max) nor / (prefix/len)");
- }
- }
-
- /// @brief sets storage for value of this parameter
- ///
- /// See @ref dhcpv6ConfigInherit for details.
- ///
- /// @param storage pointer to the storage container
- void setStorage(PoolStorage* storage) {
- pools_ = storage;
- }
-
- /// @brief Stores the parsed values in a storage provided
- /// by an upper level parser.
- virtual void commit() {
- if (pools_) {
- // local_pools_ holds the values produced by the build function.
- // At this point parsing should have completed successfuly so
- // we can append new data to the supplied storage.
- pools_->insert(pools_->end(), local_pools_.begin(),
- local_pools_.end());
- }
- }
-
- /// @brief factory that constructs PoolParser objects
- ///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new PoolParser(param_name));
- }
-
-private:
- /// @brief pointer to the actual Pools storage
- ///
- /// This is typically a storage somewhere in Subnet parser
- /// (an upper level parser).
- PoolStorage* pools_;
- /// A temporary storage for pools configuration. It is a
- /// storage where pools are stored by build function.
- PoolStorage local_pools_;
-};
-
-
-/// @brief Parser for option data value.
+/// @brief Parser for DHCP6 option data value.
///
/// This parser parses configuration entries that specify value of
-/// a single option. These entries include option name, option code
-/// and data carried by the option. The option data can be specified
-/// in one of the two available formats: binary value represented as
-/// a string of hexadecimal digits or a list of comma separated values.
-/// The format being used is controlled by csv-format configuration
-/// parameter. When setting this value to True, the latter format is
-/// used. The subsequent values in the CSV format apply to relevant
-/// option data fields in the configured option. For example the
-/// configuration: "data" : "192.168.2.0, 56, hello world" can be
-/// used to set values for the option comprising IPv4 address,
-/// integer and string data field. Note that order matters. If the
-/// order of values does not match the order of data fields within
-/// an option the configuration will not be accepted. If parsing
-/// is successful then an instance of an option is created and
-/// added to the storage provided by the calling class.
-class OptionDataParser : public DhcpConfigParser {
+/// a single option specific to DHCP6. It provides the DHCP6-specific
+/// implementation of the abstract class OptionDataParser.
+class Dhcp6OptionDataParser : public OptionDataParser {
public:
-
/// @brief Constructor.
///
- /// Class constructor.
- OptionDataParser(const std::string&)
- : options_(NULL),
- // initialize option to NULL ptr
- option_descriptor_(false) { }
-
- /// @brief Parses the single option data.
- ///
- /// This method parses the data of a single option from the configuration.
- /// The option data includes option name, option code and data being
- /// carried by this option. Eventually it creates the instance of the
- /// option.
- ///
- /// @warning setStorage must be called with valid storage pointer prior
- /// to calling this method.
- ///
- /// @param option_data_entries collection of entries that define value
- /// for a particular option.
- /// @throw DhcpConfigError if invalid parameter specified in
- /// the configuration.
- /// @throw isc::InvalidOperation if failed to set storage prior to
- /// calling build.
- virtual void build(ConstElementPtr option_data_entries) {
-
- if (options_ == NULL) {
- isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
- "parsing option data.");
- }
- BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
- ParserPtr parser;
- if (param.first == "name" || param.first == "data" ||
- param.first == "space") {
- boost::shared_ptr<StringParser>
- name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
- if (name_parser) {
- name_parser->setStorage(&string_values_);
- parser = name_parser;
- }
- } else if (param.first == "code") {
- boost::shared_ptr<Uint32Parser>
- code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(param.first)));
- if (code_parser) {
- code_parser->setStorage(&uint32_values_);
- parser = code_parser;
- }
- } else if (param.first == "csv-format") {
- boost::shared_ptr<BooleanParser>
- value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
- if (value_parser) {
- value_parser->setStorage(&boolean_values_);
- parser = value_parser;
- }
- } else {
- isc_throw(DhcpConfigError,
- "parser error: option-data parameter not supported: "
- << param.first);
- }
- parser->build(param.second);
- // Before we can create an option we need to get the data from
- // the child parsers. The only way to do it is to invoke commit
- // on them so as they store the values in appropriate storages
- // that this class provided to them. Note that this will not
- // modify values stored in the global storages so the configuration
- // will remain consistent even parsing fails somewhere further on.
- parser->commit();
- }
- // Try to create the option instance.
- createOption();
+ /// @param dummy first param, option names are always "Dhcp6/option-data[n]"
+ /// @param options is the option storage in which to store the parsed option
+ /// upon "commit".
+ /// @param global_context is a pointer to the global context which
+ /// stores global scope parameters, options, option defintions.
+ Dhcp6OptionDataParser(const std::string&, OptionStoragePtr options,
+ ParserContextPtr global_context)
+ :OptionDataParser("", options, global_context) {
}
- /// @brief Commits option value.
+ /// @brief static factory method for instantiating Dhcp4OptionDataParsers
///
- /// This function adds a new option to the storage or replaces an existing option
- /// with the same code.
- ///
- /// @throw isc::InvalidOperation if failed to set pointer to storage or failed
- /// to call build() prior to commit. If that happens data in the storage
- /// remain un-modified.
- virtual void commit() {
- if (options_ == NULL) {
- isc_throw(isc::InvalidOperation, "parser logic error: storage must be set before "
- "committing option data.");
- } else if (!option_descriptor_.option) {
- // Before we can commit the new option should be configured. If it is not
- // than somebody must have called commit() before build().
- isc_throw(isc::InvalidOperation, "parser logic error: no option has been configured and"
- " thus there is nothing to commit. Has build() been called?");
- }
- uint16_t opt_type = option_descriptor_.option->getType();
- Subnet::OptionContainerPtr options = options_->getItems(option_space_);
- // The getItems() should never return NULL pointer. If there are no
- // options configured for the particular option space a pointer
- // to an empty container should be returned.
- assert(options);
- Subnet::OptionContainerTypeIndex& idx = options->get<1>();
- // Try to find options with the particular option code in the main
- // storage. If found, remove these options because they will be
- // replaced with new one.
- Subnet::OptionContainerTypeRange range =
- idx.equal_range(opt_type);
- if (std::distance(range.first, range.second) > 0) {
- idx.erase(range.first, range.second);
- }
- // Append new option to the main storage.
- options_->addItem(option_descriptor_, option_space_);
- }
-
- /// @brief Set storage for the parser.
- ///
- /// Sets storage for the parser. This storage points to the
- /// vector of options and is used by multiple instances of
- /// OptionDataParser. Each instance creates exactly one object
- /// of dhcp::Option or derived type and appends it to this
- /// storage.
- ///
- /// @param storage pointer to the options storage
- void setStorage(OptionStorage* storage) {
- options_ = storage;
- }
-
-private:
-
- /// @brief Create option instance.
- ///
- /// Creates an instance of an option and adds it to the provided
- /// options storage. If the option data parsed by \ref build function
- /// are invalid or insufficient this function emits an exception.
- ///
- /// @warning this function does not check if options_ storage pointer
- /// is intitialized but this check is not needed here because it is done
- /// in the \ref build function.
- ///
- /// @throw DhcpConfigError if parameters provided in the configuration
- /// are invalid.
- void createOption() {
-
- // Option code is held in the uint32_t storage but is supposed to
- // be uint16_t value. We need to check that value in the configuration
- // does not exceed range of uint16_t and is not zero.
- uint32_t option_code = getParam<uint32_t>("code", uint32_values_);
- if (option_code == 0) {
- isc_throw(DhcpConfigError, "option code must not be zero."
- << " Option code '0' is reserved in DHCPv6.");
- } else if (option_code > std::numeric_limits<uint16_t>::max()) {
- isc_throw(DhcpConfigError, "invalid option code '" << option_code
- << "', it must not exceed '"
- << std::numeric_limits<uint16_t>::max() << "'");
- }
- // Check that the option name has been specified, is non-empty and does not
- // contain spaces.
- std::string option_name = getParam<std::string>("name", string_values_);
- if (option_name.empty()) {
- isc_throw(DhcpConfigError, "name of the option with code '"
- << option_code << "' is empty");
- } else if (option_name.find(" ") != std::string::npos) {
- isc_throw(DhcpConfigError, "invalid option name '" << option_name
- << "', space character is not allowed");
- }
-
- std::string option_space = getParam<std::string>("space", string_values_);
- if (!OptionSpace::validateName(option_space)) {
- isc_throw(DhcpConfigError, "invalid option space name '"
- << option_space << "' specified for option '"
- << option_name << "' (code '" << option_code
- << "')");
- }
-
+ /// @param param_name name of the parameter to be parsed.
+ /// @param options storage where the parameter value is to be stored.
+ /// @param global_context is a pointer to the global context which
+ /// stores global scope parameters, options, option defintions.
+ /// @return returns a pointer to a new OptionDataParser. Caller is
+ /// is responsible for deleting it when it is no longer needed.
+ static OptionDataParser* factory(const std::string& param_name,
+ OptionStoragePtr options, ParserContextPtr global_context) {
+ return (new Dhcp6OptionDataParser(param_name, options, global_context));
+ }
+
+
+protected:
+ /// @brief Finds an option definition within the server's option space
+ ///
+ /// Given an option space and an option code, find the correpsonding
+ /// option defintion within the server's option defintion storage.
+ ///
+ /// @param option_space name of the parameter option space
+ /// @param option_code numeric value of the parameter to find
+ /// @return OptionDefintionPtr of the option defintion or an
+ /// empty OptionDefinitionPtr if not found.
+ /// @throw DhcpConfigError if the option space requested is not valid
+ /// for this server.
+ virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
+ std::string& option_space, uint32_t option_code) {
OptionDefinitionPtr def;
if (option_space == "dhcp6" &&
LibDHCP::isStandardOption(Option::V6, option_code)) {
def = LibDHCP::getOptionDef(Option::V6, option_code);
-
} else if (option_space == "dhcp4") {
isc_throw(DhcpConfigError, "'dhcp4' option space name is reserved"
- << " for DHCPv4 server");
- } else {
- // If we are not dealing with a standard option then we
- // need to search for its definition among user-configured
- // options. They are expected to be in the global storage
- // already.
- OptionDefContainerPtr defs = option_def_intermediate.getItems(option_space);
- // The getItems() should never return the NULL pointer. If there are
- // no option definitions for the particular option space a pointer
- // to an empty container should be returned.
- assert(defs);
- const OptionDefContainerTypeIndex& idx = defs->get<1>();
- OptionDefContainerTypeRange range = idx.equal_range(option_code);
- if (std::distance(range.first, range.second) > 0) {
- def = *range.first;
- }
- if (!def) {
- isc_throw(DhcpConfigError, "definition for the option '"
- << option_space << "." << option_name
- << "' having code '" << option_code
- << "' does not exist");
- }
-
- }
-
- // Get option data from the configuration database ('data' field).
- const std::string option_data = getParam<std::string>("data", string_values_);
- const bool csv_format = getParam<bool>("csv-format", boolean_values_);
-
- // Transform string of hexadecimal digits into binary format.
- std::vector<uint8_t> binary;
- std::vector<std::string> data_tokens;
-
- if (csv_format) {
- // If the option data is specified as a string of comma
- // separated values then we need to split this string into
- // individual values - each value will be used to initialize
- // one data field of an option.
- data_tokens = isc::util::str::tokens(option_data, ",");
- } else {
- // Otherwise, the option data is specified as a string of
- // hexadecimal digits that we have to turn into binary format.
- try {
- util::encode::decodeHex(option_data, binary);
- } catch (...) {
- isc_throw(DhcpConfigError, "Parser error: option data is not a valid"
- << " string of hexadecimal digits: " << option_data);
- }
+ << " for DHCPv4 server");
}
- OptionPtr option;
- if (!def) {
- if (csv_format) {
- isc_throw(DhcpConfigError, "the CSV option data format can be"
- " used to specify values for an option that has a"
- " definition. The option with code " << option_code
- << " does not have a definition.");
- }
-
- // @todo We have a limited set of option definitions intiialized at the moment.
- // In the future we want to initialize option definitions for all options.
- // Consequently an error will be issued if an option definition does not exist
- // for a particular option code. For now it is ok to create generic option
- // if definition does not exist.
- OptionPtr option(new Option(Option::V6, static_cast<uint16_t>(option_code),
- binary));
- // The created option is stored in option_descriptor_ class member until the
- // commit stage when it is inserted into the main storage. If an option with the
- // same code exists in main storage already the old option is replaced.
- option_descriptor_.option = option;
- option_descriptor_.persistent = false;
- } else {
-
- // Option name should match the definition. The option name
- // may seem to be redundant but in the future we may want
- // to reference options and definitions using their names
- // and/or option codes so keeping the option name in the
- // definition of option value makes sense.
- if (def->getName() != option_name) {
- isc_throw(DhcpConfigError, "specified option name '"
- << option_name << "' does not match the "
- << "option definition: '" << option_space
- << "." << def->getName() << "'");
- }
-
- // Option definition has been found so let's use it to create
- // an instance of our option.
- try {
- OptionPtr option = csv_format ?
- def->optionFactory(Option::V6, option_code, data_tokens) :
- def->optionFactory(Option::V6, option_code, binary);
- Subnet::OptionDescriptor desc(option, false);
- option_descriptor_.option = option;
- option_descriptor_.persistent = false;
- } catch (const isc::Exception& ex) {
- isc_throw(DhcpConfigError, "option data does not match"
- << " option definition (space: " << option_space
- << ", code: " << option_code << "): "
- << ex.what());
- }
- }
- // All went good, so we can set the option space name.
- option_space_ = option_space;
+ return def;
}
-
- /// Storage for uint32 values (e.g. option code).
- Uint32Storage uint32_values_;
- /// Storage for string values (e.g. option name or data).
- StringStorage string_values_;
- /// Storage for boolean values.
- BooleanStorage boolean_values_;
- /// Pointer to options storage. This storage is provided by
- /// the calling class and is shared by all OptionDataParser objects.
- OptionStorage* options_;
- /// Option descriptor holds newly configured option.
- isc::dhcp::Subnet::OptionDescriptor option_descriptor_;
- /// Option space name where the option belongs to.
- std::string option_space_;
};
-/// @brief Parser for option data values within a subnet.
+/// @brief Parser for IPv4 pool definitions.
///
-/// This parser iterates over all entries that define options
-/// data for a particular subnet and creates a collection of options.
-/// If parsing is successful, all these options are added to the Subnet
-/// object.
-class OptionDataListParser : public DhcpConfigParser {
+/// This is the IPv6 derivation of the PoolParser class and handles pool
+/// definitions, i.e. a list of entries of one of two syntaxes: min-max and
+/// prefix/len for IPv6 pools. Pool6 objects are created and stored in chosen
+/// PoolStorage container.
+///
+/// It is useful for parsing Dhcp6/subnet6[X]/pool parameters.
+class Pool6Parser : public PoolParser {
public:
/// @brief Constructor.
///
- /// Unless otherwise specified, parsed options will be stored in
- /// a global option container (option_default). That storage location
- /// is overriden on a subnet basis.
- OptionDataListParser(const std::string&)
- : options_(&option_defaults), local_options_() { }
-
- /// @brief Parses entries that define options' data for a subnet.
- ///
- /// This method iterates over all entries that define option data
- /// for options within a single subnet and creates options' instances.
- ///
- /// @param option_data_list pointer to a list of options' data sets.
- /// @throw DhcpConfigError if option parsing failed.
- void build(ConstElementPtr option_data_list) {
- BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
- boost::shared_ptr<OptionDataParser> parser(new OptionDataParser("option-data"));
- // options_ member will hold instances of all options thus
- // each OptionDataParser takes it as a storage.
- parser->setStorage(&local_options_);
- // Build the instance of a single option.
- parser->build(option_value);
- // Store a parser as it will be used to commit.
- parsers_.push_back(parser);
- }
- }
-
- /// @brief Set storage for option instances.
- ///
- /// @param storage pointer to options storage.
- void setStorage(OptionStorage* storage) {
- options_ = storage;
- }
-
-
- /// @brief Commit all option values.
- ///
- /// This function invokes commit for all option values.
- void commit() {
- BOOST_FOREACH(ParserPtr parser, parsers_) {
- parser->commit();
- }
- // Parsing was successful and we have all configured
- // options in local storage. We can now replace old values
- // with new values.
- std::swap(local_options_, *options_);
- }
-
- /// @brief Create DhcpDataListParser object
- ///
- /// @param param_name param name.
- ///
- /// @return DhcpConfigParser object.
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new OptionDataListParser(param_name));
+ /// @param param_name name of the parameter. Note, it is passed through
+ /// but unused, parameter is currently always "Dhcp6/subnet6[X]/pool"
+ /// @param pools storage container in which to store the parsed pool
+ /// upon "commit"
+ Pool6Parser(const std::string& param_name, PoolStoragePtr pools)
+ :PoolParser(param_name, pools) {
+ }
+
+protected:
+ /// @brief Creates a Pool6 object given a IPv6 prefix and the prefix length.
+ ///
+ /// @param addr is the IPv6 prefix of the pool.
+ /// @param len is the prefix length.
+ /// @param ptype is the type of IPv6 pool (Pool6::Pool6Type). Note this is
+ /// passed in as an int32_t and cast to Pool6Type to accommodate a
+ /// polymorphic interface.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (IOAddress &addr, uint32_t len, int32_t ptype)
+ {
+ return (PoolPtr(new Pool6(static_cast<isc::dhcp::Pool6::Pool6Type>
+ (ptype), addr, len)));
+ }
+
+ /// @brief Creates a Pool6 object given starting and ending IPv6 addresses.
+ ///
+ /// @param min is the first IPv6 address in the pool.
+ /// @param max is the last IPv6 address in the pool.
+ /// @param ptype is the type of IPv6 pool (Pool6::Pool6Type). Note this is
+ /// passed in as an int32_t and cast to Pool6Type to accommodate a
+ /// polymorphic interface.
+ /// @return returns a PoolPtr to the new Pool4 object.
+ PoolPtr poolMaker (IOAddress &min, IOAddress &max, int32_t ptype)
+ {
+ return (PoolPtr(new Pool6(static_cast<isc::dhcp::Pool6::Pool6Type>
+ (ptype), min, max)));
}
-
- /// Pointer to options instances storage.
- OptionStorage* options_;
- /// Intermediate option storage. This storage is used by
- /// lower level parsers to add new options. Values held
- /// in this storage are assigned to main storage (options_)
- /// if overall parsing was successful.
- OptionStorage local_options_;
- /// Collection of parsers;
- ParserCollection parsers_;
};
-/// @brief Parser for a single option definition.
+/// @brief This class parses a single IPv6 subnet.
///
-/// This parser creates an instance of a single option definition.
-class OptionDefParser: DhcpConfigParser {
+/// This is the IPv6 derivation of the SubnetConfigParser class and it parses
+/// the whole subnet definition. It creates parsersfor received configuration
+/// parameters as needed.
+class Subnet6ConfigParser : public SubnetConfigParser {
public:
- /// @brief Constructor.
- ///
- /// This constructor sets the pointer to the option definitions
- /// storage to NULL. It must be set to point to the actual storage
- /// before \ref build is called.
- OptionDefParser(const std::string&)
- : storage_(NULL) {
- }
-
- /// @brief Parses an entry that describes single option definition.
- ///
- /// @param option_def a configuration entry to be parsed.
+ /// @brief Constructor
///
- /// @throw DhcpConfigError if parsing was unsuccessful.
- void build(ConstElementPtr option_def) {
- if (storage_ == NULL) {
- isc_throw(DhcpConfigError, "parser logic error: storage must be set"
- " before parsing option definition data");
- }
- // Parse the elements that make up the option definition.
- BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
- std::string entry(param.first);
- ParserPtr parser;
- if (entry == "name" || entry == "type" || entry == "record-types" ||
- entry == "space" || entry == "encapsulate") {
- StringParserPtr
- str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
- if (str_parser) {
- str_parser->setStorage(&string_values_);
- parser = str_parser;
- }
- } else if (entry == "code") {
- Uint32ParserPtr
- code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(entry)));
- if (code_parser) {
- code_parser->setStorage(&uint32_values_);
- parser = code_parser;
- }
- } else if (entry == "array") {
- BooleanParserPtr
- array_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(entry)));
- if (array_parser) {
- array_parser->setStorage(&boolean_values_);
- parser = array_parser;
- }
- } else {
- isc_throw(DhcpConfigError, "invalid parameter: " << entry);
- }
-
- parser->build(param.second);
- parser->commit();
- }
-
- // Create an instance of option definition.
- createOptionDef();
-
- // Get all items we collected so far for the particular option space.
- OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
- // Check if there are any items with option code the same as the
- // one specified for the definition we are now creating.
- const OptionDefContainerTypeIndex& idx = defs->get<1>();
- const OptionDefContainerTypeRange& range =
- idx.equal_range(option_definition_->getCode());
- // If there are any items with this option code already we need
- // to issue an error because we don't allow duplicates for
- // option definitions within an option space.
- if (std::distance(range.first, range.second) > 0) {
- isc_throw(DhcpConfigError, "duplicated option definition for"
- << " code '" << option_definition_->getCode() << "'");
- }
+ /// @param ignored first parameter
+ /// stores global scope parameters, options, option defintions.
+ Subnet6ConfigParser(const std::string&)
+ :SubnetConfigParser("", globalContext()) {
}
- /// @brief Stores the parsed option definition in the data store.
+ /// @brief Adds the created subnet to a server's configuration.
+ /// @throw throws Unexpected if dynamic cast fails.
void commit() {
- if (storage_ && option_definition_ &&
- OptionSpace::validateName(option_space_name_)) {
- storage_->addItem(option_definition_, option_space_name_);
- }
- }
-
- /// @brief Sets a pointer to the data store.
- ///
- /// The newly created instance of an option definition will be
- /// added to the data store given by the argument.
- ///
- /// @param storage pointer to the data store where the option definition
- /// will be added to.
- void setStorage(OptionDefStorage* storage) {
- storage_ = storage;
- }
-
-private:
-
- /// @brief Create option definition from the parsed parameters.
- void createOptionDef() {
- // Get the option space name and validate it.
- std::string space = getParam<std::string>("space", string_values_);
- if (!OptionSpace::validateName(space)) {
- isc_throw(DhcpConfigError, "invalid option space name '"
- << space << "'");
- }
-
- // Get other parameters that are needed to create the
- // option definition.
- std::string name = getParam<std::string>("name", string_values_);
- uint32_t code = getParam<uint32_t>("code", uint32_values_);
- std::string type = getParam<std::string>("type", string_values_);
- bool array_type = getParam<bool>("array", boolean_values_);
- std::string encapsulates = getParam<std::string>("encapsulate",
- string_values_);
-
- // Create option definition.
- OptionDefinitionPtr def;
- // We need to check if user has set encapsulated option space
- // name. If so, different constructor will be used.
- if (!encapsulates.empty()) {
- // Arrays can't be used together with sub-options.
- if (array_type) {
- isc_throw(DhcpConfigError, "option '" << space << "."
- << "name" << "', comprising an array of data"
- << " fields may not encapsulate any option space");
-
- } else if (encapsulates == space) {
- isc_throw(DhcpConfigError, "option must not encapsulate"
- << " an option space it belongs to: '"
- << space << "." << name << "' is set to"
- << " encapsulate '" << space << "'");
-
- } else {
- def.reset(new OptionDefinition(name, code, type,
- encapsulates.c_str()));
- }
-
- } else {
- def.reset(new OptionDefinition(name, code, type, array_type));
-
- }
-
- // The record-types field may carry a list of comma separated names
- // of data types that form a record.
- std::string record_types = getParam<std::string>("record-types",
- string_values_);
- // Split the list of record types into tokens.
- std::vector<std::string> record_tokens =
- isc::util::str::tokens(record_types, ",");
- // Iterate over each token and add a record type into
- // option definition.
- BOOST_FOREACH(std::string record_type, record_tokens) {
- try {
- boost::trim(record_type);
- if (!record_type.empty()) {
- def->addRecordField(record_type);
- }
- } catch (const Exception& ex) {
- isc_throw(DhcpConfigError, "invalid record type values"
- << " specified for the option definition: "
- << ex.what());
+ if (subnet_) {
+ Subnet6Ptr sub6ptr = boost::dynamic_pointer_cast<Subnet6>(subnet_);
+ if (!sub6ptr) {
+ // If we hit this, it is a programming error.
+ isc_throw(Unexpected,
+ "Invalid cast in Subnet4ConfigParser::commit");
}
+ isc::dhcp::CfgMgr::instance().addSubnet6(sub6ptr);
}
-
- // Check the option definition parameters are valid.
- try {
- def->validate();
- } catch (const isc::Exception& ex) {
- isc_throw(DhcpConfigError, "invalid option definition"
- << " parameters: " << ex.what());
- }
- // Option definition has been created successfully.
- option_space_name_ = space;
- option_definition_ = def;
}
- /// Instance of option definition being created by this parser.
- OptionDefinitionPtr option_definition_;
- /// Name of the space the option definition belongs to.
- std::string option_space_name_;
-
- /// Pointer to a storage where the option definition will be
- /// added when \ref commit is called.
- OptionDefStorage* storage_;
-
- /// Storage for boolean values.
- BooleanStorage boolean_values_;
- /// Storage for string values.
- StringStorage string_values_;
- /// Storage for uint32 values.
- Uint32Storage uint32_values_;
-};
-
-/// @brief Parser for a list of option definitions.
-///
-/// This parser iterates over all configuration entries that define
-/// option definitions and creates instances of these definitions.
-/// If the parsing is successful, the collection of created definitions
-/// is put into the provided storage.
-class OptionDefListParser : DhcpConfigParser {
-public:
+protected:
- /// @brief Constructor.
- ///
- /// This constructor initializes the pointer to option definitions
- /// storage to NULL value. This pointer has to be set to point to
- /// the actual storage before the \ref build function is called.
- OptionDefListParser(const std::string&) {
- }
-
- /// @brief Parse configuration entries.
+ /// @brief creates parsers for entries in subnet definition
///
- /// This function parses configuration entries and creates instances
- /// of option definitions.
+ /// @param config_id name of the entry
///
- /// @param option_def_list pointer to an element that holds entries
- /// that define option definitions.
- /// @throw DhcpConfigError if configuration parsing fails.
- void build(ConstElementPtr option_def_list) {
- // Clear existing items in the global storage.
- // We are going to replace all of them.
- option_def_intermediate.clearItems();
-
- if (!option_def_list) {
- isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
- << " option definitions is NULL");
- }
-
- BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
- boost::shared_ptr<OptionDefParser>
- parser(new OptionDefParser("single-option-def"));
- parser->setStorage(&option_def_intermediate);
- parser->build(option_def);
- parser->commit();
- }
- }
-
- /// @brief Stores option definitions in the CfgMgr.
- void commit() {
-
- CfgMgr& cfg_mgr = CfgMgr::instance();
-
- cfg_mgr.deleteOptionDefs();
-
- // We need to move option definitions from the temporary
- // storage to the global storage.
- std::list<std::string> space_names =
- option_def_intermediate.getOptionSpaceNames();
- BOOST_FOREACH(std::string space_name, space_names) {
-
- BOOST_FOREACH(OptionDefinitionPtr def,
- *option_def_intermediate.getItems(space_name)) {
- // All option definitions should be initialized to non-NULL
- // values. The validation is expected to be made by the
- // OptionDefParser when creating an option definition.
- assert(def);
- cfg_mgr.addOptionDef(def, space_name);
- }
+ /// @return parser object for specified entry name. Note the caller is
+ /// responsible for deleting the parser created.
+ /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
+ /// for unknown config element
+ DhcpConfigParser* createSubnetConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ if ((config_id.compare("preferred-lifetime") == 0) ||
+ (config_id.compare("valid-lifetime") == 0) ||
+ (config_id.compare("renew-timer") == 0) ||
+ (config_id.compare("rebind-timer") == 0)) {
+ parser = new Uint32Parser(config_id, uint32_values_);
+ } else if ((config_id.compare("subnet") == 0) ||
+ (config_id.compare("interface") == 0) ||
+ (config_id.compare("interface-id") == 0)) {
+ parser = new StringParser(config_id, string_values_);
+ } else if (config_id.compare("pool") == 0) {
+ parser = new Pool6Parser(config_id, pools_);
+ } else if (config_id.compare("option-data") == 0) {
+ parser = new OptionDataListParser(config_id, options_,
+ global_context_,
+ Dhcp6OptionDataParser::factory);
+ } else {
+ isc_throw(NotImplemented,
+ "parser error: Subnet6 parameter not supported: " << config_id);
}
- }
- /// @brief Create an OptionDefListParser object.
- ///
- /// @param param_name configuration entry holding option definitions.
- ///
- /// @return OptionDefListParser object.
- static DhcpConfigParser* factory(const std::string& param_name) {
- return (new OptionDefListParser(param_name));
+ return (parser);
}
-};
-
-/// @brief this class parses a single subnet
-///
-/// This class parses the whole subnet definition. It creates parsers
-/// for received configuration parameters as needed.
-class Subnet6ConfigParser : public DhcpConfigParser {
-public:
-
- /// @brief constructor
- Subnet6ConfigParser(const std::string& ) {
- // The parameter should always be "subnet", but we don't check
- // against that here in case some wants to reuse this parser somewhere.
- }
- /// @brief parses parameter value
- ///
- /// @param subnet pointer to the content of subnet definition
+ /// @brief Determines if the given option space name and code describe
+ /// a standard option for the DHCP6 server.
///
- /// @throw isc::DhcpConfigError if subnet configuration parsing failed.
- void build(ConstElementPtr subnet) {
-
- BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
- ParserPtr parser(createSubnet6ConfigParser(param.first));
- // The actual type of the parser is unknown here. We have to discover
- // the parser type here to invoke the corresponding setStorage function
- // on it. We discover parser type by trying to cast the parser to various
- // parser types and checking which one was successful. For this one
- // a setStorage and build methods are invoked.
-
- // Try uint32 type parser.
- if (!buildParser<Uint32Parser, Uint32Storage >(parser, uint32_values_,
- param.second) &&
- // Try string type parser.
- !buildParser<StringParser, StringStorage >(parser, string_values_,
- param.second) &&
- // Try pool parser.
- !buildParser<PoolParser, PoolStorage >(parser, pools_,
- param.second) &&
- // Try option data parser.
- !buildParser<OptionDataListParser, OptionStorage >(parser, options_,
- param.second)) {
- // Appropriate parsers are created in the createSubnet6ConfigParser
- // and they should be limited to those that we check here for. Thus,
- // if we fail to find a matching parser here it is a programming error.
- isc_throw(DhcpConfigError, "failed to find suitable parser");
- }
- }
-
- // In order to create new subnet we need to get the data out
- // of the child parsers first. The only way to do it is to
- // invoke commit on them because it will make them write
- // parsed data into storages we have supplied.
- // Note that triggering commits on child parsers does not
- // affect global data because we supplied pointers to storages
- // local to this object. Thus, even if this method fails
- // later on, the configuration remains consistent.
- BOOST_FOREACH(ParserPtr parser, parsers_) {
- parser->commit();
- }
-
- // Create a subnet.
- createSubnet();
+ /// @param option_space is the name of the option space to consider
+ /// @param code is the numeric option code to consider
+ /// @return returns true if the space and code are part of the server's
+ /// standard options.
+ bool isServerStdOption(std::string option_space, uint32_t code) {
+ return ((option_space.compare("dhcp6") == 0)
+ && LibDHCP::isStandardOption(Option::V6, code));
}
- /// @brief Adds the created subnet to a server's configuration.
- void commit() {
- if (subnet_) {
- isc::dhcp::CfgMgr::instance().addSubnet6(subnet_);
- }
+ /// @brief Returns the option definition for a given option code from
+ /// the DHCP6 server's standard set of options.
+ /// @param code is the numeric option code of the desired option definition.
+ /// @return returns a pointer the option definition
+ OptionDefinitionPtr getServerStdOptionDefinition (uint32_t code) {
+ return (LibDHCP::getOptionDef(Option::V6, code));
}
-private:
-
- /// @brief Set storage for a parser and invoke build.
- ///
- /// This helper method casts the provided parser pointer to the specified
- /// type. If the cast is successful it sets the corresponding storage for
- /// this parser, invokes build on it and saves the parser.
+ /// @brief Issues a DHCP6 server specific warning regarding duplicate subnet
+ /// options.
///
- /// @tparam T parser type to which parser argument should be cast.
- /// @tparam Y storage type for the specified parser type.
- /// @param parser parser on which build must be invoked.
- /// @param storage reference to a storage that will be set for a parser.
- /// @param subnet subnet element read from the configuration and being parsed.
- /// @return true if parser pointer was successfully cast to specialized
- /// parser type provided as Y.
- template<typename T, typename Y>
- bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
- // We need to cast to T in order to set storage for the parser.
- boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
- // It is common that this cast is not successful because we try to cast to all
- // supported parser types as we don't know the type of a parser in advance.
- if (cast_parser) {
- // Cast, successful so we go ahead with setting storage and actual parse.
- cast_parser->setStorage(&storage);
- parser->build(subnet);
- parsers_.push_back(parser);
- // We indicate that cast was successful so as the calling function
- // may skip attempts to cast to other parser types and proceed to
- // next element.
- return (true);
- }
- // It was not successful. Indicate that another parser type
- // should be tried.
- return (false);
+ /// @param code is the numeric option code of the duplicate option
+ /// @param addr is the subnet address
+ /// @todo A means to know the correct logger and perhaps a common
+ /// message would allow this message to be emitted by the base class.
+ virtual void duplicate_option_warning(uint32_t code,
+ isc::asiolink::IOAddress& addr) {
+ LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
+ .arg(code).arg(addr.toText());
}
- /// @brief Append sub-options to an option.
+ /// @brief Instantiates the IPv6 Subnet based on a given IPv6 address
+ /// and prefix length.
///
- /// @param option_space a name of the encapsulated option space.
- /// @param option option instance to append sub-options to.
- void appendSubOptions(const std::string& option_space, OptionPtr& option) {
- // Only non-NULL options are stored in option container.
- // If this option pointer is NULL this is a serious error.
- assert(option);
-
- OptionDefinitionPtr def;
- if (option_space == "dhcp6" &&
- LibDHCP::isStandardOption(Option::V6, option->getType())) {
- def = LibDHCP::getOptionDef(Option::V6, option->getType());
- // Definitions for some of the standard options hasn't been
- // implemented so it is ok to leave here.
- if (!def) {
- return;
- }
- } else {
- const OptionDefContainerPtr defs =
- option_def_intermediate.getItems(option_space);
- const OptionDefContainerTypeIndex& idx = defs->get<1>();
- const OptionDefContainerTypeRange& range =
- idx.equal_range(option->getType());
- // There is no definition so we have to leave.
- if (std::distance(range.first, range.second) == 0) {
- return;
- }
-
- def = *range.first;
-
- // If the definition exists, it must be non-NULL.
- // Otherwise it is a programming error.
- assert(def);
- }
-
- // We need to get option definition for the particular option space
- // and code. This definition holds the information whether our
- // option encapsulates any option space.
- // Get the encapsulated option space name.
- std::string encapsulated_space = def->getEncapsulatedSpace();
- // If option space name is empty it means that our option does not
- // encapsulate any option space (does not include sub-options).
- if (!encapsulated_space.empty()) {
- // Get the sub-options that belong to the encapsulated
- // option space.
- const Subnet::OptionContainerPtr sub_opts =
- option_defaults.getItems(encapsulated_space);
- // Append sub-options to the option.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, *sub_opts) {
- if (desc.option) {
- option->addOption(desc.option);
- }
- }
- }
- }
-
- /// @brief Create a new subnet using a data from child parsers.
- ///
- /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
- void createSubnet() {
-
- // Find a subnet string.
- StringStorage::const_iterator it = string_values_.find("subnet");
- if (it == string_values_.end()) {
- isc_throw(DhcpConfigError,
- "Mandatory subnet definition in subnet missing");
- }
- // Remove any spaces or tabs.
- string subnet_txt = it->second;
- boost::erase_all(subnet_txt, " ");
- boost::erase_all(subnet_txt, "\t");
- // The subnet format is prefix/len. We are going to extract
- // the prefix portion of a subnet string to create IOAddress
- // object from it. IOAddress will be passed to the Subnet's
- // constructor later on. In order to extract the prefix we
- // need to get all characters preceding "/".
- size_t pos = subnet_txt.find("/");
- if (pos == string::npos) {
- isc_throw(DhcpConfigError,
- "Invalid subnet syntax (prefix/len expected):" << it->second);
- }
-
- // Try to create the address object. It also validates that
- // the address syntax is ok.
- IOAddress addr(subnet_txt.substr(0, pos));
- uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
-
+ /// @param addr is IPv6 prefix of the subnet.
+ /// @param len is the prefix length
+ void initSubnet(isc::asiolink::IOAddress addr, uint8_t len) {
// Get all 'time' parameters using inheritance.
// If the subnet-specific value is defined then use it, else
// use the global value. The global value must always be
@@ -1485,187 +281,59 @@ private:
Triplet<uint32_t> pref = getParam("preferred-lifetime");
Triplet<uint32_t> valid = getParam("valid-lifetime");
- // Get interface name. If it is defined, then the subnet is available
- // directly over specified network interface.
+ // Get interface-id option content. For now we support string
+ // represenation only
+ std::string ifaceid;
+ try {
+ ifaceid = string_values_->getParam("interface-id");
+ } catch (const DhcpConfigError &) {
+ // interface-id is not mandatory
+ }
+
+ // Specifying both interface for locally reachable subnets and
+ // interface id for relays is mutually exclusive. Need to test for
+ // this condition.
+ if (!ifaceid.empty()) {
+ std::string iface;
+ try {
+ iface = string_values_->getParam("interface");
+ } catch (const DhcpConfigError &) {
+ // iface not mandatory
+ }
- string iface;
- StringStorage::const_iterator iface_iter = string_values_.find("interface");
- if (iface_iter != string_values_.end()) {
- iface = iface_iter->second;
+ if (!iface.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError,
+ "parser error: interface (defined for locally reachable "
+ "subnets) and interface-id (defined for subnets reachable"
+ " via relays) cannot be defined at the same time for "
+ "subnet " << addr.toText() << "/" << (int)len);
+ }
}
- /// @todo: Convert this to logger once the parser is working reliably
stringstream tmp;
- tmp << addr.toText() << "/" << (int)len
+ tmp << addr.toText() << "/" << static_cast<int>(len)
<< " with params t1=" << t1 << ", t2=" << t2 << ", pref="
<< pref << ", valid=" << valid;
LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(tmp.str());
// Create a new subnet.
- subnet_.reset(new Subnet6(addr, len, t1, t2, pref, valid));
-
- // Add pools to it.
- for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
- subnet_->addPool(*it);
- }
-
- // Configure interface, if defined
- if (!iface.empty()) {
- if (!IfaceMgr::instance().getIface(iface)) {
- isc_throw(DhcpConfigError, "Specified interface name " << iface
- << " for subnet " << subnet_->toText() << " is not present"
- << " in the system.");
- }
-
- subnet_->setIface(iface);
- }
-
- // We are going to move configured options to the Subnet object.
- // Configured options reside in the container where options
- // are grouped by space names. Thus we need to get all space names
- // and iterate over all options that belong to them.
- std::list<std::string> space_names = options_.getOptionSpaceNames();
- BOOST_FOREACH(std::string option_space, space_names) {
- // Get all options within a particular option space.
- BOOST_FOREACH(Subnet::OptionDescriptor desc,
- *options_.getItems(option_space)) {
- // The pointer should be non-NULL. The validation is expected
- // to be performed by the OptionDataParser before adding an
- // option descriptor to the container.
- assert(desc.option);
- // We want to check whether an option with the particular
- // option code has been already added. If so, we want
- // to issue a warning.
- Subnet::OptionDescriptor existing_desc =
- subnet_->getOptionDescriptor("option_space",
- desc.option->getType());
- if (existing_desc.option) {
- LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
- .arg(desc.option->getType()).arg(addr.toText());
- }
- // Add sub-options (if any).
- appendSubOptions(option_space, desc.option);
- // In any case, we add the option to the subnet.
- subnet_->addOption(desc.option, false, option_space);
- }
- }
+ Subnet6* subnet6 = new Subnet6(addr, len, t1, t2, pref, valid);
- // Check all global options and add them to the subnet object if
- // they have been configured in the global scope. If they have been
- // configured in the subnet scope we don't add global option because
- // the one configured in the subnet scope always takes precedence.
- space_names = option_defaults.getOptionSpaceNames();
- BOOST_FOREACH(std::string option_space, space_names) {
- // Get all global options for the particular option space.
- BOOST_FOREACH(Subnet::OptionDescriptor desc,
- *option_defaults.getItems(option_space)) {
- // The pointer should be non-NULL. The validation is expected
- // to be performed by the OptionDataParser before adding an
- // option descriptor to the container.
- assert(desc.option);
- // Check if the particular option has been already added.
- // This would mean that it has been configured in the
- // subnet scope. Since option values configured in the
- // subnet scope take precedence over globally configured
- // values we don't add option from the global storage
- // if there is one already.
- Subnet::OptionDescriptor existing_desc =
- subnet_->getOptionDescriptor(option_space, desc.option->getType());
- if (!existing_desc.option) {
- // Add sub-options (if any).
- appendSubOptions(option_space, desc.option);
-
- subnet_->addOption(desc.option, false, option_space);
- }
- }
+ // Configure interface-id for remote interfaces, if defined
+ if (!ifaceid.empty()) {
+ OptionBuffer tmp(ifaceid.begin(), ifaceid.end());
+ OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ subnet6->setInterfaceId(opt);
}
- }
- /// @brief creates parsers for entries in subnet definition
- ///
- /// @param config_id name od the entry
- ///
- /// @return parser object for specified entry name
- /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
- /// for unknown config element
- DhcpConfigParser* createSubnet6ConfigParser(const std::string& config_id) {
- FactoryMap factories;
-
- factories["preferred-lifetime"] = Uint32Parser::factory;
- factories["valid-lifetime"] = Uint32Parser::factory;
- factories["renew-timer"] = Uint32Parser::factory;
- factories["rebind-timer"] = Uint32Parser::factory;
- factories["subnet"] = StringParser::factory;
- factories["pool"] = PoolParser::factory;
- factories["option-data"] = OptionDataListParser::factory;
- factories["interface"] = StringParser::factory;
-
- FactoryMap::iterator f = factories.find(config_id);
- if (f == factories.end()) {
- // Used for debugging only.
- // return new DebugParser(config_id);
-
- isc_throw(isc::dhcp::DhcpConfigError,
- "parser error: subnet6 parameter not supported: "
- << config_id);
- }
- return (f->second(config_id));
+ subnet_.reset(subnet6);
}
- /// @brief Returns value for a given parameter (after using inheritance)
- ///
- /// This method implements inheritance. For a given parameter name, it first
- /// checks if there is a global value for it and overwrites it with specific
- /// value if such value was defined in subnet.
- ///
- /// @param name name of the parameter
- /// @return triplet with the parameter name
- /// @throw DhcpConfigError when requested parameter is not present
- isc::dhcp::Triplet<uint32_t> getParam(const std::string& name) {
- uint32_t value = 0;
- bool found = false;
- Uint32Storage::iterator global = uint32_defaults.find(name);
- if (global != uint32_defaults.end()) {
- value = global->second;
- found = true;
- }
-
- Uint32Storage::iterator local = uint32_values_.find(name);
- if (local != uint32_values_.end()) {
- value = local->second;
- found = true;
- }
-
- if (found) {
- return (isc::dhcp::Triplet<uint32_t>(value));
- } else {
- isc_throw(isc::dhcp::DhcpConfigError, "Mandatory parameter " << name
- << " missing (no global default and no subnet-"
- << "specific value)");
- }
- }
-
- /// storage for subnet-specific uint32 values
- Uint32Storage uint32_values_;
-
- /// storage for subnet-specific integer values
- StringStorage string_values_;
-
- /// storage for pools belonging to this subnet
- PoolStorage pools_;
-
- /// storage for options belonging to this subnet
- OptionStorage options_;
-
- /// parsers are stored here
- ParserCollection parsers_;
-
- /// Pointer to the created subnet object.
- isc::dhcp::Subnet6Ptr subnet_;
};
-/// @brief this class parses a list of subnets
+
+/// @brief this class parses a list of DHCP6 subnets
///
/// This is a wrapper parser that handles the whole list of Subnet6
/// definitions. It iterates over all entries and creates Subnet6ConfigParser
@@ -1675,8 +343,9 @@ public:
/// @brief constructor
///
+ /// @param dummy first argument, always ignored. All parsers accept a
+ /// string parameter "name" as their first argument.
Subnets6ListConfigParser(const std::string&) {
- /// parameter name is ignored
}
/// @brief parses contents of the list
@@ -1686,12 +355,7 @@ public:
///
/// @param subnets_list pointer to a list of IPv6 subnets
void build(ConstElementPtr subnets_list) {
-
- // No need to define FactoryMap here. There's only one type
- // used: Subnet6ConfigParser
-
BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
-
ParserPtr parser(new Subnet6ConfigParser("subnet"));
parser->build(subnet);
subnets_.push_back(parser);
@@ -1701,8 +365,8 @@ public:
/// @brief commits subnets definitions.
///
- /// Iterates over all Subnet6 parsers. Each parser contains definitions
- /// of a single subnet and its parameters and commits each subnet separately.
+ /// Iterates over all Subnet6 parsers. Each parser contains definitions of
+ /// a single subnet and its parameters and commits each subnet separately.
void commit() {
// @todo: Implement more subtle reconfiguration than toss
// the old one and replace with the new one.
@@ -1739,56 +403,74 @@ namespace dhcp {
///
/// @param config_id pointer to received global configuration entry
/// @return parser for specified global DHCPv6 parameter
-/// @throw NotImplemented if trying to create a parser for unknown config element
-DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
- FactoryMap factories;
-
- factories["preferred-lifetime"] = Uint32Parser::factory;
- factories["valid-lifetime"] = Uint32Parser::factory;
- factories["renew-timer"] = Uint32Parser::factory;
- factories["rebind-timer"] = Uint32Parser::factory;
- factories["interface"] = InterfaceListConfigParser::factory;
- factories["subnet6"] = Subnets6ListConfigParser::factory;
- factories["option-data"] = OptionDataListParser::factory;
- factories["option-def"] = OptionDefListParser::factory;
- factories["version"] = StringParser::factory;
- factories["lease-database"] = DbAccessParser::factory;
-
- FactoryMap::iterator f = factories.find(config_id);
- if (f == factories.end()) {
- // Used for debugging only.
- // return new DebugParser(config_id);
-
+/// @throw NotImplemented if trying to create a parser for unknown config
+/// element
+DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id) {
+ DhcpConfigParser* parser = NULL;
+ if ((config_id.compare("preferred-lifetime") == 0) ||
+ (config_id.compare("valid-lifetime") == 0) ||
+ (config_id.compare("renew-timer") == 0) ||
+ (config_id.compare("rebind-timer") == 0)) {
+ parser = new Uint32Parser(config_id,
+ globalContext()->uint32_values_);
+ } else if (config_id.compare("interfaces") == 0) {
+ parser = new InterfaceListConfigParser(config_id);
+ } else if (config_id.compare("subnet6") == 0) {
+ parser = new Subnets6ListConfigParser(config_id);
+ } else if (config_id.compare("option-data") == 0) {
+ parser = new OptionDataListParser(config_id,
+ globalContext()->options_,
+ globalContext(),
+ Dhcp6OptionDataParser::factory);
+ } else if (config_id.compare("option-def") == 0) {
+ parser = new OptionDefListParser(config_id,
+ globalContext()->option_defs_);
+ } else if (config_id.compare("version") == 0) {
+ parser = new StringParser(config_id,
+ globalContext()->string_values_);
+ } else if (config_id.compare("lease-database") == 0) {
+ parser = new DbAccessParser(config_id);
+ } else if (config_id.compare("hooks-libraries") == 0) {
+ parser = new HooksLibrariesParser(config_id);
+ } else {
isc_throw(NotImplemented,
- "Parser error: Global configuration parameter not supported: "
- << config_id);
+ "Parser error: Global configuration parameter not supported: "
+ << config_id);
}
- return (f->second(config_id));
+
+ return (parser);
}
-ConstElementPtr
-configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
+isc::data::ConstElementPtr
+configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
if (!config_set) {
ConstElementPtr answer = isc::config::createAnswer(1,
string("Can't parse NULL config"));
return (answer);
}
- /// @todo: append most essential info here (like "2 new subnets configured")
+ /// @todo: Append most essential info here (like "2 new subnets configured")
string config_details;
- LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_START).arg(config_set->str());
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND,
+ DHCP6_CONFIG_START).arg(config_set->str());
// Some of the values specified in the configuration depend on
- // other values. Typically, the values in the subnet4 structure
+ // other values. Typically, the values in the subnet6 structure
// depend on the global values. Also, option values configuration
// must be performed after the option definitions configurations.
// Thus we group parsers and will fire them in the right order:
- // all parsers other than subnet4 and option-data parser,
- // option-data parser, subnet4 parser.
+ // all parsers other than subnet6 and option-data parser,
+ // option-data parser, subnet6 parser.
ParserCollection independent_parsers;
ParserPtr subnet_parser;
ParserPtr option_parser;
+ ParserPtr iface_parser;
+
+ // Some of the parsers alter state of the system that can't easily
+ // be undone. (Or alter it in a way such that undoing the change
+ // has the same risk of failure as doing the change.)
+ ParserPtr hooks_parser;
// The subnet parsers implement data inheritance by directly
// accessing global storage. For this reason the global data
@@ -1797,10 +479,7 @@ configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
// parsing operation fails after the global storage has been
// modified. We need to preserve the original global data here
// so as we can rollback changes when an error occurs.
- Uint32Storage uint32_local(uint32_defaults);
- StringStorage string_local(string_defaults);
- OptionStorage option_local(option_defaults);
- OptionDefStorage option_def_local(option_def_intermediate);
+ ParserContext original_context(*globalContext());
// answer will hold the result.
ConstElementPtr answer;
@@ -1817,15 +496,25 @@ configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
const std::map<std::string, ConstElementPtr>& values_map =
config_set->mapValue();
BOOST_FOREACH(config_pair, values_map) {
- ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
+ ParserPtr parser(createGlobal6DhcpConfigParser(config_pair.first));
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PARSER_CREATED)
.arg(config_pair.first);
if (config_pair.first == "subnet6") {
subnet_parser = parser;
-
} else if (config_pair.first == "option-data") {
option_parser = parser;
-
+ } else if (config_pair.first == "hooks-libraries") {
+ // Executing the commit will alter currently loaded hooks
+ // libraries. Check if the supplied libraries are valid,
+ // but defer the commit until after everything else has
+ // committed.
+ hooks_parser = parser;
+ hooks_parser->build(config_pair.second);
+ } else if (config_pair.first == "interfaces") {
+ // The interface parser is independent from any other parser and
+ // can be run here before other parsers.
+ parser->build(config_pair.second);
+ iface_parser = parser;
} else {
// Those parsers should be started before other
// parsers so we can call build straight away.
@@ -1879,6 +568,17 @@ configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
if (subnet_parser) {
subnet_parser->commit();
}
+
+ if (iface_parser) {
+ iface_parser->commit();
+ }
+
+ // This occurs last as if it succeeds, there is no easy way to
+ // revert it. As a result, the failure to commit a subsequent
+ // change causes problems when trying to roll back.
+ if (hooks_parser) {
+ hooks_parser->commit();
+ }
}
catch (const isc::Exception& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_FAIL).arg(ex.what());
@@ -1898,10 +598,7 @@ configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
// Rollback changes as the configuration parsing failed.
if (rollback) {
- std::swap(uint32_defaults, uint32_local);
- std::swap(string_defaults, string_local);
- std::swap(option_defaults, option_local);
- std::swap(option_def_intermediate, option_def_local);
+ globalContext().reset(new ParserContext(original_context));
return (answer);
}
@@ -1912,5 +609,10 @@ configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
return (answer);
}
+ParserContextPtr& globalContext() {
+ static ParserContextPtr global_context_ptr(new ParserContext(Option::V6));
+ return (global_context_ptr);
+}
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/config_parser.h
index 6d7a807..bd571af 100644
--- a/src/bin/dhcp6/config_parser.h
+++ b/src/bin/dhcp6/config_parser.h
@@ -20,6 +20,8 @@
#include <cc/data.h>
#include <exceptions/exceptions.h>
+#include <dhcpsrv/dhcp_parsers.h>
+
#include <string>
namespace isc {
@@ -29,9 +31,9 @@ class Dhcpv6Srv;
/// @brief Configures DHCPv6 server
///
-/// This function is called every time a new configuration is received. The extra
-/// parameter is a reference to DHCPv6 server component. It is currently not used
-/// and CfgMgr::instance() is accessed instead.
+/// This function is called every time a new configuration is received. The
+/// extra parameter is a reference to DHCPv6 server component. It is currently
+/// not used and CfgMgr::instance() is accessed instead.
///
/// This method does not throw. It catches all exceptions and returns them as
/// reconfiguration statuses. It may return the following response codes:
@@ -47,6 +49,11 @@ class Dhcpv6Srv;
isc::data::ConstElementPtr
configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set);
+/// @brief Returns the global context
+///
+/// @returns a reference to the global context
+ParserContextPtr& globalContext();
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index e4e17f1..7168b91 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// 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,21 +20,26 @@
#include <config/ccsession.h>
#include <dhcp/iface_mgr.h>
#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/cfgmgr.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/spec_config.h>
#include <exceptions/exceptions.h>
+#include <hooks/hooks_manager.h>
#include <util/buffer.h>
#include <cassert>
#include <iostream>
+#include <string>
+#include <vector>
using namespace isc::asiolink;
using namespace isc::cc;
using namespace isc::config;
using namespace isc::data;
using namespace isc::dhcp;
+using namespace isc::hooks;
using namespace isc::log;
using namespace isc::util;
using namespace std;
@@ -100,7 +105,27 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
}
// Configure the server.
- return (configureDhcp6Server(*server_, merged_config));
+ ConstElementPtr answer = configureDhcp6Server(*server_, merged_config);
+
+ // Check that configuration was successful. If not, do not reopen sockets.
+ int rcode = 0;
+ parseAnswer(rcode, answer);
+ if (rcode != 0) {
+ return (answer);
+ }
+
+ // Configuration may change active interfaces. Therefore, we have to reopen
+ // sockets according to new configuration. This operation is not exception
+ // safe and we really don't want to emit exceptions to the callback caller.
+ // Instead, catch an exception and create appropriate answer.
+ try {
+ server_->openActiveSockets(server_->getPort());
+ } catch (const std::exception& ex) {
+ std::ostringstream err;
+ err << "failed to open sockets after server reconfiguration: " << ex.what();
+ answer = isc::config::createAnswer(1, err.str());
+ }
+ return (answer);
}
ConstElementPtr
@@ -120,6 +145,21 @@ ControlledDhcpv6Srv::dhcp6CommandHandler(const string& command, ConstElementPtr
ConstElementPtr answer = isc::config::createAnswer(0,
"Shutting down.");
return (answer);
+
+ } else if (command == "libreload") {
+ // TODO delete any stored CalloutHandles referring to the old libraries
+ // Get list of currently loaded libraries and reload them.
+ vector<string> loaded = HooksManager::getLibraryNames();
+ bool status = HooksManager::loadLibraries(loaded);
+ if (!status) {
+ LOG_ERROR(dhcp6_logger, DHCP6_HOOKS_LIBS_RELOAD_FAIL);
+ ConstElementPtr answer = isc::config::createAnswer(1,
+ "Failed to reload hooks libraries.");
+ return (answer);
+ }
+ ConstElementPtr answer = isc::config::createAnswer(0,
+ "Hooks libraries successfully reloaded.");
+ return (answer);
}
ConstElementPtr answer = isc::config::createAnswer(1,
@@ -155,7 +195,7 @@ void ControlledDhcpv6Srv::establishSession() {
// Dumy configuration handler is internally invoked by the
// constructor and on success the constructor updates
// the current session with the configuration that had been
- // commited in the previous session. If we did not install
+ // committed in the previous session. If we did not install
// the dummy handler, the previous configuration would have
// been lost.
config_session_ = new ModuleCCSession(specfile, *cc_session_,
@@ -172,8 +212,13 @@ void ControlledDhcpv6Srv::establishSession() {
try {
// Pull the full configuration out from the session.
configureDhcp6Server(*this, config_session_->getFullConfig());
+ // Configuration may disable or enable interfaces so we have to
+ // reopen sockets according to new configuration.
+ openActiveSockets(getPort());
+
} catch (const DhcpConfigError& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
+
}
/// Integrate the asynchronous I/O model of BIND 10 configuration
@@ -228,6 +273,5 @@ ControlledDhcpv6Srv::execDhcpv6ServerCommand(const std::string& command_id,
}
}
-
};
};
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h
index 0e699ce..ffd43c3 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.h
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.h
@@ -95,7 +95,7 @@ protected:
/// @brief A dummy configuration handler that always returns success.
///
/// This configuration handler does not perform configuration
- /// parsing and always returns success. A dummy hanlder should
+ /// parsing and always returns success. A dummy handler should
/// be installed using \ref isc::config::ModuleCCSession ctor
/// to get the initial configuration. This initial configuration
/// comprises values for only those elements that were modified
@@ -134,7 +134,7 @@ protected:
/// @brief Helper session object that represents raw connection to msgq.
isc::cc::Session* cc_session_;
- /// @brief Session that receives configuation and commands
+ /// @brief Session that receives configuration and commands
isc::config::ModuleCCSession* config_session_;
};
diff --git a/src/bin/dhcp6/dhcp6.dox b/src/bin/dhcp6/dhcp6.dox
index 3a738ec..4cdea53 100644
--- a/src/bin/dhcp6/dhcp6.dox
+++ b/src/bin/dhcp6/dhcp6.dox
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -51,7 +51,7 @@
list of parsers for each received entry. Parser is an object that is derived
from a DhcpConfigParser class. Once a parser is created
(constructor), its value is set (using build() method). Once all parsers are
- build, the configuration is then applied ("commited") and commit() method is
+ build, the configuration is then applied ("committed") and commit() method is
called.
All parsers are defined in src/bin/dhcp6/config_parser.cc file. Some of them
@@ -90,6 +90,85 @@
simple as possible. In fact, currently the code has to call Subnet6->getT1() and
do not implement any fancy inheritance logic.
+ at section dhcpv6DDNSIntegration DHCPv6 Server Support for the Dynamic DNS Updates
+
+The DHCPv6 server supports processing of the DHCPv6 Client FQDN Option described in
+the RFC4704. This Option is sent by the DHCPv6 client to instruct the server to
+update the DNS mappings for the acquired lease. A client may send its fully
+qualified domain name, a partial name or it may choose that server will generate
+the name. In the last case, the client sends an empty domain-name field in the
+DHCPv6 Client FQDN Option.
+
+As described in RFC4704, client may choose that the server delegates the forward
+DNS update to the client and that the server performs the reverse update only. Current
+version of the DHCPv6 server does not support delegation of the forward update
+to the client. The implementation of this feature is planned for the future releases.
+
+The b10-dhcp-ddns process is responsible for the actual communication with the DNS
+server, i.e. to send DNS Update messages. The b10-dhcp6 module is responsible
+for generating so called @ref isc::dhcp_ddns::NameChangeRequest and sending it to the
+b10-dhcp-ddns module. The @ref isc::dhcp_ddns::NameChangeRequest object represents changes to the
+DNS bindings, related to acquisition, renewal or release of the lease. The bind10-dhcp6
+module implements the simple FIFO queue of the NameChangeRequest objects. The module
+logic, which processes the incoming DHCPv6 Client FQDN Options puts these requests
+into the FIFO queue.
+
+ at todo Currently the FIFO queue is not processed after the NameChangeRequests are
+generated and added to it. In the future implementation steps it is planned to create
+a code which will check if there are any outstanding requests in the queue and
+send them to the bind10-dhcp-ddns module when server is idle waiting for DHCP messages.
+
+In the simplest case, when client gets one address from the server, a DHCPv6 server
+may generate 0, 1 or 2 NameChangeRequests during single message processing.
+Server generates no NameChangeRequests if it is not configured to update DNS
+ or it rejects the DNS update for any other reason.
+
+Server may generate 1 NameChangeRequests in a situation when a client acquires a
+new lease or it releases an existing lease. In the former case, the NameChangeRequest
+type is CHG_ADD, which indicates that the bind10-dhcp-ddns module should add a new DNS
+binding for the client, and it is assumed that there is no DNS binding for this
+client already. In the latter case, the NameChangeRequest type is CHG_REMOVE to
+indicate to the bind10-dhcp-ddns module that the existing DNS binding should be removed
+from the DNS. The binding consists of the forward and reverse mapping.
+A server may only remove the mapping which it had added. Therefore, the lease database
+holds an information which updates (no update, reverse only update, forward only update,
+both reverse and forward update) have been performed when the lease was acquired.
+Server checks this information to make a decision which mapping it is supposed to
+remove when a lease is released.
+
+Server may generate 2 NameChangeRequests in case the client is renewing a lease and
+it already has a DNS binding for that lease. Note, that renewal may be triggered
+as a result of sending a RENEW message as well as the REQUEST message. In both cases
+DHCPv6 server will check if there is an existing lease for the client which has sent
+a message, and if there is it will check in the lease database if the DNS Updates had
+been performed for this client. If the notion of client's FQDN changes comparing to
+the information stored in the lease database, the DHCPv6 has to remove an existing
+binding from the DNS and then add a new binding according to the new FQDN information
+received from the client. If the FQDN sent in the message which triggered a renewal
+doesn't change (comparing to the information in the lease database) the NameChangeRequest
+is not generated.
+
+In the more complex scenarios, when server sends multiple IA_NA options, each holding
+multiple IAADDR options, server will generate more NameChangeRequests for a single
+message being processed. That is 0, 1, 2 for the individual IA_NA. Generation of
+the distinct NameChangeRequests for each IADDR is not supported yet.
+
+The DHCPv6 Client FQDN Option comprises "NOS" flags which communicate to the
+server what updates (if any), client expects the server to perform. Server
+may be configured to obey client's preference or do FQDN processing in a
+different way. If the server overrides client's preference it will communicate it
+by sending the DHCPv6 Client FQDN Option in its responses to a client, with
+appropriate flags set.
+
+ at todo Note, that current implementation doesn't allow configuration of the server's behaviour
+with respect to DNS Updates. This is planned for the future. The default behaviour is
+constituted by the set of constants defined in the (upper part of) dhcp6_srv.cc file.
+Once the configuration is implemented, these constants will be removed.
+
@todo Add section about setting up options and their definitions with bindctl.
+ @section dhcpv6Other Other DHCPv6 topics
+
+ For hooks API support in DHCPv6, see @ref dhcpv6Hooks.
+
*/
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index 1129aec..634b046 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -3,16 +3,30 @@
"module_name": "Dhcp6",
"module_description": "DHCPv6 server daemon",
"config_data": [
- { "item_name": "interface",
+ {
+ "item_name": "hooks-libraries",
+ "item_type": "list",
+ "item_optional": true,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "hooks-library",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }
+ },
+
+ { "item_name": "interfaces",
"item_type": "list",
"item_optional": false,
- "item_default": [ "all" ],
+ "item_default": [ "*" ],
"list_item_spec":
{
"item_name": "interface_name",
"item_type": "string",
"item_optional": false,
- "item_default": "all"
+ "item_default": "*"
}
} ,
@@ -199,6 +213,12 @@
"item_default": ""
},
+ { "item_name": "interface-id",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
{ "item_name": "renew-timer",
"item_type": "integer",
"item_optional": false,
@@ -289,6 +309,12 @@
"item_optional": true
}
]
+ },
+
+ {
+ "command_name": "libreload",
+ "command_description": "Reloads the current hooks libraries.",
+ "command_args": []
}
]
}
diff --git a/src/bin/dhcp6/dhcp6_hooks.dox b/src/bin/dhcp6/dhcp6_hooks.dox
new file mode 100644
index 0000000..b21ad74
--- /dev/null
+++ b/src/bin/dhcp6/dhcp6_hooks.dox
@@ -0,0 +1,239 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/**
+ @page dhcpv6Hooks The Hooks API for the DHCPv6 Server
+
+ @section dhcpv6HooksIntroduction Introduction
+ BIND10 features an API (the "Hooks" API) that allows user-written code to
+ be integrated into BIND 10 and called at specific points in its processing.
+ An overview of the API and a tutorial for writing such code can be found in
+ the @ref hooksdgDevelopersGuide. Information for BIND 10 maintainers can be
+ found in the @ref hooksComponentDeveloperGuide.
+
+ This manual is more specialised and is aimed at developers of hook
+ code for the DHCPv6 server. It describes each hook point, what the callouts
+ attached to the hook are able to do, and the arguments passed to the
+ callouts. Each entry in this manual has the following information:
+
+ - Name of the hook point.
+ - Arguments for the callout. As well as the argument name and data type, the
+ information includes the direction, which can be one of:
+ - @b in - the server passes values to the callout but ignored any data
+ returned.
+ - @b out - the callout is expected to set this value.
+ - <b>in/out</b> - the server passes a value to the callout and uses whatever
+ value the callout sends back. Note that the callout may choose not to
+ do any modification, in which case the server will use whatever value
+ it sent to the callout.
+ - Description of the hook. This explains where in the processing the hook
+ is located, the possible actions a callout attached to this hook could take,
+ and a description of the data passed to the callouts.
+ - Skip flag action: the action taken by the server if a callout chooses to set
+ the "skip" flag.
+
+ at section dhcpv6HooksHookPoints Hooks in the DHCPv6 Server
+
+The following list is ordered by appearance of specific hook points during
+packet processing. Hook points that are not specific to packet processing
+(e.g. lease expiration) will be added to the end of this list.
+
+ @subsection dhcpv6HooksBuffer6Receive buffer6_receive
+
+ - @b Arguments:
+ - name: @b query6, type: isc::dhcp::Pkt6Ptr, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when an incoming DHCPv6
+ packet is received and the data stored in a buffer. The sole argument -
+ query6 - contains a pointer to an isc::dhcp::Pkt6 object that contains
+ the received information stored in the data_ field. Basic information
+ like protocol, source/destination addresses and ports are set, but
+ the contents of the buffer have not yet been parsed. That means that
+ the options_ field (that will eventually contain a list of objects
+ representing the received options) is empty, so none of the methods
+ that operate on it (e.g., getOption()) will work. The primary purpose
+ of this early call is to offer the ability to modify incoming packets
+ in their raw form. Unless you need to access to the raw data, it is
+ usually better to install your callout on the pkt6_receive hook point.
+
+ - <b>Skip flag action</b>: If any callout sets the skip flag, the
+ server will assume that the callout parsed the buffer and added then
+ necessary option objects to the options_ field; the server will not
+ do any parsing. If the callout sets the skip flag but does not parse
+ the buffer, the server will most probably drop the packet due to
+ the absence of mandatory options. If you want to drop the packet,
+ see the description of the skip flag in the pkt6_receive hook point.
+
+ @subsection dhcpv6HooksPkt6Receive pkt6_receive
+
+ - @b Arguments:
+ - name: @b query6, type: isc::dhcp::Pkt6Ptr, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when an incoming DHCPv6
+ packet is received and its content is parsed. The sole argument -
+ query6 - contains a pointer to an isc::dhcp::Pkt6 object that contains
+ all information regarding incoming packet, including its source and
+ destination addresses, the interface over which it was received, a list
+ of all options present within and relay information. All fields of
+ the Pkt6 object can be modified at this time, except data_. (data_
+ contains the incoming packet as raw buffer. By the time this hook is
+ reached, that information has already been parsed and is available though
+ other fields in the Pkt6 object. For this reason, it doesn't make
+ sense to modify it.)
+
+ - <b>Skip flag action</b>: If any callout sets the skip flag, the server will
+ drop the packet and start processing the next one. The reason for the drop
+ will be logged if logging is set to the appropriate debug level.
+
+ at subsection dhcpv6HooksSubnet6Select subnet6_select
+
+ - @b Arguments:
+ - name: @b query6, type: isc::dhcp::Pkt6Ptr, direction: <b>in/out</b>
+ - name: @b subnet6, type: isc::dhcp::Subnet6Ptr, direction: <b>in/out</b>
+ - name: @b subnet6collection, type: const isc::dhcp::Subnet6Collection *, direction: <b>in</b>
+
+ - @b Description: This callout is executed when a subnet is being
+ selected for the incoming packet. All parameters, addresses and
+ prefixes will be assigned from that subnet. A callout can select a
+ different subnet if it wishes so, the list of all subnets currently
+ configured being provided as 'subnet6collection'. The list itself must
+ not be modified.
+
+ - <b>Skip flag action</b>: If any callout installed on 'subnet6_select'
+ sets the skip flag, the server will not select any subnet. Packet processing
+ will continue, but will be severely limited (i.e. only global options
+ will be assigned).
+
+ at subsection dhcpv6HooksLease6Select lease6_select
+
+ - @b Arguments:
+ - name: @b subnet6, type: isc::dhcp::Subnet6Ptr, direction: <b>in</b>
+ - name: @b fake_allocation, type: bool, direction: <b>in</b>
+ - name: @b lease6, type: isc::dhcp::Lease6Ptr, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed after the server engine
+ has selected a lease for client's request but before the lease
+ has been inserted into the database. Any modifications made to the
+ isc::dhcp::Lease6 object will be stored in the lease's record in the
+ database. The callout should make sure that any modifications are
+ sanity checked as the server will use that data as is with no further
+ checking.\n\n The server processes lease requests for SOLICIT and
+ REQUEST in a very similar way. The only major difference is that
+ for SOLICIT the lease is just selected; it is not inserted into
+ the database. It is possible to distinguish between SOLICIT and
+ REQUEST by checking value of the fake_allocation flag: a value of true
+ means that the lease won't be inserted into the database (SOLICIT),
+ a value of false means that it will (REQUEST).
+
+ - <b>Skip flag action</b>: If any callout installed on 'lease6_select'
+ sets the skip flag, the server will not assign that particular lease.
+ Packet processing will continue and the client may get other addresses
+ or prefixes if it requested more than one address and/or prefix.
+
+ at subsection dhcpv6HooksLease6Renew lease6_renew
+
+ - @b Arguments:
+ - name: @b query6, type: isc::dhcp::PktPtr, direction: <b>in</b>
+ - name: @b lease6, type: isc::dhcp::Lease6Ptr, direction: <b>in/out</b>
+ - name: @b ia_na, type: boost::shared_ptr<Option6IA>, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when the server engine is
+ about to renew an existing lease. The client's request is provided as
+ the query6 argument and the existing lease with the appropriate fields
+ already modified is given in the lease6 argument. The final argument,
+ ia_na, is the IA_NA option that will be sent back to the client.
+ Callouts installed on the lease6_renew may modify the content of
+ the lease6 object. Care should be taken however, as that modified
+ information will be written to the database without any further
+ checking. \n\n Although the envisaged usage assumes modification of T1,
+ T2, preferred and valid lifetimes only, other parameters associated
+ with the lease may be modified as well. The only exception is the addr_
+ field, which must not be modified as it is used by the database to
+ select the existing lease to be updated. Care should also be taken to
+ modify the ia_na argument to match any changes in the lease6 argument.
+ If a client sends more than one IA_NA option, callouts will be called
+ separately for each IA_NA instance. The callout will be called only
+ when the update is valid, i.e. conditions such as an invalid addresses
+ or invalid iaid renewal attempts will not trigger this hook point.
+
+ - <b>Skip flag action</b>: If any callout installed on 'lease6_renew'
+ sets the skip flag, the server will not renew the lease. Under these
+ circumstances, the callout should modify the ia_na argument to reflect
+ this fact; otherwise the client will think the lease was renewed and continue
+ to operate under this assumption.
+
+ at subsection dhcpv6HooksLease6Release lease6_release
+
+ - @b Arguments:
+ - name: @b query6, type: isc::dhcp::PktPtr, direction: <b>in</b>
+ - name: @b lease6, type: isc::dhcp::Lease6Ptr, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when the server engine is
+ about to release an existing lease. The client's request is provided
+ as the query6 argument and the existing lease is given in the lease6
+ argument. Although the lease6 structure may be modified, it doesn't
+ make sense to do so as it will be destroyed immediately the callouts
+ finish execution.
+
+ - <b>Skip flag action</b>: If any callout installed on 'lease6_release'
+ sets the skip flag, the server will not delete the lease, which will
+ remain in the database until it expires. However, the server will send out
+ the response back to the client as if it did.
+
+ at subsection dhcpv6HooksPkt6Send pkt6_send
+
+ - @b Arguments:
+ - name: @b response6, type: isc::dhcp::Pkt6Ptr, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when server's response
+ is about to be send back to the client. The sole argument - response6 -
+ contains a pointer to an isc::dhcp::Pkt6 object that contains the
+ packet, with set source and destination addresses, interface over which
+ it will be send, list of all options and relay information. All fields
+ of the Pkt6 object can be modified at this time. It should be noted that
+ unless the callout sets the skip flag (see below), anything placed in the
+ bufferOut_ field will be overwritten when the callout returns.
+ (bufferOut_ is scratch space used for constructing the packet.)
+
+ - <b>Skip flag action</b>: If any callout sets the skip flag, the server
+ will assume that the callout did pack the transaction-id, message type and
+ option objects into the bufferOut_ field and will skip packing part.
+ Note that if the callout sets skip flag, but did not prepare the
+ output buffer, the server will send a zero sized message that will be
+ ignored by the client. If you want to drop the packet, please see
+ skip flag in the buffer6_send hook point.
+
+ at subsection dhcpv6HooksBuffer6Send buffer6_send
+
+ - @b Arguments:
+ - name: @b response6, type: isc::dhcp::Pkt6Ptr, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when server's response is
+ assembled into binary form and is about to be send back to the
+ client. The sole argument - response6 - contains a pointer to an
+ isc::dhcp::Pkt6 object that contains the packet, with set source and
+ destination addresses, interface over which it will be sent, list of
+ all options and relay information. All options are already encoded
+ in bufferOut_ field. It doesn't make sense to modify anything but the
+ contents of bufferOut_ at this time (although if it is a requirement
+ to modify that data, it will probably be found easier to modify the
+ option objects in a callout attached to the pkt6_send hook).
+
+ - <b>Skip flag action</b>: If any callout sets the skip flag, the server
+ will drop this response packet. However, the original request packet
+ from a client has been processed, so server's state has most likely changed
+ (e.g. lease was allocated). Setting this flag merely stops the change
+ being communicated to the client.
+
+*/
diff --git a/src/bin/dhcp6/dhcp6_log.h b/src/bin/dhcp6/dhcp6_log.h
index 23202da..244151c 100644
--- a/src/bin/dhcp6/dhcp6_log.h
+++ b/src/bin/dhcp6/dhcp6_log.h
@@ -38,6 +38,9 @@ const int DBG_DHCP6_COMMAND = DBGLVL_COMMAND;
// Trace basic operations within the code.
const int DBG_DHCP6_BASIC = DBGLVL_TRACE_BASIC;
+// Trace hook related operations
+const int DBG_DHCP6_HOOKS = DBGLVL_TRACE_BASIC;
+
// Trace detailed operations, including errors raised when processing invalid
// packets. (These are not logged at severities of WARN or higher for fear
// that a set of deliberately invalid packets set to the server could overwhelm
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index 6b61473..f851a7b 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -14,6 +14,11 @@
$NAMESPACE isc::dhcp
+% DHCP6_ACTIVATE_INTERFACE activating interface %1
+This message is printed when DHCPv6 server enabled an interface to be used
+to receive DHCPv6 traffic. IPv6 socket on this interface will be opened once
+Interface Manager starts up procedure of opening sockets.
+
% DHCP6_CCSESSION_STARTED control channel session started on socket %1
A debug message issued during startup after the IPv6 DHCP server has
successfully established a session with the BIND 10 control channel.
@@ -65,6 +70,102 @@ This informational message is printed every time the IPv6 DHCP server
is started. It indicates what database backend type is being to store
lease and other information.
+% DHCP6_DDNS_CREATE_ADD_NAME_CHANGE_REQUEST created name change request: %1
+This debug message is logged when the new Name Change Request has been created
+to perform the DNS Update, which adds new RRs.
+
+% DHCP6_DDNS_CREATE_REMOVE_NAME_CHANGE_REQUEST created name change request: %1
+This debug message is logged when the new Name Change Request has been created
+to perform the DNS Update, which removes RRs from the DNS.
+
+% DHCP6_DDNS_LEASE_ASSIGN_FQDN_CHANGE FQDN for the allocated lease: %1 has changed. New values: hostname = %2, reverse mapping = %3, forward mapping = %4
+This debug message is logged when FQDN mapping for a particular lease has
+been changed by the recent Request message. This mapping will be changed in DNS.
+
+% DHCP6_DDNS_LEASE_RENEW_FQDN_CHANGE FQDN for the renewed lease: %1 has changed.
+New values: hostname = %2, reverse mapping = %3, forward mapping = %4
+This debug message is logged when FQDN mapping for a particular lease has been
+changed by the recent Renew message. This mapping will be changed in DNS.
+
+% DHCP6_DEACTIVATE_INTERFACE deactivate interface %1
+This message is printed when DHCPv6 server disables an interface from being
+used to receive DHCPv6 traffic. Sockets on this interface will not be opened
+by the Interface Manager until interface is enabled.
+
+% DHCP6_DDNS_SEND_FQDN sending DHCPv6 Client FQDN Option to the client: %1
+This debug message is logged when server includes an DHCPv6 Client FQDN Option
+in its response to the client.
+
+% DHCP6_DDNS_RECEIVE_FQDN received DHCPv6 Client FQDN Option: %1
+This debug message is logged when server has found the DHCPv6 Client FQDN Option
+sent by a client and started processing it.
+
+% DHCP6_DDNS_REMOVE_EMPTY_HOSTNAME FQDN for the lease being deleted is empty: %1
+This error message is issued when a lease being deleted contains an indication
+that the DNS Update has been performed for it, but the FQDN is missing for this
+lease. This is an indication that someone may have messed up in the lease
+database.
+
+% DHCP6_DDNS_REMOVE_INVALID_HOSTNAME FQDN for the lease being deleted has invalid format: %1
+This error message is issued when a lease being deleted contains an indication
+that the DNS Update has been performed for it, but the FQDN held in the lease
+database has invalid format and can't be transformed to the canonical on-wire
+format.
+
+% DHCP6_HOOK_BUFFER_RCVD_SKIP received DHCPv6 buffer was dropped because a callout set the skip flag.
+This debug message is printed when a callout installed on buffer6_receive
+hook point set the skip flag. For this particular hook point, the
+setting of the flag by a callout instructs the server to drop the packet.
+
+% DHCP6_HOOK_BUFFER_SEND_SKIP prepared DHCPv6 response was dropped because a callout set the skip flag.
+This debug message is printed when a callout installed on buffer6_send
+hook point set the skip flag. For this particular hook point, the
+setting of the flag by a callout instructs the server to drop the packet.
+Server completed all the processing (e.g. may have assigned, updated
+or released leases), but the response will not be send to the client.
+
+% DHCP6_HOOK_LEASE6_RENEW_SKIP DHCPv6 lease was not renewed because a callout set the skip flag.
+This debug message is printed when a callout installed on lease6_renew
+hook point set the skip flag. For this particular hook point, the setting
+of the flag by a callout instructs the server to not renew a lease. If
+client requested renewal of multiples leases (by sending multiple IA
+options), the server will skip the renewal of the one in question and
+will proceed with other renewals as usual.
+
+% DHCP6_HOOK_LEASE6_RELEASE_SKIP DHCPv6 lease was not released because a callout set the skip flag.
+This debug message is printed when a callout installed on lease6_release
+hook point set the skip flag. For this particular hook point, the
+setting of the flag by a callout instructs the server to not release
+a lease. If client requested release of multiples leases (by sending
+multiple IA options), the server will retains this particular lease and
+will proceed with other renewals as usual.
+
+% DHCP6_HOOK_PACKET_RCVD_SKIP received DHCPv6 packet was dropped because a callout set the skip flag.
+This debug message is printed when a callout installed on the pkt6_receive
+hook point set the skip flag. For this particular hook point, the
+setting of the flag by a callout instructs the server to drop the packet.
+
+% DHCP6_HOOK_PACKET_SEND_SKIP prepared DHCPv6 response was not sent because a callout set the skip flag.
+This debug message is printed when a callout installed on the pkt6_send
+hook point set the skip flag. For this particular hook point, the setting
+of the flag by a callout instructs the server to drop the packet. This
+effectively means that the client will not get any response, even though
+the server processed client's request and acted on it (e.g. possibly
+allocated a lease).
+
+% DHCP6_HOOK_SUBNET6_SELECT_SKIP no subnet was selected because a callout set the skip flag.
+This debug message is printed when a callout installed on the
+subnet6_select hook point set the skip flag. For this particular hook
+point, the setting of the flag instructs the server not to choose a
+subnet, an action that severely limits further processing; the server
+will be only able to offer global options - no addresses or prefixes
+will be assigned.
+
+% DHCP6_HOOKS_LIBS_RELOAD_FAIL reload of hooks libraries failed
+A "libreload" command was issued to reload the hooks libraries but for
+some reason the reload failed. Other error messages issued from the
+hooks framework will indicate the nature of the problem.
+
% DHCP6_LEASE_ADVERT lease %1 advertised (client duid=%2, iaid=%3)
This debug message indicates that the server successfully advertised
a lease. It is up to the client to choose one server out of the
@@ -101,6 +202,11 @@ IPv6 DHCP server but it is not running.
During startup the IPv6 DHCP server failed to detect any network
interfaces and is therefore shutting down.
+% DHCP6_NO_SOCKETS_OPEN no interface configured to listen to DHCP traffic
+This warning message is issued when current server configuration specifies
+no interfaces that server should listen on, or specified interfaces are not
+configured to receive the traffic.
+
% DHCP6_OPEN_SOCKET opening sockets on port %1
A debug message issued during startup, this indicates that the IPv6 DHCP
server is about to open sockets on the specified port.
@@ -159,10 +265,10 @@ actions and committal of changes failed. The message has been output in
response to a non-BIND 10 exception being raised. Additional messages
may give further information.
-The most likely cause of this is that the specification file for the server
-(which details the allowable contents of the configuration) is not correct for
-this version of BIND 10. This former may be the result of an interrupted
-installation of an update to BIND 10.
+The most likely cause of this is that the specification file for the
+server (which details the allowable contents of the configuration) is
+not correct for this version of BIND 10. This may be the result of an
+interrupted installation of an update to BIND 10.
% DHCP6_PARSER_FAIL failed to create or run parser for configuration element %1: %2
On receipt of message containing details to a change of its configuration,
@@ -308,6 +414,12 @@ to a misconfiguration of the server. The packet processing will continue, but
the response will only contain generic configuration parameters and no
addresses or prefixes.
+% DHCP6_UNKNOWN_MSG_RECEIVED received unknown message (type %d) on interface %2
+This debug message is printed when server receives a message of unknown type.
+That could either mean missing functionality or invalid or broken relay or client.
+The list of formally defined message types is available here:
+www.iana.org/assignments/dhcpv6-parameters.
+
% DHCP6_UNKNOWN_RELEASE received RELEASE from unknown client (duid=%1, iaid=%2)
This warning message is printed when client attempts to release a lease,
but no such lease is known by the server. See DHCP6_UNKNOWN_RENEW for
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 5f1580f..41e2cee 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -15,11 +15,13 @@
#include <config.h>
#include <asiolink/io_address.h>
+#include <dhcp_ddns/ncr_msg.h>
#include <dhcp/dhcp6.h>
#include <dhcp/duid.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_client_fqdn.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_iaaddr.h>
@@ -28,15 +30,18 @@
#include <dhcp/pkt6.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/dhcp6_srv.h>
+#include <dhcpsrv/callout_handle_store.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/utils.h>
#include <exceptions/exceptions.h>
+#include <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
+#include <util/encode/hex.h>
#include <util/io_utilities.h>
#include <util/range_utilities.h>
-#include <util/encode/hex.h>
#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>
@@ -49,13 +54,75 @@
using namespace isc;
using namespace isc::asiolink;
+using namespace isc::dhcp_ddns;
using namespace isc::dhcp;
+using namespace isc::hooks;
using namespace isc::util;
using namespace std;
+namespace {
+
+/// Structure that holds registered hook indexes
+struct Dhcp6Hooks {
+ int hook_index_buffer6_receive_;///< index for "buffer6_receive" hook point
+ int hook_index_pkt6_receive_; ///< index for "pkt6_receive" hook point
+ int hook_index_subnet6_select_; ///< index for "subnet6_select" hook point
+ int hook_index_lease6_renew_; ///< index for "lease6_renew" hook point
+ int hook_index_lease6_release_; ///< index for "lease6_release" hook point
+ int hook_index_pkt6_send_; ///< index for "pkt6_send" hook point
+ int hook_index_buffer6_send_; ///< index for "buffer6_send" hook point
+
+ /// Constructor that registers hook points for DHCPv6 engine
+ Dhcp6Hooks() {
+ hook_index_buffer6_receive_= HooksManager::registerHook("buffer6_receive");
+ hook_index_pkt6_receive_ = HooksManager::registerHook("pkt6_receive");
+ hook_index_subnet6_select_ = HooksManager::registerHook("subnet6_select");
+ hook_index_lease6_renew_ = HooksManager::registerHook("lease6_renew");
+ hook_index_lease6_release_ = HooksManager::registerHook("lease6_release");
+ hook_index_pkt6_send_ = HooksManager::registerHook("pkt6_send");
+ hook_index_buffer6_send_ = HooksManager::registerHook("buffer6_send");
+ }
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+Dhcp6Hooks Hooks;
+
+}; // anonymous namespace
+
namespace isc {
namespace dhcp {
+namespace {
+
+// The following constants describe server's behavior with respect to the
+// DHCPv6 Client FQDN Option sent by a client. They will be removed
+// when DDNS parameters for DHCPv6 are implemented with the ticket #3034.
+
+// Should server always include the FQDN option in its response, regardless
+// if it has been requested in ORO (Disabled).
+const bool FQDN_ALWAYS_INCLUDE = false;
+// Enable AAAA RR update delegation to the client (Disabled).
+const bool FQDN_ALLOW_CLIENT_UPDATE = false;
+// Globally enable updates (Enabled).
+const bool FQDN_ENABLE_UPDATE = true;
+// The partial name generated for the client if empty name has been
+// supplied.
+const char* FQDN_GENERATED_PARTIAL_NAME = "myhost";
+// Do update, even if client requested no updates with N flag (Disabled).
+const bool FQDN_OVERRIDE_NO_UPDATE = false;
+// Server performs an update when client requested delegation (Enabled).
+const bool FQDN_OVERRIDE_CLIENT_UPDATE = true;
+// The fully qualified domain-name suffix if partial name provided by
+// a client.
+const char* FQDN_PARTIAL_SUFFIX = "example.com";
+// Should server replace the domain-name supplied by the client (Disabled).
+const bool FQDN_REPLACE_CLIENT_NAME = false;
+
+}
+
/// @brief file name of a server-id file
///
/// Server must store its duid in persistent storage that must not change
@@ -67,7 +134,8 @@ namespace dhcp {
static const char* SERVER_DUID_FILE = "b10-dhcp6-serverid";
Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
- : alloc_engine_(), serverid_(), shutdown_(true) {
+:alloc_engine_(), serverid_(), shutdown_(true), port_(port)
+{
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
@@ -82,7 +150,7 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
return;
}
- IfaceMgr::instance().openSockets6(port);
+ IfaceMgr::instance().openSockets6(port_);
}
string duid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_DUID_FILE);
@@ -100,12 +168,13 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
LOG_WARN(dhcp6_logger, DHCP6_SERVERID_WRITE_FAIL)
.arg(duid_file);
}
-
}
// Instantiate allocation engine
alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
+ /// @todo call loadLibraries() when handling configuration changes
+
} catch (const std::exception &e) {
LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what());
return;
@@ -126,123 +195,271 @@ void Dhcpv6Srv::shutdown() {
shutdown_ = true;
}
+Pkt6Ptr Dhcpv6Srv::receivePacket(int timeout) {
+ return (IfaceMgr::instance().receive6(timeout));
+}
+
+void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) {
+ IfaceMgr::instance().send(packet);
+}
+
bool Dhcpv6Srv::run() {
while (!shutdown_) {
- /// @todo: calculate actual timeout to the next event (e.g. lease
+ /// @todo Calculate actual timeout to the next event (e.g. lease
/// expiration) once we have lease database. The idea here is that
/// it is possible to do everything in a single process/thread.
/// For now, we are just calling select for 1000 seconds. There
/// were some issues reported on some systems when calling select()
/// with too large values. Unfortunately, I don't recall the details.
- int timeout = 1000;
+ //cppcheck-suppress variableScope This is temporary anyway
+ const int timeout = 1000;
// client's message and server's response
Pkt6Ptr query;
Pkt6Ptr rsp;
try {
- query = IfaceMgr::instance().receive6(timeout);
+ query = receivePacket(timeout);
} catch (const std::exception& e) {
LOG_ERROR(dhcp6_logger, DHCP6_PACKET_RECEIVE_FAIL).arg(e.what());
}
- if (query) {
+ // Timeout may be reached or signal received, which breaks select() with no packet received
+ if (!query) {
+ continue;
+ }
+
+ bool skip_unpack = false;
+
+ // The packet has just been received so contains the uninterpreted wire
+ // data; execute callouts registered for buffer6_receive.
+ if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_buffer6_receive_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Delete previously set arguments
+ callout_handle->deleteAllArguments();
+
+ // Pass incoming packet as argument
+ callout_handle->setArgument("query6", query);
+
+ // Call callouts
+ HooksManager::callCallouts(Hooks.hook_index_buffer6_receive_, *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to parse the packet, so skip at this
+ // stage means that callouts did the parsing already, so server
+ // should skip parsing.
+ if (callout_handle->getSkip()) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_RCVD_SKIP);
+ skip_unpack = true;
+ }
+
+ callout_handle->getArgument("query6", query);
+ }
+
+ // Unpack the packet information unless the buffer6_receive callouts
+ // indicated they did it
+ if (!skip_unpack) {
if (!query->unpack()) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
DHCP6_PACKET_PARSE_FAIL);
continue;
}
- LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
- .arg(query->getName());
- LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
- .arg(static_cast<int>(query->getType()))
- .arg(query->getBuffer().getLength())
- .arg(query->toText());
-
- try {
- switch (query->getType()) {
- case DHCPV6_SOLICIT:
- rsp = processSolicit(query);
- break;
-
- case DHCPV6_REQUEST:
- rsp = processRequest(query);
- break;
-
- case DHCPV6_RENEW:
- rsp = processRenew(query);
- break;
-
- case DHCPV6_REBIND:
- rsp = processRebind(query);
- break;
+ }
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
+ .arg(query->getName());
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
+ .arg(static_cast<int>(query->getType()))
+ .arg(query->getBuffer().getLength())
+ .arg(query->toText());
+
+ // At this point the information in the packet has been unpacked into
+ // the various packet fields and option objects has been cretated.
+ // Execute callouts registered for packet6_receive.
+ if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_pkt6_receive_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Delete previously set arguments
+ callout_handle->deleteAllArguments();
+
+ // Pass incoming packet as argument
+ callout_handle->setArgument("query6", query);
+
+ // Call callouts
+ HooksManager::callCallouts(Hooks.hook_index_pkt6_receive_, *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to process the packet, so skip at this
+ // stage means drop.
+ if (callout_handle->getSkip()) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_RCVD_SKIP);
+ continue;
+ }
- case DHCPV6_CONFIRM:
- rsp = processConfirm(query);
- break;
+ callout_handle->getArgument("query6", query);
+ }
- case DHCPV6_RELEASE:
- rsp = processRelease(query);
+ try {
+ NameChangeRequestPtr ncr;
+ switch (query->getType()) {
+ case DHCPV6_SOLICIT:
+ rsp = processSolicit(query);
break;
- case DHCPV6_DECLINE:
- rsp = processDecline(query);
- break;
+ case DHCPV6_REQUEST:
+ rsp = processRequest(query);
+ break;
+
+ case DHCPV6_RENEW:
+ rsp = processRenew(query);
+ break;
+
+ case DHCPV6_REBIND:
+ rsp = processRebind(query);
+ break;
+
+ case DHCPV6_CONFIRM:
+ rsp = processConfirm(query);
+ break;
+
+ case DHCPV6_RELEASE:
+ rsp = processRelease(query);
+ break;
+
+ case DHCPV6_DECLINE:
+ rsp = processDecline(query);
+ break;
+
+ case DHCPV6_INFORMATION_REQUEST:
+ rsp = processInfRequest(query);
+ break;
+
+ default:
+ // We received a packet type that we do not recognize.
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_UNKNOWN_MSG_RECEIVED)
+ .arg(static_cast<int>(query->getType()))
+ .arg(query->getIface());
+ // Only action is to output a message if debug is enabled,
+ // and that will be covered by the debug statement before
+ // the "switch" statement.
+ ;
+ }
- case DHCPV6_INFORMATION_REQUEST:
- rsp = processInfRequest(query);
- break;
+ } catch (const RFCViolation& e) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL)
+ .arg(query->getName())
+ .arg(query->getRemoteAddr().toText())
+ .arg(e.what());
+
+ } catch (const isc::Exception& e) {
+
+ // Catch-all exception (at least for ones based on the isc
+ // Exception class, which covers more or less all that
+ // are explicitly raised in the BIND 10 code). Just log
+ // the problem and ignore the packet. (The problem is logged
+ // as a debug message because debug is disabled by default -
+ // it prevents a DDOS attack based on the sending of problem
+ // packets.)
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_PACKET_PROCESS_FAIL)
+ .arg(query->getName())
+ .arg(query->getRemoteAddr().toText())
+ .arg(e.what());
+ }
- default:
- // Only action is to output a message if debug is enabled,
- // and that will be covered by the debug statement before
- // the "switch" statement.
- ;
+ if (rsp) {
+ rsp->setRemoteAddr(query->getRemoteAddr());
+ rsp->setLocalAddr(query->getLocalAddr());
+ rsp->setRemotePort(DHCP6_CLIENT_PORT);
+ rsp->setLocalPort(DHCP6_SERVER_PORT);
+ rsp->setIndex(query->getIndex());
+ rsp->setIface(query->getIface());
+
+ // Specifies if server should do the packing
+ bool skip_pack = false;
+
+ // Server's reply packet now has all options and fields set.
+ // Options are represented by individual objects, but the
+ // output wire data has not been prepared yet.
+ // Execute all callouts registered for packet6_send
+ if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_pkt6_send_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Delete all previous arguments
+ callout_handle->deleteAllArguments();
+
+ // Set our response
+ callout_handle->setArgument("response6", rsp);
+
+ // Call all installed callouts
+ HooksManager::callCallouts(Hooks.hook_index_pkt6_send_, *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to pack the packet (create wire data).
+ // That step will be skipped if any callout sets skip flag.
+ // It essentially means that the callout already did packing,
+ // so the server does not have to do it again.
+ if (callout_handle->getSkip()) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_SEND_SKIP);
+ skip_pack = true;
}
+ }
- } catch (const RFCViolation& e) {
- LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL)
- .arg(query->getName())
- .arg(query->getRemoteAddr())
- .arg(e.what());
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
+ DHCP6_RESPONSE_DATA)
+ .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());
+
+ if (!skip_pack) {
+ try {
+ rsp->pack();
+ } catch (const std::exception& e) {
+ LOG_ERROR(dhcp6_logger, DHCP6_PACK_FAIL)
+ .arg(e.what());
+ continue;
+ }
- } catch (const isc::Exception& e) {
-
- // Catch-all exception (at least for ones based on the isc
- // Exception class, which covers more or less all that
- // are explicitly raised in the BIND 10 code). Just log
- // the problem and ignore the packet. (The problem is logged
- // as a debug message because debug is disabled by default -
- // it prevents a DDOS attack based on the sending of problem
- // packets.)
- LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_PACKET_PROCESS_FAIL)
- .arg(query->getName())
- .arg(query->getRemoteAddr())
- .arg(e.what());
}
- if (rsp) {
- rsp->setRemoteAddr(query->getRemoteAddr());
- rsp->setLocalAddr(query->getLocalAddr());
- rsp->setRemotePort(DHCP6_CLIENT_PORT);
- rsp->setLocalPort(DHCP6_SERVER_PORT);
- rsp->setIndex(query->getIndex());
- rsp->setIface(query->getIface());
+ try {
+
+ // Now all fields and options are constructed into output wire buffer.
+ // Option objects modification does not make sense anymore. Hooks
+ // can only manipulate wire buffer at this stage.
+ // Let's execute all callouts registered for buffer6_send
+ if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_buffer6_send_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Delete previously set arguments
+ callout_handle->deleteAllArguments();
+
+ // Pass incoming packet as argument
+ callout_handle->setArgument("response6", rsp);
+
+ // Call callouts
+ HooksManager::callCallouts(Hooks.hook_index_buffer6_send_, *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to parse the packet, so skip at this
+ // stage means drop.
+ if (callout_handle->getSkip()) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_SEND_SKIP);
+ continue;
+ }
+
+ callout_handle->getArgument("response6", rsp);
+ }
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
DHCP6_RESPONSE_DATA)
.arg(static_cast<int>(rsp->getType())).arg(rsp->toText());
- if (rsp->pack()) {
- try {
- IfaceMgr::instance().send(rsp);
- } catch (const std::exception& e) {
- LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL).arg(e.what());
- }
- } else {
- LOG_ERROR(dhcp6_logger, DHCP6_PACK_FAIL);
- }
+ sendPacket(rsp);
+ } catch (const std::exception& e) {
+ LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL)
+ .arg(e.what());
}
+
+ // Send NameChangeRequests to the b10-dhcp_ddns module.
+ sendNameChangeRequests();
}
}
@@ -329,7 +546,7 @@ Dhcpv6Srv::generateServerID() {
// we will grow knobs to selectively turn them on or off. Also,
// this code is used only *once* during first start on a new machine
// and then server-id is stored. (or at least it will be once
- // DUID storage is implemente
+ // DUID storage is implemented)
// I wish there was a this_is_a_real_physical_interface flag...
@@ -353,7 +570,7 @@ Dhcpv6Srv::generateServerID() {
}
// Some interfaces (like lo on Linux) report 6-bytes long
- // MAC adress 00:00:00:00:00:00. Let's not use such weird interfaces
+ // MAC address 00:00:00:00:00:00. Let's not use such weird interfaces
// to generate DUID.
if (isRangeZero(iface->getMac(), iface->getMac() + iface->getMacLen())) {
continue;
@@ -403,8 +620,13 @@ Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
if (clientid) {
answer->addOption(clientid);
}
+ /// @todo: Should throw if there is no client-id (except anonymous INF-REQUEST)
+
+ // If this is a relayed message, we need to copy relay information
+ if (!question->relay_info_.empty()) {
+ answer->copyRelayInfo(question);
+ }
- // TODO: Should throw if there is no client-id (except anonymous INF-REQUEST)
}
void
@@ -458,10 +680,9 @@ Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
assert(status_code_def);
// As there is no dedicated class to represent Status Code
- // the OptionCustom class should be returned here.
- boost::shared_ptr<OptionCustom> option_status =
- boost::dynamic_pointer_cast<
- OptionCustom>(status_code_def->optionFactory(Option::V6, D6O_STATUS_CODE));
+ // the OptionCustom class is used here instead.
+ OptionCustomPtr option_status =
+ OptionCustomPtr(new OptionCustom(*status_code_def, Option::V6));
assert(option_status);
// Set status code to 'code' (0 - means data field #0).
@@ -523,22 +744,75 @@ Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
Subnet6Ptr
Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
- /// @todo: pass interface information only if received direct (non-relayed) message
+ Subnet6Ptr subnet;
+
+ if (question->relay_info_.empty()) {
+ // This is a direct (non-relayed) message
+
+ // Try to find a subnet if received packet from a directly connected client
+ subnet = CfgMgr::instance().getSubnet6(question->getIface());
+ if (!subnet) {
+ // If no subnet was found, try to find it based on remote address
+ subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+ }
+ } else {
+
+ // This is a relayed message
+ OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID,
+ Pkt6::RELAY_GET_FIRST);
+ if (interface_id) {
+ subnet = CfgMgr::instance().getSubnet6(interface_id);
+ }
+
+ if (!subnet) {
+ // If no interface-id was specified (or not configured on server), let's
+ // try address matching
+ IOAddress link_addr = question->relay_info_.back().linkaddr_;
- // Try to find a subnet if received packet from a directly connected client
- Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getIface());
- if (subnet) {
- return (subnet);
+ // if relay filled in link_addr field, then let's use it
+ if (link_addr != IOAddress("::")) {
+ subnet = CfgMgr::instance().getSubnet6(link_addr);
+ }
+ }
}
- // If no subnet was found, try to find it based on remote address
- subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+ // Let's execute all callouts registered for subnet6_receive
+ if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_subnet6_select_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(question);
+
+ // We're reusing callout_handle from previous calls
+ callout_handle->deleteAllArguments();
+
+ // Set new arguments
+ callout_handle->setArgument("query6", question);
+ callout_handle->setArgument("subnet6", subnet);
+
+ // We pass pointer to const collection for performance reasons.
+ // Otherwise we would get a non-trivial performance penalty each
+ // time subnet6_select is called.
+ callout_handle->setArgument("subnet6collection", CfgMgr::instance().getSubnets6());
+
+ // Call user (and server-side) callouts
+ HooksManager::callCallouts(Hooks.hook_index_subnet6_select_, *callout_handle);
+
+ // Callouts decided to skip this step. This means that no subnet will be
+ // selected. Packet processing will continue, but it will be severly limited
+ // (i.e. only global options will be assigned)
+ if (callout_handle->getSkip()) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_SUBNET6_SELECT_SKIP);
+ return (Subnet6Ptr());
+ }
+
+ // Use whatever subnet was specified by the callout
+ callout_handle->getArgument("subnet6", subnet);
+ }
return (subnet);
}
void
-Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
+Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
+ const Option6ClientFqdnPtr& fqdn) {
// We need to allocate addresses for all IA_NA options in the client's
// question (i.e. SOLICIT or REQUEST) message.
@@ -593,7 +867,9 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
switch (opt->second->getType()) {
case D6O_IA_NA: {
OptionPtr answer_opt = assignIA_NA(subnet, duid, question,
- boost::dynamic_pointer_cast<Option6IA>(opt->second));
+ boost::dynamic_pointer_cast<
+ Option6IA>(opt->second),
+ fqdn);
if (answer_opt) {
answer->addOption(answer_opt);
}
@@ -605,9 +881,274 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
}
}
+Option6ClientFqdnPtr
+Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question) {
+ // Get Client FQDN Option from the client's message. If this option hasn't
+ // been included, do nothing.
+ Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
+ Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN));
+ if (!fqdn) {
+ return (fqdn);
+ }
+
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+ DHCP6_DDNS_RECEIVE_FQDN).arg(fqdn->toText());
+
+
+ // Prepare the FQDN option which will be included in the response to
+ // the client.
+ Option6ClientFqdnPtr fqdn_resp(new Option6ClientFqdn(*fqdn));
+ // RFC 4704, section 6. - all flags set to 0.
+ fqdn_resp->resetFlags();
+
+ // Conditions when N flag has to be set to indicate that server will not
+ // perform DNS updates:
+ // 1. Updates are globally disabled,
+ // 2. Client requested no update and server respects it,
+ // 3. Client requested that the AAAA update is delegated to the client but
+ // server neither respects delegation of updates nor it is configured
+ // to send update on its own when client requested delegation.
+ if (!FQDN_ENABLE_UPDATE ||
+ (fqdn->getFlag(Option6ClientFqdn::FLAG_N) &&
+ !FQDN_OVERRIDE_NO_UPDATE) ||
+ (!fqdn->getFlag(Option6ClientFqdn::FLAG_S) &&
+ !FQDN_ALLOW_CLIENT_UPDATE && !FQDN_OVERRIDE_CLIENT_UPDATE)) {
+ fqdn_resp->setFlag(Option6ClientFqdn::FLAG_N, true);
+
+ // Conditions when S flag is set to indicate that server will perform
+ // DNS update on its own:
+ // 1. Client requested that server performs DNS update and DNS updates are
+ // globally enabled
+ // 2. Client requested that server delegates AAAA update to the client but
+ // server doesn't respect delegation and it is configured to perform
+ // an update on its own when client requested delegation.
+ } else if (fqdn->getFlag(Option6ClientFqdn::FLAG_S) ||
+ (!fqdn->getFlag(Option6ClientFqdn::FLAG_S) &&
+ !FQDN_ALLOW_CLIENT_UPDATE && FQDN_OVERRIDE_CLIENT_UPDATE)) {
+ fqdn_resp->setFlag(Option6ClientFqdn::FLAG_S, true);
+ }
+
+ // Server MUST set the O flag if it has overridden the client's setting
+ // of S flag.
+ if (fqdn->getFlag(Option6ClientFqdn::FLAG_S) !=
+ fqdn_resp->getFlag(Option6ClientFqdn::FLAG_S)) {
+ fqdn_resp->setFlag(Option6ClientFqdn::FLAG_O, true);
+ }
+
+ // If client supplied partial or empty domain-name, server should
+ // generate one.
+ if (fqdn->getDomainNameType() == Option6ClientFqdn::PARTIAL) {
+ std::ostringstream name;
+ if (fqdn->getDomainName().empty()) {
+ name << FQDN_GENERATED_PARTIAL_NAME;
+ } else {
+ name << fqdn->getDomainName();
+ }
+ name << "." << FQDN_PARTIAL_SUFFIX;
+ fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);
+
+ // Server may be configured to replace a name supplied by a client,
+ // even if client supplied fully qualified domain-name.
+ } else if (FQDN_REPLACE_CLIENT_NAME) {
+ std::ostringstream name;
+ name << FQDN_GENERATED_PARTIAL_NAME << "." << FQDN_PARTIAL_SUFFIX;
+ fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);
+
+ }
+
+ // Return the FQDN option which can be included in the server's response.
+ // Note that it doesn't have to be included, if client didn't request
+ // it using ORO and server is not configured to always include it.
+ return (fqdn_resp);
+}
+
+
+void
+Dhcpv6Srv::appendClientFqdn(const Pkt6Ptr& question,
+ Pkt6Ptr& answer,
+ const Option6ClientFqdnPtr& fqdn) {
+
+ // If FQDN is NULL, it means that client did not request DNS Update, plus
+ // server doesn't force updates.
+ if (fqdn) {
+ return;
+ }
+
+ // Server sends back the FQDN option to the client if client has requested
+ // it using Option Request Option. However, server may be configured to
+ // send the FQDN option in its response, regardless whether client requested
+ // it or not.
+ bool include_fqdn = FQDN_ALWAYS_INCLUDE;
+ if (!include_fqdn) {
+ OptionUint16ArrayPtr oro = boost::dynamic_pointer_cast<
+ OptionUint16Array>(question->getOption(D6O_ORO));
+ if (oro) {
+ const std::vector<uint16_t>& values = oro->getValues();
+ for (int i = 0; i < values.size(); ++i) {
+ if (values[i] == D6O_CLIENT_FQDN) {
+ include_fqdn = true;
+ }
+ }
+ }
+ }
+
+ if (include_fqdn) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+ DHCP6_DDNS_SEND_FQDN).arg(fqdn->toText());
+ answer->addOption(fqdn);
+ }
+
+}
+
+void
+Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
+ const Option6ClientFqdnPtr& opt_fqdn) {
+
+ // It is likely that client haven't included the FQDN option in the message
+ // and server is not configured to always update DNS. In such cases,
+ // FQDN option will be NULL. This is valid state, so we simply return.
+ if (!opt_fqdn) {
+ return;
+ }
+
+ // The response message instance is always required. For instance it
+ // holds the Client Identifier. It is a programming error if supplied
+ // message is NULL.
+ if (!answer) {
+ isc_throw(isc::Unexpected, "an instance of the object"
+ << " encapsulating server's message must not be"
+ << " NULL when creating DNS NameChangeRequest");
+ }
+
+ // Get the Client Id. It is mandatory and a function creating a response
+ // would have thrown an exception if it was missing. Thus throwning
+ // Unexpected if it is missing as it is a programming error.
+ OptionPtr opt_duid = answer->getOption(D6O_CLIENTID);
+ if (!opt_duid) {
+ isc_throw(isc::Unexpected,
+ "client identifier is required when creating a new"
+ " DNS NameChangeRequest");
+ }
+ DuidPtr duid = DuidPtr(new DUID(opt_duid->getData()));
+
+ // Get the FQDN in the on-wire format. It will be needed to compute
+ // DHCID.
+ OutputBuffer name_buf(1);
+ opt_fqdn->packDomainName(name_buf);
+ const uint8_t* name_data = static_cast<const uint8_t*>(name_buf.getData());
+ // @todo currently D2Dhcid accepts a vector holding FQDN.
+ // However, it will be faster if we used a pointer name_data.
+ std::vector<uint8_t> buf_vec(name_data, name_data + name_buf.getLength());
+ // Compute DHCID from Client Identifier and FQDN.
+ isc::dhcp_ddns::D2Dhcid dhcid(*duid, buf_vec);
+
+ // Get all IAs from the answer. For each IA, holding an address we will
+ // create a corresponding NameChangeRequest.
+ Option::OptionCollection answer_ias = answer->getOptions(D6O_IA_NA);
+ for (Option::OptionCollection::const_iterator answer_ia =
+ answer_ias.begin(); answer_ia != answer_ias.end(); ++answer_ia) {
+ // @todo IA_NA may contain multiple addresses. We should process
+ // each address individually. Currently we get only one.
+ Option6IAAddrPtr iaaddr = boost::static_pointer_cast<
+ Option6IAAddr>(answer_ia->second->getOption(D6O_IAADDR));
+ // We need an address to create a name-to-address mapping.
+ // If address is missing for any reason, go to the next IA.
+ if (!iaaddr) {
+ continue;
+ }
+ // Create new NameChangeRequest. Use the domain name from the FQDN.
+ // This is an FQDN included in the response to the client, so it
+ // holds a fully qualified domain-name already (not partial).
+ // Get the IP address from the lease. Also, use the S flag to determine
+ // if forward change should be performed. This flag will always be
+ // set if server has taken responsibility for the forward update.
+ NameChangeRequest ncr(isc::dhcp_ddns::CHG_ADD,
+ opt_fqdn->getFlag(Option6ClientFqdn::FLAG_S),
+ true, opt_fqdn->getDomainName(),
+ iaaddr->getAddress().toText(),
+ dhcid, 0, iaaddr->getValid());
+ // Add the request to the queue. This queue will be read elsewhere in
+ // the code and all requests from this queue will be sent to the
+ // D2 module.
+ name_change_reqs_.push(ncr);
+
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+ DHCP6_DDNS_CREATE_ADD_NAME_CHANGE_REQUEST).arg(ncr.toText());
+ }
+}
+
+void
+Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) {
+ // If we haven't performed a DNS Update when lease was acquired,
+ // there is nothing to do here.
+ if (!lease->fqdn_fwd_ && !lease->fqdn_rev_) {
+ return;
+ }
+
+ // When lease was added into a database the host name should have
+ // been added. The hostname can be empty if someone messed up in the
+ // lease data base and removed the hostname.
+ if (lease->hostname_.empty()) {
+ LOG_ERROR(dhcp6_logger, DHCP6_DDNS_REMOVE_EMPTY_HOSTNAME)
+ .arg(lease->addr_.toText());
+ return;
+ }
+
+ // If hostname is non-empty, try to convert it to wire format so as
+ // DHCID can be computed from it. This may throw an exception if hostname
+ // has invalid format. Again, this should be only possible in case of
+ // manual intervention in the database. Note that the last parameter
+ // passed to the writeFqdn function forces conversion of the FQDN
+ // to lower case. This is required by the RFC4701, section 3.5.
+ // The DHCID computation is further in this function.
+ std::vector<uint8_t> hostname_wire;
+ try {
+ OptionDataTypeUtil::writeFqdn(lease->hostname_, hostname_wire, true);
+ } catch (const Exception& ex) {
+ LOG_ERROR(dhcp6_logger, DHCP6_DDNS_REMOVE_INVALID_HOSTNAME)
+ .arg(lease->hostname_);
+ return;
+ }
+
+ // DUID must have been checked already by the caller of this function.
+ // Let's be on the safe side and make sure it is non-NULL and throw
+ // an exception if it is NULL.
+ if (!lease->duid_) {
+ isc_throw(isc::Unexpected, "DUID must be set when creating"
+ << " NameChangeRequest for DNS records removal for "
+ << lease->addr_.toText());
+
+ }
+ isc::dhcp_ddns::D2Dhcid dhcid(*lease->duid_, hostname_wire);
+
+ // Create a NameChangeRequest to remove the entry.
+ NameChangeRequest ncr(isc::dhcp_ddns::CHG_REMOVE,
+ lease->fqdn_fwd_, lease->fqdn_rev_,
+ lease->hostname_,
+ lease->addr_.toText(),
+ dhcid, 0, lease->valid_lft_);
+ name_change_reqs_.push(ncr);
+
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+ DHCP6_DDNS_CREATE_REMOVE_NAME_CHANGE_REQUEST).arg(ncr.toText());
+
+}
+
+void
+Dhcpv6Srv::sendNameChangeRequests() {
+ while (!name_change_reqs_.empty()) {
+ // @todo Once next NameChangeRequest is picked from the queue
+ // we should send it to the bind10-dhcp_ddns module. Currently we
+ // just drop it.
+ name_change_reqs_.pop();
+ }
+}
+
+
OptionPtr
Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
- Pkt6Ptr question, boost::shared_ptr<Option6IA> ia) {
+ const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia,
+ const Option6ClientFqdnPtr& fqdn) {
// If there is no subnet selected for handling this IA_NA, the only thing to do left is
// to say that we are sorry, but the user won't get an address. As a convenience, we
// use a different status text to indicate that (compare to the same status code,
@@ -646,17 +1187,49 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
// it should include this hint. That will help us during the actual lease
// allocation.
bool fake_allocation = false;
- if (question->getType() == DHCPV6_SOLICIT) {
+ if (query->getType() == DHCPV6_SOLICIT) {
/// @todo: Check if we support rapid commit
fake_allocation = true;
}
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // At this point, we have to make make some decisions with respect to the
+ // FQDN option that we have generated as a result of receiving client's
+ // FQDN. In particular, we have to get to know if the DNS update will be
+ // performed or not. It is possible that option is NULL, which is valid
+ // condition if client didn't request DNS updates and server didn't force
+ // the update.
+ bool do_fwd = false;
+ bool do_rev = false;
+ if (fqdn) {
+ // Flag S must not coexist with flag N being set to 1, so if S=1
+ // server takes responsibility for both reverse and forward updates.
+ // Otherwise, we have to check N.
+ if (fqdn->getFlag(Option6ClientFqdn::FLAG_S)) {
+ do_fwd = true;
+ do_rev = true;
+ } else if (!fqdn->getFlag(Option6ClientFqdn::FLAG_N)) {
+ do_rev = true;
+ }
+ }
+ // Set hostname only in case any of the updates is being performed.
+ std::string hostname;
+ if (do_fwd || do_rev) {
+ hostname = fqdn->getDomainName();
+ }
+
// Use allocation engine to pick a lease for this client. Allocation engine
// will try to honour the hint, but it is just a hint - some other address
// may be used instead. If fake_allocation is set to false, the lease will
// be inserted into the LeaseMgr as well.
- Lease6Ptr lease = alloc_engine_->allocateAddress6(subnet, duid, ia->getIAID(),
- hint, fake_allocation);
+ Lease6Ptr lease = alloc_engine_->allocateAddress6(subnet, duid,
+ ia->getIAID(),
+ hint,
+ do_fwd, do_rev,
+ hostname,
+ fake_allocation,
+ callout_handle);
// Create IA_NA that we will put in the response.
// Do not use OptionDefinition to create option's instance so
@@ -685,6 +1258,29 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
// It would be possible to insert status code=0(success) as well,
// but this is considered waste of bandwidth as absence of status
// code is considered a success.
+
+ // Allocation engine may have returned an existing lease. If so, we
+ // have to check that the FQDN settings we provided are the same
+ // that were set. If they aren't, we will have to remove existing
+ // DNS records and update the lease with the new settings.
+ if ((lease->hostname_ != hostname) || (lease->fqdn_fwd_ != do_fwd) ||
+ (lease->fqdn_rev_ != do_rev)) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+ DHCP6_DDNS_LEASE_ASSIGN_FQDN_CHANGE)
+ .arg(lease->toText())
+ .arg(hostname)
+ .arg(do_rev ? "true" : "false")
+ .arg(do_fwd ? "true" : "false");
+
+ // Schedule removal of the existing lease.
+ createRemovalNameChangeRequest(lease);
+ // Set the new lease properties and update.
+ lease->hostname_ = hostname;
+ lease->fqdn_fwd_ = do_fwd;
+ lease->fqdn_rev_ = do_rev;
+ LeaseMgrFactory::instance().updateLease6(lease);
+ }
+
} else {
// Allocation engine did not allocate a lease. The engine logged
// cause of that failure. The only thing left is to insert
@@ -703,7 +1299,8 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
OptionPtr
Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
- Pkt6Ptr /* question */, boost::shared_ptr<Option6IA> ia) {
+ const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia,
+ const Option6ClientFqdnPtr& fqdn) {
if (!subnet) {
// There's no subnet select for this client. There's nothing to renew.
boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
@@ -740,13 +1337,53 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
return (ia_rsp);
}
+ // Keep the old data in case the callout tells us to skip update
+ Lease6 old_data = *lease;
+
+ // At this point, we have to make make some decisions with respect to the
+ // FQDN option that we have generated as a result of receiving client's
+ // FQDN. In particular, we have to get to know if the DNS update will be
+ // performed or not. It is possible that option is NULL, which is valid
+ // condition if client didn't request DNS updates and server didn't force
+ // the update.
+ bool do_fwd = false;
+ bool do_rev = false;
+ if (fqdn) {
+ if (fqdn->getFlag(Option6ClientFqdn::FLAG_S)) {
+ do_fwd = true;
+ do_rev = true;
+ } else if (!fqdn->getFlag(Option6ClientFqdn::FLAG_N)) {
+ do_rev = true;
+ }
+ }
+
+ std::string hostname;
+ if (do_fwd || do_rev) {
+ hostname = fqdn->getDomainName();
+ }
+
+ // If the new FQDN settings have changed for the lease, we need to
+ // delete any existing FQDN records for this lease.
+ if ((lease->hostname_ != hostname) || (lease->fqdn_fwd_ != do_fwd) ||
+ (lease->fqdn_rev_ != do_rev)) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
+ DHCP6_DDNS_LEASE_RENEW_FQDN_CHANGE)
+ .arg(lease->toText())
+ .arg(hostname)
+ .arg(do_rev ? "true" : "false")
+ .arg(do_fwd ? "true" : "false");
+
+ createRemovalNameChangeRequest(lease);
+ }
+
lease->preferred_lft_ = subnet->getPreferred();
lease->valid_lft_ = subnet->getValid();
lease->t1_ = subnet->getT1();
lease->t2_ = subnet->getT2();
lease->cltt_ = time(NULL);
-
- LeaseMgrFactory::instance().updateLease6(lease);
+ lease->hostname_ = hostname;
+ lease->fqdn_fwd_ = do_fwd;
+ lease->fqdn_rev_ = do_rev;
// Create empty IA_NA option with IAID matching the request.
boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
@@ -758,11 +1395,52 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
lease->addr_, lease->preferred_lft_,
lease->valid_lft_));
ia_rsp->addOption(addr);
+
+ bool skip = false;
+ // Execute all callouts registered for packet6_send
+ if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_lease6_renew_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Delete all previous arguments
+ callout_handle->deleteAllArguments();
+
+ // Pass the original packet
+ callout_handle->setArgument("query6", query);
+
+ // Pass the lease to be updated
+ callout_handle->setArgument("lease6", lease);
+
+ // Pass the IA option to be sent in response
+ callout_handle->setArgument("ia_na", ia_rsp);
+
+ // Call all installed callouts
+ HooksManager::callCallouts(Hooks.hook_index_lease6_renew_, *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to actually renew the lease, so skip at this
+ // stage means "keep the old lease as it is".
+ if (callout_handle->getSkip()) {
+ skip = true;
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_LEASE6_RENEW_SKIP);
+ }
+ }
+
+ if (!skip) {
+ LeaseMgrFactory::instance().updateLease6(lease);
+ } else {
+ // Copy back the original date to the lease. For MySQL it doesn't make
+ // much sense, but for memfile, the Lease6Ptr points to the actual lease
+ // in memfile, so the actual update is performed when we manipulate fields
+ // of returned Lease6Ptr, the actual updateLease6() is no-op.
+ *lease = old_data;
+ }
+
return (ia_rsp);
}
void
-Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
+Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply,
+ const Option6ClientFqdnPtr& fqdn) {
// We need to renew addresses for all IA_NA options in the client's
// RENEW message.
@@ -806,7 +1484,9 @@ Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
switch (opt->second->getType()) {
case D6O_IA_NA: {
OptionPtr answer_opt = renewIA_NA(subnet, duid, renew,
- boost::dynamic_pointer_cast<Option6IA>(opt->second));
+ boost::dynamic_pointer_cast<
+ Option6IA>(opt->second),
+ fqdn);
if (answer_opt) {
reply->addOption(answer_opt);
}
@@ -876,7 +1556,7 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
}
OptionPtr
-Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr /* question */,
+Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query,
int& general_status, boost::shared_ptr<Option6IA> ia) {
// Release can be done in one of two ways:
// Approach 1: extract address from client's IA_NA and see if it belongs
@@ -960,9 +1640,43 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr /* question */,
// It is not necessary to check if the address matches as we used
// getLease6(addr) method that is supposed to return a proper lease.
+ bool skip = false;
+ // Execute all callouts registered for packet6_send
+ if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_lease6_release_)) {
+ CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+ // Delete all previous arguments
+ callout_handle->deleteAllArguments();
+
+ // Pass the original packet
+ callout_handle->setArgument("query6", query);
+
+ // Pass the lease to be updated
+ callout_handle->setArgument("lease6", lease);
+
+ // Call all installed callouts
+ HooksManager::callCallouts(Hooks.hook_index_lease6_release_, *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to send the packet, so skip at this
+ // stage means "drop response".
+ if (callout_handle->getSkip()) {
+ skip = true;
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_LEASE6_RELEASE_SKIP);
+ }
+ }
+
// Ok, we've passed all checks. Let's release this address.
+ bool success = false; // was the removal operation succeessful?
+
+ if (!skip) {
+ success = LeaseMgrFactory::instance().deleteLease(lease->addr_);
+ }
- if (!LeaseMgrFactory::instance().deleteLease(lease->addr_)) {
+ // Here the success should be true if we removed lease successfully
+ // and false if skip flag was set or the removal failed for whatever reason
+
+ if (!success) {
ia_rsp->addOption(createStatusCode(STATUS_UnspecFail,
"Server failed to release a lease"));
@@ -982,6 +1696,11 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr /* question */,
ia_rsp->addOption(createStatusCode(STATUS_Success,
"Lease released. Thank you, please come again."));
+ // Check if a lease has flags indicating that the FQDN update has
+ // been performed. If so, create NameChangeRequest which removes
+ // the entries.
+ createRemovalNameChangeRequest(lease);
+
return (ia_rsp);
}
}
@@ -997,7 +1716,12 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
appendDefaultOptions(solicit, advertise);
appendRequestedOptions(solicit, advertise);
- assignLeases(solicit, advertise);
+ Option6ClientFqdnPtr fqdn = processClientFqdn(solicit);
+ assignLeases(solicit, advertise, fqdn);
+ appendClientFqdn(solicit, advertise, fqdn);
+ // Note, that we don't create NameChangeRequests here because we don't
+ // perform DNS Updates for Solicit. Client must send Request to update
+ // DNS.
return (advertise);
}
@@ -1013,7 +1737,10 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
appendDefaultOptions(request, reply);
appendRequestedOptions(request, reply);
- assignLeases(request, reply);
+ Option6ClientFqdnPtr fqdn = processClientFqdn(request);
+ assignLeases(request, reply, fqdn);
+ appendClientFqdn(request, reply, fqdn);
+ createNameChangeRequests(reply, fqdn);
return (reply);
}
@@ -1029,13 +1756,17 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
appendDefaultOptions(renew, reply);
appendRequestedOptions(renew, reply);
- renewLeases(renew, reply);
+ Option6ClientFqdnPtr fqdn = processClientFqdn(renew);
+ renewLeases(renew, reply, fqdn);
+ appendClientFqdn(renew, reply, fqdn);
+ createNameChangeRequests(reply, fqdn);
return reply;
}
Pkt6Ptr
Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
+
/// @todo: Implement this
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, rebind->getTransid()));
return reply;
@@ -1060,7 +1791,10 @@ Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
releaseLeases(release, reply);
- return reply;
+ // @todo If client sent a release and we should remove outstanding
+ // DNS records.
+
+ return (reply);
}
Pkt6Ptr
@@ -1077,5 +1811,47 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& infRequest) {
return reply;
}
+void
+Dhcpv6Srv::openActiveSockets(const uint16_t port) {
+ IfaceMgr::instance().closeSockets();
+
+ // Get the reference to the collection of interfaces. This reference should be
+ // valid as long as the program is run because IfaceMgr is a singleton.
+ // Therefore we can safely iterate over instances of all interfaces and modify
+ // their flags. Here we modify flags which indicate wheter socket should be
+ // open for a particular interface or not.
+ const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+ for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
+ iface != ifaces.end(); ++iface) {
+ Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
+ if (iface_ptr == NULL) {
+ isc_throw(isc::Unexpected, "Interface Manager returned NULL"
+ << " instance of the interface when DHCPv6 server was"
+ << " trying to reopen sockets after reconfiguration");
+ }
+ if (CfgMgr::instance().isActiveIface(iface->getName())) {
+ iface_ptr->inactive4_ = false;
+ LOG_INFO(dhcp6_logger, DHCP6_ACTIVATE_INTERFACE)
+ .arg(iface->getFullName());
+
+ } else {
+ // For deactivating interface, it should be sufficient to log it
+ // on the debug level because it is more useful to know what
+ // interface is activated which is logged on the info level.
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC,
+ DHCP6_DEACTIVATE_INTERFACE).arg(iface->getName());
+ iface_ptr->inactive6_ = true;
+
+ }
+ }
+ // Let's reopen active sockets. openSockets6 will check internally whether
+ // sockets are marked active or inactive.
+ // @todo Optimization: we should not reopen all sockets but rather select
+ // those that have been affected by the new configuration.
+ if (!IfaceMgr::instance().openSockets6(port)) {
+ LOG_WARN(dhcp6_logger, DHCP6_NO_SOCKETS_OPEN);
+ }
+}
+
};
};
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index bdcb560..f9e5dc5 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -15,18 +15,22 @@
#ifndef DHCPV6_SRV_H
#define DHCPV6_SRV_H
+#include <dhcp_ddns/ncr_msg.h>
#include <dhcp/dhcp6.h>
#include <dhcp/duid.h>
#include <dhcp/option.h>
+#include <dhcp/option6_client_fqdn.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option_definition.h>
#include <dhcp/pkt6.h>
#include <dhcpsrv/alloc_engine.h>
#include <dhcpsrv/subnet.h>
+#include <hooks/callout_handle.h>
#include <boost/noncopyable.hpp>
#include <iostream>
+#include <queue>
namespace isc {
namespace dhcp {
@@ -40,7 +44,7 @@ namespace dhcp {
/// packets, processes them, manages leases assignment and generates
/// appropriate responses.
///
-/// @note Only one instance of this class is instantated as it encompasses
+/// @note Only one instance of this class is instantiated as it encompasses
/// the whole operation of the server. Nothing, however, enforces the
/// singleton status of the object.
class Dhcpv6Srv : public boost::noncopyable {
@@ -69,7 +73,7 @@ public:
/// @brief Destructor. Used during DHCPv6 service shutdown.
virtual ~Dhcpv6Srv();
- /// @brief Returns server-intentifier option.
+ /// @brief Returns server-indentifier option.
///
/// @return server-id option
OptionPtr getServerID() { return serverid_; }
@@ -78,7 +82,7 @@ public:
///
/// Main server processing loop. Receives incoming packets, verifies
/// their correctness, generates appropriate answer (if needed) and
- /// transmits respones.
+ /// transmits responses.
///
/// @return true, if being shut down gracefully, fail if experienced
/// critical error.
@@ -87,6 +91,32 @@ public:
/// @brief Instructs the server to shut down.
void shutdown();
+ /// @brief Get UDP port on which server should listen.
+ ///
+ /// Typically, server listens on UDP port 547. Other ports are only
+ /// used for testing purposes.
+ ///
+ /// This accessor must be public because sockets are reopened from the
+ /// static configuration callback handler. This callback handler invokes
+ /// @c ControlledDhcpv4Srv::openActiveSockets which requires port parameter
+ /// which has to be retrieved from the @c ControlledDhcpv4Srv object.
+ /// They are retrieved using this public function.
+ ///
+ /// @return UDP port on which server should listen.
+ uint16_t getPort() const {
+ return (port_);
+ }
+
+ /// @brief Open sockets which are marked as active in @c CfgMgr.
+ ///
+ /// This function reopens sockets according to the current settings in the
+ /// Configuration Manager. It holds the list of the interfaces which server
+ /// should listen on. This function will open sockets on these interfaces
+ /// only. This function is not exception safe.
+ ///
+ /// @param port UDP port on which server should listen.
+ static void openActiveSockets(const uint16_t port);
+
protected:
/// @brief verifies if specified packet meets RFC requirements
@@ -105,7 +135,7 @@ protected:
///
/// Processes received SOLICIT message and verifies that its sender
/// should be served. In particular IA, TA and PD options are populated
- /// with to-be assinged addresses, temporary addresses and delegated
+ /// with to-be assigned addresses, temporary addresses and delegated
/// prefixes, respectively. In the usual 4 message exchange, server is
/// expected to respond with ADVERTISE message. However, if client
/// requests rapid-commit and server supports it, REPLY will be sent
@@ -121,7 +151,7 @@ protected:
///
/// Processes incoming REQUEST message and verifies that its sender
/// should be served. In particular IA, TA and PD options are populated
- /// with assinged addresses, temporary addresses and delegated
+ /// with assigned addresses, temporary addresses and delegated
/// prefixes, respectively. Uses LeaseMgr to allocate or update existing
/// leases.
///
@@ -183,13 +213,16 @@ protected:
///
/// @param subnet subnet the client is connected to
/// @param duid client's duid
- /// @param question client's message (typically SOLICIT or REQUEST)
+ /// @param query client's message (typically SOLICIT or REQUEST)
/// @param ia pointer to client's IA_NA option (client's request)
+ /// @param fqdn A DHCPv6 Client FQDN %Option generated in a response to the
+ /// FQDN option sent by a client.
/// @return IA_NA option (server's response)
OptionPtr assignIA_NA(const isc::dhcp::Subnet6Ptr& subnet,
const isc::dhcp::DuidPtr& duid,
- isc::dhcp::Pkt6Ptr question,
- boost::shared_ptr<Option6IA> ia);
+ const isc::dhcp::Pkt6Ptr& query,
+ Option6IAPtr ia,
+ const Option6ClientFqdnPtr& fqdn);
/// @brief Renews specific IA_NA option
///
@@ -199,11 +232,13 @@ protected:
///
/// @param subnet subnet the sender belongs to
/// @param duid client's duid
- /// @param question client's message
+ /// @param query client's message
/// @param ia IA_NA option that is being renewed
+ /// @param fqdn DHCPv6 Client FQDN Option included in the server's response
/// @return IA_NA option (server's response)
OptionPtr renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
- Pkt6Ptr question, boost::shared_ptr<Option6IA> ia);
+ const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia,
+ const Option6ClientFqdnPtr& fqdn);
/// @brief Releases specific IA_NA option
///
@@ -218,11 +253,11 @@ protected:
/// release process fails.
///
/// @param duid client's duid
- /// @param question client's message
+ /// @param query client's message
/// @param general_status a global status (it may be updated in case of errors)
/// @param ia IA_NA option that is being renewed
/// @return IA_NA option (server's response)
- OptionPtr releaseIA_NA(const DuidPtr& duid, Pkt6Ptr question,
+ OptionPtr releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query,
int& general_status,
boost::shared_ptr<Option6IA> ia);
@@ -262,7 +297,94 @@ protected:
///
/// @param question client's message (with requested IA_NA)
/// @param answer server's message (IA_NA options will be added here)
- void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer);
+ /// @param fqdn an FQDN option generated in a response to the client's
+ /// FQDN option.
+ void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
+ const Option6ClientFqdnPtr& fqdn);
+
+ /// @brief Processes Client FQDN Option.
+ ///
+ /// This function retrieves DHCPv6 Client FQDN %Option (if any) from the
+ /// packet sent by a client and takes necessary actions upon this option.
+ /// Received option comprises flags field which controls what DNS updates
+ /// server should do. Server may override client's preference based on
+ /// the current configuration. Server indicates that it has overridden
+ /// the preference by storing DHCPv6 Client Fqdn %Option with the
+ /// appropriate flags in the response to a client. This option is also
+ /// used to communicate the client's domain-name which should be sent
+ /// to the DNS in the update. Again, server may act upon the received
+ /// domain-name, i.e. if the provided domain-name is partial it should
+ /// generate the fully qualified domain-name.
+ ///
+ /// All the logic required to form appropriate answer to the client is
+ /// held in this function.
+ ///
+ /// @param question Client's message.
+ ///
+ /// @return FQDN option produced in the response to the client's message.
+ Option6ClientFqdnPtr processClientFqdn(const Pkt6Ptr& question);
+
+ /// @brief Adds DHCPv6 Client FQDN %Option to the server response.
+ ///
+ /// This function will add the specified FQDN option into the server's
+ /// response when FQDN is not NULL and server is either configured to
+ /// always include the FQDN in the response or client requested it using
+ /// %Option Request %Option.
+ /// This function is exception safe.
+ ///
+ /// @param question A message received from the client.
+ /// @param [out] answer A server's response where FQDN option will be added.
+ /// @param fqdn A DHCPv6 Client FQDN %Option to be added to the server's
+ /// response to a client.
+ void appendClientFqdn(const Pkt6Ptr& question,
+ Pkt6Ptr& answer,
+ const Option6ClientFqdnPtr& fqdn);
+
+ /// @brief Creates a number of @c isc::dhcp_ddns::NameChangeRequest objects
+ /// based on the DHCPv6 Client FQDN %Option.
+ ///
+ /// The @c isc::dhcp_ddns::NameChangeRequest class encapsulates the request
+ /// from the DHCPv6 server to the DHCP-DDNS module to perform DNS Update.
+ /// The FQDN option carries response to the client about DNS updates that
+ /// server intents to perform for the DNS client. Based on this, the
+ /// function will create zero or more @c isc::dhcp_ddns::NameChangeRequest
+ /// objects and store them in the internal queue. Requests created by this
+ /// function are only adding or updating DNS records. In order to generate
+ /// requests for DNS records removal, use @c createRemovalNameChangeRequest.
+ ///
+ /// @todo Add support for multiple IAADDR options in the IA_NA.
+ ///
+ /// @param answer A message beging sent to the Client.
+ /// @param fqdn_answer A DHCPv6 Client FQDN %Option which is included in the
+ /// response message sent to a client.
+ void createNameChangeRequests(const Pkt6Ptr& answer,
+ const Option6ClientFqdnPtr& fqdn_answer);
+
+ /// @brief Creates a @c isc::dhcp_ddns::NameChangeRequest which requests
+ /// removal of DNS entries for a particular lease.
+ ///
+ /// This function should be called upon removal of the lease from the lease
+ /// database, i.e, when client sent Release or Decline message. It will
+ /// create a single @c isc::dhcp_ddns::NameChangeRequest which removes the
+ /// existing DNS records for the lease, which server is responsible for.
+ /// Note that this function will not remove the entries which server hadn't
+ /// added. This is the case, when client performs forward DNS update on its
+ /// own.
+ ///
+ /// @param lease A lease for which the the removal of corresponding DNS
+ /// records will be performed.
+ void createRemovalNameChangeRequest(const Lease6Ptr& lease);
+
+ /// @brief Sends all outstanding NameChangeRequests to bind10-d2 module.
+ ///
+ /// The purpose of this function is to pick all outstanding
+ /// NameChangeRequests from the FIFO queue and send them to bind10-dhcp-ddns
+ /// module.
+ ///
+ /// @todo Currently this function simply removes all requests from the
+ /// queue but doesn't send them anywhere. In the future, the
+ /// NameChangeSender will be used to deliver requests to the other module.
+ void sendNameChangeRequests();
/// @brief Attempts to renew received addresses
///
@@ -272,7 +394,10 @@ protected:
/// as IA_NA/IAADDR to reply packet.
/// @param renew client's message asking for renew
/// @param reply server's response
- void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply);
+ /// @param fqdn A DHCPv6 Client FQDN %Option generated in the response to the
+ /// client's FQDN option.
+ void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply,
+ const Option6ClientFqdnPtr& fqdn);
/// @brief Attempts to release received addresses
///
@@ -321,6 +446,19 @@ protected:
/// @return string representation
static std::string duidToString(const OptionPtr& opt);
+
+ /// @brief dummy wrapper around IfaceMgr::receive6
+ ///
+ /// This method is useful for testing purposes, where its replacement
+ /// simulates reception of a packet. For that purpose it is protected.
+ virtual Pkt6Ptr receivePacket(int timeout);
+
+ /// @brief dummy wrapper around IfaceMgr::send()
+ ///
+ /// This method is useful for testing purposes, where its replacement
+ /// simulates transmission of a packet. For that purpose it is protected.
+ virtual void sendPacket(const Pkt6Ptr& pkt);
+
private:
/// @brief Allocation Engine.
/// Pointer to the allocation engine that we are currently using
@@ -334,6 +472,20 @@ private:
/// Indicates if shutdown is in progress. Setting it to true will
/// initiate server shutdown procedure.
volatile bool shutdown_;
+
+ /// Indexes for registered hook points
+ int hook_index_pkt6_receive_;
+ int hook_index_subnet6_select_;
+ int hook_index_pkt6_send_;
+
+ /// UDP port number on which server listens.
+ uint16_t port_;
+
+protected:
+
+ /// Holds a list of @c isc::dhcp_ddns::NameChangeRequest objects, which
+ /// are waiting for sending to b10-dhcp-ddns module.
+ std::queue<isc::dhcp_ddns::NameChangeRequest> name_change_reqs_;
};
}; // namespace isc::dhcp
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index feb4bfa..7ea7163 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -7,7 +7,7 @@ EXTRA_DIST = $(PYTESTS)
# about python not being able to load liblog library.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
@@ -27,7 +27,8 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/bin
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
-CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
+CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
+CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
AM_CXXFLAGS = $(B10_CXXFLAGS)
if USE_CLANGPP
@@ -44,18 +45,32 @@ TESTS_ENVIRONMENT = \
TESTS =
if HAVE_GTEST
+# Build shared libraries for testing.
+lib_LTLIBRARIES = libco1.la libco2.la
-TESTS += dhcp6_unittests
+libco1_la_SOURCES = callout_library_1.cc callout_library_common.h
+libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco1_la_CPPFLAGS = $(AM_CPPFLAGS)
+
+libco2_la_SOURCES = callout_library_2.cc callout_library_common.h
+libco2_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco2_la_CPPFLAGS = $(AM_CPPFLAGS)
+TESTS += dhcp6_unittests
dhcp6_unittests_SOURCES = dhcp6_unittests.cc
dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
+dhcp6_unittests_SOURCES += hooks_unittest.cc
+dhcp6_unittests_SOURCES += dhcp6_test_utils.h
dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
dhcp6_unittests_SOURCES += config_parser_unittest.cc
+dhcp6_unittests_SOURCES += marker_file.cc
dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc
dhcp6_unittests_SOURCES += ../config_parser.cc ../config_parser.h
-nodist_dhcp6_unittests_SOURCES = ../dhcp6_messages.h ../dhcp6_messages.cc
+
+nodist_dhcp6_unittests_SOURCES = ../dhcp6_messages.h ../dhcp6_messages.cc
+nodist_dhcp6_unittests_SOURCES += marker_file.h test_libraries.h
dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
@@ -64,7 +79,9 @@ dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
diff --git a/src/bin/dhcp6/tests/callout_library_1.cc b/src/bin/dhcp6/tests/callout_library_1.cc
new file mode 100644
index 0000000..471bb6f
--- /dev/null
+++ b/src/bin/dhcp6/tests/callout_library_1.cc
@@ -0,0 +1,22 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Marker file callout library
+///
+/// This is the source of a test library for the DHCP parser and configuration
+/// tests. See callout_common.cc for details.
+
+static const int LIBRARY_NUMBER = 1;
+#include "callout_library_common.h"
diff --git a/src/bin/dhcp6/tests/callout_library_2.cc b/src/bin/dhcp6/tests/callout_library_2.cc
new file mode 100644
index 0000000..b0b4637
--- /dev/null
+++ b/src/bin/dhcp6/tests/callout_library_2.cc
@@ -0,0 +1,22 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Marker file callout library
+///
+/// This is the source of a test library for the DHCP parser and configuration
+/// tests. See callout_common.cc for details.
+
+static const int LIBRARY_NUMBER = 2;
+#include "callout_library_common.h"
diff --git a/src/bin/dhcp6/tests/callout_library_common.h b/src/bin/dhcp6/tests/callout_library_common.h
new file mode 100644
index 0000000..cbabcda
--- /dev/null
+++ b/src/bin/dhcp6/tests/callout_library_common.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Marker file callout library
+///
+/// This is the source of a test library for the DHCP parser and configuration
+/// tests.
+///
+/// To check that they libraries are loaded and unloaded correctly, the load
+/// and unload functions in this library maintain two marker files - the load
+/// marker file and the unload marker file. The functions append a single
+/// line to the file, creating the file if need be. In this way, the test code
+/// can determine whether the load/unload functions have been run and, if so,
+/// in what order.
+///
+/// This file is the common library file for the tests. It will not compile
+/// by itself - it is included into each callout library which specifies the
+/// missing constant LIBRARY_NUMBER before the inclusion.
+
+#include <hooks/hooks.h>
+#include "marker_file.h"
+
+#include <fstream>
+
+using namespace isc::hooks;
+using namespace std;
+
+extern "C" {
+
+/// @brief Append digit to marker file
+///
+/// If the marker file does not exist, create it. Then append the single
+/// digit (given by the constant LIBRARY_NUMBER) defined earlier to it and
+/// close the file.
+///
+/// @param name Name of the file to open
+///
+/// @return 0 on success, non-zero on error.
+int
+appendDigit(const char* name) {
+ // Open the file and check if successful.
+ fstream file(name, fstream::out | fstream::app);
+ if (!file.good()) {
+ return (1);
+ }
+
+ // Add the library number to it and close.
+ file << LIBRARY_NUMBER;
+ file.close();
+
+ return (0);
+}
+
+// Framework functions
+int
+version() {
+ return (BIND10_HOOKS_VERSION);
+}
+
+int
+load(LibraryHandle&) {
+ return (appendDigit(LOAD_MARKER_FILE));
+}
+
+int
+unload() {
+ return (appendDigit(UNLOAD_MARKER_FILE));
+}
+
+};
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index 4430cb2..0c46d26 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -24,22 +24,32 @@
#include <dhcp6/dhcp6_srv.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/subnet.h>
+#include <hooks/hooks_manager.h>
+
+#include "test_libraries.h"
+#include "marker_file.h"
#include <boost/foreach.hpp>
#include <gtest/gtest.h>
#include <fstream>
#include <iostream>
+#include <fstream>
#include <sstream>
+#include <string>
+#include <vector>
#include <arpa/inet.h>
+#include <unistd.h>
-using namespace std;
using namespace isc;
-using namespace isc::dhcp;
using namespace isc::asiolink;
-using namespace isc::data;
using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::hooks;
+using namespace std;
namespace {
@@ -66,11 +76,27 @@ public:
<< " while the test assumes that it doesn't, to execute"
<< " some negative scenarios. Can't continue this test.";
}
+
+ // Reset configuration for each test.
+ resetConfiguration();
+ }
+
+ // Check that no hooks libraries are loaded. This is a pre-condition for
+ // a number of tests, so is checked in one place. As this uses an
+ // ASSERT call - and it is not clear from the documentation that Gtest
+ // predicates can be used in a constructor - the check is placed in SetUp.
+ void SetUp() {
+ std::vector<std::string> libraries = HooksManager::getLibraryNames();
+ ASSERT_TRUE(libraries.empty());
}
~Dhcp6ParserTest() {
// Reset configuration database after each test.
resetConfiguration();
+
+ // ... and delete the hooks library marker files if present
+ unlink(LOAD_MARKER_FILE);
+ unlink(UNLOAD_MARKER_FILE);
};
// Checks if config_result (result of DHCP server configuration) has
@@ -88,7 +114,7 @@ public:
/// option value. These parameters are: "name", "code", "data" and
/// "csv-format".
///
- /// @param param_value string holiding option parameter value to be
+ /// @param param_value string holding option parameter value to be
/// injected into the configuration string.
/// @param parameter name of the parameter to be configured with
/// param value.
@@ -133,7 +159,7 @@ public:
std::string>& params)
{
std::ostringstream stream;
- stream << "{ \"interface\": [ \"all\" ],"
+ stream << "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -169,50 +195,80 @@ public:
return (stream.str());
}
- /// @brief Reset configuration database.
+ /// @brief Parse and Execute configuration
///
- /// This function resets configuration data base by
- /// removing all subnets and option-data. Reset must
- /// be performed after each test to make sure that
- /// contents of the database do not affect result of
- /// subsequent tests.
- void resetConfiguration() {
+ /// Parses a configuration and executes a configuration of the server.
+ /// If the operation fails, the current test will register a failure.
+ ///
+ /// @param config Configuration to parse
+ /// @param operation Operation being performed. In the case of an error,
+ /// the error text will include the string "unable to <operation>.".
+ ///
+ /// @return true if the configuration succeeded, false if not. In the
+ /// latter case, a failure will have been added to the current test.
+ bool
+ executeConfiguration(const std::string& config, const char* operation) {
ConstElementPtr status;
-
- string config = "{ \"interface\": [ \"all\" ],"
- "\"preferred-lifetime\": 3000,"
- "\"rebind-timer\": 2000, "
- "\"renew-timer\": 1000, "
- "\"valid-lifetime\": 4000, "
- "\"subnet6\": [ ], "
- "\"option-def\": [ ], "
- "\"option-data\": [ ] }";
-
try {
ElementPtr json = Element::fromJSON(config);
status = configureDhcp6Server(srv_, json);
} catch (const std::exception& ex) {
- FAIL() << "Fatal error: unable to reset configuration database"
- << " after the test. The following configuration was used"
- << " to reset database: " << std::endl
+ ADD_FAILURE() << "Unable to " << operation << ". "
+ << "The following configuration was used: " << std::endl
<< config << std::endl
<< " and the following error message was returned:"
<< ex.what() << std::endl;
+ return (false);
}
- // status object must not be NULL
+ // The status object must not be NULL
if (!status) {
- FAIL() << "Fatal error: unable to reset configuration database"
- << " after the test. Configuration function returned"
- << " NULL pointer" << std::endl;
+ ADD_FAILURE() << "Unable to " << operation << ". "
+ << "The configuration function returned a null pointer.";
+ return (false);
}
+
+ // Store the answer if we need it.
+
+ // Returned value should be 0 (configuration success)
comment_ = parseAnswer(rcode_, status);
- // returned value should be 0 (configuration success)
if (rcode_ != 0) {
- FAIL() << "Fatal error: unable to reset configuration database"
- << " after the test. Configuration function returned"
- << " error code " << rcode_ << std::endl;
+ string reason = "";
+ if (comment_) {
+ reason = string(" (") + comment_->stringValue() + string(")");
+ }
+ ADD_FAILURE() << "Unable to " << operation << ". "
+ << "The configuration function returned error code "
+ << rcode_ << reason;
+ return (false);
}
+
+ return (true);
+ }
+
+ /// @brief Reset configuration database.
+ ///
+ /// This function resets configuration data base by removing all subnets
+ /// option-data, and hooks libraries. The reset must be performed after each
+ /// test to make sure that contents of the database do not affect the
+ /// results of subsequent tests.
+ void resetConfiguration() {
+ string config = "{ \"interfaces\": [ \"all\" ],"
+ "\"hooks-libraries\": [ ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet6\": [ ], "
+ "\"option-def\": [ ], "
+ "\"option-data\": [ ] }";
+ static_cast<void>(executeConfiguration(config,
+ "reset configuration database"));
+ // The default setting is to listen on all interfaces. In order to
+ // properly test interface configuration we disable listening on
+ // all interfaces before each test and later check that this setting
+ // has been overriden by the configuration used in the test.
+ CfgMgr::instance().deleteActiveIfaces();
}
/// @brief Test invalid option parameter value.
@@ -277,13 +333,11 @@ public:
expected_data_len));
}
- int rcode_;
- Dhcpv6Srv srv_;
-
- ConstElementPtr comment_;
-
- string valid_iface_;
- string bogus_iface_;
+ int rcode_; ///< Return code (see @ref isc::config::parseAnswer)
+ Dhcpv6Srv srv_; ///< Instance of the Dhcp6Srv used during tests
+ ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
+ string valid_iface_; ///< Valid network interface name (present in system)
+ string bogus_iface_; ///< invalid network interface name (not in system)
};
// Goal of this test is a verification if a very simple config update
@@ -324,7 +378,7 @@ TEST_F(Dhcp6ParserTest, emptySubnet) {
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
- Element::fromJSON("{ \"interface\": [ \"all\" ],"
+ Element::fromJSON("{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -343,7 +397,7 @@ TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
ConstElementPtr status;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -377,7 +431,7 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
ConstElementPtr status;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -415,7 +469,7 @@ TEST_F(Dhcp6ParserTest, subnetInterface) {
// There should be at least one interface
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -448,7 +502,7 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {
// There should be at least one interface
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -479,7 +533,7 @@ TEST_F(Dhcp6ParserTest, interfaceGlobal) {
ConstElementPtr status;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -500,13 +554,111 @@ TEST_F(Dhcp6ParserTest, interfaceGlobal) {
EXPECT_EQ(1, rcode_);
}
+
+// This test checks if it is possible to define a subnet with an
+// interface-id option defined.
+TEST_F(Dhcp6ParserTest, subnetInterfaceId) {
+
+ const string valid_interface_id = "foobar";
+ const string bogus_interface_id = "blah";
+
+ // There should be at least one interface
+
+ const string config = "{ "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+ " \"interface-id\": \"" + valid_interface_id + "\","
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // Returned value should be 0 (configuration success)
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(0, rcode_);
+
+ // Try to get a subnet based on bogus interface-id option
+ OptionBuffer tmp(bogus_interface_id.begin(), bogus_interface_id.end());
+ OptionPtr ifaceid(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(ifaceid);
+ EXPECT_FALSE(subnet);
+
+ // Now try to get subnet for valid interface-id value
+ tmp = OptionBuffer(valid_interface_id.begin(), valid_interface_id.end());
+ ifaceid.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ subnet = CfgMgr::instance().getSubnet6(ifaceid);
+ ASSERT_TRUE(subnet);
+ EXPECT_TRUE(ifaceid->equal(subnet->getInterfaceId()));
+}
+
+
+// This test checks if it is not allowed to define global interface
+// parameter.
+TEST_F(Dhcp6ParserTest, interfaceIdGlobal) {
+
+ const string config = "{ \"interfaces\": [ \"*\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"interface-id\": \"foobar\"," // Not valid. Can be defined in subnet only
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // Returned value should be 1 (parse error)
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(1, rcode_);
+}
+
+// This test checks if it is not possible to define a subnet with an
+// interface (i.e. local subnet) and interface-id (remote subnet) defined.
+TEST_F(Dhcp6ParserTest, subnetInterfaceAndInterfaceId) {
+
+ const string config = "{ \"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+ " \"interface\": \"" + valid_iface_ + "\","
+ " \"interface-id\": \"foobar\","
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // Returned value should be 1 (configuration error)
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(1, rcode_);
+
+}
+
+
+
// Test verifies that a subnet with pool values that do not belong to that
// pool are rejected.
TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
ConstElementPtr status;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -534,7 +686,7 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
ConstElementPtr x;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -598,7 +750,7 @@ TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType());
}
-// The goal of this test is to check whether an option definiiton
+// The goal of this test is to check whether an option definition
// that defines an option carrying a record of data fields can
// be created.
TEST_F(Dhcp6ParserTest, optionDefRecord) {
@@ -1054,7 +1206,7 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
// configuration does not include options configuration.
TEST_F(Dhcp6ParserTest, optionDataDefaults) {
ConstElementPtr x;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
@@ -1136,7 +1288,7 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
// The definition is not required for the option that
// belongs to the 'dhcp6' option space as it is the
// standard option.
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1176,7 +1328,7 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
ASSERT_TRUE(status);
checkResult(status, 0);
- // Options should be now availabe for the subnet.
+ // Options should be now available for the subnet.
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
// Try to get the option from the space dhcp6.
@@ -1214,7 +1366,7 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
// at the very end (when all other parameters are configured).
// Starting stage 1. Configure sub-options and their definitions.
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1263,7 +1415,7 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
// the configuration from the stage 2 is repeated because BIND
// configuration manager sends whole configuration for the lists
// where at least one element is being modified or added.
- config = "{ \"interface\": [ \"all\" ],"
+ config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1357,7 +1509,7 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
// for multiple subnets.
TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
ConstElementPtr x;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -1484,7 +1636,7 @@ TEST_F(Dhcp6ParserTest, optionDataInvalidChar) {
testInvalidOptionParam("01020R", "data");
}
-// Verify that option data containins '0x' prefix is rejected
+// Verify that option data containing '0x' prefix is rejected
// by the configuration.
TEST_F(Dhcp6ParserTest, optionDataUnexpectedPrefix) {
// Option code 0 is reserved and should not be accepted
@@ -1582,7 +1734,7 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
boost::shared_ptr<Option6IA> optionIA =
boost::dynamic_pointer_cast<Option6IA>(option);
// If cast is unsuccessful than option returned was of a
- // differnt type than Option6IA. This is wrong.
+ // different type than Option6IA. This is wrong.
ASSERT_TRUE(optionIA);
// If cast was successful we may use accessors exposed by
// Option6IA to validate that the content of this option
@@ -1600,7 +1752,7 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
// In the first stahe we create definitions of suboptions
// that we will add to the base option.
// Let's create some dummy options: foo and foo2.
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1653,7 +1805,7 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
// We add our dummy options to this option space and thus
// they should be included as sub-options in the 'vendor-opts'
// option.
- config = "{ \"interface\": [ \"all\" ],"
+ config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1752,4 +1904,225 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
EXPECT_FALSE(desc.option->getOption(112));
}
+// Tests of the hooks libraries configuration. All tests have the pre-
+// condition (checked in the test fixture's SetUp() method) that no hooks
+// libraries are loaded at the start of the tests.
+
+
+// Helper function to return a configuration containing an arbitrary number
+// of hooks libraries.
+std::string
+buildHooksLibrariesConfig(const std::vector<std::string>& libraries) {
+ const string quote("\"");
+
+ // Create the first part of the configuration string.
+ string config =
+ "{ \"interfaces\": [ \"all\" ],"
+ "\"hooks-libraries\": [";
+
+ // Append the libraries (separated by commas if needed)
+ for (int i = 0; i < libraries.size(); ++i) {
+ if (i > 0) {
+ config += string(", ");
+ }
+ config += (quote + libraries[i] + quote);
+ }
+
+ // Append the remainder of the configuration.
+ config += string(
+ "],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"vendor-opts-space\","
+ " \"code\": 110,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"space\": \"vendor-opts-space\","
+ " \"code\": 111,"
+ " \"data\": \"192.168.2.1\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 110,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"vendor-opts-space\","
+ " \"encapsulate\": \"\""
+ " },"
+ " {"
+ " \"name\": \"foo2\","
+ " \"code\": 111,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"vendor-opts-space\","
+ " \"encapsulate\": \"\""
+ " } ]"
+ "}");
+
+ return (config);
+}
+
+// Convenience function for creating hooks library configuration with one or
+// two character string constants.
+std::string
+buildHooksLibrariesConfig(const char* library1 = NULL,
+ const char* library2 = NULL) {
+ std::vector<std::string> libraries;
+ if (library1 != NULL) {
+ libraries.push_back(string(library1));
+ if (library2 != NULL) {
+ libraries.push_back(string(library2));
+ }
+ }
+ return (buildHooksLibrariesConfig(libraries));
+}
+
+
+// The goal of this test is to verify the configuration of hooks libraries if
+// none are specified.
+TEST_F(Dhcp6ParserTest, NoHooksLibraries) {
+ // Parse a configuration containing no names.
+ string config = buildHooksLibrariesConfig();
+ if (!executeConfiguration(config,
+ "set configuration with no hooks libraries")) {
+ FAIL() << "Unable to execute configuration";
+
+ } else {
+ // No libraries should be loaded at the end of the test.
+ std::vector<std::string> libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(libraries.empty());
+ }
+}
+
+// Verify parsing fails with one library that will fail validation.
+TEST_F(Dhcp6ParserTest, InvalidLibrary) {
+ // Parse a configuration containing a failing library.
+ string config = buildHooksLibrariesConfig(NOT_PRESENT_LIBRARY);
+
+ ConstElementPtr status;
+ ElementPtr json = Element::fromJSON(config);
+ ASSERT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // The status object must not be NULL
+ ASSERT_TRUE(status);
+
+ // Returned value should not be 0
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_NE(0, rcode_);
+}
+
+// Verify the configuration of hooks libraries with two being specified.
+TEST_F(Dhcp6ParserTest, LibrariesSpecified) {
+ // Marker files should not be present.
+ EXPECT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Set up the configuration with two libraries and load them.
+ string config = buildHooksLibrariesConfig(CALLOUT_LIBRARY_1,
+ CALLOUT_LIBRARY_2);
+ ASSERT_TRUE(executeConfiguration(config,
+ "load two valid libraries"));
+
+ // Expect two libraries to be loaded in the correct order (load marker file
+ // is present, no unload marker file).
+ std::vector<std::string> libraries = HooksManager::getLibraryNames();
+ ASSERT_EQ(2, libraries.size());
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Unload the libraries. The load file should not have changed, but
+ // the unload one should indicate the unload() functions have been run.
+ config = buildHooksLibrariesConfig();
+ ASSERT_TRUE(executeConfiguration(config, "unloading libraries"));
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+ EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "21"));
+
+ // Expect the hooks system to say that none are loaded.
+ libraries = HooksManager::getLibraryNames();
+ EXPECT_TRUE(libraries.empty());
+
+}
+
+
+// This test verifies that it is possible to select subset of interfaces on
+// which server should listen.
+TEST_F(Dhcp6ParserTest, selectedInterfaces) {
+
+ // Make sure there is no garbage interface configuration in the CfgMgr.
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+
+ ConstElementPtr status;
+
+ string config = "{ \"interfaces\": [ \"eth0\", \"eth1\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000 }";
+
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // returned value must be 1 (values error)
+ // as the pool does not belong to that subnet
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(0, rcode_);
+
+ // eth0 and eth1 were explicitly selected. eth2 was not.
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
+ EXPECT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+}
+
+// This test verifies that it is possible to configure the server to listen on
+// all interfaces.
+TEST_F(Dhcp6ParserTest, allInterfaces) {
+
+ // Make sure there is no garbage interface configuration in the CfgMgr.
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+
+ ConstElementPtr status;
+
+ // This configuration specifies two interfaces on which server should listen
+ // bu also includes keyword 'all'. This keyword switches server into the
+ // mode when it listens on all interfaces regardless of what interface names
+ // were specified in the "interfaces" parameter.
+ string config = "{ \"interfaces\": [ \"eth0\", \"eth1\", \"*\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000 }";
+
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // returned value must be 1 (values error)
+ // as the pool does not belong to that subnet
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(0, rcode_);
+
+ // All interfaces should be now active.
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
+ EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2"));
+}
+
+
};
diff --git a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
index ea359fc..6dfb981 100644
--- a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -14,29 +14,37 @@
#include <config.h>
+#include <config/ccsession.h>
#include <dhcp/dhcp6.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
-#include <config/ccsession.h>
+#include <hooks/hooks_manager.h>
+
+#include "marker_file.h"
+#include "test_libraries.h"
+#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
-#include <iostream>
#include <fstream>
+#include <iostream>
#include <sstream>
#include <arpa/inet.h>
+#include <unistd.h>
using namespace std;
using namespace isc;
-using namespace isc::dhcp;
using namespace isc::asiolink;
-using namespace isc::data;
using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+using namespace isc::hooks;
namespace {
class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
- // "naked" DHCPv6 server, exposes internal fields
+ // "Naked" DHCPv6 server, exposes internal fields
public:
NakedControlledDhcpv6Srv():ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000) { }
};
@@ -44,29 +52,44 @@ public:
class CtrlDhcpv6SrvTest : public ::testing::Test {
public:
CtrlDhcpv6SrvTest() {
+ reset();
}
~CtrlDhcpv6SrvTest() {
+ reset();
};
+
+ /// @brief Reset hooks data
+ ///
+ /// Resets the data for the hooks-related portion of the test by ensuring
+ /// that no libraries are loaded and that any marker files are deleted.
+ void reset() {
+ // Unload any previously-loaded libraries.
+ HooksManager::unloadLibraries();
+
+ // Get rid of any marker files.
+ static_cast<void>(unlink(LOAD_MARKER_FILE));
+ static_cast<void>(unlink(UNLOAD_MARKER_FILE));
+ }
};
TEST_F(CtrlDhcpv6SrvTest, commands) {
- ControlledDhcpv6Srv* srv = NULL;
- ASSERT_NO_THROW({
- srv = new ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000);
- });
+ boost::scoped_ptr<ControlledDhcpv6Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000))
+ );
- // use empty parameters list
+ // Use empty parameters list
ElementPtr params(new isc::data::MapElement());
int rcode = -1;
- // case 1: send bogus command
+ // Case 1: send bogus command
ConstElementPtr result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("blah", params);
ConstElementPtr comment = parseAnswer(rcode, result);
EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
- // case 2: send shutdown command without any parameters
+ // Case 2: send shutdown command without any parameters
result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params);
comment = parseAnswer(rcode, result);
EXPECT_EQ(0, rcode); // expect success
@@ -75,13 +98,54 @@ TEST_F(CtrlDhcpv6SrvTest, commands) {
ConstElementPtr x(new isc::data::IntElement(pid));
params->set("pid", x);
- // case 3: send shutdown command with 1 parameter: pid
+ // Case 3: send shutdown command with 1 parameter: pid
result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params);
comment = parseAnswer(rcode, result);
- EXPECT_EQ(0, rcode); // expect success
+ EXPECT_EQ(0, rcode); // Expect success
+}
+
+// Check that the "libreload" command will reload libraries
+
+TEST_F(CtrlDhcpv6SrvTest, libreload) {
+ // Ensure no marker files to start with.
+ ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
+ ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+
+ // Load two libraries
+ std::vector<std::string> libraries;
+ libraries.push_back(CALLOUT_LIBRARY_1);
+ libraries.push_back(CALLOUT_LIBRARY_2);
+ HooksManager::loadLibraries(libraries);
+
+ // Check they are loaded.
+ std::vector<std::string> loaded_libraries =
+ HooksManager::getLibraryNames();
+ ASSERT_TRUE(libraries == loaded_libraries);
+
+ // ... which also included checking that the marker file created by the
+ // load functions exists and holds the correct value (of "12" - the
+ // first library appends "1" to the file, the second appends "2"). Also
+ // check that the unload marker file does not yet exist.
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "12"));
+ EXPECT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
+ // Now execute the "libreload" command. This should cause the libraries
+ // to unload and to reload.
+
+ // Use empty parameters list
+ ElementPtr params(new isc::data::MapElement());
+ int rcode = -1;
+
+ ConstElementPtr result =
+ ControlledDhcpv6Srv::execDhcpv6ServerCommand("libreload", params);
+ ConstElementPtr comment = parseAnswer(rcode, result);
+ EXPECT_EQ(0, rcode); // Expect success
- delete srv;
+ // Check that the libraries have unloaded and reloaded. The libraries are
+ // unloaded in the reverse order to which they are loaded. When they load,
+ // they should append information to the loading marker file.
+ EXPECT_TRUE(checkMarkerFile(UNLOAD_MARKER_FILE, "21"));
+ EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "1212"));
}
-} // end of anonymous namespace
+} // End of anonymous namespace
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index b742a13..c3435af 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -15,24 +15,29 @@
#include <config.h>
#include <asiolink/io_address.h>
-#include <config/ccsession.h>
+#include <dhcp_ddns/ncr_msg.h>
#include <dhcp/dhcp6.h>
#include <dhcp/duid.h>
#include <dhcp/option.h>
#include <dhcp/option_custom.h>
#include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_client_fqdn.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option_int_array.h>
+#include <dhcp/iface_mgr.h>
#include <dhcp6/config_parser.h>
-#include <dhcp6/dhcp6_srv.h>
+#include <dhcp/dhcp6.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/utils.h>
#include <util/buffer.h>
#include <util/range_utilities.h>
+#include <hooks/server_hooks.h>
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <boost/pointer_cast.hpp>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <unistd.h>
@@ -41,247 +46,385 @@
#include <sstream>
using namespace isc;
+using namespace isc::test;
using namespace isc::asiolink;
-using namespace isc::config;
-using namespace isc::data;
using namespace isc::dhcp;
+using namespace isc::dhcp_ddns;
using namespace isc::util;
+using namespace isc::hooks;
using namespace std;
// namespace has to be named, because friends are defined in Dhcpv6Srv class
// Maybe it should be isc::test?
namespace {
-class NakedDhcpv6Srv: public Dhcpv6Srv {
- // "naked" Interface Manager, exposes internal members
+// This is a test fixture class for testing the processing of the DHCPv6 Client
+// FQDN Option.
+class FqdnDhcpv6SrvTest : public Dhcpv6SrvTest {
public:
- NakedDhcpv6Srv(uint16_t port) : Dhcpv6Srv(port) {
- // Open the "memfile" database for leases
- std::string memfile = "type=memfile";
- LeaseMgrFactory::create(memfile);
- }
+ // Constructor
+ FqdnDhcpv6SrvTest()
+ : Dhcpv6SrvTest() {
+ // generateClientId assigns DUID to duid_.
+ generateClientId();
+ lease_.reset(new Lease6(Lease6::LEASE_IA_NA, IOAddress("2001:db8:1::1"),
+ duid_, 1234, 501, 502, 503,
+ 504, 1, 0));
- virtual ~NakedDhcpv6Srv() {
- // Close the lease database
- LeaseMgrFactory::destroy();
}
- using Dhcpv6Srv::processSolicit;
- using Dhcpv6Srv::processRequest;
- using Dhcpv6Srv::processRenew;
- using Dhcpv6Srv::processRelease;
- using Dhcpv6Srv::createStatusCode;
- using Dhcpv6Srv::selectSubnet;
- using Dhcpv6Srv::sanityCheck;
- using Dhcpv6Srv::loadServerID;
- using Dhcpv6Srv::writeServerID;
-};
+ // Destructor
+ virtual ~FqdnDhcpv6SrvTest() {
+ }
-static const char* DUID_FILE = "server-id-test.txt";
+ // Construct the DHCPv6 Client FQDN Option using flags and domain-name.
+ Option6ClientFqdnPtr
+ createClientFqdn(const uint8_t flags,
+ const std::string& fqdn_name,
+ const Option6ClientFqdn::DomainNameType fqdn_type) {
+ return (Option6ClientFqdnPtr(new Option6ClientFqdn(flags,
+ fqdn_name,
+ fqdn_type)));
+ }
-class Dhcpv6SrvTest : public ::testing::Test {
-public:
- /// Name of the server-id file (used in server-id tests)
+ // Create a message holding DHCPv6 Client FQDN Option.
+ Pkt6Ptr generatePktWithFqdn(uint8_t msg_type,
+ const uint8_t fqdn_flags,
+ const std::string& fqdn_domain_name,
+ const Option6ClientFqdn::DomainNameType
+ fqdn_type,
+ const bool include_oro,
+ OptionPtr srvid = OptionPtr()) {
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234));
+ pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+ Option6IAPtr ia = generateIA(234, 1500, 3000);
+
+ if (msg_type != DHCPV6_REPLY) {
+ IOAddress hint("2001:db8:1:1::dead:beef");
+ OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500));
+ ia->addOption(hint_opt);
+ pkt->addOption(ia);
+ }
- // these are empty for now, but let's keep them around
- Dhcpv6SrvTest() : rcode_(-1) {
- subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
- 2000, 3000, 4000));
- pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
- subnet_->addPool(pool_);
+ OptionPtr clientid = generateClientId();
+ pkt->addOption(clientid);
+ if (srvid && (msg_type != DHCPV6_SOLICIT)) {
+ pkt->addOption(srvid);
+ }
- CfgMgr::instance().deleteSubnets6();
- CfgMgr::instance().addSubnet6(subnet_);
+ pkt->addOption(createClientFqdn(fqdn_flags, fqdn_domain_name,
+ fqdn_type));
- // it's ok if that fails. There should not be such a file anyway
- unlink(DUID_FILE);
- }
+ if (include_oro) {
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6,
+ D6O_ORO));
+ oro->addValue(D6O_CLIENT_FQDN);
+ pkt->addOption(oro);
+ }
- // Generate IA_NA option with specified parameters
- boost::shared_ptr<Option6IA> generateIA(uint32_t iaid, uint32_t t1, uint32_t t2) {
- boost::shared_ptr<Option6IA> ia =
- boost::shared_ptr<Option6IA>(new Option6IA(D6O_IA_NA, iaid));
- ia->setT1(t1);
- ia->setT2(t2);
- return (ia);
+ return (pkt);
}
- // Generate client-id option
- OptionPtr generateClientId(size_t duid_size = 32) {
-
- OptionBuffer clnt_duid(duid_size);
- for (int i = 0; i < duid_size; i++) {
- clnt_duid[i] = 100 + i;
+ // Creates instance of the DHCPv6 message with client id and server id.
+ Pkt6Ptr generateMessageWithIds(const uint8_t msg_type,
+ NakedDhcpv6Srv& srv) {
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(msg_type, 1234));
+ // Generate client-id.
+ OptionPtr opt_clientid = generateClientId();
+ pkt->addOption(opt_clientid);
+
+ if (msg_type != DHCPV6_SOLICIT) {
+ // Generate server-id.
+ pkt->addOption(srv.getServerID());
}
- duid_ = DuidPtr(new DUID(clnt_duid));
-
- return (OptionPtr(new Option(Option::V6, D6O_CLIENTID,
- clnt_duid.begin(),
- clnt_duid.begin() + duid_size)));
+ return (pkt);
}
- // Checks if server response (ADVERTISE or REPLY) includes proper server-id.
- void checkServerId(const Pkt6Ptr& rsp, const OptionPtr& expected_srvid) {
- // check that server included its server-id
- OptionPtr tmp = rsp->getOption(D6O_SERVERID);
- EXPECT_EQ(tmp->getType(), expected_srvid->getType() );
- ASSERT_EQ(tmp->len(), expected_srvid->len() );
- EXPECT_TRUE(tmp->getData() == expected_srvid->getData());
+ // Returns an instance of the option carrying FQDN.
+ Option6ClientFqdnPtr getClientFqdnOption(const Pkt6Ptr& pkt) {
+ return (boost::dynamic_pointer_cast<Option6ClientFqdn>
+ (pkt->getOption(D6O_CLIENT_FQDN)));
}
- // Checks if server response (ADVERTISE or REPLY) includes proper client-id.
- void checkClientId(const Pkt6Ptr& rsp, const OptionPtr& expected_clientid) {
- // check that server included our own client-id
- OptionPtr tmp = rsp->getOption(D6O_CLIENTID);
- ASSERT_TRUE(tmp);
- EXPECT_EQ(expected_clientid->getType(), tmp->getType());
- ASSERT_EQ(expected_clientid->len(), tmp->len());
+ // Adds IA option to the message. Option holds an address.
+ void addIA(const uint32_t iaid, const IOAddress& addr, Pkt6Ptr& pkt) {
+ Option6IAPtr opt_ia = generateIA(iaid, 1500, 3000);
+ Option6IAAddrPtr opt_iaaddr(new Option6IAAddr(D6O_IAADDR, addr,
+ 300, 500));
+ opt_ia->addOption(opt_iaaddr);
+ pkt->addOption(opt_ia);
+ }
- // check that returned client-id is valid
- EXPECT_TRUE(expected_clientid->getData() == tmp->getData());
+ // Adds IA option to the message. Option holds status code.
+ void addIA(const uint32_t iaid, const uint16_t status_code, Pkt6Ptr& pkt) {
+ Option6IAPtr opt_ia = generateIA(iaid, 1500, 3000);
+ addStatusCode(status_code, "", opt_ia);
+ pkt->addOption(opt_ia);
}
- // Checks that server response (ADVERTISE or REPLY) contains proper IA_NA option
- // It returns IAADDR option for each chaining with checkIAAddr method.
- boost::shared_ptr<Option6IAAddr> checkIA_NA(const Pkt6Ptr& rsp, uint32_t expected_iaid,
- uint32_t expected_t1, uint32_t expected_t2) {
- OptionPtr tmp = rsp->getOption(D6O_IA_NA);
- // Can't use ASSERT_TRUE() in method that returns something
- if (!tmp) {
- ADD_FAILURE() << "IA_NA option not present in response";
- return (boost::shared_ptr<Option6IAAddr>());
+ // Creates status code with the specified code and message.
+ OptionCustomPtr createStatusCode(const uint16_t code,
+ const std::string& msg) {
+ OptionDefinition def("status-code", D6O_STATUS_CODE, "record");
+ def.addRecordField("uint16");
+ def.addRecordField("string");
+ OptionCustomPtr opt_status(new OptionCustom(def, Option::V6));
+ opt_status->writeInteger(code);
+ if (!msg.empty()) {
+ opt_status->writeString(msg, 1);
}
+ return (opt_status);
+ }
- boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
- EXPECT_EQ(expected_iaid, ia->getIAID() );
- EXPECT_EQ(expected_t1, ia->getT1());
- EXPECT_EQ(expected_t2, ia->getT2());
+ // Adds Status Code option to the IA.
+ void addStatusCode(const uint16_t code, const std::string& msg,
+ Option6IAPtr& opt_ia) {
+ opt_ia->addOption(createStatusCode(code, msg));
+ }
- tmp = ia->getOption(D6O_IAADDR);
- boost::shared_ptr<Option6IAAddr> addr = boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
- return (addr);
+ // Test processing of the DHCPv6 Client FQDN Option.
+ void testFqdn(const uint16_t msg_type,
+ const bool use_oro,
+ const uint8_t in_flags,
+ const std::string& in_domain_name,
+ const Option6ClientFqdn::DomainNameType in_domain_type,
+ const uint8_t exp_flags,
+ const std::string& exp_domain_name) {
+ NakedDhcpv6Srv srv(0);
+ Pkt6Ptr question = generatePktWithFqdn(msg_type,
+ in_flags,
+ in_domain_name,
+ in_domain_type,
+ use_oro);
+ ASSERT_TRUE(getClientFqdnOption(question));
+
+ Option6ClientFqdnPtr answ_fqdn;
+ ASSERT_NO_THROW(answ_fqdn = srv.processClientFqdn(question));
+ ASSERT_TRUE(answ_fqdn);
+
+ const bool flag_n = (exp_flags & Option6ClientFqdn::FLAG_N) != 0;
+ const bool flag_s = (exp_flags & Option6ClientFqdn::FLAG_S) != 0;
+ const bool flag_o = (exp_flags & Option6ClientFqdn::FLAG_O) != 0;
+
+ EXPECT_EQ(flag_n, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ(flag_s, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_EQ(flag_o, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_O));
+
+ EXPECT_EQ(exp_domain_name, answ_fqdn->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, answ_fqdn->getDomainNameType());
}
- // Checks that server rejected IA_NA, i.e. that it has no addresses and
- // that expected status code really appears there. In some limited cases
- // (reply to RELEASE) it may be used to verify positive case, where
- // IA_NA response is expected to not include address.
- //
- // Status code indicates type of error encountered (in theory it can also
- // indicate success, but servers typically don't send success status
- // as this is the default result and it saves bandwidth)
- void checkIA_NAStatusCode(const boost::shared_ptr<Option6IA>& ia,
- uint16_t expected_status_code) {
- // Make sure there is no address assigned.
- EXPECT_FALSE(ia->getOption(D6O_IAADDR));
-
- // T1, T2 should be zeroed
- EXPECT_EQ(0, ia->getT1());
- EXPECT_EQ(0, ia->getT2());
-
- boost::shared_ptr<OptionCustom> status =
- boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
-
- // It is ok to not include status success as this is the default behavior
- if (expected_status_code == STATUS_Success && !status) {
+ // Tests that the client message holding an FQDN is processed and the
+ // lease is acquired.
+ void testProcessMessage(const uint8_t msg_type,
+ const std::string& hostname,
+ NakedDhcpv6Srv& srv) {
+ // Create a message of a specified type, add server id and
+ // FQDN option.
+ OptionPtr srvid = srv.getServerID();
+ Pkt6Ptr req = generatePktWithFqdn(msg_type, Option6ClientFqdn::FLAG_S,
+ hostname,
+ Option6ClientFqdn::FULL,
+ true, srvid);
+
+ // For different client's message types we have to invoke different
+ // functions to generate response.
+ Pkt6Ptr reply;
+ if (msg_type == DHCPV6_SOLICIT) {
+ ASSERT_NO_THROW(reply = srv.processSolicit(req));
+
+ } else if (msg_type == DHCPV6_REQUEST) {
+ ASSERT_NO_THROW(reply = srv.processRequest(req));
+
+ } else if (msg_type == DHCPV6_RENEW) {
+ ASSERT_NO_THROW(reply = srv.processRequest(req));
+
+ } else if (msg_type == DHCPV6_RELEASE) {
+ // For Release no lease will be acquired so we have to leave
+ // function here.
+ ASSERT_NO_THROW(reply = srv.processRelease(req));
+ return;
+ } else {
+ // We are not interested in testing other message types.
return;
}
- EXPECT_TRUE(status);
+ // For Solicit, we will get different message type obviously.
+ if (msg_type == DHCPV6_SOLICIT) {
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ } else {
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+ }
- if (status) {
- // We don't have dedicated class for status code, so let's just interpret
- // first 2 bytes as status. Remainder of the status code option content is
- // just a text explanation what went wrong.
- EXPECT_EQ(static_cast<uint16_t>(expected_status_code),
- status->readInteger<uint16_t>(0));
+ // Check verify that IA_NA is correct.
+ Option6IAAddrPtr addr =
+ checkIA_NA(reply, 234, subnet_->getT1(), subnet_->getT2());
+ ASSERT_TRUE(addr);
+
+ // Check that we have got the address we requested.
+ checkIAAddr(addr, IOAddress("2001:db8:1:1::dead:beef"),
+ subnet_->getPreferred(),
+ subnet_->getValid());
+
+ if (msg_type != DHCPV6_SOLICIT) {
+ // Check that the lease exists.
+ Lease6Ptr lease =
+ checkLease(duid_, reply->getOption(D6O_IA_NA), addr);
+ ASSERT_TRUE(lease);
}
}
+ // Verify that NameChangeRequest holds valid values.
+ void verifyNameChangeRequest(NakedDhcpv6Srv& srv,
+ const isc::dhcp_ddns::NameChangeType type,
+ const bool reverse, const bool forward,
+ const std::string& addr,
+ const std::string& dhcid,
+ const uint16_t expires,
+ const uint16_t len) {
+ NameChangeRequest ncr = srv.name_change_reqs_.front();
+ EXPECT_EQ(type, ncr.getChangeType());
+ EXPECT_EQ(forward, ncr.isForwardChange());
+ EXPECT_EQ(reverse, ncr.isReverseChange());
+ EXPECT_EQ(addr, ncr.getIpAddress());
+ EXPECT_EQ(dhcid, ncr.getDhcid().toStr());
+ EXPECT_EQ(expires, ncr.getLeaseExpiresOn());
+ EXPECT_EQ(len, ncr.getLeaseLength());
+ EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr.getStatus());
+ srv.name_change_reqs_.pop();
+ }
- void checkMsgStatusCode(const Pkt6Ptr& msg, uint16_t expected_status) {
- boost::shared_ptr<OptionCustom> status =
- boost::dynamic_pointer_cast<OptionCustom>(msg->getOption(D6O_STATUS_CODE));
+ // Holds a lease used by a test.
+ Lease6Ptr lease_;
- // It is ok to not include status success as this is the default behavior
- if (expected_status == STATUS_Success && !status) {
- return;
- }
+};
- EXPECT_TRUE(status);
- if (status) {
- // We don't have dedicated class for status code, so let's just interpret
- // first 2 bytes as status. Remainder of the status code option content is
- // just a text explanation what went wrong.
- EXPECT_EQ(static_cast<uint16_t>(expected_status),
- status->readInteger<uint16_t>(0));
- }
- }
+// This test verifies that incoming SOLICIT can be handled properly when
+// there are no subnets configured.
+//
+// This test sends a SOLICIT and the expected response
+// is an ADVERTISE with STATUS_NoAddrsAvail and no address provided in the
+// response
+TEST_F(NakedDhcpv6SrvTest, SolicitNoSubnet) {
+ NakedDhcpv6Srv srv(0);
- // Check that generated IAADDR option contains expected address.
- void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
- const IOAddress& expected_addr,
- uint32_t /* expected_preferred */,
- uint32_t /* expected_valid */) {
-
- // Check that the assigned address is indeed from the configured pool.
- // Note that when comparing addresses, we compare the textual
- // representation. IOAddress does not support being streamed to
- // an ostream, which means it can't be used in EXPECT_EQ.
- EXPECT_TRUE(subnet_->inPool(addr->getAddress()));
- EXPECT_EQ(expected_addr.toText(), addr->getAddress().toText());
- EXPECT_EQ(addr->getPreferred(), subnet_->getPreferred());
- EXPECT_EQ(addr->getValid(), subnet_->getValid());
- }
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->addOption(generateIA(234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
- // Basic checks for generated response (message type and transaction-id).
- void checkResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
- uint32_t expected_transid) {
- ASSERT_TRUE(rsp);
- EXPECT_EQ(expected_message_type, rsp->getType());
- EXPECT_EQ(expected_transid, rsp->getTransid());
- }
+ // Pass it to the server and get an advertise
+ Pkt6Ptr reply = srv.processSolicit(sol);
- // Checks if the lease sent to client is present in the database
- Lease6Ptr checkLease(const DuidPtr& duid, const OptionPtr& ia_na,
- boost::shared_ptr<Option6IAAddr> addr) {
- boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(ia_na);
+ // check that we get the right NAK
+ checkNakResponse (reply, DHCPV6_ADVERTISE, 1234, STATUS_NoAddrsAvail);
+}
- Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(addr->getAddress());
- if (!lease) {
- cout << "Lease for " << addr->getAddress().toText()
- << " not found in the database backend.";
- return (Lease6Ptr());
- }
+// This test verifies that incoming REQUEST can be handled properly when
+// there are no subnets configured.
+//
+// This test sends a REQUEST and the expected response
+// is an REPLY with STATUS_NoAddrsAvail and no address provided in the
+// response
+TEST_F(NakedDhcpv6SrvTest, RequestNoSubnet) {
+ NakedDhcpv6Srv srv(0);
- EXPECT_EQ(addr->getAddress().toText(), lease->addr_.toText());
- EXPECT_TRUE(*lease->duid_ == *duid);
- EXPECT_EQ(ia->getIAID(), lease->iaid_);
- EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
+ // Let's create a REQUEST
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(234, 1500, 3000);
- return (lease);
- }
+ // with a hint
+ IOAddress hint("2001:db8:1:1::dead:beef");
+ OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500));
+ ia->addOption(hint_opt);
+ req->addOption(ia);
+ OptionPtr clientid = generateClientId();
+ req->addOption(clientid);
- ~Dhcpv6SrvTest() {
- CfgMgr::instance().deleteSubnets6();
+ // server-id is mandatory in REQUEST
+ req->addOption(srv.getServerID());
- // Let's clean up if there is such a file.
- unlink(DUID_FILE);
- };
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRequest(req);
- // A subnet used in most tests
- Subnet6Ptr subnet_;
+ // check that we get the right NAK
+ checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoAddrsAvail);
+}
- // A pool used in most tests
- Pool6Ptr pool_;
+// This test verifies that incoming RENEW can be handled properly, even when
+// no subnets are configured.
+//
+// This test sends a RENEW and the expected response
+// is an REPLY with STATUS_NoBinding and no address provided in the
+// response
+TEST_F(NakedDhcpv6SrvTest, RenewNoSubnet) {
+ NakedDhcpv6Srv srv(0);
- // A DUID used in most tests (typically as client-id)
- DuidPtr duid_;
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Let's create a RENEW
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+ OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(renewed_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RENEW
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRenew(req);
+
+ // check that we get the right NAK
+ checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoBinding);
+}
+
+// This test verifies that incoming RELEASE can be handled properly, even when
+// no subnets are configured.
+//
+// This test sends a RELEASE and the expected response
+// is an REPLY with STATUS_NoBinding and no address provided in the
+// response
+TEST_F(NakedDhcpv6SrvTest, ReleaseNoSubnet) {
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ // check that we get the right NAK
+ checkNakResponse (reply, DHCPV6_REPLY, 1234, STATUS_NoBinding);
+}
- int rcode_;
- ConstElementPtr comment_;
-};
// Test verifies that the Dhcpv6_srv class can be instantiated. It checks a mode
// without open sockets and with sockets opened on a high port (to not require
@@ -309,7 +452,7 @@ TEST_F(Dhcpv6SrvTest, DUID) {
boost::scoped_ptr<Dhcpv6Srv> srv;
ASSERT_NO_THROW( {
- srv.reset(new Dhcpv6Srv(0));
+ srv.reset(new NakedDhcpv6Srv(0));
});
OptionPtr srvid = srv->getServerID();
@@ -384,7 +527,7 @@ TEST_F(Dhcpv6SrvTest, DUID) {
// and the requested options are actually assigned.
TEST_F(Dhcpv6SrvTest, advertiseOptions) {
ConstElementPtr x;
- string config = "{ \"interface\": [ \"all\" ],"
+ string config = "{ \"interfaces\": [ \"all\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -425,7 +568,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- boost::shared_ptr<Pkt6> adv = srv.processSolicit(sol);
+ Pkt6Ptr adv = srv.processSolicit(sol);
// check if we get response at all
ASSERT_TRUE(adv);
@@ -517,6 +660,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
// check that IA_NA was returned and that there's an address included
boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
+ ASSERT_TRUE(addr);
// Check that the assigned address is indeed from the configured pool
checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
@@ -570,6 +714,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
// check that IA_NA was returned and that there's an address included
boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
+ ASSERT_TRUE(addr);
// check that we've got the address we requested
checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
@@ -618,6 +763,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
// check that IA_NA was returned and that there's an address included
boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
+ ASSERT_TRUE(addr);
// Check that the assigned address is indeed from the configured pool
checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
@@ -679,6 +825,9 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
subnet_->getT2());
boost::shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
subnet_->getT2());
+ ASSERT_TRUE(addr1);
+ ASSERT_TRUE(addr2);
+ ASSERT_TRUE(addr3);
// Check that the assigned address is indeed from the configured pool
checkIAAddr(addr1, addr1->getAddress(), subnet_->getPreferred(), subnet_->getValid());
@@ -749,6 +898,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
// check that IA_NA was returned and that there's an address included
boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
+ ASSERT_TRUE(addr);
// check that we've got the address we requested
checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
@@ -773,6 +923,8 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
TEST_F(Dhcpv6SrvTest, ManyRequests) {
NakedDhcpv6Srv srv(0);
+ ASSERT_TRUE(subnet_);
+
Pkt6Ptr req1 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
Pkt6Ptr req2 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 2345));
Pkt6Ptr req3 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 3456));
@@ -817,6 +969,10 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
boost::shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
subnet_->getT2());
+ ASSERT_TRUE(addr1);
+ ASSERT_TRUE(addr2);
+ ASSERT_TRUE(addr3);
+
// Check that the assigned address is indeed from the configured pool
checkIAAddr(addr1, addr1->getAddress(), subnet_->getPreferred(), subnet_->getValid());
checkIAAddr(addr2, addr2->getAddress(), subnet_->getPreferred(), subnet_->getValid());
@@ -905,6 +1061,8 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
boost::shared_ptr<Option6IAAddr> addr_opt = checkIA_NA(reply, 234, subnet_->getT1(),
subnet_->getT2());
+ ASSERT_TRUE(addr_opt);
+
// Check that we've got the address we requested
checkIAAddr(addr_opt, addr, subnet_->getPreferred(), subnet_->getValid());
@@ -1431,6 +1589,113 @@ TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
EXPECT_EQ(subnet3, srv.selectSubnet(pkt));
}
+// This test verifies if selectSubnet() selects proper subnet for a given
+// linkaddr in RELAY-FORW message
+TEST_F(Dhcpv6SrvTest, selectSubnetRelayLinkaddr) {
+ NakedDhcpv6Srv srv(0);
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+
+ Pkt6::RelayInfo relay;
+ relay.linkaddr_ = IOAddress("2001:db8:2::1234");
+ relay.peeraddr_ = IOAddress("fe80::1");
+
+ // CASE 1: We have only one subnet defined and we received relayed traffic.
+ // The only available subnet should NOT be selected.
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ pkt->relay_info_.push_back(relay);
+
+ Subnet6Ptr selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+ // CASE 2: We have three subnets defined and we received relayed traffic.
+ // Nothing should be selected.
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+ selected = srv.selectSubnet(pkt);
+ EXPECT_EQ(selected, subnet2);
+
+ // CASE 3: We have three subnets defined and we received relayed traffic
+ // that came out of subnet 2. We should select subnet2 then
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+
+ // Source of the packet should have no meaning. Selection is based
+ // on linkaddr field in the relay
+ pkt->setRemoteAddr(IOAddress("2001:db8:1::baca"));
+ selected = srv.selectSubnet(pkt);
+ EXPECT_EQ(selected, subnet2);
+
+ // CASE 4: We have three subnets defined and we received relayed traffic
+ // that came out of undefined subnet. We should select nothing
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+ pkt->relay_info_.clear();
+ relay.linkaddr_ = IOAddress("2001:db8:4::1234");
+ pkt->relay_info_.push_back(relay);
+ selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+}
+
+// This test verifies if selectSubnet() selects proper subnet for a given
+// interface-id option
+TEST_F(Dhcpv6SrvTest, selectSubnetRelayInterfaceId) {
+ NakedDhcpv6Srv srv(0);
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+
+ subnet1->setInterfaceId(generateInterfaceId("relay1"));
+ subnet2->setInterfaceId(generateInterfaceId("relay2"));
+
+ // CASE 1: We have only one subnet defined and it is for interface-id "relay1"
+ // Packet came with interface-id "relay2". We should not select subnet1
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ Pkt6::RelayInfo relay;
+ relay.linkaddr_ = IOAddress("2001:db8:2::1234");
+ relay.peeraddr_ = IOAddress("fe80::1");
+ OptionPtr opt = generateInterfaceId("relay2");
+ relay.options_.insert(make_pair(opt->getType(), opt));
+ pkt->relay_info_.push_back(relay);
+
+ // There is only one subnet configured and we are outside of that subnet
+ Subnet6Ptr selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+ // CASE 2: We have only one subnet defined and it is for interface-id "relay2"
+ // Packet came with interface-id "relay2". We should select it
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet2); // just a single subnet
+ selected = srv.selectSubnet(pkt);
+ EXPECT_EQ(selected, subnet2);
+
+ // CASE 3: We have only 3 subnets defined: one remote for interface-id "relay1",
+ // one remote for interface-id "relay2" and third local
+ // packet comes with interface-id "relay2". We should select subnet2
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+
+ EXPECT_EQ(subnet2, srv.selectSubnet(pkt));
+}
+
// This test verifies if the server-id disk operations (read, write) are
// working properly.
TEST_F(Dhcpv6SrvTest, ServerID) {
@@ -1463,6 +1728,378 @@ TEST_F(Dhcpv6SrvTest, ServerID) {
EXPECT_EQ(duid1_text, text);
}
+// A set of tests verifying server's behaviour when it receives the DHCPv6
+// Client Fqdn Option.
+// @todo: Extend these tests once appropriate configuration parameters are
+// implemented (ticket #3034).
+
+// Test server's response when client requests that server performs AAAA update.
+TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdate) {
+ testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S,
+ "myhost.example.com.");
+}
+
+// Test server's response when client provides partial domain-name and requests
+// that server performs AAAA update.
+TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdatePartialName) {
+ testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S, "myhost",
+ Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
+ "myhost.example.com.");
+}
+
+// Test server's response when client provides empty domain-name and requests
+// that server performs AAAA update.
+TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdateNoName) {
+ testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S, "",
+ Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
+ "myhost.example.com.");
+}
+
+// Test server's response when client requests no DNS update.
+TEST_F(FqdnDhcpv6SrvTest, noUpdate) {
+ testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_N,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_N,
+ "myhost.example.com.");
+}
+
+// Test server's response when client requests that server delegates the AAAA
+// update to the client and this delegation is not allowed.
+TEST_F(FqdnDhcpv6SrvTest, clientAAAAUpdateNotAllowed) {
+ testFqdn(DHCPV6_SOLICIT, true, 0, "myhost.example.com.",
+ Option6ClientFqdn::FULL,
+ Option6ClientFqdn::FLAG_S | Option6ClientFqdn::FLAG_O,
+ "myhost.example.com.");
+}
+
+// Test that exception is thrown if supplied NULL answer packet when
+// creating NameChangeRequests.
+TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAnswer) {
+ NakedDhcpv6Srv srv(0);
+
+ Pkt6Ptr answer;
+ Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL);
+ EXPECT_THROW(srv.createNameChangeRequests(answer, fqdn),
+ isc::Unexpected);
+
+}
+
+// Test that exception is thrown if supplied answer from the server
+// contains no DUID when creating NameChangeRequests.
+TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoDUID) {
+ NakedDhcpv6Srv srv(0);
+
+ Pkt6Ptr answer = Pkt6Ptr(new Pkt6(DHCPV6_REPLY, 1234));
+ Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL);
+
+ EXPECT_THROW(srv.createNameChangeRequests(answer, fqdn),
+ isc::Unexpected);
+
+}
+
+// Test no NameChangeRequests are added if FQDN option is NULL.
+TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoFQDN) {
+ NakedDhcpv6Srv srv(0);
+
+ // Create Reply message with Client Id and Server id.
+ Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
+
+ // Pass NULL FQDN option. No NameChangeRequests should be created.
+ Option6ClientFqdnPtr fqdn;
+ ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
+
+ // There should be no new NameChangeRequests.
+ EXPECT_TRUE(srv.name_change_reqs_.empty());
+}
+
+// Test that NameChangeRequests are not generated if an answer message
+// contains no addresses.
+TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAddr) {
+ NakedDhcpv6Srv srv(0);
+
+ // Create Reply message with Client Id and Server id.
+ Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
+
+ Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL);
+
+ ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
+
+ // We didn't add any IAs, so there should be no NameChangeRequests in th
+ // queue.
+ ASSERT_TRUE(srv.name_change_reqs_.empty());
+}
+
+// Test that a number of NameChangeRequests is created as a result of
+// processing the answer message which holds 3 IAs and when FQDN is
+// specified.
+TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
+ NakedDhcpv6Srv srv(0);
+
+ // Create Reply message with Client Id and Server id.
+ Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
+
+ // Create three IAs, each having different address.
+ addIA(1234, IOAddress("2001:db8:1::1"), answer);
+ addIA(2345, IOAddress("2001:db8:1::2"), answer);
+ addIA(3456, IOAddress("2001:db8:1::3"), answer);
+
+ // Use domain name in upper case. It should be converted to lower-case
+ // before DHCID is calculated. So, we should get the same result as if
+ // we typed domain name in lower-case.
+ Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
+ "MYHOST.EXAMPLE.COM",
+ Option6ClientFqdn::FULL);
+
+ // Create NameChangeRequests. Since we have added 3 IAs, it should
+ // result in generation of 3 distinct NameChangeRequests.
+ ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
+ ASSERT_EQ(3, srv.name_change_reqs_.size());
+
+ // Verify that NameChangeRequests are correct. Each call to the
+ // verifyNameChangeRequest will pop verified request from the queue.
+
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1::1",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 500);
+
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1::2",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 500);
+
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1::3",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 500);
+
+}
+
+// Test creation of the NameChangeRequest to remove both forward and reverse
+// mapping for the given lease.
+TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestFwdRev) {
+ NakedDhcpv6Srv srv(0);
+
+ lease_->fqdn_fwd_ = true;
+ lease_->fqdn_rev_ = true;
+ // Part of the domain name is in upper case, to test that it gets converted
+ // to lower case before DHCID is computed. So, we should get the same DHCID
+ // as if we typed domain-name in lower case.
+ lease_->hostname_ = "MYHOST.example.com.";
+
+ ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+
+ ASSERT_EQ(1, srv.name_change_reqs_.size());
+
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+ "2001:db8:1::1",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 502);
+
+}
+
+// Test creation of the NameChangeRequest to remove reverse mapping for the
+// given lease.
+TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestRev) {
+ NakedDhcpv6Srv srv(0);
+
+ lease_->fqdn_fwd_ = false;
+ lease_->fqdn_rev_ = true;
+ lease_->hostname_ = "myhost.example.com.";
+
+ ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+
+ ASSERT_EQ(1, srv.name_change_reqs_.size());
+
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, false,
+ "2001:db8:1::1",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 502);
+
+}
+
+// Test that NameChangeRequest to remove DNS records is not generated when
+// neither forward nor reverse DNS update has been performed for a lease.
+TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestNoUpdate) {
+ NakedDhcpv6Srv srv(0);
+
+ lease_->fqdn_fwd_ = false;
+ lease_->fqdn_rev_ = false;
+
+ ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+
+ EXPECT_TRUE(srv.name_change_reqs_.empty());
+
+}
+
+// Test that NameChangeRequest is not generated if the hostname hasn't been
+// specified for a lease for which forward and reverse mapping has been set.
+TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestNoHostname) {
+ NakedDhcpv6Srv srv(0);
+
+ lease_->fqdn_fwd_ = true;
+ lease_->fqdn_rev_ = true;
+ lease_->hostname_ = "";
+
+ ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+
+ EXPECT_TRUE(srv.name_change_reqs_.empty());
+
+}
+
+// Test that NameChangeRequest is not generated if the invalid hostname has
+// been specified for a lease for which forward and reverse mapping has been
+// set.
+TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestWrongHostname) {
+ NakedDhcpv6Srv srv(0);
+
+ lease_->fqdn_fwd_ = true;
+ lease_->fqdn_rev_ = true;
+ lease_->hostname_ = "myhost..example.com.";
+
+ ASSERT_NO_THROW(srv.createRemovalNameChangeRequest(lease_));
+
+ EXPECT_TRUE(srv.name_change_reqs_.empty());
+
+}
+
+// Test that Advertise message generated in a response to the Solicit will
+// not result in generation if the NameChangeRequests.
+TEST_F(FqdnDhcpv6SrvTest, processSolicit) {
+ NakedDhcpv6Srv srv(0);
+
+ // Create a Solicit message with FQDN option and generate server's
+ // response using processSolicit function.
+ testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com", srv);
+ EXPECT_TRUE(srv.name_change_reqs_.empty());
+}
+
+// Test that client may send two requests, each carrying FQDN option with
+// a different domain-name. Server should use existing lease for the second
+// request but modify the DNS entries for the lease according to the contents
+// of the FQDN sent in the second request.
+TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
+ NakedDhcpv6Srv srv(0);
+
+ // Create a Request message with FQDN option and generate server's
+ // response using processRequest function. This will result in the
+ // creation of a new lease and the appropriate NameChangeRequest
+ // to add both reverse and forward mapping to DNS.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+ ASSERT_EQ(1, srv.name_change_reqs_.size());
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4000);
+
+ // Client may send another request message with a new domain-name. In this
+ // case the same lease will be returned. The existing DNS entry needs to
+ // be replaced with a new one. Server should determine that the different
+ // FQDN has been already added to the DNS. As a result, the old DNS
+ // entries should be removed and the entries for the new domain-name
+ // should be added. Therefore, we expect two NameChangeRequests. One to
+ // remove the existing entries, one to add new entries.
+ testProcessMessage(DHCPV6_REQUEST, "otherhost.example.com", srv);
+ ASSERT_EQ(2, srv.name_change_reqs_.size());
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4000);
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201D422AA463306223D269B6CB7AFE7AAD265FC"
+ "EA97F93623019B2E0D14E5323D5A",
+ 0, 4000);
+
+}
+
+// Test that client may send Request followed by the Renew, both holding
+// FQDN options, but each option holding different domain-name. The Renew
+// should result in generation of the two NameChangeRequests, one to remove
+// DNS entry added previously when Request was processed, another one to
+// add a new entry for the FQDN held in the Renew.
+TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
+ NakedDhcpv6Srv srv(0);
+
+ // Create a Request message with FQDN option and generate server's
+ // response using processRequest function. This will result in the
+ // creation of a new lease and the appropriate NameChangeRequest
+ // to add both reverse and forward mapping to DNS.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+ ASSERT_EQ(1, srv.name_change_reqs_.size());
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4000);
+
+ // Client may send Renew message with a new domain-name. In this
+ // case the same lease will be returned. The existing DNS entry needs to
+ // be replaced with a new one. Server should determine that the different
+ // FQDN has been already added to the DNS. As a result, the old DNS
+ // entries should be removed and the entries for the new domain-name
+ // should be added. Therefore, we expect two NameChangeRequests. One to
+ // remove the existing entries, one to add new entries.
+ testProcessMessage(DHCPV6_RENEW, "otherhost.example.com", srv);
+ ASSERT_EQ(2, srv.name_change_reqs_.size());
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4000);
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201D422AA463306223D269B6CB7AFE7AAD265FC"
+ "EA97F93623019B2E0D14E5323D5A",
+ 0, 4000);
+
+}
+
+TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
+ NakedDhcpv6Srv srv(0);
+
+ // Create a Request message with FQDN option and generate server's
+ // response using processRequest function. This will result in the
+ // creation of a new lease and the appropriate NameChangeRequest
+ // to add both reverse and forward mapping to DNS.
+ testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+ ASSERT_EQ(1, srv.name_change_reqs_.size());
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4000);
+
+ // Client may send Release message. In this case the lease should be
+ // removed and all existing DNS entries for this lease should be
+ // also removed. Therefore, we expect that single NameChangeRequest to
+ // remove DNS entries is generated.
+ testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com", srv);
+ ASSERT_EQ(1, srv.name_change_reqs_.size());
+ verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+ "2001:db8:1:1::dead:beef",
+ "000201415AA33D1187D148275136FA30300478"
+ "FAAAA3EBD29826B5C907B2C9268A6F52",
+ 0, 4000);
+
+}
+
+
/// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
/// to call processX() methods.
diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.h b/src/bin/dhcp6/tests/dhcp6_test_utils.h
new file mode 100644
index 0000000..5032857
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_test_utils.h
@@ -0,0 +1,407 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file dhcp6_test_utils.h
+///
+/// @brief This file contains utility classes used for DHCPv6 server testing
+
+#include <gtest/gtest.h>
+
+#include <dhcp/pkt6.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcp6/dhcp6_srv.h>
+#include <hooks/hooks_manager.h>
+#include <config/ccsession.h>
+
+#include <list>
+
+using namespace isc::dhcp;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace isc::asiolink;
+using namespace isc::util;
+using namespace isc::hooks;
+
+namespace isc {
+namespace test {
+
+/// @brief "naked" Dhcpv6Srv class that exposes internal members
+class NakedDhcpv6Srv: public isc::dhcp::Dhcpv6Srv {
+public:
+ NakedDhcpv6Srv(uint16_t port) : Dhcpv6Srv(port) {
+ // Open the "memfile" database for leases
+ std::string memfile = "type=memfile";
+ LeaseMgrFactory::create(memfile);
+ }
+
+ /// @brief fakes packet reception
+ /// @param timeout ignored
+ ///
+ /// The method receives all packets queued in receive
+ /// queue, one after another. Once the queue is empty,
+ /// it initiates the shutdown procedure.
+ ///
+ /// See fake_received_ field for description
+ virtual isc::dhcp::Pkt6Ptr receivePacket(int /*timeout*/) {
+
+ // If there is anything prepared as fake incoming
+ // traffic, use it
+ if (!fake_received_.empty()) {
+ Pkt6Ptr pkt = fake_received_.front();
+ fake_received_.pop_front();
+ return (pkt);
+ }
+
+ // If not, just trigger shutdown and
+ // return immediately
+ shutdown();
+ return (Pkt6Ptr());
+ }
+
+ /// @brief fake packet sending
+ ///
+ /// Pretend to send a packet, but instead just store
+ /// it in fake_send_ list where test can later inspect
+ /// server's response.
+ virtual void sendPacket(const Pkt6Ptr& pkt) {
+ fake_sent_.push_back(pkt);
+ }
+
+ /// @brief adds a packet to fake receive queue
+ ///
+ /// See fake_received_ field for description
+ void fakeReceive(const Pkt6Ptr& pkt) {
+ fake_received_.push_back(pkt);
+ }
+
+ virtual ~NakedDhcpv6Srv() {
+ // Close the lease database
+ LeaseMgrFactory::destroy();
+ }
+
+ using Dhcpv6Srv::processSolicit;
+ using Dhcpv6Srv::processRequest;
+ using Dhcpv6Srv::processRenew;
+ using Dhcpv6Srv::processRelease;
+ using Dhcpv6Srv::processClientFqdn;
+ using Dhcpv6Srv::createNameChangeRequests;
+ using Dhcpv6Srv::createRemovalNameChangeRequest;
+ using Dhcpv6Srv::createStatusCode;
+ using Dhcpv6Srv::selectSubnet;
+ using Dhcpv6Srv::sanityCheck;
+ using Dhcpv6Srv::loadServerID;
+ using Dhcpv6Srv::writeServerID;
+ using Dhcpv6Srv::name_change_reqs_;
+
+ /// @brief packets we pretend to receive
+ ///
+ /// Instead of setting up sockets on interfaces that change between OSes, it
+ /// is much easier to fake packet reception. This is a list of packets that
+ /// we pretend to have received. You can schedule new packets to be received
+ /// using fakeReceive() and NakedDhcpv6Srv::receivePacket() methods.
+ std::list<Pkt6Ptr> fake_received_;
+
+ std::list<Pkt6Ptr> fake_sent_;
+};
+
+static const char* DUID_FILE = "server-id-test.txt";
+
+// test fixture for any tests requiring blank/empty configuration
+// serves as base class for additional tests
+class NakedDhcpv6SrvTest : public ::testing::Test {
+public:
+
+ NakedDhcpv6SrvTest() : rcode_(-1) {
+ // it's ok if that fails. There should not be such a file anyway
+ unlink(DUID_FILE);
+
+ const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+ // There must be some interface detected
+ if (ifaces.empty()) {
+ // We can't use ASSERT in constructor
+ ADD_FAILURE() << "No interfaces detected.";
+ }
+
+ valid_iface_ = ifaces.begin()->getName();
+ }
+
+ // Generate IA_NA option with specified parameters
+ boost::shared_ptr<Option6IA> generateIA(uint32_t iaid, uint32_t t1, uint32_t t2) {
+ boost::shared_ptr<Option6IA> ia =
+ boost::shared_ptr<Option6IA>(new Option6IA(D6O_IA_NA, iaid));
+ ia->setT1(t1);
+ ia->setT2(t2);
+ return (ia);
+ }
+
+ /// @brief generates interface-id option, based on text
+ ///
+ /// @param iface_id textual representation of the interface-id content
+ ///
+ /// @return pointer to the option object
+ OptionPtr generateInterfaceId(const std::string& iface_id) {
+ OptionBuffer tmp(iface_id.begin(), iface_id.end());
+ return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+ }
+
+ // Generate client-id option
+ OptionPtr generateClientId(size_t duid_size = 32) {
+
+ OptionBuffer clnt_duid(duid_size);
+ for (int i = 0; i < duid_size; i++) {
+ clnt_duid[i] = 100 + i;
+ }
+
+ duid_ = DuidPtr(new DUID(clnt_duid));
+
+ return (OptionPtr(new Option(Option::V6, D6O_CLIENTID,
+ clnt_duid.begin(),
+ clnt_duid.begin() + duid_size)));
+ }
+
+ // Checks if server response (ADVERTISE or REPLY) includes proper server-id.
+ void checkServerId(const Pkt6Ptr& rsp, const OptionPtr& expected_srvid) {
+ // check that server included its server-id
+ OptionPtr tmp = rsp->getOption(D6O_SERVERID);
+ EXPECT_EQ(tmp->getType(), expected_srvid->getType() );
+ ASSERT_EQ(tmp->len(), expected_srvid->len() );
+ EXPECT_TRUE(tmp->getData() == expected_srvid->getData());
+ }
+
+ // Checks if server response (ADVERTISE or REPLY) includes proper client-id.
+ void checkClientId(const Pkt6Ptr& rsp, const OptionPtr& expected_clientid) {
+ // check that server included our own client-id
+ OptionPtr tmp = rsp->getOption(D6O_CLIENTID);
+ ASSERT_TRUE(tmp);
+ EXPECT_EQ(expected_clientid->getType(), tmp->getType());
+ ASSERT_EQ(expected_clientid->len(), tmp->len());
+
+ // check that returned client-id is valid
+ EXPECT_TRUE(expected_clientid->getData() == tmp->getData());
+ }
+
+ // Checks if server response is a NAK
+ void checkNakResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
+ uint32_t expected_transid,
+ uint16_t expected_status_code) {
+ // Check if we get response at all
+ checkResponse(rsp, expected_message_type, expected_transid);
+
+ // Check that IA_NA was returned
+ OptionPtr option_ia_na = rsp->getOption(D6O_IA_NA);
+ ASSERT_TRUE(option_ia_na);
+
+ // check that the status is no address available
+ boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(option_ia_na);
+ ASSERT_TRUE(ia);
+
+ checkIA_NAStatusCode(ia, expected_status_code);
+ }
+
+ // Checks that server rejected IA_NA, i.e. that it has no addresses and
+ // that expected status code really appears there. In some limited cases
+ // (reply to RELEASE) it may be used to verify positive case, where
+ // IA_NA response is expected to not include address.
+ //
+ // Status code indicates type of error encountered (in theory it can also
+ // indicate success, but servers typically don't send success status
+ // as this is the default result and it saves bandwidth)
+ void checkIA_NAStatusCode(const boost::shared_ptr<Option6IA>& ia,
+ uint16_t expected_status_code) {
+ // Make sure there is no address assigned.
+ EXPECT_FALSE(ia->getOption(D6O_IAADDR));
+
+ // T1, T2 should be zeroed
+ EXPECT_EQ(0, ia->getT1());
+ EXPECT_EQ(0, ia->getT2());
+
+ OptionCustomPtr status =
+ boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
+
+ // It is ok to not include status success as this is the default behavior
+ if (expected_status_code == STATUS_Success && !status) {
+ return;
+ }
+
+ EXPECT_TRUE(status);
+
+ if (status) {
+ // We don't have dedicated class for status code, so let's just interpret
+ // first 2 bytes as status. Remainder of the status code option content is
+ // just a text explanation what went wrong.
+ EXPECT_EQ(static_cast<uint16_t>(expected_status_code),
+ status->readInteger<uint16_t>(0));
+ }
+ }
+
+ void checkMsgStatusCode(const Pkt6Ptr& msg, uint16_t expected_status) {
+ OptionCustomPtr status =
+ boost::dynamic_pointer_cast<OptionCustom>(msg->getOption(D6O_STATUS_CODE));
+
+ // It is ok to not include status success as this is the default behavior
+ if (expected_status == STATUS_Success && !status) {
+ return;
+ }
+
+ EXPECT_TRUE(status);
+ if (status) {
+ // We don't have dedicated class for status code, so let's just interpret
+ // first 2 bytes as status. Remainder of the status code option content is
+ // just a text explanation what went wrong.
+ EXPECT_EQ(static_cast<uint16_t>(expected_status),
+ status->readInteger<uint16_t>(0));
+ }
+ }
+
+ // Basic checks for generated response (message type and transaction-id).
+ void checkResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
+ uint32_t expected_transid) {
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(expected_message_type, rsp->getType());
+ EXPECT_EQ(expected_transid, rsp->getTransid());
+ }
+
+ virtual ~NakedDhcpv6SrvTest() {
+ // Let's clean up if there is such a file.
+ unlink(DUID_FILE);
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+ "buffer6_receive");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+ "buffer6_send");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+ "lease6_renew");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+ "lease6_release");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+ "pkt6_receive");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+ "pkt6_send");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+ "subnet6_select");
+
+ };
+
+ // A DUID used in most tests (typically as client-id)
+ DuidPtr duid_;
+
+ int rcode_;
+ ConstElementPtr comment_;
+
+ // Name of a valid network interface
+ std::string valid_iface_;
+};
+
+// Provides suport for tests against a preconfigured subnet6
+// extends upon NakedDhcp6SrvTest
+class Dhcpv6SrvTest : public NakedDhcpv6SrvTest {
+public:
+ /// Name of the server-id file (used in server-id tests)
+
+ // these are empty for now, but let's keep them around
+ Dhcpv6SrvTest() {
+ subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
+ 2000, 3000, 4000));
+ pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
+ subnet_->addPool(pool_);
+
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet_);
+ }
+
+ // Checks that server response (ADVERTISE or REPLY) contains proper IA_NA option
+ // It returns IAADDR option for each chaining with checkIAAddr method.
+ boost::shared_ptr<Option6IAAddr> checkIA_NA(const Pkt6Ptr& rsp, uint32_t expected_iaid,
+ uint32_t expected_t1, uint32_t expected_t2) {
+ OptionPtr tmp = rsp->getOption(D6O_IA_NA);
+ // Can't use ASSERT_TRUE() in method that returns something
+ if (!tmp) {
+ ADD_FAILURE() << "IA_NA option not present in response";
+ return (boost::shared_ptr<Option6IAAddr>());
+ }
+
+ boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ if (!ia) {
+ ADD_FAILURE() << "IA_NA cannot convert option ptr to Option6";
+ return (boost::shared_ptr<Option6IAAddr>());
+ }
+
+ EXPECT_EQ(expected_iaid, ia->getIAID());
+ EXPECT_EQ(expected_t1, ia->getT1());
+ EXPECT_EQ(expected_t2, ia->getT2());
+
+ tmp = ia->getOption(D6O_IAADDR);
+ boost::shared_ptr<Option6IAAddr> addr = boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
+ return (addr);
+ }
+
+ // Check that generated IAADDR option contains expected address
+ // and lifetime values match the configured subnet
+ void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
+ const IOAddress& expected_addr,
+ uint32_t /* expected_preferred */,
+ uint32_t /* expected_valid */) {
+
+ // Check that the assigned address is indeed from the configured pool.
+ // Note that when comparing addresses, we compare the textual
+ // representation. IOAddress does not support being streamed to
+ // an ostream, which means it can't be used in EXPECT_EQ.
+ EXPECT_TRUE(subnet_->inPool(addr->getAddress()));
+ EXPECT_EQ(expected_addr.toText(), addr->getAddress().toText());
+ EXPECT_EQ(addr->getPreferred(), subnet_->getPreferred());
+ EXPECT_EQ(addr->getValid(), subnet_->getValid());
+ }
+
+ // Checks if the lease sent to client is present in the database
+ // and is valid when checked agasint the configured subnet
+ Lease6Ptr checkLease(const DuidPtr& duid, const OptionPtr& ia_na,
+ boost::shared_ptr<Option6IAAddr> addr) {
+ boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(ia_na);
+
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(addr->getAddress());
+ if (!lease) {
+ std::cout << "Lease for " << addr->getAddress().toText()
+ << " not found in the database backend.";
+ return (Lease6Ptr());
+ }
+
+ EXPECT_EQ(addr->getAddress().toText(), lease->addr_.toText());
+ EXPECT_TRUE(*lease->duid_ == *duid);
+ EXPECT_EQ(ia->getIAID(), lease->iaid_);
+ EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
+
+ return (lease);
+ }
+
+ ~Dhcpv6SrvTest() {
+ CfgMgr::instance().deleteSubnets6();
+ };
+
+ /// A subnet used in most tests
+ Subnet6Ptr subnet_;
+
+ /// A pool used in most tests
+ Pool6Ptr pool_;
+};
+
+}; // end of isc::test namespace
+}; // end of isc namespace
diff --git a/src/bin/dhcp6/tests/hooks_unittest.cc b/src/bin/dhcp6/tests/hooks_unittest.cc
new file mode 100644
index 0000000..b12374c
--- /dev/null
+++ b/src/bin/dhcp6/tests/hooks_unittest.cc
@@ -0,0 +1,1453 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/duid.h>
+#include <dhcp6/config_parser.h>
+#include <dhcp/dhcp6.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/utils.h>
+#include <util/buffer.h>
+#include <util/range_utilities.h>
+#include <hooks/server_hooks.h>
+
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <unistd.h>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::test;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace isc::hooks;
+using namespace std;
+
+// namespace has to be named, because friends are defined in Dhcpv6Srv class
+// Maybe it should be isc::test?
+namespace {
+
+// Checks if hooks are implemented properly.
+TEST_F(Dhcpv6SrvTest, Hooks) {
+ NakedDhcpv6Srv srv(0);
+
+ // check if appropriate hooks are registered
+ int hook_index_buffer6_receive = -1;
+ int hook_index_buffer6_send = -1;
+ int hook_index_lease6_renew = -1;
+ int hook_index_lease6_release = -1;
+ int hook_index_pkt6_received = -1;
+ int hook_index_select_subnet = -1;
+ int hook_index_pkt6_send = -1;
+
+ // check if appropriate indexes are set
+ EXPECT_NO_THROW(hook_index_buffer6_receive = ServerHooks::getServerHooks()
+ .getIndex("buffer6_receive"));
+ EXPECT_NO_THROW(hook_index_buffer6_send = ServerHooks::getServerHooks()
+ .getIndex("buffer6_send"));
+ EXPECT_NO_THROW(hook_index_lease6_renew = ServerHooks::getServerHooks()
+ .getIndex("lease6_renew"));
+ EXPECT_NO_THROW(hook_index_lease6_release = ServerHooks::getServerHooks()
+ .getIndex("lease6_release"));
+ EXPECT_NO_THROW(hook_index_pkt6_received = ServerHooks::getServerHooks()
+ .getIndex("pkt6_receive"));
+ EXPECT_NO_THROW(hook_index_select_subnet = ServerHooks::getServerHooks()
+ .getIndex("subnet6_select"));
+ EXPECT_NO_THROW(hook_index_pkt6_send = ServerHooks::getServerHooks()
+ .getIndex("pkt6_send"));
+
+ EXPECT_TRUE(hook_index_pkt6_received > 0);
+ EXPECT_TRUE(hook_index_select_subnet > 0);
+ EXPECT_TRUE(hook_index_pkt6_send > 0);
+ EXPECT_TRUE(hook_index_buffer6_receive > 0);
+ EXPECT_TRUE(hook_index_buffer6_send > 0);
+ EXPECT_TRUE(hook_index_lease6_renew > 0);
+ EXPECT_TRUE(hook_index_lease6_release > 0);
+}
+
+// This function returns buffer for very simple Solicit
+Pkt6* captureSimpleSolicit() {
+ Pkt6* pkt;
+ uint8_t data[] = {
+ 1, // type 1 = SOLICIT
+ 0xca, 0xfe, 0x01, // trans-id = 0xcafe01
+ 0, 1, // option type 1 (client-id)
+ 0, 10, // option lenth 10
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // DUID
+ 0, 3, // option type 3 (IA_NA)
+ 0, 12, // option length 12
+ 0, 0, 0, 1, // iaid = 1
+ 0, 0, 0, 0, // T1 = 0
+ 0, 0, 0, 0 // T2 = 0
+ };
+
+ pkt = new Pkt6(data, sizeof(data));
+ pkt->setRemotePort(546);
+ pkt->setRemoteAddr(IOAddress("fe80::1"));
+ pkt->setLocalPort(0);
+ pkt->setLocalAddr(IOAddress("ff02::1:2"));
+ pkt->setIndex(2);
+ pkt->setIface("eth0");
+
+ return (pkt);
+}
+
+/// @brief a class dedicated to Hooks testing in DHCPv6 server
+///
+/// This class has a number of static members, because each non-static
+/// method has implicit 'this' parameter, so it does not match callout
+/// signature and couldn't be registered. Furthermore, static methods
+/// can't modify non-static members (for obvious reasons), so many
+/// fields are declared static. It is still better to keep them as
+/// one class rather than unrelated collection of global objects.
+class HooksDhcpv6SrvTest : public Dhcpv6SrvTest {
+
+public:
+
+ /// @brief creates Dhcpv6Srv and prepares buffers for callouts
+ HooksDhcpv6SrvTest() {
+
+ // Allocate new DHCPv6 Server
+ srv_.reset(new NakedDhcpv6Srv(0));
+
+ // Clear static buffers
+ resetCalloutBuffers();
+ }
+
+ /// @brief destructor (deletes Dhcpv6Srv)
+ ~HooksDhcpv6SrvTest() {
+ }
+
+ /// @brief creates an option with specified option code
+ ///
+ /// This method is static, because it is used from callouts
+ /// that do not have a pointer to HooksDhcpv6SSrvTest object
+ ///
+ /// @param option_code code of option to be created
+ ///
+ /// @return pointer to create option object
+ static OptionPtr createOption(uint16_t option_code) {
+
+ uint8_t payload[] = {
+ 0xa, 0xb, 0xc, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14
+ };
+
+ OptionBuffer tmp(payload, payload + sizeof(payload));
+ return OptionPtr(new Option(Option::V6, option_code, tmp));
+ }
+
+ /// test callback that stores received callout name and pkt6 value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_receive_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("pkt6_receive");
+
+ callout_handle.getArgument("query6", callback_pkt6_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ /// test callback that changes client-id value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_receive_change_clientid(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("query6", pkt);
+
+ // Get rid of the old client-id
+ pkt->delOption(D6O_CLIENTID);
+
+ // Add a new option
+ pkt->addOption(createOption(D6O_CLIENTID));
+
+ // Carry on as usual
+ return pkt6_receive_callout(callout_handle);
+ }
+
+ /// Test callback that deletes client-id
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_receive_delete_clientid(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("query6", pkt);
+
+ // Get rid of the old client-id
+ pkt->delOption(D6O_CLIENTID);
+
+ // Carry on as usual
+ return pkt6_receive_callout(callout_handle);
+ }
+
+ /// Test callback that sets skip flag
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_receive_skip(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("query6", pkt);
+
+ callout_handle.setSkip(true);
+
+ // Carry on as usual
+ return pkt6_receive_callout(callout_handle);
+ }
+
+ /// Test callback that stores received callout name and pkt6 value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer6_receive_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("buffer6_receive");
+
+ callout_handle.getArgument("query6", callback_pkt6_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ /// Test callback that changes first byte of client-id value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer6_receive_change_clientid(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("query6", pkt);
+
+ // If there is at least one option with data
+ if (pkt->data_.size() > Pkt6::DHCPV6_PKT_HDR_LEN + Option::OPTION6_HDR_LEN) {
+ // Offset of the first byte of the first option. Let's set this byte
+ // to some new value that we could later check
+ pkt->data_[Pkt6::DHCPV6_PKT_HDR_LEN + Option::OPTION6_HDR_LEN] = 0xff;
+ }
+
+ // Carry on as usual
+ return buffer6_receive_callout(callout_handle);
+ }
+
+ /// Test callback that deletes client-id
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer6_receive_delete_clientid(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("query6", pkt);
+
+ // this is modified SOLICIT (with missing mandatory client-id)
+ uint8_t data[] = {
+ 1, // type 1 = SOLICIT
+ 0xca, 0xfe, 0x01, // trans-id = 0xcafe01
+ 0, 3, // option type 3 (IA_NA)
+ 0, 12, // option length 12
+ 0, 0, 0, 1, // iaid = 1
+ 0, 0, 0, 0, // T1 = 0
+ 0, 0, 0, 0 // T2 = 0
+ };
+
+ OptionBuffer modifiedMsg(data, data + sizeof(data));
+
+ pkt->data_ = modifiedMsg;
+
+ // carry on as usual
+ return buffer6_receive_callout(callout_handle);
+ }
+
+ /// Test callback that sets skip flag
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ buffer6_receive_skip(CalloutHandle& callout_handle) {
+ callout_handle.setSkip(true);
+
+ // Carry on as usual
+ return buffer6_receive_callout(callout_handle);
+ }
+
+ /// Test callback that stores received callout name and pkt6 value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_send_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("pkt6_send");
+
+ callout_handle.getArgument("response6", callback_pkt6_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ // Test callback that changes server-id
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_send_change_serverid(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("response6", pkt);
+
+ // Get rid of the old server-id
+ pkt->delOption(D6O_SERVERID);
+
+ // Add a new option
+ pkt->addOption(createOption(D6O_SERVERID));
+
+ // Carry on as usual
+ return pkt6_send_callout(callout_handle);
+ }
+
+ /// Test callback that deletes server-id
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_send_delete_serverid(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("response6", pkt);
+
+ // Get rid of the old client-id
+ pkt->delOption(D6O_SERVERID);
+
+ // Carry on as usual
+ return pkt6_send_callout(callout_handle);
+ }
+
+ /// Test callback that sets skip flag
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ pkt6_send_skip(CalloutHandle& callout_handle) {
+
+ Pkt6Ptr pkt;
+ callout_handle.getArgument("response6", pkt);
+
+ callout_handle.setSkip(true);
+
+ // carry on as usual
+ return pkt6_send_callout(callout_handle);
+ }
+
+ /// Test callback that stores received callout name and subnet6 values
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ subnet6_select_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("subnet6_select");
+
+ callout_handle.getArgument("query6", callback_pkt6_);
+ callout_handle.getArgument("subnet6", callback_subnet6_);
+ callout_handle.getArgument("subnet6collection", callback_subnet6collection_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ /// Test callback that picks the other subnet if possible.
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ subnet6_select_different_subnet_callout(CalloutHandle& callout_handle) {
+
+ // Call the basic calllout to record all passed values
+ subnet6_select_callout(callout_handle);
+
+ const Subnet6Collection* subnets;
+ Subnet6Ptr subnet;
+ callout_handle.getArgument("subnet6", subnet);
+ callout_handle.getArgument("subnet6collection", subnets);
+
+ // Let's change to a different subnet
+ if (subnets->size() > 1) {
+ subnet = (*subnets)[1]; // Let's pick the other subnet
+ callout_handle.setArgument("subnet6", subnet);
+ }
+
+ return (0);
+ }
+
+ /// Test callback that stores received callout name and pkt6 value
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_renew_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease6_renew");
+
+ callout_handle.getArgument("query6", callback_pkt6_);
+ callout_handle.getArgument("lease6", callback_lease6_);
+ callout_handle.getArgument("ia_na", callback_ia_na_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ /// The following values are used by the callout to override
+ /// renewed lease parameters
+ static const uint32_t override_iaid_;
+ static const uint32_t override_t1_;
+ static const uint32_t override_t2_;
+ static const uint32_t override_preferred_;
+ static const uint32_t override_valid_;
+
+ /// Test callback that overrides received lease. It updates
+ /// T1, T2, preferred and valid lifetimes
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_renew_update_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease6_renew");
+
+ callout_handle.getArgument("query6", callback_pkt6_);
+ callout_handle.getArgument("lease6", callback_lease6_);
+ callout_handle.getArgument("ia_na", callback_ia_na_);
+
+ // Let's override some values in the lease
+ callback_lease6_->iaid_ = override_iaid_;
+ callback_lease6_->t1_ = override_t1_;
+ callback_lease6_->t2_ = override_t2_;
+ callback_lease6_->preferred_lft_ = override_preferred_;
+ callback_lease6_->valid_lft_ = override_valid_;
+
+ // Override the values to be sent to the client as well
+ callback_ia_na_->setIAID(override_iaid_);
+ callback_ia_na_->setT1(override_t1_);
+ callback_ia_na_->setT2(override_t2_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ /// Test callback that sets the skip flag
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_renew_skip_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease6_renew");
+
+ callout_handle.setSkip(true);
+
+ return (0);
+ }
+
+ /// Test callback that stores received callout name passed parameters
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_release_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease6_release");
+
+ callout_handle.getArgument("query6", callback_pkt6_);
+ callout_handle.getArgument("lease6", callback_lease6_);
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ /// Test callback that sets the skip flag
+ /// @param callout_handle handle passed by the hooks framework
+ /// @return always 0
+ static int
+ lease6_release_skip_callout(CalloutHandle& callout_handle) {
+ callback_name_ = string("lease6_release");
+
+ callout_handle.setSkip(true);
+
+ return (0);
+ }
+
+ /// Resets buffers used to store data received by callouts
+ void resetCalloutBuffers() {
+ callback_name_ = string("");
+ callback_pkt6_.reset();
+ callback_subnet6_.reset();
+ callback_lease6_.reset();
+ callback_ia_na_.reset();
+ callback_subnet6collection_ = NULL;
+ callback_argument_names_.clear();
+ }
+
+ /// Pointer to Dhcpv6Srv that is used in tests
+ boost::scoped_ptr<NakedDhcpv6Srv> srv_;
+
+ // The following fields are used in testing pkt6_receive_callout
+
+ /// String name of the received callout
+ static string callback_name_;
+
+ /// Pkt6 structure returned in the callout
+ static Pkt6Ptr callback_pkt6_;
+
+ /// Pointer to lease6
+ static Lease6Ptr callback_lease6_;
+
+ /// Pointer to IA_NA option being renewed
+ static boost::shared_ptr<Option6IA> callback_ia_na_;
+
+ /// Pointer to a subnet received by callout
+ static Subnet6Ptr callback_subnet6_;
+
+ /// A list of all available subnets (received by callout)
+ static const Subnet6Collection* callback_subnet6collection_;
+
+ /// A list of all received arguments
+ static vector<string> callback_argument_names_;
+};
+
+// The following parameters are used by callouts to override
+// renewed lease parameters
+const uint32_t HooksDhcpv6SrvTest::override_iaid_ = 1000;
+const uint32_t HooksDhcpv6SrvTest::override_t1_ = 1001;
+const uint32_t HooksDhcpv6SrvTest::override_t2_ = 1002;
+const uint32_t HooksDhcpv6SrvTest::override_preferred_ = 1003;
+const uint32_t HooksDhcpv6SrvTest::override_valid_ = 1004;
+
+// The following fields are used in testing pkt6_receive_callout.
+// See fields description in the class for details
+string HooksDhcpv6SrvTest::callback_name_;
+Pkt6Ptr HooksDhcpv6SrvTest::callback_pkt6_;
+Subnet6Ptr HooksDhcpv6SrvTest::callback_subnet6_;
+const Subnet6Collection* HooksDhcpv6SrvTest::callback_subnet6collection_;
+vector<string> HooksDhcpv6SrvTest::callback_argument_names_;
+Lease6Ptr HooksDhcpv6SrvTest::callback_lease6_;
+boost::shared_ptr<Option6IA> HooksDhcpv6SrvTest::callback_ia_na_;
+
+// Checks if callouts installed on pkt6_receive are indeed called and the
+// all necessary parameters are passed.
+//
+// Note that the test name does not follow test naming convention,
+// but the proper hook name is "buffer6_receive".
+TEST_F(HooksDhcpv6SrvTest, simple_buffer6_receive) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer6_receive", buffer6_receive_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer6_receive callback.
+ srv_->run();
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("buffer6_receive", callback_name_);
+
+ // Check that pkt6 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_pkt6_.get() == sol.get());
+
+ // Check that all expected parameters are there
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back(string("query6"));
+
+ EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+}
+
+// Checks if callouts installed on buffer6_receive is able to change
+// the values and the parameters are indeed used by the server.
+TEST_F(HooksDhcpv6SrvTest, valueChange_buffer6_receive) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer6_receive", buffer6_receive_change_clientid));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the server did send a reposonce
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // Make sure that we received a response
+ Pkt6Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Get client-id...
+ OptionPtr clientid = adv->getOption(D6O_CLIENTID);
+
+ ASSERT_TRUE(clientid);
+
+ // ... and check if it is the modified value
+ EXPECT_EQ(0xff, clientid->getData()[0]);
+}
+
+// Checks if callouts installed on buffer6_receive is able to delete
+// existing options and that change impacts server processing (mandatory
+// client-id option is deleted, so the packet is expected to be dropped)
+TEST_F(HooksDhcpv6SrvTest, deleteClientId_buffer6_receive) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer6_receive", buffer6_receive_delete_clientid));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the server dropped the packet and did not send a response
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+}
+
+// Checks if callouts installed on buffer6_received is able to set skip flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv6SrvTest, skip_buffer6_receive) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "buffer6_receive", buffer6_receive_skip));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the server dropped the packet and did not produce any response
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+}
+
+// Checks if callouts installed on pkt6_receive are indeed called and the
+// all necessary parameters are passed.
+//
+// Note that the test name does not follow test naming convention,
+// but the proper hook name is "pkt6_receive".
+TEST_F(HooksDhcpv6SrvTest, simple_pkt6_receive) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_receive", pkt6_receive_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("pkt6_receive", callback_name_);
+
+ // Check that pkt6 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_pkt6_.get() == sol.get());
+
+ // Check that all expected parameters are there
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back(string("query6"));
+
+ EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+}
+
+// Checks if callouts installed on pkt6_received is able to change
+// the values and the parameters are indeed used by the server.
+TEST_F(HooksDhcpv6SrvTest, valueChange_pkt6_receive) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_receive", pkt6_receive_change_clientid));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the server did send a reposonce
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // Make sure that we received a response
+ Pkt6Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Get client-id...
+ OptionPtr clientid = adv->getOption(D6O_CLIENTID);
+
+ // ... and check if it is the modified value
+ OptionPtr expected = createOption(D6O_CLIENTID);
+ EXPECT_TRUE(clientid->equal(expected));
+}
+
+// Checks if callouts installed on pkt6_received is able to delete
+// existing options and that change impacts server processing (mandatory
+// client-id option is deleted, so the packet is expected to be dropped)
+TEST_F(HooksDhcpv6SrvTest, deleteClientId_pkt6_receive) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_receive", pkt6_receive_delete_clientid));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the server dropped the packet and did not send a response
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+}
+
+// Checks if callouts installed on pkt6_received is able to set skip flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv6SrvTest, skip_pkt6_receive) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_receive", pkt6_receive_skip));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the server dropped the packet and did not produce any response
+ ASSERT_EQ(0, srv_->fake_sent_.size());
+}
+
+
+// Checks if callouts installed on pkt6_send are indeed called and the
+// all necessary parameters are passed.
+TEST_F(HooksDhcpv6SrvTest, simple_pkt6_send) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_send", pkt6_send_callout));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("pkt6_send", callback_name_);
+
+ // Check that there is one packet sent
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+ Pkt6Ptr adv = srv_->fake_sent_.front();
+
+ // Check that pkt6 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_pkt6_.get() == adv.get());
+
+ // Check that all expected parameters are there
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back(string("response6"));
+ EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+}
+
+// Checks if callouts installed on pkt6_send is able to change
+// the values and the packet sent contains those changes
+TEST_F(HooksDhcpv6SrvTest, valueChange_pkt6_send) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_send", pkt6_send_change_serverid));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the server did send a response
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // Make sure that we received a response
+ Pkt6Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Get client-id...
+ OptionPtr clientid = adv->getOption(D6O_SERVERID);
+
+ // ... and check if it is the modified value
+ OptionPtr expected = createOption(D6O_SERVERID);
+ EXPECT_TRUE(clientid->equal(expected));
+}
+
+// Checks if callouts installed on pkt6_send is able to delete
+// existing options and that server applies those changes. In particular,
+// we are trying to send a packet without server-id. The packet should
+// be sent
+TEST_F(HooksDhcpv6SrvTest, deleteServerId_pkt6_send) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_send", pkt6_send_delete_serverid));
+
+ // Let's create a simple SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the server indeed sent a malformed ADVERTISE
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // Get that ADVERTISE
+ Pkt6Ptr adv = srv_->fake_sent_.front();
+ ASSERT_TRUE(adv);
+
+ // Make sure that it does not have server-id
+ EXPECT_FALSE(adv->getOption(D6O_SERVERID));
+}
+
+// Checks if callouts installed on pkt6_skip is able to set skip flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv6SrvTest, skip_pkt6_send) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "pkt6_send", pkt6_send_skip));
+
+ // Let's create a simple REQUEST
+ Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+ // Simulate that we have received that traffic
+ srv_->fakeReceive(sol);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive6(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered pkt6_receive callback.
+ srv_->run();
+
+ // Check that the server send the packet
+ ASSERT_EQ(1, srv_->fake_sent_.size());
+
+ // But the sent packet should have 0 length (we told the server to
+ // skip pack(), but did not do packing outselves)
+ Pkt6Ptr sent = srv_->fake_sent_.front();
+
+ // The actual size of sent packet should be 0
+ EXPECT_EQ(0, sent->getBuffer().getLength());
+}
+
+// This test checks if subnet6_select callout is triggered and reports
+// valid parameters
+TEST_F(HooksDhcpv6SrvTest, subnet6_select) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "subnet6_select", subnet6_select_callout));
+
+ // Configure 2 subnets, both directly reachable over local interface
+ // (let's not complicate the matter with relays)
+ string config = "{ \"interfaces\": [ \"*\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/64\" ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"" + valid_iface_ + "\" "
+ " }, {"
+ " \"pool\": [ \"2001:db8:2::/64\" ],"
+ " \"subnet\": \"2001:db8:2::/48\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ // Prepare solicit packet. Server should select first subnet for it
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface(valid_iface_);
+ sol->addOption(generateIA(234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ Pkt6Ptr adv = srv_->processSolicit(sol);
+
+ // Check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("subnet6_select", callback_name_);
+
+ // Check that pkt6 argument passing was successful and returned proper value
+ EXPECT_TRUE(callback_pkt6_.get() == sol.get());
+
+ const Subnet6Collection* exp_subnets = CfgMgr::instance().getSubnets6();
+
+ // The server is supposed to pick the first subnet, because of matching
+ // interface. Check that the value is reported properly.
+ ASSERT_TRUE(callback_subnet6_);
+ EXPECT_EQ(callback_subnet6_.get(), exp_subnets->front().get());
+
+ // Server is supposed to report two subnets
+ ASSERT_EQ(exp_subnets->size(), callback_subnet6collection_->size());
+
+ // Compare that the available subnets are reported as expected
+ EXPECT_TRUE((*exp_subnets)[0].get() == (*callback_subnet6collection_)[0].get());
+ EXPECT_TRUE((*exp_subnets)[1].get() == (*callback_subnet6collection_)[1].get());
+}
+
+// This test checks if callout installed on subnet6_select hook point can pick
+// a different subnet.
+TEST_F(HooksDhcpv6SrvTest, subnet_select_change) {
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "subnet6_select", subnet6_select_different_subnet_callout));
+
+ // Configure 2 subnets, both directly reachable over local interface
+ // (let's not complicate the matter with relays)
+ string config = "{ \"interfaces\": [ \"*\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/64\" ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"" + valid_iface_ + "\" "
+ " }, {"
+ " \"pool\": [ \"2001:db8:2::/64\" ],"
+ " \"subnet\": \"2001:db8:2::/48\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ // Prepare solicit packet. Server should select first subnet for it
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->setIface(valid_iface_);
+ sol->addOption(generateIA(234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ Pkt6Ptr adv = srv_->processSolicit(sol);
+
+ // Check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // The response should have an address from second pool, so let's check it
+ OptionPtr tmp = adv->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ tmp = ia->getOption(D6O_IAADDR);
+ ASSERT_TRUE(tmp);
+ boost::shared_ptr<Option6IAAddr> addr_opt =
+ boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
+ ASSERT_TRUE(addr_opt);
+
+ // Get all subnets and use second subnet for verification
+ const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+ ASSERT_EQ(2, subnets->size());
+
+ // Advertised address must belong to the second pool (in subnet's range,
+ // in dynamic pool)
+ EXPECT_TRUE((*subnets)[1]->inRange(addr_opt->getAddress()));
+ EXPECT_TRUE((*subnets)[1]->inPool(addr_opt->getAddress()));
+}
+
+// This test verifies that incoming (positive) RENEW can be handled properly,
+// and the lease6_renew callouts are triggered.
+TEST_F(HooksDhcpv6SrvTest, basic_lease6_renew) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_renew", lease6_renew_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt really set and not using
+ // previous (500, 501, etc.) values
+ EXPECT_NE(l->t1_, subnet_->getT1());
+ EXPECT_NE(l->t2_, subnet_->getT2());
+ EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_NE(l->valid_lft_, subnet_->getValid());
+ EXPECT_NE(l->cltt_, time(NULL));
+
+ // Let's create a RENEW
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+ OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(renewed_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RENEW
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRenew(req);
+ ASSERT_TRUE(reply);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease6_renew", callback_name_);
+
+ // Check that appropriate parameters are passed to the callouts
+ EXPECT_TRUE(callback_pkt6_);
+ EXPECT_TRUE(callback_lease6_);
+ EXPECT_TRUE(callback_ia_na_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("lease6");
+ expected_argument_names.push_back("ia_na");
+
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr_opt = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
+
+ ASSERT_TRUE(addr_opt);
+ // Check that the lease is really in the database
+ l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+ ASSERT_TRUE(l);
+
+ // Check that the lease has been returned
+ ASSERT_TRUE(callback_lease6_);
+
+ // Check that the returned lease6 in callout is the same as the one in the
+ // database
+ EXPECT_TRUE(*callback_lease6_ == *l);
+}
+
+// This test verifies that incoming (positive) RENEW can be handled properly,
+// and the lease6_renew callouts are able to change the lease being updated.
+TEST_F(HooksDhcpv6SrvTest, leaseUpdate_lease6_renew) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_renew", lease6_renew_update_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt really set and not using
+ // previous (500, 501, etc.) values
+ EXPECT_NE(l->t1_, subnet_->getT1());
+ EXPECT_NE(l->t2_, subnet_->getT2());
+ EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_NE(l->valid_lft_, subnet_->getValid());
+ EXPECT_NE(l->cltt_, time(NULL));
+
+ // Let's create a RENEW
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+ OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(renewed_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RENEW
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRenew(req);
+ ASSERT_TRUE(reply);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr_opt = checkIA_NA(reply, 1000, 1001, 1002);
+
+ ASSERT_TRUE(addr_opt);
+ // Check that the lease is really in the database
+ l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+ ASSERT_TRUE(l);
+
+ // Check that we chose the distinct override values
+ ASSERT_NE(override_t1_, subnet_->getT1());
+ ASSERT_NE(override_t2_, subnet_->getT2());
+ ASSERT_NE(override_preferred_, subnet_->getPreferred());
+ EXPECT_NE(override_valid_, subnet_->getValid());
+
+ // Check that T1, T2, preferred, valid were overridden the the callout
+ EXPECT_EQ(override_t1_, l->t1_);
+ EXPECT_EQ(override_t2_, l->t2_);
+ EXPECT_EQ(override_preferred_, l->preferred_lft_);
+ EXPECT_EQ(override_valid_, l->valid_lft_);
+
+ // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+ int32_t cltt = static_cast<int32_t>(l->cltt_);
+ int32_t expected = static_cast<int32_t>(time(NULL));
+ // Equality or difference by 1 between cltt and expected is ok.
+ EXPECT_GE(1, abs(cltt - expected));
+
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr_opt->getAddress()));
+}
+
+// This test verifies that incoming (positive) RENEW can be handled properly,
+// and the lease6_renew callouts are able to set the skip flag that will
+// reject the renewal
+TEST_F(HooksDhcpv6SrvTest, skip_lease6_renew) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_renew", lease6_renew_skip_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt really set and not using
+ // previous (500, 501, etc.) values
+ EXPECT_NE(l->t1_, subnet_->getT1());
+ EXPECT_NE(l->t2_, subnet_->getT2());
+ EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_NE(l->valid_lft_, subnet_->getValid());
+ EXPECT_NE(l->cltt_, time(NULL));
+
+ // Let's create a RENEW
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+ OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(renewed_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RENEW
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRenew(req);
+ ASSERT_TRUE(reply);
+
+ // Check that our callback was called
+ EXPECT_EQ("lease6_renew", callback_name_);
+
+ l = LeaseMgrFactory::instance().getLease6(addr);
+
+ // Check that the old values are still there and they were not
+ // updated by the renewal
+ EXPECT_NE(l->t1_, subnet_->getT1());
+ EXPECT_NE(l->t2_, subnet_->getT2());
+ EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_NE(l->valid_lft_, subnet_->getValid());
+ EXPECT_NE(l->cltt_, time(NULL));
+}
+
+// This test verifies that incoming (positive) RELEASE can be handled properly,
+// that a REPLY is generated, that the response has status code and that the
+// lease is indeed removed from the database.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that does not include an IAADDR
+// - lease is actually removed from LeaseMgr
+TEST_F(HooksDhcpv6SrvTest, basic_lease6_release) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_release", lease6_release_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ ASSERT_TRUE(reply);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease6_release", callback_name_);
+
+ // Check that appropriate parameters are passed to the callouts
+ EXPECT_TRUE(callback_pkt6_);
+ EXPECT_TRUE(callback_lease6_);
+
+ // Check if all expected parameters were really received
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("query6");
+ expected_argument_names.push_back("lease6");
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+ // Check that the lease is really gone in the database
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // Get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(*duid_, iaid, subnet_->getID());
+ ASSERT_FALSE(l);
+}
+
+// This test verifies that incoming (positive) RELEASE can be handled properly,
+// that a REPLY is generated, that the response has status code and that the
+// lease is indeed removed from the database.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that does not include an IAADDR
+// - lease is actually removed from LeaseMgr
+TEST_F(HooksDhcpv6SrvTest, skip_lease6_release) {
+ NakedDhcpv6Srv srv(0);
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_release", lease6_release_skip_callout));
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ ASSERT_TRUE(reply);
+
+ // Check that the callback called is indeed the one we installed
+ EXPECT_EQ("lease6_release", callback_name_);
+
+ // Check that the lease is still there
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(*duid_, iaid, subnet_->getID());
+ ASSERT_TRUE(l);
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/marker_file.cc b/src/bin/dhcp6/tests/marker_file.cc
new file mode 100644
index 0000000..d1c4aba
--- /dev/null
+++ b/src/bin/dhcp6/tests/marker_file.cc
@@ -0,0 +1,66 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 "marker_file.h"
+
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+using namespace std;
+
+// Check the marker file.
+
+bool
+checkMarkerFile(const char* name, const char* expected) {
+ // Open the file for input
+ fstream file(name, fstream::in);
+
+ // Is it open?
+ if (!file.is_open()) {
+ ADD_FAILURE() << "Unable to open " << name << ". It was expected "
+ << "to be present and to contain the string '"
+ << expected << "'";
+ return (false);
+ }
+
+ // OK, is open, so read the data and see what we have. Compare it
+ // against what is expected.
+ string content;
+ getline(file, content);
+
+ string expected_str(expected);
+ EXPECT_EQ(expected_str, content) << "Marker file " << name
+ << "did not contain the expected data";
+ file.close();
+
+ return (expected_str == content);
+}
+
+// Check if the marker file exists - this is a wrapper for "access(2)" and
+// really tests if the file exists and is accessible
+
+bool
+checkMarkerFileExists(const char* name) {
+ return (access(name, F_OK) == 0);
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/bin/dhcp6/tests/marker_file.h.in b/src/bin/dhcp6/tests/marker_file.h.in
new file mode 100644
index 0000000..52fc006
--- /dev/null
+++ b/src/bin/dhcp6/tests/marker_file.h.in
@@ -0,0 +1,69 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 MARKER_FILE_H
+#define MARKER_FILE_H
+
+/// @file
+/// Define a marker file that is used in tests to prove that an "unload"
+/// function has been called
+
+namespace {
+const char* const LOAD_MARKER_FILE = "@abs_builddir@/load_marker.txt";
+const char* const UNLOAD_MARKER_FILE = "@abs_builddir@/unload_marker.txt";
+}
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Check marker file
+///
+/// This function is used in some of the DHCP server tests.
+///
+/// Marker files are used by the load/unload functions in the hooks
+/// libraries in these tests to signal whether they have been loaded or
+/// unloaded. The file (if present) contains a single line holding
+/// a set of characters.
+///
+/// This convenience function checks the file to see if the characters
+/// are those expected.
+///
+/// @param name Name of the marker file.
+/// @param expected Characters expected. If a marker file is present,
+/// it is expected to contain characters.
+///
+/// @return true if all tests pass, false if not (in which case a failure
+/// will have been logged).
+bool
+checkMarkerFile(const char* name, const char* expected);
+
+/// @brief Check marker file exists
+///
+/// This function is used in some of the DHCP server tests.
+///
+/// Checkes that the specified file does NOT exist.
+///
+/// @param name Name of the marker file.
+///
+/// @return true if file exists, false if not.
+bool
+checkMarkerFileExists(const char* name);
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // MARKER_FILE_H
+
diff --git a/src/bin/dhcp6/tests/test_libraries.h.in b/src/bin/dhcp6/tests/test_libraries.h.in
new file mode 100644
index 0000000..8b03dc2
--- /dev/null
+++ b/src/bin/dhcp6/tests/test_libraries.h.in
@@ -0,0 +1,51 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TEST_LIBRARIES_H
+#define TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+
+// Take care of differences in DLL naming between operating systems.
+
+#ifdef OS_OSX
+#define DLL_SUFFIX ".dylib"
+
+#else
+#define DLL_SUFFIX ".so"
+
+#endif
+
+
+// Names of the libraries used in these tests. These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// shared library.
+
+// Library with load/unload functions creating marker files to check their
+// operation.
+const char* const CALLOUT_LIBRARY_1 = "@abs_builddir@/.libs/libco1"
+ DLL_SUFFIX;
+const char* const CALLOUT_LIBRARY_2 = "@abs_builddir@/.libs/libco2"
+ DLL_SUFFIX;
+
+// Name of a library which is not present.
+const char* const NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere"
+ DLL_SUFFIX;
+} // anonymous namespace
+
+
+#endif // TEST_LIBRARIES_H
diff --git a/src/bin/loadzone/.gitignore b/src/bin/loadzone/.gitignore
index 41e280a..286abba 100644
--- a/src/bin/loadzone/.gitignore
+++ b/src/bin/loadzone/.gitignore
@@ -1,5 +1,4 @@
/b10-loadzone
-/b10-loadzone.py
/loadzone.py
/run_loadzone.sh
/b10-loadzone.8
diff --git a/src/bin/loadzone/b10-loadzone.xml b/src/bin/loadzone/b10-loadzone.xml
index aa14053..b47421f 100644
--- a/src/bin/loadzone/b10-loadzone.xml
+++ b/src/bin/loadzone/b10-loadzone.xml
@@ -52,6 +52,12 @@
<arg choice="req">zone name</arg>
<arg choice="req">zone file</arg>
</cmdsynopsis>
+ <cmdsynopsis>
+ <command>b10-loadzone</command>
+ <arg><option>-e</option></arg>
+ <arg><option>other options</option></arg>
+ <arg choice="req">zone name</arg>
+ </cmdsynopsis>
</refsynopsisdiv>
<refsect1>
@@ -61,6 +67,9 @@
in a BIND 10 ready data source format.
Master files are text files that contain DNS Resource Records
in text form.
+ This utility can also be used to create an empty zone on the
+ specified data source so the existence of the zone is recognized
+ in the data source without any content (resource records).
</para>
<note><simpara>Currently only the SQLITE3 data source is supported.
</simpara></note>
@@ -104,6 +113,18 @@
old version will still remain accessible for other applications.
</para>
+ <para>
+ If the <command>-e</command> command line option is specified,
+ <command>b10-loadzone</command> does not take the zone name
+ argument.
+ In this case it creates an empty zone without any content
+ while the data source still recognizes the existence of the
+ zone.
+ If the specified zone already has some content, this mode of
+ operation will remove it (but the existence of the zone in the
+ data source will be still recognized).
+ </para>
+
</refsect1>
<refsect1>
@@ -142,6 +163,18 @@
</varlistentry>
<varlistentry>
+ <term>-e</term>
+ <listitem><para>
+ Create an empty zone, or empty existing zone content
+ instead of loading new one.
+ When this option is specified, the zone file command line
+ argument must not be provided.
+ The <command>-i</command> option has no effect, but it
+ does not cause a failure; it will be simply ignored.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
<term>-i <replaceable class="parameter">report_interval</replaceable></term>
<listitem><para>
Specifies the interval of status update by the number of RRs
diff --git a/src/bin/loadzone/loadzone.py.in b/src/bin/loadzone/loadzone.py.in
index 736aa31..1203e45 100755
--- a/src/bin/loadzone/loadzone.py.in
+++ b/src/bin/loadzone/loadzone.py.in
@@ -66,6 +66,8 @@ Example: '{"database_file": "/path/to/dbfile/db.sqlite3"}'""",
parser.add_option("-d", "--debug", dest="debug_level",
type='int', action="store", default=None,
help="enable debug logs with the specified level [0-99]")
+ parser.add_option("-e", "--empty", dest="empty_zone",
+ action="store_true", help="empty zone content (no load)")
parser.add_option("-i", "--report-interval", dest="report_interval",
type='int', action="store",
default=LOAD_INTERVAL_DEFAULT,
@@ -113,6 +115,7 @@ class LoadZoneRunner:
self._datasrc_type = None
self._log_severity = 'INFO'
self._log_debuglevel = 0
+ self._empty_zone = False
self._report_interval = LOAD_INTERVAL_DEFAULT
self._start_time = None
# This one will be used in (rare) cases where we want to allow tests to
@@ -140,7 +143,8 @@ class LoadZoneRunner:
'''
usage_txt = \
- 'usage: %prog [options] -c datasrc_config zonename zonefile'
+ 'usage: %prog [options] -c datasrc_config zonename zonefile\n' + \
+ ' %prog [options] -c datasrc_config -e zonename'
parser = OptionParser(usage=usage_txt)
set_cmd_options(parser)
(options, args) = parser.parse_args(args=self.__command_args)
@@ -174,15 +178,22 @@ class LoadZoneRunner:
'Invalid report interval (must be non negative): %d' %
self._report_interval)
- if len(args) != 2:
- raise BadArgument('Unexpected number of arguments: %d (must be 2)'
- % (len(args)))
+ if options.empty_zone:
+ self._empty_zone = True
+
+ # Check number of non option arguments: must be 1 with -e; 2 otherwise.
+ num_args = 1 if self._empty_zone else 2
+
+ if len(args) != num_args:
+ raise BadArgument('Unexpected number of arguments: %d (must be %d)'
+ % (len(args), num_args))
try:
self._zone_name = Name(args[0])
except Exception as ex: # too broad, but there's no better granurality
raise BadArgument("Invalid zone name '" + args[0] + "': " +
str(ex))
- self._zone_file = args[1]
+ if len(args) > 1:
+ self._zone_file = args[1]
def _get_datasrc_config(self, datasrc_type):
''''Return the default data source configuration of given type.
@@ -254,6 +265,34 @@ class LoadZoneRunner:
else:
logger.info(LOADZONE_ZONE_UPDATING, self._zone_name,
self._zone_class)
+ if self._empty_zone:
+ self.__make_empty_zone(datasrc_client)
+ else:
+ self.__load_from_file(datasrc_client)
+ except Exception as ex:
+ if created:
+ datasrc_client.delete_zone(self._zone_name)
+ logger.error(LOADZONE_CANCEL_CREATE_ZONE, self._zone_name,
+ self._zone_class)
+ raise LoadFailure(str(ex))
+
+ def __make_empty_zone(self, datasrc_client):
+ """Subroutine of _do_load(), create an empty zone or make it empty."""
+ try:
+ updater = datasrc_client.get_updater(self._zone_name, True)
+ updater.commit()
+ logger.info(LOADZONE_EMPTY_DONE, self._zone_name,
+ self._zone_class)
+ except Exception:
+ # once updater is created, it's very unlikely that commit() fails,
+ # but in case it happens, clear updater to release any remaining
+ # lock.
+ updater = None
+ raise
+
+ def __load_from_file(self, datasrc_client):
+ """Subroutine of _do_load(), load a zone file into data source."""
+ try:
loader = ZoneLoader(datasrc_client, self._zone_name,
self._zone_file)
self._start_time = time.time()
@@ -279,14 +318,14 @@ class LoadZoneRunner:
sys.stdout.write('\n')
# record the final count of the loaded RRs for logging
self._loaded_rrs = loader.get_rr_count()
- except Exception as ex:
+
+ total_elapsed_txt = "%.2f" % (time.time() - self._start_time)
+ logger.info(LOADZONE_DONE, self._loaded_rrs, self._zone_name,
+ self._zone_class, total_elapsed_txt)
+ except Exception:
# release any remaining lock held in the loader
loader = None
- if created:
- datasrc_client.delete_zone(self._zone_name)
- logger.error(LOADZONE_CANCEL_CREATE_ZONE, self._zone_name,
- self._zone_class)
- raise LoadFailure(str(ex))
+ raise
def _set_signal_handlers(self):
signal.signal(signal.SIGINT, self._interrupt_handler)
@@ -302,9 +341,6 @@ class LoadZoneRunner:
self._set_signal_handlers()
self._parse_args()
self._do_load()
- total_elapsed_txt = "%.2f" % (time.time() - self._start_time)
- logger.info(LOADZONE_DONE, self._loaded_rrs, self._zone_name,
- self._zone_class, total_elapsed_txt)
return 0
except BadArgument as ex:
logger.error(LOADZONE_ARGUMENT_ERROR, ex)
diff --git a/src/bin/loadzone/loadzone_messages.mes b/src/bin/loadzone/loadzone_messages.mes
index 744a1a4..206b02e 100644
--- a/src/bin/loadzone/loadzone_messages.mes
+++ b/src/bin/loadzone/loadzone_messages.mes
@@ -33,6 +33,11 @@ an old version of the zone in the data source, it is now deleted.
It also prints the number of RRs that have been loaded
and the time spent for the loading.
+% LOADZONE_EMPTY_DONE Completed emptying zone %1/%2
+b10-loadzone has successfully emptied content of the specified zone.
+This includes the case where the content didn't previously exist, in which
+case it just still reamins empty.
+
% LOADZONE_LOAD_ERROR Failed to load zone %1/%2: %3
Loading a zone by b10-loadzone fails for some reason in the middle of
the loading. This is most likely due to an error in the specified
diff --git a/src/bin/loadzone/run_loadzone.sh.in b/src/bin/loadzone/run_loadzone.sh.in
index 178cf11..9064416 100755
--- a/src/bin/loadzone/run_loadzone.sh.in
+++ b/src/bin/loadzone/run_loadzone.sh.in
@@ -25,7 +25,7 @@ export PYTHONPATH
# required by loadable python modules.
SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
if test $SET_ENV_LIBRARY_PATH = yes; then
- @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/threads/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
export @ENV_LIBRARY_PATH@
fi
diff --git a/src/bin/loadzone/tests/Makefile.am b/src/bin/loadzone/tests/Makefile.am
index 8459f83..235ed22 100644
--- a/src/bin/loadzone/tests/Makefile.am
+++ b/src/bin/loadzone/tests/Makefile.am
@@ -13,7 +13,7 @@ EXTRA_DIST += testdata/example-nons.org.zone
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/loadzone/tests/correct/Makefile.am b/src/bin/loadzone/tests/correct/Makefile.am
index 7ed500d..366e85c 100644
--- a/src/bin/loadzone/tests/correct/Makefile.am
+++ b/src/bin/loadzone/tests/correct/Makefile.am
@@ -20,7 +20,7 @@ noinst_SCRIPTS = correct_test.sh
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# TODO: maybe use TESTS?
diff --git a/src/bin/loadzone/tests/correct/correct_test.sh.in b/src/bin/loadzone/tests/correct/correct_test.sh.in
index 505411c..627ec68 100755
--- a/src/bin/loadzone/tests/correct/correct_test.sh.in
+++ b/src/bin/loadzone/tests/correct/correct_test.sh.in
@@ -53,7 +53,7 @@ echo "I:test master file BIND 8 compatibility TTL and \$TTL semantics"
echo "I:test master file RFC1035 TTL and \$TTL semantics"
echo "I:test master file BIND8 compatibility and mixed \$INCLUDE with \$TTL semantics"
echo "I:test master file RFC1035 TTL and mixed \$INCLUDE with \$TTL semantics"
-echo "I:test master file BIND9 extenstion of TTL"
+echo "I:test master file BIND9 extension of TTL"
echo "I:test master file RFC1035 missing CLASS, TTL, NAME semantics"
echo "I:test master file comments"
diff --git a/src/bin/loadzone/tests/correct/ttlext.db b/src/bin/loadzone/tests/correct/ttlext.db
index f8b96ea..3aa37e8 100644
--- a/src/bin/loadzone/tests/correct/ttlext.db
+++ b/src/bin/loadzone/tests/correct/ttlext.db
@@ -11,7 +11,7 @@ ns A 10.53.0.1
a TXT "soa minttl 3"
b 2S TXT "explicit ttl 2"
c TXT "soa minttl 3"
-$TTL 10M ; bind9 extention ttl
+$TTL 10M ; bind9 extension ttl
d TXT "default ttl 600"
e 4 TXT "explicit ttl 4"
f TXT "default ttl 600"
diff --git a/src/bin/loadzone/tests/loadzone_test.py b/src/bin/loadzone/tests/loadzone_test.py
index 351bc59..83894bd 100755
--- a/src/bin/loadzone/tests/loadzone_test.py
+++ b/src/bin/loadzone/tests/loadzone_test.py
@@ -41,7 +41,7 @@ ORIG_SOA_TXT = 'example.org. 3600 IN SOA ns1.example.org. ' +\
'admin.example.org. 1234 3600 1800 2419200 7200\n'
NEW_SOA_TXT = 'example.org. 3600 IN SOA ns.example.org. ' +\
'admin.example.org. 1235 3600 1800 2419200 7200\n'
-# This is the brandnew SOA for a newly created zone
+# This is the brand new SOA for a newly created zone
ALT_NEW_SOA_TXT = 'example.com. 3600 IN SOA ns.example.com. ' +\
'admin.example.com. 1234 3600 1800 2419200 7200\n'
@@ -82,6 +82,7 @@ class TestLoadZoneRunner(unittest.TestCase):
self.assertEqual(RRClass.IN, self.__runner._zone_class) # default
self.assertEqual('INFO', self.__runner._log_severity) # default
self.assertEqual(0, self.__runner._log_debuglevel)
+ self.assertFalse(self.__runner._empty_zone)
def test_set_loglevel(self):
runner = LoadZoneRunner(['-d', '1'] + self.__args)
@@ -90,13 +91,19 @@ class TestLoadZoneRunner(unittest.TestCase):
self.assertEqual(1, runner._log_debuglevel)
def test_parse_bad_args(self):
- # There must be exactly 2 non-option arguments: zone name and zone file
+ # There must usually be exactly 2 non-option arguments: zone name and
+ # zone file.
self.assertRaises(BadArgument, LoadZoneRunner([])._parse_args)
self.assertRaises(BadArgument, LoadZoneRunner(['example']).
_parse_args)
self.assertRaises(BadArgument, LoadZoneRunner(self.__args + ['0']).
_parse_args)
+ # With -e it must be only zone name
+ self.assertRaises(BadArgument, LoadZoneRunner(
+ ['-e', 'example', 'example.zone'])._parse_args)
+ self.assertRaises(BadArgument, LoadZoneRunner(['-e'])._parse_args)
+
# Bad zone name
args = ['example.org', 'example.zone'] # otherwise valid args
self.assertRaises(BadArgument,
@@ -134,22 +141,24 @@ class TestLoadZoneRunner(unittest.TestCase):
self.assertRaises(BadArgument, self.__runner._get_datasrc_config,
'memory')
- def __common_load_setup(self):
+ def __common_load_setup(self, empty=False):
self.__runner._zone_class = RRClass.IN
self.__runner._zone_name = TEST_ZONE_NAME
self.__runner._zone_file = NEW_ZONE_TXT_FILE
self.__runner._datasrc_type = 'sqlite3'
self.__runner._datasrc_config = DATASRC_CONFIG
self.__runner._report_interval = 1
+ self.__runner._empty_zone = empty
self.__reports = []
self.__runner._report_progress = lambda x, _: self.__reports.append(x)
def __check_zone_soa(self, soa_txt, zone_name=TEST_ZONE_NAME):
"""Check that the given SOA RR exists and matches the expected string
- If soa_txt is None, the zone is expected to be non-existent.
- Otherwise, if soa_txt is False, the zone should exist but SOA is
- expected to be missing.
+ If soa_txt is None, the zone is expected to be non-existent;
+ if it's 'empty', the zone should exist but is expected to be empty;
+ if soa_txt is False, the zone should exist but SOA is expected to be
+ missing.
"""
@@ -160,7 +169,10 @@ class TestLoadZoneRunner(unittest.TestCase):
return
self.assertEqual(client.SUCCESS, result)
result, rrset, _ = finder.find(zone_name, RRType.SOA)
- if soa_txt:
+ if soa_txt == 'empty':
+ self.assertEqual(finder.NXDOMAIN, result)
+ self.assertIsNone(rrset)
+ elif soa_txt:
self.assertEqual(finder.SUCCESS, result)
self.assertEqual(soa_txt, rrset.to_text())
else:
@@ -269,6 +281,19 @@ class TestLoadZoneRunner(unittest.TestCase):
# _do_load() should have once created the zone but then canceled it.
self.__check_zone_soa(None, zone_name=Name('example.com'))
+ def test_create_and_empty(self):
+ self.__common_load_setup(True)
+ self.__runner._zone_name = Name('example.com')
+ self.__check_zone_soa(None, zone_name=Name('example.com'))
+ self.__runner._do_load()
+ self.__check_zone_soa('empty', zone_name=Name('example.com'))
+
+ def test_empty(self):
+ self.__common_load_setup(True)
+ self.__check_zone_soa(ORIG_SOA_TXT)
+ self.__runner._do_load()
+ self.__check_zone_soa('empty')
+
def __common_post_load_setup(self, zone_file):
'''Common setup procedure for post load tests which should fail.'''
# replace the LoadZoneRunner's original _post_load_warning() for
diff --git a/src/bin/memmgr/.gitignore b/src/bin/memmgr/.gitignore
new file mode 100644
index 0000000..b92e3f5
--- /dev/null
+++ b/src/bin/memmgr/.gitignore
@@ -0,0 +1,5 @@
+/b10-memmgr
+/memmgr.py
+/memmgr.spec
+/b10-memmgr.8
+/memmgr.spec.pre
diff --git a/src/bin/memmgr/Makefile.am b/src/bin/memmgr/Makefile.am
new file mode 100644
index 0000000..55c4601
--- /dev/null
+++ b/src/bin/memmgr/Makefile.am
@@ -0,0 +1,62 @@
+SUBDIRS = . tests
+
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+pkglibexec_SCRIPTS = b10-memmgr
+
+b10_memmgrdir = $(pkgdatadir)
+b10_memmgr_DATA = memmgr.spec
+
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.py
+pylogmessagedir = $(pyexecdir)/isc/log_messages/
+
+CLEANFILES = b10-memmgr memmgr.pyc
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.pyc
+CLEANFILES += memmgr.spec
+
+EXTRA_DIST = memmgr_messages.mes
+
+man_MANS = b10-memmgr.8
+DISTCLEANFILES = $(man_MANS)
+EXTRA_DIST += $(man_MANS) b10-memmgr.xml
+
+if GENERATE_DOCS
+
+b10-memmgr.8: b10-memmgr.xml
+ @XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-memmgr.xml
+
+else
+
+$(man_MANS):
+ @echo Man generation disabled. Creating dummy $@. Configure with --enable-generate-docs to enable it.
+ @echo Man generation disabled. Remove this file, configure with --enable-generate-docs, and rebuild BIND 10 > $@
+
+endif
+
+# Define rule to build logging source files from message file
+$(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.py : memmgr_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message \
+ -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/memmgr_messages.mes
+
+memmgr.spec: memmgr.spec.pre
+ $(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" memmgr.spec.pre > $@
+
+# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
+b10-memmgr: memmgr.py $(PYTHON_LOGMSGPKG_DIR)/work/memmgr_messages.py
+ $(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" memmgr.py >$@
+ chmod a+x $@
+
+CLEANDIRS = __pycache__
+
+# install the default directory for memory-mapped files. Note that the
+# path must be identical to the default value in memmgr.spec. We'll make
+# it readable only for the owner to minimize the risk of accidents.
+install-data-local:
+ $(mkinstalldirs) $(DESTDIR)@localstatedir@/@PACKAGE@/mapped_files
+
+install-data-hook:
+ -chmod 700 $(DESTDIR)@localstatedir@/@PACKAGE@/mapped_files
+
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/bin/memmgr/b10-memmgr.xml b/src/bin/memmgr/b10-memmgr.xml
new file mode 100644
index 0000000..ee7fee2
--- /dev/null
+++ b/src/bin/memmgr/b10-memmgr.xml
@@ -0,0 +1,109 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+ [<!ENTITY mdash "—">]>
+<!--
+ - Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+ -
+ - Permission to use, copy, modify, and/or distribute this software for any
+ - purpose with or without fee is hereby granted, provided that the above
+ - copyright notice and this permission notice appear in all copies.
+ -
+ - THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ - PERFORMANCE OF THIS SOFTWARE.
+-->
+
+<refentry>
+
+ <refentryinfo>
+ <date>June 11, 2013</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>b10-memmgr</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo>BIND10</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>b10-memmgr</refname>
+ <refpurpose>BIND 10 memory manager daemon</refpurpose>
+ </refnamediv>
+
+ <docinfo>
+ <copyright>
+ <year>2013</year>
+ <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+ </copyright>
+ </docinfo>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>b10-memmgr</command>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>The <command>b10-memmgr</command> daemon manages shared
+ memory segments storing in-memory DNS zone data, and
+ communicates with other BIND 10 modules about the segment
+ information so the entire system will use these segments
+ in a consistent manner.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>ARGUMENTS</title>
+
+ <para>The <command>b10-memmgr</command> daemon does not take
+ any command line arguments.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>CONFIGURATION AND COMMANDS</title>
+ <para>
+ The configurable settings are:
+ </para>
+ <para>
+ <varname>mapped_file_dir</varname>
+ A path to store files to be mapped to memory. This must be
+ writable to the <command>b10-memmgr</command> daemon.
+ </para>
+
+ <para>
+ The module commands are:
+ </para>
+ <para>
+ <command>shutdown</command> exits <command>b10-memmgr</command>.
+ </para>
+ </refsect1>
+
+
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citetitle>BIND 10 Guide</citetitle>.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>HISTORY</title>
+ <para>
+ The <command>b10-memmgr</command> daemon was first implemented
+ in 2013 for the ISC BIND 10 project.
+ </para>
+ </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->
diff --git a/src/bin/memmgr/memmgr.py.in b/src/bin/memmgr/memmgr.py.in
new file mode 100755
index 0000000..4bf27fe
--- /dev/null
+++ b/src/bin/memmgr/memmgr.py.in
@@ -0,0 +1,214 @@
+#!@PYTHON@
+
+# Copyright (C) 2013 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 copy
+import os
+import sys
+import signal
+import socket
+import threading
+
+sys.path.append('@@PYTHONPATH@@')
+import isc.log
+from isc.config import ModuleSpecError, ModuleCCSessionError
+from isc.log_messages.memmgr_messages import *
+from isc.server_common.bind10_server import BIND10Server, BIND10ServerFatal
+from isc.server_common.datasrc_clients_mgr \
+ import DataSrcClientsMgr, ConfigError
+from isc.memmgr.datasrc_info import DataSrcInfo
+from isc.memmgr.builder import MemorySegmentBuilder
+import isc.util.process
+
+MODULE_NAME = 'memmgr'
+
+isc.log.init('b10-memmgr', buffer=True)
+logger = isc.log.Logger(MODULE_NAME)
+
+isc.util.process.rename()
+
+class ConfigError(Exception):
+ """An exception class raised for configuration errors of Memmgr."""
+ pass
+
+class Memmgr(BIND10Server):
+ def __init__(self):
+ BIND10Server.__init__(self)
+ # Running configurable parameters: on initial configuration this will
+ # be a dict: str=>config_value.
+ # This is defined as "protected" so tests can inspect it; others
+ # shouldn't use it directly.
+ self._config_params = None
+
+ # The manager to keep track of data source configuration. Allow
+ # tests to inspect/tweak it.
+ self._datasrc_clients_mgr = DataSrcClientsMgr(use_cache=True)
+
+ # List of DataSrcInfo. Used as a queue to maintain info for all
+ # active configuration generations. Allow tests to inspec it.
+ self._datasrc_info_list = []
+
+ self._builder_setup = False
+ self._builder_command_queue = []
+ self._builder_response_queue = []
+
+ def _config_handler(self, new_config):
+ """Configuration handler, called via BIND10Server.
+
+ This method must be exception free. We assume minimum validity
+ about the parameter, though: it should be a valid dict, and conform
+ to the type specification of the spec file.
+
+ """
+ logger.debug(logger.DBGLVL_TRACE_BASIC, MEMMGR_CONFIG_UPDATE)
+
+ # Default answer:
+ answer = isc.config.create_answer(0)
+
+ # If this is the first time, initialize the local attributes with the
+ # latest full config data, which consist of the defaults with
+ # possibly overridden by user config. Otherwise, just apply the latest
+ # diff.
+ if self._config_params is None:
+ new_config = self.mod_ccsession.get_full_config()
+ try:
+ self.__update_config(new_config)
+ except Exception as ex:
+ logger.error(MEMMGR_CONFIG_FAIL, ex)
+ answer = isc.config.create_answer(
+ 1, 'Memmgr failed to apply configuration updates: ' + str(ex))
+
+ return answer
+
+ def __update_config(self, new_config):
+ """Apply config changes to local attributes.
+
+ This is a subroutine of _config_handler. It's supposed to provide
+ strong exception guarantee: either all changes successfully apply
+ or, if any error is found, none applies. In the latter case the
+ entire original configuration should be kept.
+
+ Errors are to be reported as an exception.
+
+ """
+ # If this is the first time, build everything from the scratch.
+ # Otherwise, make a full local copy and update it.
+ if self._config_params is None:
+ new_config_params = {}
+ else:
+ new_config_params = copy.deepcopy(self._config_params)
+
+ new_mapped_file_dir = new_config.get('mapped_file_dir')
+ if new_mapped_file_dir is not None:
+ if not os.path.isdir(new_mapped_file_dir):
+ raise ConfigError('mapped_file_dir is not a directory: ' +
+ new_mapped_file_dir)
+ if not os.access(new_mapped_file_dir, os.W_OK):
+ raise ConfigError('mapped_file_dir is not writable: ' +
+ new_mapped_file_dir)
+ new_config_params['mapped_file_dir'] = new_mapped_file_dir
+
+ # All copy, switch to the new configuration.
+ self._config_params = new_config_params
+
+ def __notify_from_builder(self):
+ # Nothing is implemented here for now. This method should have
+ # code to handle responses from the builder in
+ # self._builder_response_queue[]. Access must be synchronized
+ # using self._builder_lock.
+ pass
+
+ def __create_builder_thread(self):
+ # We get responses from the builder thread on this socket pair.
+ (self._master_sock, self._builder_sock) = \
+ socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.watch_fileno(self._master_sock, rcallback=self.__notify_from_builder)
+
+ # See the documentation for MemorySegmentBuilder on how the
+ # following are used.
+ self._builder_lock = threading.Lock()
+ self._builder_cv = threading.Condition(lock=self._builder_lock)
+
+ self._builder = MemorySegmentBuilder(self._builder_sock,
+ self._builder_cv,
+ self._builder_command_queue,
+ self._builder_response_queue)
+ self._builder_thread = threading.Thread(target=self._builder.run)
+ self._builder_thread.start()
+
+ self._builder_setup = True
+
+ def __shutdown_builder_thread(self):
+ # Some unittests do not create the builder thread, so we check
+ # that.
+ if not self._builder_setup:
+ return
+
+ self._builder_setup = False
+
+ # This makes the MemorySegmentBuilder exit its main loop. It
+ # should make the builder thread joinable.
+ with self._builder_cv:
+ self._builder_command_queue.append(('shutdown',))
+ self._builder_cv.notify_all()
+
+ self._builder_thread.join()
+
+ self._master_sock.close()
+ self._builder_sock.close()
+
+ def _setup_module(self):
+ """Module specific initialization for BIND10Server."""
+ try:
+ # memmgr isn't usable if data source is not configured, and
+ # as long as cfgmgr is ready there's no timing issue. So we
+ # immediately shut it down if it's missing. See ddns.py.in
+ # about exceptions to catch.
+ self.mod_ccsession.add_remote_config_by_name(
+ 'data_sources', self._datasrc_config_handler)
+ except (ModuleSpecError, ModuleCCSessionError) as ex:
+ logger.error(MEMMGR_NO_DATASRC_CONF, ex)
+ raise BIND10ServerFatal('failed to setup memmgr module')
+
+ self.__create_builder_thread()
+
+ def _shutdown_module(self):
+ """Module specific finalization."""
+ self.__shutdown_builder_thread()
+
+ def _datasrc_config_handler(self, new_config, config_data):
+ """Callback of data_sources configuration update.
+
+ This method must be exception free, so we catch all expected
+ exceptions internally; unexpected ones should mean a programming
+ error and will terminate the program.
+
+ """
+ try:
+ self._datasrc_clients_mgr.reconfigure(new_config, config_data)
+ genid, clients_map = self._datasrc_clients_mgr.get_clients_map()
+ datasrc_info = DataSrcInfo(genid, clients_map, self._config_params)
+ self._datasrc_info_list.append(datasrc_info)
+
+ # Full datasrc reconfig will be rare, so would be worth logging
+ # at the info level.
+ logger.info(MEMMGR_DATASRC_RECONFIGURED, genid)
+ except isc.server_common.datasrc_clients_mgr.ConfigError as ex:
+ logger.error(MEMMGR_DATASRC_CONFIG_ERROR, ex)
+
+if '__main__' == __name__:
+ mgr = Memmgr()
+ sys.exit(mgr.run(MODULE_NAME))
diff --git a/src/bin/memmgr/memmgr.spec.pre.in b/src/bin/memmgr/memmgr.spec.pre.in
new file mode 100644
index 0000000..6729690
--- /dev/null
+++ b/src/bin/memmgr/memmgr.spec.pre.in
@@ -0,0 +1,25 @@
+{
+ "module_spec": {
+ "module_name": "Memmgr",
+ "config_data": [
+ { "item_name": "mapped_file_dir",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/mapped_files"
+ }
+ ],
+ "commands": [
+ {
+ "command_name": "shutdown",
+ "command_description": "Shut down Memmgr",
+ "command_args": [
+ {
+ "item_name": "pid",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/src/bin/memmgr/memmgr_messages.mes b/src/bin/memmgr/memmgr_messages.mes
new file mode 100644
index 0000000..6ca5c0f
--- /dev/null
+++ b/src/bin/memmgr/memmgr_messages.mes
@@ -0,0 +1,51 @@
+# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# When you add a message to this file, it is a good idea to run
+# <topsrcdir>/tools/reorder_message_file.py to make sure the
+# messages are in the correct order.
+
+% MEMMGR_CONFIG_FAIL failed to apply configuration updates: %1
+The memmgr daemon tried to apply configuration updates but found an error.
+The cause of the error is included in the message. None of the received
+updates applied, and the daemon keeps running with the previous configuration.
+
+% MEMMGR_CONFIG_UPDATE received new configuration
+A debug message. The memmgr daemon receives configuratiopn updates
+and is now applying them to its running configurations.
+
+% MEMMGR_DATASRC_CONFIG_ERROR failed to update data source configuration: %1
+Configuration for the global data sources is updated, but the update
+cannot be applied to memmgr. The memmgr module will still keep running
+with the previous configuration, but the cause of the failure and
+other log messages must be carefully examined because if only memmgr
+rejects the new configuration then the entire BIND 10 system will have
+inconsistent state among different modules. If other modules accept
+the update but memmgr produces this error, it's quite likely that the
+system isn't working as expected, and is probably better to be shut down
+to figure out and fix the cause.
+
+% MEMMGR_DATASRC_RECONFIGURED data source configuration has been updated, generation ID %1
+The memmgr daemon received a new version of data source configuration,
+and has successfully applied it to the local state. Loading of new zone
+data into memory will possibly take place.
+
+% MEMMGR_NO_DATASRC_CONF failed to add data source configuration: %1
+The memmgr daemon tried to incorporate data source configuration on
+its startup but failed to do so. Due to internal implementation
+details this shouldn't happen as long as the BIND 10 system has been
+installed correctly. So, if this error message is logged, you should
+probably reinstall the entire system, preferably from the scratch, and
+see if it still happens. The memmgr daemon cannot do any meaningful
+work without data sources, so it immediately terminates itself.
diff --git a/src/bin/memmgr/tests/Makefile.am b/src/bin/memmgr/tests/Makefile.am
new file mode 100644
index 0000000..347ff87
--- /dev/null
+++ b/src/bin/memmgr/tests/Makefile.am
@@ -0,0 +1,30 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
+PYTESTS = memmgr_test.py
+EXTRA_DIST = $(PYTESTS)
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+LIBRARY_PATH_PLACEHOLDER =
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$$$(ENV_LIBRARY_PATH)
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+# We set B10_FROM_BUILD below, so that the test can refer to the in-source
+# spec file.
+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 ; \
+ B10_FROM_SOURCE=$(abs_top_srcdir) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/memmgr:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/util/io/.libs \
+ TESTDATASRCDIR=$(abs_srcdir)/testdata/ \
+ TESTDATA_PATH=$(abs_top_srcdir)/src/lib/testutils/testdata \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
diff --git a/src/bin/memmgr/tests/memmgr_test.py b/src/bin/memmgr/tests/memmgr_test.py
new file mode 100755
index 0000000..3dae17f
--- /dev/null
+++ b/src/bin/memmgr/tests/memmgr_test.py
@@ -0,0 +1,235 @@
+# Copyright (C) 2013 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 os
+import re
+
+import isc.log
+from isc.dns import RRClass
+import isc.config
+from isc.config import parse_answer
+import memmgr
+from isc.testutils.ccsession_mock import MockModuleCCSession
+
+class MyCCSession(MockModuleCCSession, isc.config.ConfigData):
+ def __init__(self, specfile, config_handler, command_handler):
+ super().__init__()
+ specfile = os.environ['B10_FROM_BUILD'] + '/src/bin/memmgr/memmgr.spec'
+ module_spec = isc.config.module_spec_from_file(specfile)
+ isc.config.ConfigData.__init__(self, module_spec)
+ self.add_remote_params = [] # for inspection
+ self.add_remote_exception = None # to raise exception from the method
+
+ def start(self):
+ pass
+
+ def add_remote_config_by_name(self, mod_name, handler):
+ if self.add_remote_exception is not None:
+ raise self.add_remote_exception
+ self.add_remote_params.append((mod_name, handler))
+
+class MockMemmgr(memmgr.Memmgr):
+ def _setup_ccsession(self):
+ orig_cls = isc.config.ModuleCCSession
+ isc.config.ModuleCCSession = MyCCSession
+ try:
+ super()._setup_ccsession()
+ finally:
+ isc.config.ModuleCCSession = orig_cls
+
+# Defined for easier tests with DataSrcClientsMgr.reconfigure(), which
+# only needs get_value() method
+class MockConfigData:
+ def __init__(self, data):
+ self.__data = data
+
+ def get_value(self, identifier):
+ return self.__data[identifier], False
+
+class TestMemmgr(unittest.TestCase):
+ def setUp(self):
+ # Some tests use this directory. Make sure it doesn't pre-exist.
+ self.__test_mapped_file_dir = \
+ os.environ['B10_FROM_BUILD'] + \
+ '/src/bin/memmgr/tests/test_mapped_files'
+ if os.path.isdir(self.__test_mapped_file_dir):
+ os.rmdir(self.__test_mapped_file_dir)
+
+ self.__mgr = MockMemmgr()
+ # Fake some 'os' module functions for easier tests
+ self.__orig_os_access = os.access
+ self.__orig_isdir = os.path.isdir
+
+ def tearDown(self):
+ # Not all unittests cause this method to be called, so we call
+ # it explicitly as it may be necessary in some cases where the
+ # builder thread has been created.
+ self.__mgr._shutdown_module()
+
+ # Assert that all commands sent to the builder thread were
+ # handled.
+ self.assertEqual(len(self.__mgr._builder_command_queue), 0)
+
+ # Restore faked values
+ os.access = self.__orig_os_access
+ os.path.isdir = self.__orig_isdir
+
+ # If at test created a mapped-files directory, delete it.
+ if os.path.isdir(self.__test_mapped_file_dir):
+ os.rmdir(self.__test_mapped_file_dir)
+
+ def test_init(self):
+ """Check some initial conditions"""
+ self.assertIsNone(self.__mgr._config_params)
+ self.assertEqual([], self.__mgr._datasrc_info_list)
+
+ # Try to configure a data source clients with the manager. This
+ # should confirm the manager object is instantiated enabling in-memory
+ # cache.
+ cfg_data = MockConfigData(
+ {"classes": {"IN": [{"type": "MasterFiles",
+ "cache-enable": True, "params": {}}]}})
+ self.__mgr._datasrc_clients_mgr.reconfigure({}, cfg_data)
+ clist = \
+ self.__mgr._datasrc_clients_mgr.get_client_list(RRClass.IN)
+ self.assertEqual(1, len(clist.get_status()))
+
+ def test_configure(self):
+ self.__mgr._setup_ccsession()
+
+ # Pretend specified directories exist and writable
+ os.path.isdir = lambda x: True
+ os.access = lambda x, y: True
+
+ # At the initial configuration, if mapped_file_dir isn't specified,
+ # the default value will be set.
+ self.assertEqual((0, None),
+ parse_answer(self.__mgr._config_handler({})))
+ self.assertEqual('mapped_files',
+ self.__mgr._config_params['mapped_file_dir'].
+ split('/')[-1])
+
+ # Update the configuration.
+ user_cfg = {'mapped_file_dir': '/some/path/dir'}
+ self.assertEqual((0, None),
+ parse_answer(self.__mgr._config_handler(user_cfg)))
+ self.assertEqual('/some/path/dir',
+ self.__mgr._config_params['mapped_file_dir'])
+
+ # Bad update: diretory doesn't exist (we assume it really doesn't
+ # exist in the tested environment). Update won't be made.
+ os.path.isdir = self.__orig_isdir # use real library
+ user_cfg = {'mapped_file_dir': '/another/path/dir'}
+ answer = parse_answer(self.__mgr._config_handler(user_cfg))
+ self.assertEqual(1, answer[0])
+ self.assertIsNotNone(re.search('not a directory', answer[1]))
+
+ @unittest.skipIf(os.getuid() == 0,
+ 'test cannot be run as root user')
+ def test_configure_bad_permissions(self):
+ self.__mgr._setup_ccsession()
+
+ # Pretend specified directories exist and writable
+ os.path.isdir = lambda x: True
+ os.access = lambda x, y: True
+
+ # Initial configuration.
+ self.assertEqual((0, None),
+ parse_answer(self.__mgr._config_handler({})))
+
+ os.path.isdir = self.__orig_isdir
+ os.access = self.__orig_os_access
+
+ # Bad update: directory exists but is not writable.
+ os.mkdir(self.__test_mapped_file_dir, 0o500) # drop writable bit
+ user_cfg = {'mapped_file_dir': self.__test_mapped_file_dir}
+ answer = parse_answer(self.__mgr._config_handler(user_cfg))
+ self.assertEqual(1, answer[0])
+ self.assertIsNotNone(re.search('not writable', answer[1]))
+
+ def test_setup_module(self):
+ # _setup_module should add data_sources remote module with
+ # expected parameters.
+ self.__mgr._setup_ccsession()
+ self.assertEqual([], self.__mgr.mod_ccsession.add_remote_params)
+ self.__mgr._setup_module()
+ self.assertEqual([('data_sources',
+ self.__mgr._datasrc_config_handler)],
+ self.__mgr.mod_ccsession.add_remote_params)
+
+ # If data source isn't configured it's considered fatal (checking the
+ # same scenario with two possible exception types)
+ self.__mgr.mod_ccsession.add_remote_exception = \
+ isc.config.ModuleCCSessionError('faked exception')
+ self.assertRaises(isc.server_common.bind10_server.BIND10ServerFatal,
+ self.__mgr._setup_module)
+
+ self.__mgr.mod_ccsession.add_remote_exception = \
+ isc.config.ModuleSpecError('faked exception')
+ self.assertRaises(isc.server_common.bind10_server.BIND10ServerFatal,
+ self.__mgr._setup_module)
+
+ def test_datasrc_config_handler(self):
+ self.__mgr._config_params = {'mapped_file_dir': '/some/path'}
+
+ # A simple (boring) case with real class implementations. This
+ # confirms the methods are called as expected.
+ cfg_data = MockConfigData(
+ {"classes": {"IN": [{"type": "MasterFiles",
+ "cache-enable": True, "params": {}}]}})
+ self.__mgr._datasrc_config_handler({}, cfg_data)
+ self.assertEqual(1, len(self.__mgr._datasrc_info_list))
+ self.assertEqual(1, self.__mgr._datasrc_info_list[0].gen_id)
+
+ # Below we're using a mock DataSrcClientMgr for easier tests
+ class MockDataSrcClientMgr:
+ def __init__(self, status_list, raise_on_reconfig=False):
+ self.__status_list = status_list
+ self.__raise_on_reconfig = raise_on_reconfig
+
+ def reconfigure(self, new_config, config_data):
+ if self.__raise_on_reconfig:
+ raise isc.server_common.datasrc_clients_mgr.ConfigError(
+ 'test error')
+ # otherwise do nothing
+
+ def get_clients_map(self):
+ return 42, {RRClass.IN: self}
+
+ def get_status(self): # mocking get_clients_map()[1].get_status()
+ return self.__status_list
+
+ # This confirms memmgr's config is passed and handled correctly.
+ # From memmgr's point of view it should be enough we have an object
+ # in segment_info_map. Note also that the new DataSrcInfo is appended
+ # to the list
+ self.__mgr._datasrc_clients_mgr = \
+ MockDataSrcClientMgr([('sqlite3', 'mapped', None)])
+ self.__mgr._datasrc_config_handler(None, None) # params don't matter
+ self.assertEqual(2, len(self.__mgr._datasrc_info_list))
+ self.assertIsNotNone(
+ self.__mgr._datasrc_info_list[1].segment_info_map[
+ (RRClass.IN, 'sqlite3')])
+
+ # Emulate the case reconfigure() fails. Exception isn't propagated,
+ # but the list doesn't change.
+ self.__mgr._datasrc_clients_mgr = MockDataSrcClientMgr(None, True)
+ self.__mgr._datasrc_config_handler(None, None)
+ self.assertEqual(2, len(self.__mgr._datasrc_info_list))
+
+if __name__== "__main__":
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/bin/msgq/Makefile.am b/src/bin/msgq/Makefile.am
index a49b125..185d47a 100644
--- a/src/bin/msgq/Makefile.am
+++ b/src/bin/msgq/Makefile.am
@@ -10,6 +10,7 @@ b10_msgq_DATA = msgq.spec
CLEANFILES = b10-msgq msgq.pyc
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.pyc
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.pyo
man_MANS = b10-msgq.8
DISTCLEANFILES = $(man_MANS)
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index 63d008e..a3e30d3 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -61,12 +61,14 @@ VERSION = "b10-msgq 20110127 (BIND 10 @PACKAGE_VERSION@)"
# If B10_FROM_BUILD is set in the environment, we use data files
# from a directory relative to that, otherwise we use the ones
# installed on the system
-if "B10_FROM_BUILD" in os.environ:
- SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/msgq"
+if "B10_FROM_SOURCE" in os.environ:
+ SPECFILE_PATH = os.environ["B10_FROM_SOURCE"] + "/src/bin/msgq"
else:
PREFIX = "@prefix@"
DATAROOTDIR = "@datarootdir@"
- SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+ SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}",
+ DATAROOTDIR). \
+ replace("${prefix}", PREFIX)
SPECFILE_LOCATION = SPECFILE_PATH + "/msgq.spec"
class MsgQReceiveError(Exception): pass
@@ -122,12 +124,17 @@ class SubscriptionManager:
if target in self.subscriptions:
if socket in self.subscriptions[target]:
self.subscriptions[target].remove(socket)
+ return True
+ return False
def unsubscribe_all(self, socket):
"""Remove the socket from all subscriptions."""
- for socklist in self.subscriptions.values():
+ removed_from = []
+ for subs, socklist in self.subscriptions.items():
if socket in socklist:
socklist.remove(socket)
+ removed_from.append(subs)
+ return removed_from
def find_sub(self, group, instance):
"""Return an array of sockets which want this specific group,
@@ -143,7 +150,7 @@ class SubscriptionManager:
this group, instance pair. This includes wildcard subscriptions."""
target = (group, instance)
partone = self.find_sub(group, instance)
- parttwo = self.find_sub(group, "*")
+ parttwo = self.find_sub(group, CC_INSTANCE_WILDCARD)
return list(set(partone + parttwo))
class MsgQ:
@@ -175,8 +182,6 @@ class MsgQ:
self.socket_file = socket_file
self.verbose = verbose
- self.poller = None
- self.kqueue = None
self.runnable = False
self.listen_socket = False
self.sockets = {}
@@ -184,6 +189,7 @@ class MsgQ:
self.hostname = socket.gethostname()
self.subs = SubscriptionManager(self.cfgmgr_ready)
self.lnames = {}
+ self.fd_to_lname = {}
self.sendbuffs = {}
self.running = False
self.__cfgmgr_ready = None
@@ -195,6 +201,34 @@ class MsgQ:
# not for performance, so we use wide lock scopes to be on the safe
# side.
self.__lock = threading.Lock()
+ self._session = None
+ self.__poller_sock = None
+
+ def members_notify(self, event, params):
+ """
+ Thin wrapper around ccs's notify. Send a notification about change
+ of some list that can be requested by the members command.
+
+ The event is one of:
+ - connected (client connected to MsgQ)
+ - disconected (client disconnected from MsgQ)
+ - subscribed (client subscribed to a group)
+ - unsubscribed (client unsubscribed from a group)
+
+ The params is dict containing:
+ - client: The lname of the client in question.
+ - group (for 'subscribed' and 'unsubscribed' events):
+ The group the client subscribed or unsubscribed from.
+
+ The notification occurs after the event, so client a subscribing for
+ notifications will get a notification about its own subscription, but
+ will not get a notification when it unsubscribes.
+ """
+ # Due to the interaction between threads (and fear it might influence
+ # sending stuff), we test this method in msgq_run_test, instead of
+ # mocking the ccs.
+ if self._session: # Don't send before we have started up
+ self._session.notify('cc_members', event, params)
def cfgmgr_ready(self, ready=True):
"""Notify that the config manager is either subscribed, or
@@ -229,37 +263,6 @@ class MsgQ:
self.__cfgmgr_ready_cond.wait()
return self.__cfgmgr_ready
- def setup_poller(self):
- """Set up the poll thing. Internal function."""
- try:
- self.kqueue = select.kqueue()
- except AttributeError:
- self.poller = select.poll()
-
- def add_kqueue_socket(self, socket, write_filter=False):
- """Add a kqueue filter for a socket. By default the read
- filter is used; if write_filter is set to True, the write
- filter is used. We use a boolean value instead of a specific
- filter constant, because kqueue filter values do not seem to
- be defined on some systems. The use of boolean makes the
- interface restrictive because there are other filters, but this
- method is mostly only for our internal use, so it should be
- acceptable at least for now."""
- filter_type = select.KQ_FILTER_WRITE if write_filter else \
- select.KQ_FILTER_READ
- event = select.kevent(socket.fileno(), filter_type,
- select.KQ_EV_ADD | select.KQ_EV_ENABLE)
- self.kqueue.control([event], 0)
-
- def delete_kqueue_socket(self, socket, write_filter=False):
- """Delete a kqueue filter for socket. See add_kqueue_socket()
- for the semantics and notes about write_filter."""
- filter_type = select.KQ_FILTER_WRITE if write_filter else \
- select.KQ_FILTER_READ
- event = select.kevent(socket.fileno(), filter_type,
- select.KQ_EV_DELETE)
- self.kqueue.control([event], 0)
-
def setup_listener(self):
"""Set up the listener socket. Internal function."""
logger.debug(TRACE_BASIC, MSGQ_LISTENER_SETUP, self.socket_file)
@@ -280,11 +283,6 @@ class MsgQ:
logger.fatal(MSGQ_LISTENER_FAILED, self.socket_file, e)
raise e
- if self.poller:
- self.poller.register(self.listen_socket, select.POLLIN)
- else:
- self.add_kqueue_socket(self.listen_socket)
-
def setup_signalsock(self):
"""Create a socket pair used to signal when we want to finish.
Using a socket is easy and thread/signal safe way to signal
@@ -294,18 +292,12 @@ class MsgQ:
# closed, we should shut down.
(self.__poller_sock, self.__control_sock) = socket.socketpair()
- if self.poller:
- self.poller.register(self.__poller_sock, select.POLLIN)
- else:
- self.add_kqueue_socket(self.__poller_sock)
-
def setup(self):
"""Configure listener socket, polling, etc.
Raises a socket.error if the socket_file cannot be
created.
"""
- self.setup_poller()
self.setup_signalsock()
self.setup_listener()
@@ -323,36 +315,39 @@ class MsgQ:
def register_socket(self, newsocket):
"""
- Internal function to insert a socket. Used by process_accept and some tests.
+ Internal function to insert a socket. Used by process_accept and some
+ tests.
"""
self.sockets[newsocket.fileno()] = newsocket
lname = self.newlname()
self.lnames[lname] = newsocket
+ self.fd_to_lname[newsocket.fileno()] = lname
logger.debug(TRACE_BASIC, MSGQ_SOCKET_REGISTERED, newsocket.fileno(),
lname)
- if self.poller:
- self.poller.register(newsocket, select.POLLIN)
- else:
- self.add_kqueue_socket(newsocket)
+ self.members_notify('connected', {'client': lname})
def kill_socket(self, fd, sock):
"""Fully close down the socket."""
- # Unregister events on the socket. Note that we don't have to do
- # this for kqueue because the registered events are automatically
- # deleted when the corresponding socket is closed.
- if self.poller:
- self.poller.unregister(sock)
- self.subs.unsubscribe_all(sock)
- lname = [ k for k, v in self.lnames.items() if v == sock ][0]
+ unsubscribed_from = self.subs.unsubscribe_all(sock)
+ lname = self.fd_to_lname[fd]
+ del self.fd_to_lname[fd]
del self.lnames[lname]
sock.close()
del self.sockets[fd]
if fd in self.sendbuffs:
del self.sendbuffs[fd]
logger.debug(TRACE_BASIC, MSGQ_SOCK_CLOSE, fd)
+ # Filter out just the groups.
+ unsubscribed_from_groups = set(map(lambda x: x[0], unsubscribed_from))
+ for group in unsubscribed_from_groups:
+ self.members_notify('unsubscribed', {
+ 'client': lname,
+ 'group': group
+ })
+ self.members_notify('disconnected', {'client': lname})
def __getbytes(self, fd, sock, length, continued):
"""Get exactly the requested bytes, or raise an exception if
@@ -406,7 +401,7 @@ class MsgQ:
routing, data = self.read_packet(fd, sock)
except (MsgQReceiveError, MsgQCloseOnReceive) as err:
# If it's MsgQCloseOnReceive and that happens without reading
- # any data, it basically means the remote clinet has closed the
+ # any data, it basically means the remote client has closed the
# socket, so we log it as debug information. Otherwise, it's
# a somewhat unexpected event, so we consider it an "error".
if isinstance(err, MsgQCloseOnReceive) and not err.partial_read:
@@ -429,19 +424,19 @@ class MsgQ:
"""Process a single command. This will split out into one of the
other functions."""
logger.debug(TRACE_DETAIL, MSGQ_RECV_HDR, routing)
- cmd = routing["type"]
- if cmd == 'send':
+ cmd = routing[CC_HEADER_TYPE]
+ if cmd == CC_COMMAND_SEND:
self.process_command_send(sock, routing, data)
- elif cmd == 'subscribe':
+ elif cmd == CC_COMMAND_SUBSCRIBE:
self.process_command_subscribe(sock, routing, data)
- elif cmd == 'unsubscribe':
+ elif cmd == CC_COMMAND_UNSUBSCRIBE:
self.process_command_unsubscribe(sock, routing, data)
- elif cmd == 'getlname':
+ elif cmd == CC_COMMAND_GET_LNAME:
self.process_command_getlname(sock, routing, data)
- elif cmd == 'ping':
+ elif cmd == CC_COMMAND_PING:
# Command for testing purposes
self.process_command_ping(sock, routing, data)
- elif cmd == 'stop':
+ elif cmd == CC_COMMAND_STOP:
self.stop()
else:
logger.error(MSGQ_INVALID_CMD, cmd)
@@ -536,15 +531,10 @@ class MsgQ:
else:
buff = msg[amount_sent:]
last_sent = now
- if self.poller:
- self.poller.register(fileno, select.POLLIN |
- select.POLLOUT)
- else:
- self.add_kqueue_socket(sock, True)
self.sendbuffs[fileno] = (last_sent, buff)
return True
- def __process_write(self, fileno):
+ def _process_write(self, fileno):
# Try to send some data from the buffer
(_, msg) = self.sendbuffs[fileno]
sock = self.sockets[fileno]
@@ -554,10 +544,6 @@ class MsgQ:
msg = msg[amount_sent:]
if len(msg) == 0:
# If there's no more, stop requesting for write availability
- if self.poller:
- self.poller.register(fileno, select.POLLIN)
- else:
- self.delete_kqueue_socket(sock, True)
del self.sendbuffs[fileno]
else:
self.sendbuffs[fileno] = (time.clock(), msg)
@@ -567,14 +553,16 @@ class MsgQ:
This is done by using an increasing counter and the current
time."""
self.connection_counter += 1
- return "%x_%x@%s" % (time.time(), self.connection_counter, self.hostname)
+ return "%x_%x@%s" % (time.time(), self.connection_counter,
+ self.hostname)
def process_command_ping(self, sock, routing, data):
- self.sendmsg(sock, { "type" : "pong" }, data)
+ self.sendmsg(sock, { CC_HEADER_TYPE : CC_COMMAND_PONG }, data)
def process_command_getlname(self, sock, routing, data):
lname = [ k for k, v in self.lnames.items() if v == sock ][0]
- self.sendmsg(sock, { "type" : "getlname" }, { "lname" : lname })
+ self.sendmsg(sock, { CC_HEADER_TYPE : CC_COMMAND_GET_LNAME },
+ { CC_PAYLOAD_LNAME : lname })
def process_command_send(self, sock, routing, data):
group = routing[CC_HEADER_GROUP]
@@ -638,107 +626,75 @@ class MsgQ:
self.send_prepared_msg(sock, errmsg)
def process_command_subscribe(self, sock, routing, data):
- group = routing["group"]
- instance = routing["instance"]
+ group = routing[CC_HEADER_GROUP]
+ instance = routing[CC_HEADER_INSTANCE]
if group == None or instance == None:
return # ignore invalid packets entirely
self.subs.subscribe(group, instance, sock)
+ lname = self.fd_to_lname[sock.fileno()]
+ self.members_notify('subscribed',
+ {
+ 'client': lname,
+ 'group': group
+ })
def process_command_unsubscribe(self, sock, routing, data):
- group = routing["group"]
- instance = routing["instance"]
+ group = routing[CC_HEADER_GROUP]
+ instance = routing[CC_HEADER_INSTANCE]
if group == None or instance == None:
return # ignore invalid packets entirely
- self.subs.unsubscribe(group, instance, sock)
+ if self.subs.unsubscribe(group, instance, sock):
+ lname = self.fd_to_lname[sock.fileno()]
+ self.members_notify('unsubscribed',
+ {
+ 'client': lname,
+ 'group': group
+ })
def run(self):
"""Process messages. Forever. Mostly."""
self.running = True
- if self.poller:
- self.run_poller()
- else:
- self.run_kqueue()
+ self.run_select()
- def run_poller(self):
+ def run_select(self):
while self.running:
+ reads = list(self.fd_to_lname.keys())
+ if self.listen_socket.fileno() != -1: # Skip in tests
+ reads.append(self.listen_socket.fileno())
+ if self.__poller_sock and self.__poller_sock.fileno() != -1:
+ reads.append(self.__poller_sock.fileno())
+ writes = list(self.sendbuffs.keys())
+ (read_ready, write_ready) = ([], [])
try:
- # Poll with a timeout so that every once in a while,
- # the loop checks for self.running.
- events = self.poller.poll()
+ (read_ready, write_ready, _) = select.select(reads, writes,
+ []);
except select.error as err:
if err.args[0] == errno.EINTR:
- events = []
+ continue # Just try it again if interrupted.
else:
- logger.fatal(MSGQ_POLL_ERROR, err)
+ logger.fatal(MSGQ_SELECT_ERROR, err)
break
with self.__lock:
- for (fd, event) in events:
+ write_ready = set(write_ready)
+ for fd in read_ready:
+ # Do only one operation per loop iteration on the given fd.
+ # It could be possible to perform both, but it may have
+ # undesired side effects in special situations (like, if the
+ # read closes the socket).
+ if fd in write_ready:
+ write_ready.remove(fd)
if fd == self.listen_socket.fileno():
self.process_accept()
- elif fd == self.__poller_sock.fileno():
- # If it's the signal socket, we should terminate now.
+ elif self.__poller_sock and fd == \
+ self.__poller_sock.fileno():
+ # The signal socket. We should terminate now.
self.running = False
break
else:
- writable = event & select.POLLOUT
- # Note: it may be okay to read data if available
- # immediately after write some, but due to unexpected
- # regression (see comments on the kqueue version below)
- # we restrict one operation per iteration for now.
- # In future we may clarify the point and enable the
- # "read/write" mode.
- readable = not writable and (event & select.POLLIN)
- if not writable and not readable:
- logger.error(MSGQ_POLL_UNKNOWN_EVENT, fd, event)
- self._process_fd(fd, writable, readable, False)
-
- def run_kqueue(self):
- while self.running:
- # Check with a timeout so that every once in a while,
- # the loop checks for self.running.
- events = self.kqueue.control(None, 10)
- if not events:
- raise RuntimeError('serve: kqueue returned no events')
-
- with self.__lock:
- for event in events:
- if event.ident == self.listen_socket.fileno():
- self.process_accept()
- elif event.ident == self.__poller_sock.fileno():
- # If it's the signal socket, we should terminate now.
- self.running = False
- break;
- else:
- fd = event.ident
- writable = event.filter == select.KQ_FILTER_WRITE
- readable = (event.filter == select.KQ_FILTER_READ and
- event.data > 0)
- # It seems to break some of our test cases if we
- # immediately close the socket on EOF after reading
- # some data. It may be possible to avoid by tweaking
- # the test, but unless we can be sure we'll hold off.
- closed = (not readable and
- (event.flags & select.KQ_EV_EOF))
- self._process_fd(fd, writable, readable, closed)
-
- def _process_fd(self, fd, writable, readable, closed):
- '''Process a single FD: unified subroutine of run_kqueue/poller.
-
- closed can be True only in the case of kqueue. This is essentially
- private but is defined as if it were "protected" so it's callable
- from tests.
-
- '''
- # We need to check if FD is still in the sockets dict, because
- # it's possible that the socket has been "killed" while processing
- # other FDs; it's even possible it's killed within this method.
- if writable and fd in self.sockets:
- self.__process_write(fd)
- if readable and fd in self.sockets:
- self.process_packet(fd, self.sockets[fd])
- if closed and fd in self.sockets:
- self.kill_socket(fd, self.sockets[fd])
+ self.process_packet(fd, self.sockets[fd])
+ for fd in write_ready:
+ self._process_write(fd)
def stop(self):
# Signal it should terminate.
@@ -789,21 +745,32 @@ class MsgQ:
if not self.running:
return
- # TODO: Any config handlig goes here.
+ # TODO: Any config handling goes here.
return isc.config.create_answer(0)
def command_handler(self, command, args):
- """The command handler (run in a separate thread).
- Not tested, currently effectively empty.
- """
+ """The command handler (run in a separate thread)."""
config_logger.debug(TRACE_DETAIL, MSGQ_COMMAND, command, args)
with self.__lock:
if not self.running:
return
- # TODO: Any commands go here
+ # TODO: Who does validation? The ModuleCCSession or must we?
+
+ if command == 'members':
+ # List all members of MsgQ or of a group.
+ if args is None:
+ args = {}
+ group = args.get('group')
+ if group:
+ return isc.config.create_answer(0,
+ list(map(lambda sock: self.fd_to_lname[sock.fileno()],
+ self.subs.find(group, ''))))
+ else:
+ return isc.config.create_answer(0,
+ list(self.lnames.keys()))
config_logger.error(MSGQ_COMMAND_UNKNOWN, command)
return isc.config.create_answer(1, 'unknown command: ' + command)
@@ -818,7 +785,8 @@ if __name__ == "__main__":
a valid port number. Used by OptionParser() on startup."""
intval = int(value)
if (intval < 0) or (intval > 65535):
- raise OptionValueError("%s requires a port number (0-65535)" % opt_str)
+ raise OptionValueError("%s requires a port number (0-65535)" %
+ opt_str)
parser.values.msgq_port = intval
# Parse any command-line options.
@@ -860,13 +828,23 @@ if __name__ == "__main__":
msgq.command_handler,
None, True,
msgq.socket_file)
+ msgq._session = session
session.start()
# And we create a thread that'll just wait for commands and
# handle them. We don't terminate the thread, we set it to
# daemon. Once the main thread terminates, it'll just die.
def run_session():
while True:
- session.check_command(False)
+ # As the check_command has internal mutex that is shared
+ # with sending part (which includes notify). So we don't
+ # want to hold it long-term and block using select.
+ fileno = session.get_socket().fileno()
+ try:
+ (reads, _, _) = select.select([fileno], [], [])
+ except select.error as se:
+ if se.args[0] != errno.EINTR:
+ raise
+ session.check_command(True)
background_thread = threading.Thread(target=run_session)
background_thread.daemon = True
background_thread.start()
diff --git a/src/bin/msgq/msgq.spec b/src/bin/msgq/msgq.spec
index 93204fa..4b388c5 100644
--- a/src/bin/msgq/msgq.spec
+++ b/src/bin/msgq/msgq.spec
@@ -3,6 +3,18 @@
"module_name": "Msgq",
"module_description": "The message queue",
"config_data": [],
- "commands": []
+ "commands": [
+ {
+ "command_name": "members",
+ "command_description": "Provide the list of members of a group or of the whole MsgQ if no group is given.",
+ "command_args": [
+ {
+ "item_name": "group",
+ "item_optional": true,
+ "item_type": "string"
+ }
+ ]
+ }
+ ]
}
}
diff --git a/src/bin/msgq/msgq_messages.mes b/src/bin/msgq/msgq_messages.mes
index 09c9030..909d6a3 100644
--- a/src/bin/msgq/msgq_messages.mes
+++ b/src/bin/msgq/msgq_messages.mes
@@ -85,17 +85,6 @@ Debug message. The listener is trying to open a listening socket.
Debug message. The message queue successfully opened a listening socket and
waits for incoming connections.
-% MSGQ_POLL_ERROR Error while polling for events: %1
-A low-level error happened when waiting for events, the error is logged. The
-reason for this varies, but it usually means the system is short on some
-resources.
-
-% MSGQ_POLL_UNKNOWN_EVENT Got an unknown event from the poller for fd %1: %2
-An unknown event got out from the poll() system call. This should generally not
-happen and it is either a programmer error or OS bug. The event is ignored. The
-number noted as the event is the raw encoded value, which might be useful to
-the authors when figuring the problem out.
-
% MSGQ_RECV_ERROR Error reading from socket %1: %2
There was a low-level error when reading from a socket. The error is logged and
the corresponding socket is dropped. The errors include receiving
@@ -119,6 +108,11 @@ on shutdown unless there's really something unexpected.
% MSGQ_RECV_HDR Received header: %1
Debug message. This message includes the whole routing header of a packet.
+% MSGQ_SELECT_ERROR Error while waiting for events: %1
+A low-level error happened when waiting for events, the error is logged. The
+reason for this varies, but it usually means the system is short on some
+resources.
+
% MSGQ_SEND_ERROR Error while sending to socket %1: %2
There was a low-level error when sending data to a socket. The error is logged
and the corresponding socket is dropped.
diff --git a/src/bin/msgq/run_msgq.sh.in b/src/bin/msgq/run_msgq.sh.in
index c9fef64..6b175d6 100644
--- a/src/bin/msgq/run_msgq.sh.in
+++ b/src/bin/msgq/run_msgq.sh.in
@@ -20,7 +20,7 @@ export PYTHON_EXEC
MYPATH_PATH=@abs_top_builddir@/src/bin/msgq
-PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python/isc/cc:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/log/.libs
+PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python/isc/cc:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
# If necessary (rare cases), explicitly specify paths to dynamic libraries
diff --git a/src/bin/msgq/tests/Makefile.am b/src/bin/msgq/tests/Makefile.am
index c9ef5d3..025b84d 100644
--- a/src/bin/msgq/tests/Makefile.am
+++ b/src/bin/msgq/tests/Makefile.am
@@ -1,18 +1,18 @@
-PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = msgq_test.py
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = msgq_test.py msgq_run_test.py
EXTRA_DIST = $(PYTESTS)
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
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
+ touch $(abs_top_srcdir)/.coverage
rm -f .coverage
${LN_S} $(abs_top_srcdir)/.coverage .coverage
endif
diff --git a/src/bin/msgq/tests/msgq_run_test.py b/src/bin/msgq/tests/msgq_run_test.py
new file mode 100644
index 0000000..9cf6da6
--- /dev/null
+++ b/src/bin/msgq/tests/msgq_run_test.py
@@ -0,0 +1,334 @@
+# Copyright (C) 2013 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.
+
+"""
+In this test file, we actually start msgq as a process and test it
+as a whole. It may be considered a system test instead of unit test,
+but apart from the terminology, we don't care much. We need to test
+the message queue works as expected, together with the libraries.
+
+In each test, we first start a timeout (because we do some waits
+for messages and if they wouldn't come, the test could block indefinitely).
+The timeout is long, because it is for the case the test fails.
+
+We then start the msgq and wait for the socket file to appear
+(that should indicate it is ready to receive connections). Then the
+actual test starts. After the test, we kill it and remove the test file.
+
+We also register signal handlers for many signals. Even in the case
+the test is interrupted or crashes, we should ensure the message queue
+itself is terminated.
+"""
+
+import unittest
+import os
+import signal
+import sys
+import subprocess
+import time
+
+import isc.log
+import isc.cc.session
+from isc.cc.proto_defs import *
+
+# Due to problems with too long path on build bots, we place the socket
+# into the top-level build directory. That is ugly, but works.
+SOCKET_PATH = os.path.abspath(os.environ['B10_FROM_BUILD'] + '/msgq.sock')
+MSGQ_PATH = os.environ['B10_FROM_BUILD'] + '/src/bin/msgq/run_msgq.sh'
+TIMEOUT = 15 # Some long time (seconds), for single test.
+
+class MsgqRunTest(unittest.TestCase):
+ def setUp(self):
+ """
+ As described above - check the socket file does not exist.
+ Then register signals and timeouts. Finally, launch msgq
+ and wait for it to start.
+ """
+ self.__msgq = None
+ self.__opened_connections = []
+ # A precondition check
+ self.assertFalse(os.path.exists(SOCKET_PATH))
+ signal.alarm(TIMEOUT)
+ self.__orig_signals = {}
+ # Register handlers for many signals. Most of them probably
+ # can't happen in python, but we register them anyway just to be
+ # safe.
+ for sig in [signal.SIGHUP, signal.SIGINT, signal.SIGQUIT,
+ signal.SIGILL, signal.SIGTRAP, signal.SIGABRT, signal.SIGBUS,
+ signal.SIGFPE, signal.SIGALRM, signal.SIGTERM]:
+ self.__orig_signals[sig] = signal.signal(sig, self.__signal)
+ # Start msgq
+ self.__msgq = subprocess.Popen([MSGQ_PATH, '-s', SOCKET_PATH],
+ close_fds=True)
+ # Some testing data
+ self.__no_recpt = {"result": [-1, "No such recipient"]}
+ # Wait for it to become ready (up to the alarm-set timeout)
+ connection = None
+ while not connection:
+ try:
+ # If the msgq is ready, this'll succeed. If not, it'll throw
+ # session error.
+ connection = isc.cc.session.Session(SOCKET_PATH)
+ except isc.cc.session.SessionError:
+ time.sleep(0.1) # Retry after a short time
+ # We have the connection now, that means it works. Close this
+ # connection, we won't use it. Each test gets enough new connections
+ # of its own.
+ connection.close()
+
+ def __message(self, data):
+ """
+ Provide some testing message. The data will be included in it, so
+ several different messages can be created.
+ """
+ return {"Message": "Text", "Data": data}
+
+ def tearDown(self):
+ """
+ Perform cleanup after the test.
+ """
+ self.__cleanup()
+
+ def __signal(self, signal, frame):
+ """
+ Called from a signal handler. We perform some cleanup, output
+ a complain and terminate with error.
+ """
+ self.__cleanup()
+ sys.stderr.write("Test terminating from signal " + str(signal) +
+ " in " + str(frame) + "\n")
+ sys.exit(1)
+
+ def __cleanup(self):
+ """
+ Kill msgq (if running) and restore original signal handlers.
+ """
+ # Remove the socket (as we kill, msgq might not clean up)
+ for conn in self.__opened_connections:
+ conn.close()
+ self.__opened_connections = []
+ if self.__msgq:
+ self.__msgq.kill()
+ self.__msgq = None
+ if os.path.exists(SOCKET_PATH):
+ os.unlink(SOCKET_PATH)
+ for sig in self.__orig_signals:
+ signal.signal(sig, self.__orig_signals[sig])
+ # Cancel timeout (so someone else is not hit by it)
+ signal.alarm(0)
+
+ def __get_connection(self):
+ """
+ Create a connection to the daemon and make sure it is properly closed
+ at the end of the test.
+ """
+ connection = isc.cc.session.Session(SOCKET_PATH)
+ self.__opened_connections.append(connection)
+ return connection
+
+ def test_send_direct(self):
+ """
+ Connect twice to msgq, send a message from one to another using direct
+ l-name and see it comes.
+ """
+ # Create the connections
+ conn1 = self.__get_connection()
+ conn2 = self.__get_connection()
+ # Send the message
+ lname1 = conn1.lname
+ conn2.group_sendmsg(self.__message(1), "*", to=lname1)
+ # Receive the message and see it contains correct data
+ (msg, env) = conn1.group_recvmsg(nonblock=False)
+ self.assertEqual(self.__message(1), msg)
+ # We don't check there are no extra headers, just that none are missing
+ # or wrong.
+ self.assertEqual(lname1, env[CC_HEADER_TO])
+ self.assertEqual(conn2.lname, env[CC_HEADER_FROM])
+ self.assertEqual("*", env[CC_HEADER_GROUP])
+ self.assertEqual(CC_INSTANCE_WILDCARD, env[CC_HEADER_INSTANCE])
+ self.assertEqual(CC_COMMAND_SEND, env[CC_HEADER_TYPE])
+ self.assertFalse(env[CC_HEADER_WANT_ANSWER])
+
+ def __barrier(self, connections):
+ """
+ Make sure all previous commands on all supplied connections are
+ processed, by sending a ping and waiting for an answer.
+ """
+ for c in connections:
+ c.sendmsg({"type": "ping"})
+ for c in connections:
+ pong = c.recvmsg(nonblock=False)
+ self.assertEqual(({"type": "pong"}, None), pong)
+
+ def test_send_group(self):
+ """
+ Create several connections. First, try to send a message to a (empty)
+ group and see an error is bounced back. Then subscribe the others
+ to the group and send it again. Send to a different group and see it
+ bounced back. Unsubscribe and see it is bounced again.
+
+ Then the other connections answer (after unsubscribing, strange, but
+ legal). See both answers come.
+
+ Then, look there are no more waiting messages.
+ """
+ conn_a = self.__get_connection()
+ conn_b = []
+ for i in range(0, 10):
+ conn_b.append(self.__get_connection())
+ # Send a message to empty group and get an error answer
+ seq = conn_a.group_sendmsg(self.__message(1), "group",
+ want_answer=True)
+ (msg, env) = conn_a.group_recvmsg(nonblock=False, seq=seq)
+ self.assertEqual(self.__no_recpt, msg)
+ self.assertEqual(conn_a.lname, env[CC_HEADER_TO])
+ # Subscribe the two connections
+ for c in conn_b:
+ c.group_subscribe("group")
+ # The subscribe doesn't wait for answer, so make sure it is
+ # all processed before continuing.
+ self.__barrier(conn_b)
+ # Send a message to the group (this time not empty)
+ seq = conn_a.group_sendmsg(self.__message(2), "group",
+ want_answer=True)
+ envs = []
+ for c in conn_b:
+ (msg, env) = c.group_recvmsg(nonblock=False)
+ self.assertEqual(self.__message(2), msg)
+ self.assertEqual(conn_a.lname, env[CC_HEADER_FROM])
+ # The daemon does not mangle the headers. Is it OK?
+ self.assertEqual(CC_TO_WILDCARD, env[CC_HEADER_TO])
+ self.assertEqual("group", env[CC_HEADER_GROUP])
+ self.assertEqual(CC_INSTANCE_WILDCARD, env[CC_HEADER_INSTANCE])
+ self.assertEqual(CC_COMMAND_SEND, env[CC_HEADER_TYPE])
+ self.assertTrue(env[CC_HEADER_WANT_ANSWER])
+ envs.append(env)
+ # Send to non-existing group
+ seq_ne = conn_a.group_sendmsg(self.__message(3), "no-group",
+ want_answer=True)
+ (msg, env) = conn_a.group_recvmsg(nonblock=False, seq=seq_ne)
+ self.assertEqual(self.__no_recpt, msg)
+ self.assertEqual(conn_a.lname, env[CC_HEADER_TO])
+ # Unsubscribe the connections
+ for c in conn_b:
+ c.group_unsubscribe("group")
+ # Synchronize the unsubscriptions
+ self.__barrier(conn_b)
+ seq_ne = conn_a.group_sendmsg(self.__message(4), "group",
+ want_answer=True)
+ (msg, env) = conn_a.group_recvmsg(nonblock=False, seq=seq_ne)
+ self.assertEqual(self.__no_recpt, msg)
+ self.assertEqual(conn_a.lname, env[CC_HEADER_TO])
+ # Send answers for the original message that was delivered
+ lnames = set()
+ for (c, env) in zip(conn_b, envs):
+ c.group_reply(env, self.__message("Reply"))
+ lnames.add(c.lname)
+ # Check the both answers come
+ while lnames:
+ # While there are still connections we didn't get the answer from
+ # (the order is not guaranteed, therefore the juggling with set)
+ (msg, env) = conn_a.group_recvmsg(nonblock=False, seq=seq)
+ self.assertEqual(self.__message("Reply"), msg)
+ lname = env[CC_HEADER_FROM]
+ self.assertTrue(lname in lnames)
+ lnames.remove(lname)
+
+ # The barrier makes the msgq process everything we sent. As the
+ # processing is single-threaded in it, any stray message would have
+ # arrived before the barrier ends.
+ self.__barrier(conn_b)
+ self.__barrier([conn_a])
+ for c in conn_b:
+ self.assertEqual((None, None), c.group_recvmsg())
+ self.assertEqual((None, None), conn_a.group_recvmsg())
+
+ def test_conn_disconn(self):
+ """
+ Keep connecting and disconnecting, checking we can still send
+ and receive messages.
+ """
+ conn = self.__get_connection()
+ conn.group_subscribe("group")
+ for i in range(0, 50):
+ new = self.__get_connection()
+ new.group_subscribe("group")
+ self.__barrier([conn, new])
+ new.group_sendmsg(self.__message(i), "group")
+ (msg, env) = conn.group_recvmsg(nonblock=False)
+ self.assertEqual(self.__message(i), msg)
+ conn.close()
+ conn = new
+
+ def test_notifications(self):
+ """
+ Check that the MsgQ is actually sending notifications about events.
+ We create a socket, subscribe the socket itself and see it receives
+ it's own notification.
+
+ Testing all the places where notifications happen is task for the
+ common unit tests in msgq_test.py.
+
+ The test is here, because there might be some trouble with multiple
+ threads in msgq (see the note about locking on the module CC session
+ when sending message from one thread and listening for commands in the
+ other) which would be hard to test using pure unit tests. Testing
+ runnig whole msgq tests that implicitly.
+ """
+ conn = self.__get_connection()
+ # Activate the session, pretend to be the config manager.
+ conn.group_subscribe('ConfigManager')
+ # Answer request for logging config
+ (msg, env) = conn.group_recvmsg(nonblock=False)
+ self.assertEqual({'command': ['get_config',
+ {'module_name': 'Logging'}]},
+ msg)
+ conn.group_reply(env, {'result': [0, {}]})
+ # It sends its spec.
+ (msg, env) = conn.group_recvmsg(nonblock=False)
+ self.assertEqual('module_spec', msg['command'][0])
+ conn.group_reply(env, {'result': [0]})
+ # It asks for its own config
+ (msg, env) = conn.group_recvmsg(nonblock=False)
+ self.assertEqual({'command': ['get_config',
+ {'module_name': 'Msgq'}]},
+ msg)
+ conn.group_reply(env, {'result': [0, {}]})
+ # Synchronization - make sure the session is running before
+ # we continue, so we get the notification. Similar synchronisation
+ # as in b10-init, but we don't have full ccsession here, so we
+ # do so manually.
+ synchronised = False
+ attempts = 100
+ while not synchronised and attempts > 0:
+ time.sleep(0.1)
+ seq = conn.group_sendmsg({'command': ['Are you running?']},
+ 'Msgq', want_answer=True)
+ msg = conn.group_recvmsg(nonblock=False, seq=seq)
+ synchronised = msg[0] != -1
+ attempts -= 1
+ self.assertTrue(synchronised)
+ # The actual test
+ conn.group_subscribe('notifications/cc_members')
+ (msg, env) = conn.group_recvmsg(nonblock=False)
+ self.assertEqual({'notification': ['subscribed', {
+ 'client': conn.lname,
+ 'group': 'notifications/cc_members'
+ }]}, msg)
+
+if __name__ == '__main__':
+ isc.log.init("msgq-tests")
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index 98705bf..210246d 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -19,6 +19,7 @@ from msgq import SubscriptionManager, MsgQ
import unittest
import os
import socket
+import select # needed only for #3014. can be removed once it's solved
import signal
import sys
import time
@@ -63,8 +64,11 @@ class TestSubscriptionManager(unittest.TestCase):
socks = [ 's1', 's2', 's3', 's4', 's5' ]
for s in socks:
self.sm.subscribe("a", "*", s)
- self.sm.unsubscribe("a", "*", 's3')
- self.assertEqual(self.sm.find_sub("a", "*"), [ 's1', 's2', 's4', 's5' ])
+ self.assertTrue(self.sm.unsubscribe("a", "*", 's3'))
+ # Unsubscribe from group it is not in
+ self.assertFalse(self.sm.unsubscribe("a", "*", 's42'))
+ self.assertEqual(self.sm.find_sub("a", "*"),
+ [ 's1', 's2', 's4', 's5' ])
def test_unsubscribe_all(self):
self.sm.subscribe('g1', 'i1', 's1')
@@ -75,7 +79,9 @@ class TestSubscriptionManager(unittest.TestCase):
self.sm.subscribe('g2', 'i1', 's2')
self.sm.subscribe('g2', 'i2', 's1')
self.sm.subscribe('g2', 'i2', 's2')
- self.sm.unsubscribe_all('s1')
+ self.assertEqual(set([('g1', 'i1'), ('g1', 'i2'), ('g2', 'i1'),
+ ('g2', 'i2')]),
+ set(self.sm.unsubscribe_all('s1')))
self.assertEqual(self.sm.find_sub("g1", "i1"), [ 's2' ])
self.assertEqual(self.sm.find_sub("g1", "i2"), [ 's2' ])
self.assertEqual(self.sm.find_sub("g2", "i1"), [ 's2' ])
@@ -178,6 +184,157 @@ class MsgQTest(unittest.TestCase):
data = json.loads(msg[6 + header_len:].decode('utf-8'))
return (header, data)
+ def test_unknown_command(self):
+ """
+ Test the command handler returns error when the command is unknown.
+ """
+ # Fake we are running, to disable test workarounds
+ self.__msgq.running = True
+ self.assertEqual({'result': [1, "unknown command: unknown"]},
+ self.__msgq.command_handler('unknown', {}))
+
+ def test_get_members(self):
+ """
+ Test getting members of a group or of all connected clients.
+ """
+ # Push two dummy "clients" into msgq (the ugly way, by directly
+ # tweaking relevant data structures).
+ class Sock:
+ def __init__(self, fileno):
+ self.fileno = lambda: fileno
+ self.__msgq.lnames['first'] = Sock(1)
+ self.__msgq.lnames['second'] = Sock(2)
+ self.__msgq.fd_to_lname[1] = 'first'
+ self.__msgq.fd_to_lname[2] = 'second'
+ # Subscribe them to some groups
+ self.__msgq.process_command_subscribe(self.__msgq.lnames['first'],
+ {'group': 'G1', 'instance': '*'},
+ None)
+ self.__msgq.process_command_subscribe(self.__msgq.lnames['second'],
+ {'group': 'G1', 'instance': '*'},
+ None)
+ self.__msgq.process_command_subscribe(self.__msgq.lnames['second'],
+ {'group': 'G2', 'instance': '*'},
+ None)
+ # Now query content of some groups through the command handler.
+ self.__msgq.running = True # Enable the command handler
+ def check_both(result):
+ """
+ Check the result is successful one and it contains both lnames (in
+ any order).
+ """
+ array = result['result'][1]
+ self.assertEqual(set(['first', 'second']), set(array))
+ self.assertEqual({'result': [0, array]}, result)
+ # Make sure the result can be encoded as JSON
+ # (there seems to be types that look like a list but JSON choks
+ # on them)
+ json.dumps(result)
+ # Members of the G1 and G2
+ self.assertEqual({'result': [0, ['second']]},
+ self.__msgq.command_handler('members',
+ {'group': 'G2'}))
+ check_both(self.__msgq.command_handler('members', {'group': 'G1'}))
+ # We pretend that all the possible groups exist, just that most
+ # of them are empty. So requesting for Empty is request for an empty
+ # group and should not fail.
+ self.assertEqual({'result': [0, []]},
+ self.__msgq.command_handler('members',
+ {'group': 'Empty'}))
+ # Without the name of the group, we just get all the clients.
+ check_both(self.__msgq.command_handler('members', {}))
+ # Omitting the parameters completely in such case is OK
+ check_both(self.__msgq.command_handler('members', None))
+
+ def notifications_setup(self):
+ """
+ Common setup of some notifications tests. Mock several things.
+ """
+ # Mock the method to send notifications (we don't really want
+ # to send them now, just see they'd be sent).
+ # Mock the poller, as we don't need it at all (and we don't have
+ # real socket to give it now).
+ notifications = []
+ def send_notification(event, params):
+ notifications.append((event, params))
+ class FakePoller:
+ def register(self, socket, mode):
+ pass
+ def unregister(self, sock):
+ pass
+ self.__msgq.members_notify = send_notification
+ self.__msgq.poller = FakePoller()
+
+ # Create a socket
+ class Sock:
+ def __init__(self, fileno):
+ self.fileno = lambda: fileno
+ def close(self):
+ pass
+ sock = Sock(1)
+ return notifications, sock
+
+ def test_notifies(self):
+ """
+ Test the message queue sends notifications about connecting,
+ disconnecting and subscription changes.
+ """
+ notifications, sock = self.notifications_setup()
+
+ # We should notify about new cliend when we register it
+ self.__msgq.register_socket(sock)
+ lname = self.__msgq.fd_to_lname[1] # Steal the lname
+ self.assertEqual([('connected', {'client': lname})], notifications)
+ del notifications[:]
+
+ # A notification should happen for a subscription to a group
+ self.__msgq.process_command_subscribe(sock, {'group': 'G',
+ 'instance': '*'},
+ None)
+ self.assertEqual([('subscribed', {'client': lname, 'group': 'G'})],
+ notifications)
+ del notifications[:]
+
+ # As well for unsubscription
+ self.__msgq.process_command_unsubscribe(sock, {'group': 'G',
+ 'instance': '*'},
+ None)
+ self.assertEqual([('unsubscribed', {'client': lname, 'group': 'G'})],
+ notifications)
+ del notifications[:]
+
+ # Unsubscription from a group it isn't subscribed to
+ self.__msgq.process_command_unsubscribe(sock, {'group': 'H',
+ 'instance': '*'},
+ None)
+ self.assertEqual([], notifications)
+
+ # And, finally, for removal of client
+ self.__msgq.kill_socket(sock.fileno(), sock)
+ self.assertEqual([('disconnected', {'client': lname})], notifications)
+
+ def test_notifies_implicit_kill(self):
+ """
+ Test that the unsubscription notifications are sent before the socket
+ is dropped, even in case it does not unsubscribe explicitly.
+ """
+ notifications, sock = self.notifications_setup()
+
+ # Register and subscribe. Notifications for these are in above test.
+ self.__msgq.register_socket(sock)
+ lname = self.__msgq.fd_to_lname[1] # Steal the lname
+ self.__msgq.process_command_subscribe(sock, {'group': 'G',
+ 'instance': '*'},
+ None)
+ del notifications[:]
+
+ self.__msgq.kill_socket(sock.fileno(), sock)
+ # Now, the notification for unsubscribe should be first, second for
+ # the disconnection.
+ self.assertEqual([('unsubscribed', {'client': lname, 'group': 'G'}),
+ ('disconnected', {'client': lname})
+ ], notifications)
+
def test_undeliverable_errors(self):
"""
Send several packets through the MsgQ and check it generates
@@ -186,7 +343,7 @@ class MsgQTest(unittest.TestCase):
The test is not exhaustive as it doesn't test all combination
of existence of the recipient, addressing schemes, want_answer
header and the reply header. It is not needed, these should
- be mostly independant. That means, for example, if the message
+ be mostly independent. That means, for example, if the message
is a reply and there's no recipient to send it to, the error
would not be generated no matter if we addressed the recipient
by lname or group. If we included everything, the test would
@@ -338,7 +495,7 @@ class BadSocket:
self.send_exception = send_exception
# completely wrap all calls and member access
- # (except explicitely overridden ones)
+ # (except explicitly overridden ones)
def __getattr__(self, name, *args):
attr = getattr(self.socket, name)
if isinstance(attr, collections.Callable):
@@ -412,12 +569,16 @@ class SendNonblock(unittest.TestCase):
The write end is put into the message queue, so we can check it.
It returns (msgq, read_end, write_end). It is expected the sockets
are closed by the caller afterwards.
+
+ Also check the sockets are registered correctly (eg. internal data
+ structures are there for them).
'''
msgq = MsgQ()
# We do only partial setup, so we don't create the listening socket
- msgq.setup_poller()
(read, write) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
msgq.register_socket(write)
+ self.assertEqual(1, len(msgq.lnames))
+ self.assertEqual(write, msgq.lnames[msgq.fd_to_lname[write.fileno()]])
return (msgq, read, write)
def infinite_sender(self, sender):
@@ -437,8 +598,15 @@ class SendNonblock(unittest.TestCase):
# Explicitly close temporary socket pair as the Python
# interpreter expects it. It may not be 100% exception safe,
# but since this is only for tests we prefer brevity.
+ # Actually, the write end is often closed by the sender.
+ if write.fileno() != -1:
+ # Some of the senders passed here kill the socket internally.
+ # So kill it only if not yet done so. If the socket is closed,
+ # it gets -1 as fileno().
+ msgq.kill_socket(write.fileno(), write)
+ self.assertFalse(msgq.lnames)
+ self.assertFalse(msgq.fd_to_lname)
read.close()
- write.close()
def test_infinite_sendmsg(self):
"""
@@ -504,7 +672,6 @@ class SendNonblock(unittest.TestCase):
queue_pid = os.fork()
if queue_pid == 0:
signal.alarm(120)
- msgq.setup_poller()
msgq.setup_signalsock()
msgq.register_socket(queue)
msgq.run()
@@ -581,7 +748,6 @@ class SendNonblock(unittest.TestCase):
msgq = MsgQ()
# Don't need a listen_socket
msgq.listen_socket = DummySocket
- msgq.setup_poller()
msgq.setup_signalsock()
msgq.register_socket(write)
msgq.register_socket(control_write)
@@ -640,9 +806,11 @@ class SendNonblock(unittest.TestCase):
send_exception is raised by BadSocket.
"""
(write, read) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
- (control_write, control_read) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+ (control_write, control_read) = socket.socketpair(socket.AF_UNIX,
+ socket.SOCK_STREAM)
badwrite = BadSocket(write, raise_on_send, send_exception)
- self.do_send(badwrite, read, control_write, control_read, expect_answer, expect_send_exception)
+ self.do_send(badwrite, read, control_write, control_read,
+ expect_answer, expect_send_exception)
write.close()
read.close()
control_write.close()
@@ -822,9 +990,11 @@ class SocketTests(unittest.TestCase):
self.__killed_socket = None
self.__logger = self.LoggerWrapper(msgq.logger)
msgq.logger = self.__logger
+ self.__orig_select = msgq.select.select
def tearDown(self):
msgq.logger = self.__logger.orig_logger
+ msgq.select.select = self.__orig_select
def test_send_data(self):
# Successful case: _send_data() returns the hardcoded value, and
@@ -834,7 +1004,7 @@ class SocketTests(unittest.TestCase):
self.assertIsNone(self.__killed_socket)
def test_send_data_interrupt(self):
- '''send() is interruptted. send_data() returns 0, sock isn't killed.'''
+ '''send() is interrupted. send_data() returns 0, sock isn't killed.'''
expected_blockings = []
for eno in [errno.EAGAIN, errno.EWOULDBLOCK, errno.EINTR]:
self.__sock_error.errno = eno
@@ -872,32 +1042,6 @@ class SocketTests(unittest.TestCase):
self.assertEqual(expected_errors, self.__logger.error_called)
self.assertEqual(expected_warns, self.__logger.warn_called)
- def test_process_fd_read_after_bad_write(self):
- '''Check the specific case of write fail followed by read attempt.
-
- The write failure results in kill_socket, then read shouldn't tried.
-
- '''
- self.__sock_error.errno = errno.EPIPE
- self.__sock.ex_on_send = self.__sock_error
- self.__msgq.process_socket = None # if called, trigger an exception
- self.__msgq._process_fd(42, True, True, False) # shouldn't crash
-
- # check the socket is deleted from the fileno=>sock dictionary
- self.assertEqual({}, self.__msgq.sockets)
-
- def test_process_fd_close_after_bad_write(self):
- '''Similar to the previous, but for checking dup'ed kill attempt'''
- self.__sock_error.errno = errno.EPIPE
- self.__sock.ex_on_send = self.__sock_error
- self.__msgq._process_fd(42, True, False, True) # shouldn't crash
- self.assertEqual({}, self.__msgq.sockets)
-
- def test_process_fd_writer_after_close(self):
- '''Emulate a "writable" socket has been already closed and killed.'''
- # This just shouldn't crash
- self.__msgq._process_fd(4200, True, False, False)
-
def test_process_packet(self):
'''Check some failure cases in handling an incoming message.'''
expected_errors = 0
@@ -931,6 +1075,47 @@ class SocketTests(unittest.TestCase):
self.assertEqual(expected_errors, self.__logger.error_called)
self.assertEqual(expected_debugs, self.__logger.debug_called)
+ def test_do_select(self):
+ """
+ Check the behaviour of the run_select method.
+
+ In particular, check that we skip writing to the sockets we read,
+ because a read may have side effects (like closing the socket) and
+ we want to prevent strange behavior.
+ """
+ self.__read_called = []
+ self.__write_called = []
+ self.__reads = None
+ self.__writes = None
+ def do_read(fd, socket):
+ self.__read_called.append(fd)
+ self.__msgq.running = False
+ def do_write(fd):
+ self.__write_called.append(fd)
+ self.__msgq.running = False
+ self.__msgq.process_packet = do_read
+ self.__msgq._process_write = do_write
+ self.__msgq.fd_to_lname = {42: 'lname', 44: 'other', 45: 'unused'}
+ # The do_select does index it, but just passes the value. So reuse
+ # the dict to safe typing in the test.
+ self.__msgq.sockets = self.__msgq.fd_to_lname
+ self.__msgq.sendbuffs = {42: 'data', 43: 'data'}
+ def my_select(reads, writes, errors):
+ self.__reads = reads
+ self.__writes = writes
+ self.assertEqual([], errors)
+ return ([42, 44], [42, 43], [])
+ msgq.select.select = my_select
+ self.__msgq.listen_socket = DummySocket
+
+ self.__msgq.running = True
+ self.__msgq.run_select()
+
+ self.assertEqual([42, 44], self.__read_called)
+ self.assertEqual([43], self.__write_called)
+ self.assertEqual({42, 44, 45}, set(self.__reads))
+ self.assertEqual({42, 43}, set(self.__writes))
+
if __name__ == '__main__':
isc.log.resetUnitTestRootLogger()
unittest.main()
diff --git a/src/bin/resolver/Makefile.am b/src/bin/resolver/Makefile.am
index 47e242c..38f9bf6 100644
--- a/src/bin/resolver/Makefile.am
+++ b/src/bin/resolver/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = . tests
+SUBDIRS = . tests bench
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
@@ -83,4 +83,3 @@ b10_resolver_LDFLAGS = -pthread
# and can't use @datadir@ because doesn't expand default ${prefix}
b10_resolverdir = $(pkgdatadir)
b10_resolver_DATA = resolver.spec
-
diff --git a/src/bin/resolver/bench/.gitignore b/src/bin/resolver/bench/.gitignore
new file mode 100644
index 0000000..5af2afe
--- /dev/null
+++ b/src/bin/resolver/bench/.gitignore
@@ -0,0 +1 @@
+/resolver-bench
diff --git a/src/bin/resolver/bench/Makefile.am b/src/bin/resolver/bench/Makefile.am
new file mode 100644
index 0000000..346e007
--- /dev/null
+++ b/src/bin/resolver/bench/Makefile.am
@@ -0,0 +1,24 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -I$(top_builddir)/src/bin/resolver
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+noinst_PROGRAMS = resolver-bench
+
+resolver_bench_SOURCES = main.cc
+resolver_bench_SOURCES += fake_resolution.h fake_resolution.cc
+resolver_bench_SOURCES += dummy_work.h dummy_work.cc
+resolver_bench_SOURCES += naive_resolver.h naive_resolver.cc
+
+resolver_bench_LDADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+resolver_bench_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+
diff --git a/src/bin/resolver/bench/dummy_work.cc b/src/bin/resolver/bench/dummy_work.cc
new file mode 100644
index 0000000..c17bcbf
--- /dev/null
+++ b/src/bin/resolver/bench/dummy_work.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <resolver/bench/dummy_work.h>
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+void
+dummy_work() {
+ // Function left intentonally blank.
+};
+
+}
+}
+}
diff --git a/src/bin/resolver/bench/dummy_work.h b/src/bin/resolver/bench/dummy_work.h
new file mode 100644
index 0000000..81fd0f2
--- /dev/null
+++ b/src/bin/resolver/bench/dummy_work.h
@@ -0,0 +1,36 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DUMMY_WORK_H
+#define DUMMY_WORK_H
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+/// \brief An empty function.
+///
+/// An empty function, to fill the CPU with something during the benchmark.
+/// It is expected to be called many times by whatever simulates doing some
+/// real CPU-bound work.
+///
+/// It is defined in separate translation unit, so the compiler does not
+/// know it is empty and can't optimise the call out.
+void dummy_work();
+
+}
+}
+}
+
+#endif
diff --git a/src/bin/resolver/bench/fake_resolution.cc b/src/bin/resolver/bench/fake_resolution.cc
new file mode 100644
index 0000000..e3d54a9
--- /dev/null
+++ b/src/bin/resolver/bench/fake_resolution.cc
@@ -0,0 +1,172 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <resolver/bench/fake_resolution.h>
+#include <resolver/bench/dummy_work.h>
+
+#include <asiolink/interval_timer.h>
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <algorithm>
+#include <stdlib.h> // not cstdlib, which doesn't officially have random()
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+// Parameters of the generated queries.
+// How much work is each operation?
+const size_t parse_size = 100000;
+const size_t render_size = 100000;
+const size_t send_size = 1000;
+const size_t cache_read_size = 10000;
+const size_t cache_write_size = 10000;
+// How large a change is to terminate in this iteration (either by getting
+// the complete answer, or by finding it in the cache). With 0.5, half the
+// queries are found in the cache directly. Half of the rest needs just one
+// upstream query. Etc.
+const float chance_complete = 0.5;
+// Number of milliseconds an upstream query can take. It picks a random number
+// in between.
+const size_t upstream_time_min = 2;
+const size_t upstream_time_max = 50;
+
+FakeQuery::FakeQuery(FakeInterface& interface) :
+ interface_(&interface),
+ outstanding_(false)
+{
+ // Schedule what tasks are needed.
+ // First, parse the query
+ steps_.push_back(Step(Compute, parse_size));
+ // Look into the cache if it is there
+ steps_.push_back(Step(CacheRead, cache_read_size));
+ while ((1.0 * random()) / RAND_MAX > chance_complete) {
+ // Needs another step of recursion. Render the upstream query.
+ steps_.push_back(Step(Compute, render_size));
+ // Send it and wait for the answer.
+ steps_.push_back(Step(Upstream, upstream_time_min +
+ (random() *
+ (upstream_time_max - upstream_time_min) /
+ RAND_MAX)));
+ // After it comes, parse the answer and store it in the cache.
+ steps_.push_back(Step(Compute, parse_size));
+ steps_.push_back(Step(CacheWrite, cache_write_size));
+ }
+ // Last, render the answer and send it.
+ steps_.push_back(Step(Compute, render_size));
+ steps_.push_back(Step(Send, send_size));
+ // Reverse it, so we can pop_back the tasks as we work on them.
+ std::reverse(steps_.begin(), steps_.end());
+}
+
+void
+FakeQuery::performTask(const StepCallback& callback) {
+ // nextTask also does all the sanity checking we need.
+ if (nextTask() == Upstream) {
+ outstanding_ = true;
+ interface_->scheduleUpstreamAnswer(this, callback,
+ steps_.back().second);
+ steps_.pop_back();
+ } else {
+ for (size_t i = 0; i < steps_.back().second; ++i) {
+ dummy_work();
+ }
+ steps_.pop_back();
+ callback();
+ }
+}
+
+FakeInterface::FakeInterface(size_t query_count) :
+ queries_(query_count)
+{
+ BOOST_FOREACH(FakeQueryPtr& query, queries_) {
+ query = FakeQueryPtr(new FakeQuery(*this));
+ }
+}
+
+void
+FakeInterface::processEvents() {
+ service_.run_one();
+}
+
+namespace {
+
+void
+processDone(bool* flag) {
+ *flag = true;
+}
+
+}
+
+FakeQueryPtr
+FakeInterface::receiveQuery() {
+ // Handle all the events that are already scheduled.
+ // As processEvents blocks until an event happens and we want to terminate
+ // if there are no events, we do a small trick. We post an event to the end
+ // of the queue and work until it is found. This should process all the
+ // events that were there already.
+ bool processed = false;
+ service_.post(boost::bind(&processDone, &processed));
+ while (!processed) {
+ processEvents();
+ }
+
+ // Now, look if there are more queries to return.
+ if (queries_.empty()) {
+ return (FakeQueryPtr());
+ } else {
+ // Take from the back. The order doesn't matter and it's faster from
+ // there.
+ FakeQueryPtr result(queries_.back());
+ queries_.pop_back();
+ return (result);
+ }
+}
+
+class FakeInterface::UpstreamQuery {
+public:
+ UpstreamQuery(FakeQuery* query, const FakeQuery::StepCallback& callback,
+ const boost::shared_ptr<asiolink::IntervalTimer> timer) :
+ query_(query),
+ callback_(callback),
+ timer_(timer)
+ {}
+ void trigger() {
+ query_->answerReceived();
+ callback_();
+ // We are not needed any more.
+ delete this;
+ }
+private:
+ FakeQuery* const query_;
+ const FakeQuery::StepCallback callback_;
+ // Just to hold it alive before the callback is called.
+ const boost::shared_ptr<asiolink::IntervalTimer> timer_;
+};
+
+void
+FakeInterface::scheduleUpstreamAnswer(FakeQuery* query,
+ const FakeQuery::StepCallback& callback,
+ size_t msec)
+{
+ const boost::shared_ptr<asiolink::IntervalTimer>
+ timer(new asiolink::IntervalTimer(service_));
+ UpstreamQuery* q(new UpstreamQuery(query, callback, timer));
+ timer->setup(boost::bind(&UpstreamQuery::trigger, q), msec);
+}
+
+}
+}
+}
diff --git a/src/bin/resolver/bench/fake_resolution.h b/src/bin/resolver/bench/fake_resolution.h
new file mode 100644
index 0000000..cf2219c
--- /dev/null
+++ b/src/bin/resolver/bench/fake_resolution.h
@@ -0,0 +1,228 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 FAKE_RESOLUTION_H
+#define FAKE_RESOLUTION_H
+
+#include <exceptions/exceptions.h>
+#include <asiolink/io_service.h>
+
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <utility>
+#include <vector>
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+/// \brief The kind of task a FakeQuery might want to perform.
+///
+/// The benchmark should examine which kind of task the query needs to perform
+/// to progress forward. According to the task, some resources might need to be
+/// locked, something re-scheduled, or such.
+enum Task {
+ /// \brief Some CPU-bound computation.
+ ///
+ /// The query needs to do some computation without any shared resources.
+ /// This might be parsing or rendering of the query, verification of
+ /// signatures, etc.
+ Compute,
+ /// \brief The query needs to read data from cache.
+ CacheRead,
+ /// \brief The query needs to modify the cache.
+ CacheWrite,
+ /// \brief A response is to be sent.
+ ///
+ /// This needs to access the interface/socket. If the socket is shared
+ /// between threads, it might need to lock it.
+ Send,
+ /// \brief An answer from upstream server is needed.
+ ///
+ /// The query needs to send a query to some authoritative server and wait
+ /// for the answer. Something might need to be locked (or not, depending
+ /// on the architecture of the thing that sends and receives). Also, the
+ /// task will not complete immediately, the callback of performTask
+ /// will be called at later time.
+ Upstream
+};
+
+class FakeInterface;
+
+/// \brief Imitation of the work done to resolve a query.
+///
+/// An object of this class represents some fake work that should look like
+/// the work needed to perform resolution of one query. No real work is done,
+/// but several steps are scheduled, with characteristics hopefully
+/// corresponding to steps of the real query.
+///
+/// The idea is that benchmark will repeatedly check if the query is done.
+/// If not, it examines the next task by calling nextTask(). Depending on
+/// the result, it'd lock or prepare any shared resources. After that, it'd
+/// call performTask() to do the task. Once the query calls the callback
+/// passed, it can proceed to the next step.
+///
+/// See naive_resolver.cc for example code how this could be done.
+class FakeQuery {
+private:
+ // The queries come only through an interface. Don't let others create.
+ friend class FakeInterface;
+ /// \brief Constructor
+ FakeQuery(FakeInterface& interface);
+public:
+ /// \brief Is work on the query completely done?
+ ///
+ /// If this returns true, do not call performTask or nextTask any more.
+ /// The resolution is done.
+ ///
+ /// \throw isc::InvalidOperation if upstream query is still in progress.
+ bool done() const {
+ if (outstanding_) {
+ isc_throw(isc::InvalidOperation, "Upstream query outstanding");
+ }
+ return (steps_.empty());
+ }
+ /// \brief Callback to signify a task has been performed.
+ typedef boost::function<void()> StepCallback;
+ /// \brief Perform next step in the resolution.
+ ///
+ /// Do whatever is needed to be done for the next step of resolution.
+ /// Once the step is done, the callback is called.
+ ///
+ /// The callback is usually called from within this call. However, in
+ /// the case when the nextTask() returned `Upstream`, the call to the
+ /// callback is delayed for some period of time after the method
+ /// returns.
+ ///
+ /// \throw isc::InvalidOperation if it is called when done() is true, or
+ /// if an upstream query is still in progress (performTask was called
+ /// before and the callback was not called by the query yet).
+ void performTask(const StepCallback& callback);
+ /// \brief Examine the kind of the next resolution process.
+ ///
+ /// Call this to know what kind of task will performTask do next.
+ ///
+ /// \throw isc::InvalidOperation if it is called when done() is true, or
+ /// if an upstream query is still in progress (performTask was called
+ /// before and the callback was not called by the query yet).
+ Task nextTask() const {
+ // Will check for outstanding_ internally too
+ if (done()) {
+ isc_throw(isc::InvalidOperation, "We are done, no more tasks");
+ }
+ return (steps_.back().first);
+ }
+ /// \brief Move network communication to different interface.
+ ///
+ /// By default, a query does all the "communication" on the interface
+ /// it was born on. This may be used to move a query from one interface
+ /// to another.
+ ///
+ /// You don't have to lock either of the interfaces to do so, this
+ /// only switches the data in the query.
+ ///
+ /// \throw isc::InvalidOperation if it is called while an upstream query
+ /// is in progress.
+ void migrateTo(FakeInterface& dst_interface) {
+ if (outstanding_) {
+ isc_throw(isc::InvalidOperation,
+ "Can't migrate in the middle of query");
+ }
+ interface_ = &dst_interface;
+ }
+ /// \brief The answer for upstream query was received
+ ///
+ /// This should be called from within the FakeInterface only.
+ /// It marks that the query from upstream was answered.
+ void answerReceived() {
+ outstanding_ = false;
+ }
+private:
+ // The scheduled steps for this task.
+ typedef std::pair<Task, size_t> Step;
+ // The scheduled steps. Reversed (first to be done at the end), so we can
+ // pop_back() the completed steps.
+ std::vector<Step> steps_;
+ // The interface to schedule timeouts on.
+ FakeInterface* interface_;
+ // Is an upstream query outstanding?
+ bool outstanding_;
+};
+
+typedef boost::shared_ptr<FakeQuery> FakeQueryPtr;
+
+/// \brief An imitation of interface for receiving queries.
+///
+/// This is effectively a little bit smarter factory for queries. You can
+/// request a new query from it, or let process events (incoming answers).
+///
+/// It contains its own event loop. If the benchmark has more threads, have
+/// one in each of the threads (if the threads ever handles network
+/// communication -- if it accepts queries, sends answers or does upstream
+/// queries).
+///
+/// If the model simulated would share the same interface between multiple
+/// threads, it is better to have one in each thread as well, but lock
+/// access to receiveQuery() so only one is used at once (no idea what happens
+/// if ASIO loop is accessed from multiple threads).
+///
+/// Note that the creation of the queries is not thread safe (due to
+/// the random() function inside). The interface generates all its queries
+/// in advance, on creation time. But you need to create all the needed
+/// interfaces from single thread and then distribute them to your threads.
+class FakeInterface {
+public:
+ /// \brief Constructor
+ ///
+ /// Initiarile the interface and create query_count queries for the
+ /// benchmark. They will be handed out one by one with receiveQuery().
+ FakeInterface(size_t query_count);
+ /// \brief Wait for answers from upstream servers.
+ ///
+ /// Wait until at least one "answer" comes from the remote server. This
+ /// will effectively block the calling thread until it is time to call
+ /// a callback of performTask.
+ ///
+ /// It is not legal to call it without any outstanding upstream queries
+ /// on this interface. However, the situation is not explicitly checked.
+ ///
+ /// \note Due to internal implementation, it is not impossible no or more
+ /// than one callbacks to be called from within this method.
+ void processEvents();
+ /// \brief Accept another query.
+ ///
+ /// Generate a new fake query to resolve.
+ ///
+ /// This method might call callbacks of other queries waiting for upstream
+ /// answer.
+ ///
+ /// This returns a NULL pointer when there are no more queries to answer
+ /// (the number designated for the benchmark was reached).
+ FakeQueryPtr receiveQuery();
+private:
+ class UpstreamQuery;
+ friend class FakeQuery;
+ void scheduleUpstreamAnswer(FakeQuery* query,
+ const FakeQuery::StepCallback& callback,
+ size_t msec);
+ asiolink::IOService service_;
+ std::vector<FakeQueryPtr> queries_;
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/bin/resolver/bench/main.cc b/src/bin/resolver/bench/main.cc
new file mode 100644
index 0000000..3007c40
--- /dev/null
+++ b/src/bin/resolver/bench/main.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <resolver/bench/naive_resolver.h>
+
+#include <bench/benchmark.h>
+
+const size_t count = 1000; // TODO: We may want to read this from argv.
+
+int main(int, const char**) {
+ // Run the naive implementation
+ isc::resolver::bench::NaiveResolver naive_resolver(count);
+ isc::bench::BenchMark<isc::resolver::bench::NaiveResolver>
+ (1, naive_resolver, true);
+ return 0;
+}
diff --git a/src/bin/resolver/bench/naive_resolver.cc b/src/bin/resolver/bench/naive_resolver.cc
new file mode 100644
index 0000000..1654496
--- /dev/null
+++ b/src/bin/resolver/bench/naive_resolver.cc
@@ -0,0 +1,66 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <resolver/bench/naive_resolver.h>
+
+#include <cassert>
+#include <boost/bind.hpp>
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+NaiveResolver::NaiveResolver(size_t query_count) :
+ interface_(query_count),
+ processed_(false)
+{}
+
+namespace {
+
+void
+stepDone(bool* flag) {
+ *flag = true;
+}
+
+}
+
+size_t
+NaiveResolver::run() {
+ assert(!processed_);
+ size_t count = 0;
+ FakeQueryPtr query;
+ // Process a query at a time. As the previous is already handled, the
+ // receiveQuery may never trigger other events.
+ while ((query = interface_.receiveQuery())) {
+ // Handle each step
+ while (!query->done()) {
+ bool done = false; // This step is not yet done.
+ // If there were more queries/threads/whatever, we would examine
+ // the query->nextTask() and lock or prepare resources accordingly.
+ // But as there's just one, we simply do the task, without caring.
+ query->performTask(boost::bind(&stepDone, &done));
+ // We may need to wait for the upstream query.
+ while (!done) {
+ interface_.processEvents();
+ }
+ }
+ count ++;
+ }
+ processed_ = true;
+ return (count);
+}
+
+}
+}
+}
diff --git a/src/bin/resolver/bench/naive_resolver.h b/src/bin/resolver/bench/naive_resolver.h
new file mode 100644
index 0000000..e1deff1
--- /dev/null
+++ b/src/bin/resolver/bench/naive_resolver.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RESOLVER_BENCH_NAIVE_H
+#define RESOLVER_BENCH_NAIVE_H
+
+#include <resolver/bench/fake_resolution.h>
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+/// \brief Naive implementation of resolver for the benchmark
+///
+/// This is here mostly to show how to implement the other benchmark
+/// implementations. Look at the code inside how to use the fake
+/// resolution.
+class NaiveResolver {
+public:
+ /// \brief Constructor. Initializes the data.
+ NaiveResolver(size_t query_count);
+ /// \brief Run the resolution.
+ size_t run();
+private:
+ FakeInterface interface_;
+ bool processed_;
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/bin/resolver/main.cc b/src/bin/resolver/main.cc
index 457f285..9149ebb 100644
--- a/src/bin/resolver/main.cc
+++ b/src/bin/resolver/main.cc
@@ -168,7 +168,6 @@ main(int argc, char* argv[]) {
resolver = boost::shared_ptr<Resolver>(new Resolver());
LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_CREATED);
- SimpleCallback* checkin = resolver->getCheckinProvider();
DNSLookup* lookup = resolver->getDNSLookupProvider();
DNSAnswer* answer = resolver->getDNSAnswerProvider();
@@ -217,7 +216,7 @@ main(int argc, char* argv[]) {
cache.update(root_a_rrset);
cache.update(root_aaaa_rrset);
- DNSService dns_service(io_service, checkin, lookup, answer);
+ DNSService dns_service(io_service, lookup, answer);
resolver->setDNSService(dns_service);
LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_SERVICE_CREATED);
diff --git a/src/bin/resolver/resolver.cc b/src/bin/resolver/resolver.cc
index a3de340..2ac638f 100644
--- a/src/bin/resolver/resolver.cc
+++ b/src/bin/resolver/resolver.cc
@@ -338,25 +338,9 @@ public:
}
};
-// This is a derived class of \c SimpleCallback, to serve
-// as a callback in the asiolink module. It checks for queued
-// configuration messages, and executes them if found.
-class ConfigCheck : public SimpleCallback {
-public:
- ConfigCheck(Resolver* srv) : server_(srv) {}
- virtual void operator()(const IOMessage&) const {
- if (server_->getConfigSession()->hasQueuedMsgs()) {
- server_->getConfigSession()->checkCommand();
- }
- }
-private:
- Resolver* server_;
-};
-
Resolver::Resolver() :
impl_(new ResolverImpl()),
dnss_(NULL),
- checkin_(NULL),
dns_lookup_(NULL),
dns_answer_(new MessageAnswer),
nsas_(NULL),
@@ -365,13 +349,11 @@ Resolver::Resolver() :
// Operations referring to "this" must be done in the constructor body
// (some compilers will issue warnings if "this" is referred to in the
// initialization list).
- checkin_ = new ConfigCheck(this);
dns_lookup_ = new MessageLookup(this);
}
Resolver::~Resolver() {
delete impl_;
- delete checkin_;
delete dns_lookup_;
delete dns_answer_;
}
@@ -524,7 +506,8 @@ ResolverImpl::processNormalQuery(const IOMessage& io_message,
{
const ConstQuestionPtr question = *query_message->beginQuestion();
const RRType qtype = question->getType();
- const RRClass qclass = question->getClass();
+ // Make cppcheck happy with the reference.
+ const RRClass& qclass = question->getClass();
// Apply query ACL
const Client client(io_message);
diff --git a/src/bin/resolver/resolver.h b/src/bin/resolver/resolver.h
index 725aa85..b1608c1 100644
--- a/src/bin/resolver/resolver.h
+++ b/src/bin/resolver/resolver.h
@@ -131,9 +131,6 @@ public:
/// \brief Return pointer to the DNS Answer callback function
isc::asiodns::DNSAnswer* getDNSAnswerProvider() { return (dns_answer_); }
- /// \brief Return pointer to the Checkin callback function
- isc::asiolink::SimpleCallback* getCheckinProvider() { return (checkin_); }
-
/**
* \brief Specify the list of upstream servers.
*
@@ -259,7 +256,6 @@ public:
private:
ResolverImpl* impl_;
isc::asiodns::DNSServiceBase* dnss_;
- isc::asiolink::SimpleCallback* checkin_;
isc::asiodns::DNSLookup* dns_lookup_;
isc::asiodns::DNSAnswer* dns_answer_;
isc::nsas::NameserverAddressStore* nsas_;
diff --git a/src/bin/resolver/response_scrubber.h b/src/bin/resolver/response_scrubber.h
index cb80b26..af4bd97 100644
--- a/src/bin/resolver/response_scrubber.h
+++ b/src/bin/resolver/response_scrubber.h
@@ -25,7 +25,7 @@
/// unsigned data in a response is more of a problem. (Note that even data from
/// signed zones may be not be signed, e.g. delegations are not signed.) In
/// particular, how do we know that the server from which the response was
-/// received was authoritive for the data it returned?
+/// received was authoritative for the data it returned?
///
/// The part of the code that checks for this is the "Data Scrubbing" module.
/// Although it includes the checking of IP addresses and ports, it is called
@@ -219,7 +219,7 @@
/// referral been to the com nameservers, it would be a valid response; the com
/// zone could well be serving all the data for example.com. Having said that,
/// the A record for ns1.example.net would still be regarded as being out of
-/// bailiwick becase the nameserver is not authoritative for the .net zone.
+/// bailiwick because the nameserver is not authoritative for the .net zone.
///
/// \subsection DataScrubbingEx4 Example 4: Inconsistent Answer Section
/// Qu: www.example.com\n
diff --git a/src/bin/sockcreator/README b/src/bin/sockcreator/README
index e142d19..48b10dd 100644
--- a/src/bin/sockcreator/README
+++ b/src/bin/sockcreator/README
@@ -35,7 +35,7 @@ must be a socket, not pipe.
The answer to this is either 'S' directly followed by the socket (using
sendmsg) if it is successful. If it fails, 'E' is returned instead, followed
by either 'S' or 'B' (either socket() or bind() call failed). Then there is
- one int (architecture-dependent length and endianess), which is the errno
+ one int (architecture-dependent length and endianness), which is the errno
value after the failure.
The creator may also send these messages at any time (but not in the middle
diff --git a/src/bin/sockcreator/sockcreator.h b/src/bin/sockcreator/sockcreator.h
index 8e32c48..a05cf6a 100644
--- a/src/bin/sockcreator/sockcreator.h
+++ b/src/bin/sockcreator/sockcreator.h
@@ -85,7 +85,7 @@ typedef int (*close_t)(int);
/// \param type The type of socket to create (SOCK_STREAM, SOCK_DGRAM, etc).
/// \param bind_addr The address to bind.
/// \param addr_len The actual length of bind_addr.
-/// \param close_fun The furction used to close a socket if there's an error
+/// \param close_fun The function used to close a socket if there's an error
/// after the creation.
///
/// \return The file descriptor of the newly created socket, if everything
diff --git a/src/bin/sockcreator/tests/sockcreator_tests.cc b/src/bin/sockcreator/tests/sockcreator_tests.cc
index b834e1c..67fb53e 100644
--- a/src/bin/sockcreator/tests/sockcreator_tests.cc
+++ b/src/bin/sockcreator/tests/sockcreator_tests.cc
@@ -99,7 +99,7 @@ udpCheck(const int socknum) {
}
// The check function (tcpCheck/udpCheck) is passed as a parameter to the test
-// code, so provide a conveniet typedef.
+// code, so provide a convenient typedef.
typedef void (*socket_check_t)(const int);
// Address-family-specific scoket checks.
diff --git a/src/bin/stats/stats.py.in b/src/bin/stats/stats.py.in
index 577afe6..707eeb8 100755
--- a/src/bin/stats/stats.py.in
+++ b/src/bin/stats/stats.py.in
@@ -1,6 +1,6 @@
#!@PYTHON@
-# Copyright (C) 2010, 2011, 2012 Internet Systems Consortium.
+# Copyright (C) 2010-2013 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
@@ -124,8 +124,8 @@ def _accum(a, b):
if len(a) <= i ]
# If both of args are integer or float type, two
# values are added.
- elif (type(a) is int and type(b) is int) \
- or (type(a) is float or type(b) is float):
+ elif (type(a) is int or type(a) is float) \
+ and (type(b) is int or type(b) is float):
return a + b
# If both of args are string type,
@@ -186,6 +186,11 @@ class StatsError(Exception):
"""Exception class for Stats class"""
pass
+class InitSessionTimeout(isc.cc.session.SessionTimeout):
+ """SessionTimeout Exception in the session between the stats module and the
+ init module"""
+ pass
+
class Stats:
"""
Main class of stats module
@@ -194,7 +199,7 @@ class Stats:
'''Constructor
module_ccsession_class is parameterized so that test can specify
- a mocked class to test the behavior without involing network I/O.
+ a mocked class to test the behavior without involving network I/O.
In other cases this parameter shouldn't be specified.
'''
@@ -243,14 +248,12 @@ class Stats:
"""
self.update_modules()
- if self.update_statistics_data(
+ self.update_statistics_data(
self.module_name,
self.cc_session.lname,
{'lname': self.cc_session.lname,
'boot_time': get_datetime(_BASETIME),
- 'last_update_time': get_datetime()}):
- logger.warn(STATS_RECEIVED_INVALID_STATISTICS_DATA,
- self.module_name)
+ 'last_update_time': get_datetime()})
# define the variable of the last time of polling
self._lasttime_poll = 0.0
@@ -258,12 +261,8 @@ class Stats:
"""return the current value of 'poll-interval'"""
return self.config['poll-interval']
- def do_polling(self):
- """Polls modules for statistics data. Return nothing. First
- search multiple instances of same module. Second requests
- each module to invoke 'getstats'. Finally updates internal
- statistics data every time it gets from each instance."""
-
+ def _get_multi_module_list(self):
+ """Returns a module list which is running as multiple modules."""
# It counts the number of instances of same module by
# examining the third value from the array result of
# 'show_processes' of Init
@@ -277,6 +276,8 @@ class Stats:
# TODO: Is it OK to just pass? As part of refactoring, preserving
# the original behaviour.
value = None
+ except isc.cc.session.SessionTimeout as e:
+ raise InitSessionTimeout(e)
modules = []
if type(value) is list:
# NOTE: For example, the "show_processes" command
@@ -306,6 +307,12 @@ class Stats:
# release.
modules = [ v[2] if type(v) is list and len(v) > 2 \
else None for v in value ]
+ return modules
+
+ def _query_statistics(self, modules):
+ """Queries each module statistics and returns sequences to use
+ for receiving that answers based on the argument 'modules' which
+ is a list of modules running as multiple modules"""
# start requesting each module to collect statistics data
sequences = []
for (module_name, data) in self.get_statistics_data().items():
@@ -327,11 +334,17 @@ class Stats:
if cnt > 1:
sequences = sequences + [ (module_name, seq) \
for i in range(cnt-1) ]
+ return sequences
+
+ def _collect_statistics(self, sequences):
+ """Based on sequences which is a list of values returned from
+ group_sendmsg(), collects statistics data from each module. If
+ a SessionTimeout exception is raised when collecting from the
+ module, skips it and goes to collect from the next module."""
# start receiving statistics data
_statistics_data = []
- while len(sequences) > 0:
+ for (module_name, seq) in sequences:
try:
- (module_name, seq) = sequences.pop(0)
answer, env = self.cc_session.group_recvmsg(False, seq)
if answer:
rcode, args = isc.config.ccsession.parse_answer(answer)
@@ -340,25 +353,37 @@ class Stats:
(module_name, env['from'], args))
# skip this module if SessionTimeout raised
except isc.cc.session.SessionTimeout:
- pass
+ logger.warn(STATS_SKIP_COLLECTING, module_name)
+ return _statistics_data
+ def _refresh_statistics(self, statistics_data):
+ """Refreshes statistics_data into internal statistics data to
+ display, which is a list of a tuple of module_name, lname, and
+ args"""
# update statistics data
+ _statistics_data = statistics_data[:]
self.update_modules()
while len(_statistics_data) > 0:
(_module_name, _lname, _args) = _statistics_data.pop(0)
- if self.update_statistics_data(_module_name, _lname, _args):
- logger.warn(
- STATS_RECEIVED_INVALID_STATISTICS_DATA,
- _module_name)
- else:
- if self.update_statistics_data(
+ if not self.update_statistics_data(_module_name, _lname, _args):
+ self.update_statistics_data(
self.module_name,
self.cc_session.lname,
- {'last_update_time': get_datetime()}):
- logger.warn(
- STATS_RECEIVED_INVALID_STATISTICS_DATA,
- self.module_name)
- # if successfully done, set the last time of polling
+ {'last_update_time': get_datetime()})
+
+ def do_polling(self):
+ """Polls modules for statistics data. Return nothing. First
+ search multiple instances of same module. Second requests
+ each module to invoke 'getstats'. Finally updates internal
+ statistics data every time it gets from each instance."""
+ try:
+ modules = self._get_multi_module_list()
+ sequences = self._query_statistics(modules)
+ _statistics_data = self._collect_statistics(sequences)
+ self._refresh_statistics(_statistics_data)
+ except InitSessionTimeout:
+ logger.warn(STATS_SKIP_POLLING)
+ # if successfully done or skipped, set the last time of polling
self._lasttime_poll = get_timestamp()
def _check_command(self, nonblock=False):
@@ -601,7 +626,10 @@ class Stats:
self.statistics_data[m],
_accum_bymodule(self.statistics_data_bymid[m]))
- if errors: return errors
+ if errors:
+ logger.warn(
+ STATS_RECEIVED_INVALID_STATISTICS_DATA, owner)
+ return errors
def command_status(self):
"""
diff --git a/src/bin/stats/stats_httpd.py.in b/src/bin/stats/stats_httpd.py.in
index fd9ac93..c3cdb76 100755
--- a/src/bin/stats/stats_httpd.py.in
+++ b/src/bin/stats/stats_httpd.py.in
@@ -35,6 +35,7 @@ import re
import isc.cc
import isc.config
import isc.util.process
+from isc.util.address_formatter import AddressFormatter
import isc.log
from isc.log_messages.stats_httpd_messages import *
@@ -325,8 +326,8 @@ class StatsHttpd:
server_address, HttpHandler,
self.xml_handler, self.xsd_handler, self.xsl_handler,
self.write_log)
- logger.info(STATSHTTPD_STARTED, server_address[0],
- server_address[1])
+ logger.info(STATSHTTPD_STARTED,
+ AddressFormatter(server_address, address_family))
return httpd
except (socket.gaierror, socket.error,
OverflowError, TypeError) as err:
@@ -341,8 +342,8 @@ class StatsHttpd:
"""Closes sockets for HTTP"""
while len(self.httpd)>0:
ht = self.httpd.pop()
- logger.info(STATSHTTPD_CLOSING, ht.server_address[0],
- ht.server_address[1])
+ logger.info(STATSHTTPD_CLOSING,
+ AddressFormatter(ht.server_address))
ht.server_close()
def start(self):
@@ -406,7 +407,7 @@ class StatsHttpd:
old_config = self.config.copy()
self.load_config(new_config)
# If the http sockets aren't opened or
- # if new_config doesn't have'listen_on', it returns
+ # if new_config doesn't have 'listen_on', it returns
if len(self.httpd) == 0 or 'listen_on' not in new_config:
return isc.config.ccsession.create_answer(0)
self.close_httpd()
diff --git a/src/bin/stats/stats_httpd_messages.mes b/src/bin/stats/stats_httpd_messages.mes
index 93491b6..930a745 100644
--- a/src/bin/stats/stats_httpd_messages.mes
+++ b/src/bin/stats/stats_httpd_messages.mes
@@ -24,7 +24,7 @@ The stats-httpd module was unable to connect to the BIND 10 command
and control bus. A likely problem is that the message bus daemon
(b10-msgq) is not running. The stats-httpd module will now shut down.
-% STATSHTTPD_CLOSING closing %1#%2
+% STATSHTTPD_CLOSING closing %1
The stats-httpd daemon will stop listening for requests on the given
address and port number.
@@ -80,7 +80,7 @@ and an error is sent back.
% STATSHTTPD_SHUTDOWN shutting down
The stats-httpd daemon is shutting down.
-% STATSHTTPD_STARTED listening on %1#%2
+% STATSHTTPD_STARTED listening on %1
The stats-httpd daemon will now start listening for requests on the
given address and port number.
diff --git a/src/bin/stats/stats_messages.mes b/src/bin/stats/stats_messages.mes
index b6f0b16..38dddcc 100644
--- a/src/bin/stats/stats_messages.mes
+++ b/src/bin/stats/stats_messages.mes
@@ -64,6 +64,17 @@ will respond with an error and the command will be ignored.
This debug message is printed when a request is sent to the module
to send its data to the stats module.
+% STATS_SKIP_COLLECTING skipped collecting statistics from %1
+The stats module temporarily encountered an internal messaging bus error while
+collecting statistics from the module, then it skipped collecting statistics
+from it and will try to collect from the next module. The lack of statistics
+will be recovered at the next polling round.
+
+% STATS_SKIP_POLLING skipped polling statistics to modules
+The stats module temporarily encountered an internal messaging bus error while
+collecting initial information to collect statistics from the init module, then
+it skipped polling statistics and will try to do next time.
+
% STATS_STARTING starting
The stats module will be now starting.
diff --git a/src/bin/stats/tests/Makefile.am b/src/bin/stats/tests/Makefile.am
index 7484e5d..06d11a1 100644
--- a/src/bin/stats/tests/Makefile.am
+++ b/src/bin/stats/tests/Makefile.am
@@ -1,7 +1,7 @@
SUBDIRS = testdata .
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = b10-stats_test.py b10-stats-httpd_test.py
+PYTESTS = stats_test.py stats-httpd_test.py
EXTRA_DIST = $(PYTESTS) test_utils.py
CLEANFILES = test_utils.pyc
@@ -9,7 +9,7 @@ CLEANFILES = test_utils.pyc
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/stats/tests/b10-stats-httpd_test.py b/src/bin/stats/tests/b10-stats-httpd_test.py
deleted file mode 100644
index 466e448..0000000
--- a/src/bin/stats/tests/b10-stats-httpd_test.py
+++ /dev/null
@@ -1,1082 +0,0 @@
-# Copyright (C) 2011-2012 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""
-In each of these tests we start several virtual components. They are
-not the real components, no external processes are started. They are
-just simple mock objects running each in its own thread and pretending
-to be bind10 modules. This helps testing the stats http server in a
-close to real environment.
-"""
-
-import unittest
-import os
-import imp
-import socket
-import errno
-import select
-import string
-import time
-import threading
-import http.client
-import xml.etree.ElementTree
-import random
-import urllib.parse
-import sys
-# load this module for xml validation with xsd. For this test, an
-# installation of lxml is required in advance. See http://lxml.de/.
-try:
- from lxml import etree as lxml_etree
-except ImportError:
- lxml_etree = None
-
-import isc
-import isc.log
-import stats_httpd
-import stats
-from test_utils import BaseModules, ThreadingServerManager, MyStats,\
- MyStatsHttpd, SignalHandler,\
- send_command, CONST_BASETIME
-from isc.testutils.ccsession_mock import MockModuleCCSession
-
-# This test suite uses xml.etree.ElementTree.XMLParser via
-# xml.etree.ElementTree.parse. On the platform where expat isn't
-# installed, ImportError is raised and it's failed. Check expat is
-# available before the test invocation. Skip this test if it's
-# unavailable.
-try:
- # ImportError raised if xpat is unavailable
- xml_parser = xml.etree.ElementTree.XMLParser()
-except ImportError:
- xml_parser = None
-
-# set XML Namespaces for testing
-XMLNS_XSL = "http://www.w3.org/1999/XSL/Transform"
-XMLNS_XHTML = "http://www.w3.org/1999/xhtml"
-XMLNS_XSD = "http://www.w3.org/2001/XMLSchema"
-XMLNS_XSI = stats_httpd.XMLNS_XSI
-
-DUMMY_DATA = {
- 'Init' : {
- "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME)
- },
- 'Auth' : {
- "queries.tcp": 6,
- "queries.udp": 4,
- "queries.perzone": [{
- "zonename": "test1.example",
- "queries.tcp": 10,
- "queries.udp": 8
- }, {
- "zonename": "test2.example",
- "queries.tcp": 8,
- "queries.udp": 6
- }],
- "nds_queries.perzone": {
- "test10.example": {
- "queries.tcp": 10,
- "queries.udp": 8
- },
- "test20.example": {
- "queries.tcp": 8,
- "queries.udp": 6
- }
- }
- },
- 'Stats' : {
- "report_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
- "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
- "last_update_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
- "lname": "4d70d40a_c at host",
- "timestamp": time.mktime(CONST_BASETIME)
- }
- }
-
-def get_availaddr(address='127.0.0.1', port=8001):
- """returns a tuple of address and port which is available to
- listen on the platform. The first argument is a address for
- search. The second argument is a port for search. If a set of
- address and port is failed on the search for the availability, the
- port number is increased and it goes on the next trial until the
- available set of address and port is looked up. If the port number
- reaches over 65535, it may stop the search and raise a
- OverflowError exception."""
- while True:
- for addr in socket.getaddrinfo(
- address, port, 0,
- socket.SOCK_STREAM, socket.IPPROTO_TCP):
- sock = socket.socket(addr[0], socket.SOCK_STREAM)
- try:
- sock.bind((address, port))
- return (address, port)
- except socket.error:
- continue
- finally:
- if sock: sock.close()
- # This address and port number are already in use.
- # next port number is added
- port = port + 1
-
-def is_ipv6_enabled(address='::1', port=8001):
- """checks IPv6 enabled on the platform. address for check is '::1'
- and port for check is random number between 8001 and
- 65535. Retrying is 3 times even if it fails. The built-in socket
- module provides a 'has_ipv6' parameter, but it's not used here
- because there may be a situation where the value is True on an
- environment where the IPv6 config is disabled."""
- for p in random.sample(range(port, 65535), 3):
- try:
- sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
- sock.bind((address, p))
- return True
- except socket.error:
- continue
- finally:
- if sock: sock.close()
- return False
-
-class TestItemNameList(unittest.TestCase):
-
- def test_item_name_list(self):
- # for a one-element list
- self.assertEqual(['a'],
- stats_httpd.item_name_list({'a':1}, 'a'))
- # for a dict under a dict
- self.assertEqual(['a','a/b'],
- stats_httpd.item_name_list({'a':{'b':1}}, 'a'))
- self.assertEqual(['a/b'],
- stats_httpd.item_name_list({'a':{'b':1}}, 'a/b'))
- self.assertEqual(['a','a/b','a/b/c'],
- stats_httpd.item_name_list({'a':{'b':{'c':1}}}, 'a'))
- self.assertEqual(['a/b','a/b/c'],
- stats_httpd.item_name_list({'a':{'b':{'c':1}}},
- 'a/b'))
- self.assertEqual(['a/b/c'],
- stats_httpd.item_name_list({'a':{'b':{'c':1}}},
- 'a/b/c'))
- # for a list under a dict
- self.assertEqual(['a[2]'],
- stats_httpd.item_name_list({'a':[1,2,3]}, 'a[2]'))
- self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
- stats_httpd.item_name_list({'a':[1,2,3]}, 'a'))
- self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
- stats_httpd.item_name_list({'a':[1,2,3]}, ''))
- # for a list under adict under a dict
- self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
- stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a'))
- self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
- stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, ''))
- self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
- stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a/b'))
- # for a mixed case of the above
- self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]', 'a/c'],
- stats_httpd.item_name_list(
- {'a':{'b':[1,2,3], 'c':1}}, 'a'))
- self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
- stats_httpd.item_name_list(
- {'a':{'b':[1,2,3], 'c':1}}, 'a/b'))
- self.assertEqual(['a/c'],
- stats_httpd.item_name_list(
- {'a':{'b':[1,2,3], 'c':1}}, 'a/c'))
- # for specifying a wrong identifier which is not found in
- # element
- self.assertRaises(isc.cc.data.DataNotFoundError,
- stats_httpd.item_name_list, {'x':1}, 'a')
- # for specifying a string in element and an empty string in
- # identifier
- self.assertEqual([],
- stats_httpd.item_name_list('a', ''))
- # for specifying empty strings in element and identifier
- self.assertEqual([],
- stats_httpd.item_name_list('', ''))
- # for specifying wrong element, which is an non-empty string,
- # and an non-empty string in identifier
- self.assertRaises(isc.cc.data.DataTypeError,
- stats_httpd.item_name_list, 'a', 'a')
- # for specifying None in element and identifier
- self.assertRaises(isc.cc.data.DataTypeError,
- stats_httpd.item_name_list, None, None)
- # for specifying non-dict in element
- self.assertRaises(isc.cc.data.DataTypeError,
- stats_httpd.item_name_list, [1,2,3], 'a')
- self.assertRaises(isc.cc.data.DataTypeError,
- stats_httpd.item_name_list, [1,2,3], '')
- # for checking key names sorted which consist of element
- num = 11
- keys = [ 'a', 'aa', 'b' ]
- keys.sort(reverse=True)
- dictlist = dict([ (k, list(range(num))) for k in keys ])
- keys.sort()
- ans = []
- for k in keys:
- ans += [k] + [ '%s[%d]' % (k, i) for i in range(num) ]
- self.assertEqual(ans,
- stats_httpd.item_name_list(dictlist, ''))
-
-class TestHttpHandler(unittest.TestCase):
- """Tests for HttpHandler class"""
- def setUp(self):
- # set the signal handler for deadlock
- self.sig_handler = SignalHandler(self.fail)
- self.base = BaseModules()
- self.stats_server = ThreadingServerManager(MyStats)
- self.stats = self.stats_server.server
- DUMMY_DATA['Stats']['lname'] = self.stats.cc_session.lname
- self.stats_server.run()
- (self.address, self.port) = get_availaddr()
- self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, (self.address, self.port))
- self.stats_httpd = self.stats_httpd_server.server
- self.stats_httpd_server.run()
- self.client = http.client.HTTPConnection(self.address, self.port)
- self.client._http_vsn_str = 'HTTP/1.0\n'
- self.client.connect()
-
- def tearDown(self):
- self.client.close()
- self.stats_httpd_server.shutdown()
- self.stats_server.shutdown()
- self.base.shutdown()
- # reset the signal handler
- self.sig_handler.reset()
-
- @unittest.skipIf(sys.version_info >= (3, 3), "Unsupported in Python 3.3 or higher")
- @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
- def test_do_GET(self):
- self.assertTrue(type(self.stats_httpd.httpd) is list)
- self.assertEqual(len(self.stats_httpd.httpd), 1)
- self.assertEqual((self.address, self.port), self.stats_httpd.http_addrs[0])
-
- def check_XML_URL_PATH(path=''):
- url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
- url_path = urllib.parse.quote(url_path)
- self.client.putrequest('GET', url_path)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.getheader("Content-type"), "text/xml")
- self.assertGreater(int(response.getheader("Content-Length")), 0)
- self.assertEqual(response.status, 200)
- xml_doctype = response.readline().decode()
- xsl_doctype = response.readline().decode()
- self.assertGreater(len(xml_doctype), 0)
- self.assertGreater(len(xsl_doctype), 0)
- root = xml.etree.ElementTree.parse(response).getroot()
- self.assertGreater(root.tag.find('statistics'), 0)
- schema_loc = '{%s}schemaLocation' % XMLNS_XSI
- # check the path of XSD
- self.assertEqual(root.attrib[schema_loc],
- stats_httpd.XSD_NAMESPACE + ' '
- + stats_httpd.XSD_URL_PATH)
- # check the path of XSL
- self.assertTrue(xsl_doctype.startswith(
- '<?xml-stylesheet type="text/xsl" href="' +
- stats_httpd.XSL_URL_PATH
- + '"?>'))
- # check whether the list of 'identifier' attributes in
- # root is same as the list of item names in DUMMY_DATA
- id_list = [ elm.attrib['identifier'] for elm in root ]
- item_list = [ it for it in \
- stats_httpd.item_name_list(DUMMY_DATA, path) \
- if len(it.split('/')) > 1 ]
- self.assertEqual(id_list, item_list)
- for elem in root:
- attr = elem.attrib
- value = isc.cc.data.find(DUMMY_DATA, attr['identifier'])
- # No 'value' attribute should be found in the 'item'
- # element when datatype of the value is list or dict.
- if type(value) is list or type(value) is dict:
- self.assertFalse('value' in attr)
- # The value of the 'value' attribute should be checked
- # after casting it to string type if datatype of the
- # value is int or float. Because attr['value'] returns
- # string type even if its value is int or float.
- elif type(value) is int or type(value) is float:
- self.assertEqual(attr['value'], str(value))
- else:
- self.assertEqual(attr['value'], value)
-
- # URL is '/bind10/statistics/xml'
- check_XML_URL_PATH()
- for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
- check_XML_URL_PATH(path)
-
- def check_XSD_URL_PATH():
- url_path = stats_httpd.XSD_URL_PATH
- url_path = urllib.parse.quote(url_path)
- self.client.putrequest('GET', url_path)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.getheader("Content-type"), "text/xml")
- self.assertGreater(int(response.getheader("Content-Length")), 0)
- self.assertEqual(response.status, 200)
- root = xml.etree.ElementTree.parse(response).getroot()
- url_xmlschema = '{%s}' % XMLNS_XSD
- self.assertGreater(root.tag.find('schema'), 0)
- self.assertTrue(hasattr(root, 'attrib'))
- self.assertTrue('targetNamespace' in root.attrib)
- self.assertEqual(root.attrib['targetNamespace'],
- stats_httpd.XSD_NAMESPACE)
-
- # URL is '/bind10/statistics/xsd'
- check_XSD_URL_PATH()
-
- def check_XSL_URL_PATH():
- url_path = stats_httpd.XSL_URL_PATH
- url_path = urllib.parse.quote(url_path)
- self.client.putrequest('GET', url_path)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.getheader("Content-type"), "text/xml")
- self.assertGreater(int(response.getheader("Content-Length")), 0)
- self.assertEqual(response.status, 200)
- root = xml.etree.ElementTree.parse(response).getroot()
- url_trans = '{%s}' % XMLNS_XSL
- url_xhtml = '{%s}' % XMLNS_XHTML
- self.assertEqual(root.tag, url_trans + 'stylesheet')
-
- # URL is '/bind10/statistics/xsl'
- check_XSL_URL_PATH()
-
- # 302 redirect
- self.client._http_vsn_str = 'HTTP/1.1'
- self.client.putrequest('GET', '/')
- self.client.putheader('Host', self.address)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 302)
- self.assertEqual(response.getheader('Location'),
- "http://%s:%d%s/" % (self.address, self.port, stats_httpd.XML_URL_PATH))
-
- # 404 NotFound (random path)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', '/path/to/foo/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', '/bind10/foo')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', '/bind10/statistics/foo')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + 'Auth') # with no slash
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
-
- # 200 ok
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/#foo')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/?foo=bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
-
- # 404 NotFound (too long path)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Init/boot_time/a')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
-
- # 404 NotFound (nonexistent module name)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
-
- # 404 NotFound (nonexistent item name)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
-
- # 404 NotFound (existent module but nonexistent item name)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Auth/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Auth/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
- self.client._http_vsn_str = 'HTTP/1.0'
- self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Auth/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
-
- def test_do_GET_failed1(self):
- # checks status
- self.assertEqual(send_command("status", "Stats"),
- (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
- # failure case(Stats is down)
- self.assertTrue(self.stats.running)
- self.assertEqual(send_command("shutdown", "Stats"),
- (0, None)) # Stats is down
- self.assertFalse(self.stats.running)
- self.stats_httpd.cc_session.set_timeout(milliseconds=100)
-
- # request XML
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 500)
-
- # request XSD
- self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
-
- # request XSL
- self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
-
- def test_do_GET_failed2(self):
- # failure case(Stats replies an error)
- self.stats.mccs.set_command_handler(
- lambda cmd, args: \
- isc.config.ccsession.create_answer(1, "specified arguments are incorrect: I have an error.")
- )
-
- # request XML
- self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
-
- # request XSD
- self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
-
- # request XSL
- self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
-
- def test_do_HEAD(self):
- self.client.putrequest('HEAD', stats_httpd.XML_URL_PATH + '/')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 200)
-
- self.client.putrequest('HEAD', '/path/to/foo/bar')
- self.client.endheaders()
- response = self.client.getresponse()
- self.assertEqual(response.status, 404)
-
- @unittest.skipUnless(lxml_etree, "skipping XML validation with XSD")
- def test_xml_validation_with_xsd(self):
- """Tests for XML validation with XSD. If lxml is not
- installed, this tests would be skipped."""
- def request_xsd():
- url_path = stats_httpd.XSD_URL_PATH
- url_path = urllib.parse.quote(url_path)
- self.client.putrequest('GET', url_path)
- self.client.endheaders()
- xsd_doc = self.client.getresponse()
- xsd_doc = lxml_etree.parse(xsd_doc)
- return lxml_etree.XMLSchema(xsd_doc)
-
- def request_xmldoc(path=''):
- url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
- url_path = urllib.parse.quote(url_path)
- self.client.putrequest('GET', url_path)
- self.client.endheaders()
- xml_doc = self.client.getresponse()
- return lxml_etree.parse(xml_doc)
-
- # request XSD and XML
- xsd = request_xsd()
- xml_doc = request_xmldoc()
- # do validation
- self.assertTrue(xsd.validate(xml_doc))
-
- # validate each paths in DUMMY_DATA
- for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
- # request XML
- xml_doc = request_xmldoc(path)
- # do validation
- self.assertTrue(xsd.validate(xml_doc))
-
-class TestHttpServerError(unittest.TestCase):
- """Tests for HttpServerError exception"""
- def test_raises(self):
- try:
- raise stats_httpd.HttpServerError('Nothing')
- except stats_httpd.HttpServerError as err:
- self.assertEqual(str(err), 'Nothing')
-
-class TestHttpServer(unittest.TestCase):
- """Tests for HttpServer class"""
- def setUp(self):
- # set the signal handler for deadlock
- self.sig_handler = SignalHandler(self.fail)
- self.base = BaseModules()
-
- def tearDown(self):
- if hasattr(self, "stats_httpd"):
- self.stats_httpd.stop()
- self.base.shutdown()
- # reset the signal handler
- self.sig_handler.reset()
-
- def test_httpserver(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- self.assertEqual(type(self.stats_httpd.httpd), list)
- self.assertEqual(len(self.stats_httpd.httpd), 1)
- for httpd in self.stats_httpd.httpd:
- self.assertTrue(isinstance(httpd, stats_httpd.HttpServer))
-
-class TestStatsHttpdError(unittest.TestCase):
- """Tests for StatsHttpdError exception"""
-
- def test_raises1(self):
- try:
- raise stats_httpd.StatsHttpdError('Nothing')
- except stats_httpd.StatsHttpdError as err:
- self.assertEqual(str(err), 'Nothing')
-
- def test_raises2(self):
- try:
- raise stats_httpd.StatsHttpdDataError('Nothing')
- except stats_httpd.StatsHttpdDataError as err:
- self.assertEqual(str(err), 'Nothing')
-
-class TestStatsHttpd(unittest.TestCase):
- """Tests for StatsHttpd class"""
-
- def setUp(self):
- # set the signal handler for deadlock
- self.sig_handler = SignalHandler(self.fail)
- self.base = BaseModules()
- self.stats_server = ThreadingServerManager(MyStats)
- self.stats_server.run()
- # checking IPv6 enabled on this platform
- self.ipv6_enabled = is_ipv6_enabled()
- # instantiation of StatsHttpd indirectly calls gethostbyaddr(), which
- # can block for an uncontrollable period, leading many undesirable
- # results. We should rather eliminate the reliance, but until we
- # can make such fundamental cleanup we replace it with a faked method;
- # in our test scenario the return value doesn't matter.
- self.__gethostbyaddr_orig = socket.gethostbyaddr
- socket.gethostbyaddr = lambda x: ('test.example.', [], None)
-
- def tearDown(self):
- socket.gethostbyaddr = self.__gethostbyaddr_orig
- if hasattr(self, "stats_httpd"):
- self.stats_httpd.stop()
- self.stats_server.shutdown()
- self.base.shutdown()
- # reset the signal handler
- self.sig_handler.reset()
-
- def test_init(self):
- server_address = get_availaddr()
- self.stats_httpd = MyStatsHttpd(server_address)
- self.assertEqual(self.stats_httpd.running, False)
- self.assertEqual(self.stats_httpd.poll_intval, 0.5)
- self.assertNotEqual(len(self.stats_httpd.httpd), 0)
- self.assertEqual(type(self.stats_httpd.mccs), isc.config.ModuleCCSession)
- self.assertEqual(type(self.stats_httpd.cc_session), isc.cc.Session)
- self.assertEqual(len(self.stats_httpd.config), 2)
- self.assertTrue('listen_on' in self.stats_httpd.config)
- self.assertEqual(len(self.stats_httpd.config['listen_on']), 1)
- self.assertTrue('address' in self.stats_httpd.config['listen_on'][0])
- self.assertTrue('port' in self.stats_httpd.config['listen_on'][0])
- self.assertTrue(server_address in set(self.stats_httpd.http_addrs))
- ans = send_command(
- isc.config.ccsession.COMMAND_GET_MODULE_SPEC,
- "ConfigManager", {"module_name":"StatsHttpd"})
- # assert StatsHttpd is added to ConfigManager
- self.assertNotEqual(ans, (0,{}))
- self.assertTrue(ans[1]['module_name'], 'StatsHttpd')
-
- def test_init_hterr(self):
- orig_open_httpd = stats_httpd.StatsHttpd.open_httpd
- def err_open_httpd(arg): raise stats_httpd.HttpServerError
- stats_httpd.StatsHttpd.open_httpd = err_open_httpd
- self.assertRaises(stats_httpd.HttpServerError, stats_httpd.StatsHttpd)
- ans = send_command(
- isc.config.ccsession.COMMAND_GET_MODULE_SPEC,
- "ConfigManager", {"module_name":"StatsHttpd"})
- # assert StatsHttpd is removed from ConfigManager
- self.assertEqual(ans, (0,{}))
- stats_httpd.StatsHttpd.open_httpd = orig_open_httpd
-
- def test_openclose_mccs(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- mccs = MockModuleCCSession()
- self.stats_httpd.mccs = mccs
- self.assertFalse(self.stats_httpd.mccs.stopped)
- self.assertFalse(self.stats_httpd.mccs.closed)
- self.stats_httpd.close_mccs()
- self.assertTrue(mccs.stopped)
- self.assertTrue(mccs.closed)
- self.assertEqual(self.stats_httpd.mccs, None)
- self.stats_httpd.open_mccs()
- self.assertIsNotNone(self.stats_httpd.mccs)
- self.stats_httpd.mccs = None
- self.assertEqual(self.stats_httpd.mccs, None)
- self.assertEqual(self.stats_httpd.close_mccs(), None)
-
- def test_mccs(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- self.assertIsNotNone(self.stats_httpd.mccs.get_socket())
- self.assertTrue(
- isinstance(self.stats_httpd.mccs.get_socket(), socket.socket))
- self.assertTrue(
- isinstance(self.stats_httpd.cc_session, isc.cc.session.Session))
- statistics_spec = self.stats_httpd.get_stats_spec()
- for mod in DUMMY_DATA:
- self.assertTrue(mod in statistics_spec)
- for cfg in statistics_spec[mod]:
- self.assertTrue('item_name' in cfg)
- self.assertTrue(cfg['item_name'] in DUMMY_DATA[mod])
- self.assertTrue(len(statistics_spec[mod]), len(DUMMY_DATA[mod]))
- self.stats_httpd.close_mccs()
- self.assertIsNone(self.stats_httpd.mccs)
-
- def test_httpd(self):
- # dual stack (addresses is ipv4 and ipv6)
- if self.ipv6_enabled:
- server_addresses = (get_availaddr('::1'), get_availaddr())
- self.stats_httpd = MyStatsHttpd(*server_addresses)
- for ht in self.stats_httpd.httpd:
- self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
- self.assertTrue(ht.address_family in set([socket.AF_INET, socket.AF_INET6]))
- self.assertTrue(isinstance(ht.socket, socket.socket))
-
- # dual stack (address is ipv6)
- if self.ipv6_enabled:
- server_addresses = get_availaddr('::1')
- self.stats_httpd = MyStatsHttpd(server_addresses)
- for ht in self.stats_httpd.httpd:
- self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
- self.assertEqual(ht.address_family, socket.AF_INET6)
- self.assertTrue(isinstance(ht.socket, socket.socket))
-
- # dual/single stack (address is ipv4)
- server_addresses = get_availaddr()
- self.stats_httpd = MyStatsHttpd(server_addresses)
- for ht in self.stats_httpd.httpd:
- self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
- self.assertEqual(ht.address_family, socket.AF_INET)
- self.assertTrue(isinstance(ht.socket, socket.socket))
-
- def test_httpd_anyIPv4(self):
- # any address (IPv4)
- server_addresses = get_availaddr(address='0.0.0.0')
- self.stats_httpd = MyStatsHttpd(server_addresses)
- for ht in self.stats_httpd.httpd:
- self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
- self.assertEqual(ht.address_family,socket.AF_INET)
- self.assertTrue(isinstance(ht.socket, socket.socket))
-
- def test_httpd_anyIPv6(self):
- # any address (IPv6)
- if self.ipv6_enabled:
- server_addresses = get_availaddr(address='::')
- self.stats_httpd = MyStatsHttpd(server_addresses)
- for ht in self.stats_httpd.httpd:
- self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
- self.assertEqual(ht.address_family,socket.AF_INET6)
- self.assertTrue(isinstance(ht.socket, socket.socket))
-
- def test_httpd_failed(self):
- # existent hostname
- self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
- get_availaddr(address='localhost'))
-
- # nonexistent hostname
- self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('my.host.domain', 8000))
-
- # over flow of port number
- self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', 80000))
-
- # negative
- self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', -8000))
-
- # alphabet
- self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', 'ABCDE'))
-
- # Address already in use
- server_addresses = get_availaddr()
- self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, server_addresses)
- self.stats_httpd_server.run()
- self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, server_addresses)
- send_command("shutdown", "StatsHttpd")
-
- def test_running(self):
- self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, get_availaddr())
- self.stats_httpd = self.stats_httpd_server.server
- self.assertFalse(self.stats_httpd.running)
- self.stats_httpd_server.run()
- self.assertEqual(send_command("status", "StatsHttpd"),
- (0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
- self.assertTrue(self.stats_httpd.running)
- self.assertEqual(send_command("shutdown", "StatsHttpd"), (0, None))
- self.assertFalse(self.stats_httpd.running)
- self.stats_httpd_server.shutdown()
-
- # failure case
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- self.stats_httpd.cc_session.close()
- self.assertRaises(ValueError, self.stats_httpd.start)
-
- def test_failure_with_a_select_error (self):
- """checks select.error is raised if the exception except
- errno.EINTR is raised while it's selecting"""
- def raise_select_except(*args):
- raise select.error('dummy error')
- orig_select = stats_httpd.select.select
- stats_httpd.select.select = raise_select_except
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- self.assertRaises(select.error, self.stats_httpd.start)
- stats_httpd.select.select = orig_select
-
- def test_nofailure_with_errno_EINTR(self):
- """checks no exception is raised if errno.EINTR is raised
- while it's selecting"""
- def raise_select_except(*args):
- raise select.error(errno.EINTR)
- orig_select = stats_httpd.select.select
- stats_httpd.select.select = raise_select_except
- self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, get_availaddr())
- self.stats_httpd_server.run()
- self.stats_httpd_server.shutdown()
- stats_httpd.select.select = orig_select
-
- def test_open_template(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- # successful conditions
- tmpl = self.stats_httpd.open_template(stats_httpd.XML_TEMPLATE_LOCATION)
- self.assertTrue(isinstance(tmpl, string.Template))
- opts = dict(
- xml_string="<dummy></dummy>",
- xsl_url_path="/path/to/")
- lines = tmpl.substitute(opts)
- for n in opts:
- self.assertGreater(lines.find(opts[n]), 0)
- tmpl = self.stats_httpd.open_template(stats_httpd.XSD_TEMPLATE_LOCATION)
- self.assertTrue(isinstance(tmpl, string.Template))
- opts = dict(xsd_namespace="http://host/path/to/")
- lines = tmpl.substitute(opts)
- for n in opts:
- self.assertGreater(lines.find(opts[n]), 0)
- tmpl = self.stats_httpd.open_template(stats_httpd.XSL_TEMPLATE_LOCATION)
- self.assertTrue(isinstance(tmpl, string.Template))
- opts = dict(xsd_namespace="http://host/path/to/")
- lines = tmpl.substitute(opts)
- for n in opts:
- self.assertGreater(lines.find(opts[n]), 0)
- # unsuccessful condition
- self.assertRaises(
- stats_httpd.StatsHttpdDataError,
- self.stats_httpd.open_template, '/path/to/foo/bar')
-
- def test_commands(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- self.assertEqual(self.stats_httpd.command_handler("status", None),
- isc.config.ccsession.create_answer(
- 0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
- self.stats_httpd.running = True
- self.assertEqual(self.stats_httpd.command_handler("shutdown", None),
- isc.config.ccsession.create_answer(0))
- self.assertFalse(self.stats_httpd.running)
- self.assertEqual(
- self.stats_httpd.command_handler("__UNKNOWN_COMMAND__", None),
- isc.config.ccsession.create_answer(
- 1, "Unknown command: __UNKNOWN_COMMAND__"))
-
- def test_config(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- self.assertEqual(
- self.stats_httpd.config_handler(dict(_UNKNOWN_KEY_=None)),
- isc.config.ccsession.create_answer(
- 1, "unknown item _UNKNOWN_KEY_"))
-
- addresses = get_availaddr()
- self.assertEqual(
- self.stats_httpd.config_handler(
- dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
- isc.config.ccsession.create_answer(0))
- self.assertTrue("listen_on" in self.stats_httpd.config)
- for addr in self.stats_httpd.config["listen_on"]:
- self.assertTrue("address" in addr)
- self.assertTrue("port" in addr)
- self.assertTrue(addr["address"] == addresses[0])
- self.assertTrue(addr["port"] == addresses[1])
-
- if self.ipv6_enabled:
- addresses = get_availaddr("::1")
- self.assertEqual(
- self.stats_httpd.config_handler(
- dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
- isc.config.ccsession.create_answer(0))
- self.assertTrue("listen_on" in self.stats_httpd.config)
- for addr in self.stats_httpd.config["listen_on"]:
- self.assertTrue("address" in addr)
- self.assertTrue("port" in addr)
- self.assertTrue(addr["address"] == addresses[0])
- self.assertTrue(addr["port"] == addresses[1])
-
- addresses = get_availaddr()
- self.assertEqual(
- self.stats_httpd.config_handler(
- dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
- isc.config.ccsession.create_answer(0))
- self.assertTrue("listen_on" in self.stats_httpd.config)
- for addr in self.stats_httpd.config["listen_on"]:
- self.assertTrue("address" in addr)
- self.assertTrue("port" in addr)
- self.assertTrue(addr["address"] == addresses[0])
- self.assertTrue(addr["port"] == addresses[1])
- (ret, arg) = isc.config.ccsession.parse_answer(
- self.stats_httpd.config_handler(
- dict(listen_on=[dict(address="1.2.3.4",port=543210)]))
- )
- self.assertEqual(ret, 1)
-
- @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
- def test_xml_handler(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- module_name = 'Dummy'
- stats_spec = \
- { module_name :
- [{
- "item_name": "foo",
- "item_type": "string",
- "item_optional": False,
- "item_default": "bar",
- "item_description": "foo is bar",
- "item_title": "Foo"
- },
- {
- "item_name": "foo2",
- "item_type": "list",
- "item_optional": False,
- "item_default": [
- {
- "zonename" : "test1",
- "queries.udp" : 1,
- "queries.tcp" : 2
- },
- {
- "zonename" : "test2",
- "queries.udp" : 3,
- "queries.tcp" : 4
- }
- ],
- "item_title": "Foo bar",
- "item_description": "Foo bar",
- "list_item_spec": {
- "item_name": "foo2-1",
- "item_type": "map",
- "item_optional": False,
- "item_default": {},
- "map_item_spec": [
- {
- "item_name": "foo2-1-1",
- "item_type": "string",
- "item_optional": False,
- "item_default": "",
- "item_title": "Foo2 1 1",
- "item_description": "Foo bar"
- },
- {
- "item_name": "foo2-1-2",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Foo2 1 2",
- "item_description": "Foo bar"
- },
- {
- "item_name": "foo2-1-3",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Foo2 1 3",
- "item_description": "Foo bar"
- }
- ]
- }
- }]
- }
- stats_data = \
- { module_name : { 'foo':'bar',
- 'foo2': [
- {
- "foo2-1-1" : "bar1",
- "foo2-1-2" : 10,
- "foo2-1-3" : 9
- },
- {
- "foo2-1-1" : "bar2",
- "foo2-1-2" : 8,
- "foo2-1-3" : 7
- }
- ] } }
- self.stats_httpd.get_stats_spec = lambda x,y: stats_spec
- self.stats_httpd.get_stats_data = lambda x,y: stats_data
- xml_string = self.stats_httpd.xml_handler()
- stats_xml = xml.etree.ElementTree.fromstring(xml_string)
- schema_loc = '{%s}schemaLocation' % XMLNS_XSI
- self.assertEqual(stats_xml.attrib[schema_loc],
- stats_httpd.XML_ROOT_ATTRIB['xsi:schemaLocation'])
- stats_data = stats_data[module_name]
- stats_spec = stats_spec[module_name]
- names = stats_httpd.item_name_list(stats_data, '')
- for i in range(0, len(names)):
- self.assertEqual('%s/%s' % (module_name, names[i]), stats_xml[i].attrib['identifier'])
- value = isc.cc.data.find(stats_data, names[i])
- if type(value) is int:
- value = str(value)
- if type(value) is dict or type(value) is list:
- self.assertFalse('value' in stats_xml[i].attrib)
- else:
- self.assertEqual(value, stats_xml[i].attrib['value'])
- self.assertEqual(module_name, stats_xml[i].attrib['owner'])
- self.assertEqual(urllib.parse.quote('%s/%s/%s' % (stats_httpd.XML_URL_PATH,
- module_name, names[i])),
- stats_xml[i].attrib['uri'])
- spec = isc.config.find_spec_part(stats_spec, names[i])
- self.assertEqual(spec['item_name'], stats_xml[i].attrib['name'])
- self.assertEqual(spec['item_type'], stats_xml[i].attrib['type'])
- self.assertEqual(spec['item_description'], stats_xml[i].attrib['description'])
- self.assertEqual(spec['item_title'], stats_xml[i].attrib['title'])
- self.assertEqual(str(spec['item_optional']).lower(), stats_xml[i].attrib['optional'])
- default = spec['item_default']
- if type(default) is int:
- default = str(default)
- if type(default) is dict or type(default) is list:
- self.assertFalse('default' in stats_xml[i].attrib)
- else:
- self.assertEqual(default, stats_xml[i].attrib['default'])
- self.assertFalse('item_format' in spec)
- self.assertFalse('format' in stats_xml[i].attrib)
-
- @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
- def test_xsd_handler(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- xsd_string = self.stats_httpd.xsd_handler()
- stats_xsd = xml.etree.ElementTree.fromstring(xsd_string)
- ns = '{%s}' % XMLNS_XSD
- stats_xsd = stats_xsd[1].find('%scomplexType/%ssequence/%selement' % ((ns,)*3))
- self.assertEqual('item', stats_xsd.attrib['name'])
- stats_xsd = stats_xsd.find('%scomplexType' % ns)
- type_types = ('boolean', 'integer', 'real', 'string', 'map', \
- 'list', 'named_set', 'any')
- attribs = [('identifier', 'string', 'required'),
- ('value', 'string', 'optional'),
- ('owner', 'string', 'required'),
- ('uri', 'anyURI', 'required'),
- ('name', 'string', 'required'),
- ('type', type_types, 'required'),
- ('description', 'string', 'optional'),
- ('title', 'string', 'optional'),
- ('optional', 'boolean', 'optional'),
- ('default', 'string', 'optional'),
- ('format', 'string', 'optional')]
- for i in range(0, len(attribs)):
- self.assertEqual(attribs[i][0], stats_xsd[i].attrib['name'])
- if attribs[i][0] == 'type':
- stats_xsd_types = \
- stats_xsd[i].find('%ssimpleType/%srestriction' % \
- ((ns,)*2))
- for j in range(0, len(attribs[i][1])):
- self.assertEqual(attribs[i][1][j], \
- stats_xsd_types[j].attrib['value'])
- else:
- self.assertEqual(attribs[i][1], stats_xsd[i].attrib['type'])
- self.assertEqual(attribs[i][2], stats_xsd[i].attrib['use'])
-
- @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
- def test_xsl_handler(self):
- self.stats_httpd = MyStatsHttpd(get_availaddr())
- xsl_string = self.stats_httpd.xsl_handler()
- stats_xsl = xml.etree.ElementTree.fromstring(xsl_string)
- nst = '{%s}' % XMLNS_XSL
- nsx = '{%s}' % XMLNS_XHTML
- self.assertEqual("bind10:statistics", stats_xsl[2].attrib['match'])
- stats_xsl = stats_xsl[2].find('%stable' % nsx)
- self.assertEqual('item', stats_xsl[1].attrib['select'])
- stats_xsl = stats_xsl[1].find('%str' % nsx)
- self.assertEqual('@uri', stats_xsl[0].find(
- '%selement/%sattribute/%svalue-of' % ((nst,)*3)).attrib['select'])
- self.assertEqual('@identifier', stats_xsl[0].find(
- '%selement/%svalue-of' % ((nst,)*2)).attrib['select'])
- self.assertEqual('@value', stats_xsl[1].find('%sif' % nst).attrib['test'])
- self.assertEqual('@value', stats_xsl[1].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
- self.assertEqual('@description', stats_xsl[2].find('%sif' % nst).attrib['test'])
- self.assertEqual('@description', stats_xsl[2].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
-
- def test_for_without_B10_FROM_SOURCE(self):
- # just lets it go through the code without B10_FROM_SOURCE env
- # variable
- if "B10_FROM_SOURCE" in os.environ:
- tmppath = os.environ["B10_FROM_SOURCE"]
- os.environ.pop("B10_FROM_SOURCE")
- imp.reload(stats_httpd)
- os.environ["B10_FROM_SOURCE"] = tmppath
- imp.reload(stats_httpd)
-
-if __name__ == "__main__":
- isc.log.resetUnitTestRootLogger()
- unittest.main()
diff --git a/src/bin/stats/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py
deleted file mode 100644
index 7732902..0000000
--- a/src/bin/stats/tests/b10-stats_test.py
+++ /dev/null
@@ -1,1379 +0,0 @@
-# Copyright (C) 2010, 2011, 2012 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""
-In each of these tests we start several virtual components. They are
-not the real components, no external processes are started. They are
-just simple mock objects running each in its own thread and pretending
-to be bind10 modules. This helps testing the stats module in a close
-to real environment.
-"""
-
-import unittest
-import os
-import threading
-import io
-import time
-import imp
-import sys
-
-import stats
-import isc.log
-import isc.cc.session
-from test_utils import BaseModules, ThreadingServerManager, MyStats, \
- SimpleStats, SignalHandler, MyModuleCCSession, send_command
-from isc.testutils.ccsession_mock import MockModuleCCSession
-
-class TestUtilties(unittest.TestCase):
- items = [
- { 'item_name': 'test_int1', 'item_type': 'integer', 'item_default': 12345 },
- { 'item_name': 'test_real1', 'item_type': 'real', 'item_default': 12345.6789 },
- { 'item_name': 'test_bool1', 'item_type': 'boolean', 'item_default': True },
- { 'item_name': 'test_str1', 'item_type': 'string', 'item_default': 'ABCD' },
- { 'item_name': 'test_list1', 'item_type': 'list', 'item_default': [1,2,3],
- 'list_item_spec' : { 'item_name': 'number', 'item_type': 'integer' } },
- { 'item_name': 'test_map1', 'item_type': 'map', 'item_default': {'a':1,'b':2,'c':3},
- 'map_item_spec' : [ { 'item_name': 'a', 'item_type': 'integer'},
- { 'item_name': 'b', 'item_type': 'integer'},
- { 'item_name': 'c', 'item_type': 'integer'} ] },
- { 'item_name': 'test_int2', 'item_type': 'integer' },
- { 'item_name': 'test_real2', 'item_type': 'real' },
- { 'item_name': 'test_bool2', 'item_type': 'boolean' },
- { 'item_name': 'test_str2', 'item_type': 'string' },
- { 'item_name': 'test_list2', 'item_type': 'list',
- 'list_item_spec' : { 'item_name': 'number', 'item_type': 'integer' } },
- { 'item_name': 'test_map2', 'item_type': 'map',
- 'map_item_spec' : [ { 'item_name': 'A', 'item_type': 'integer'},
- { 'item_name': 'B', 'item_type': 'integer'},
- { 'item_name': 'C', 'item_type': 'integer'} ] },
- { 'item_name': 'test_none', 'item_type': 'none' },
- { 'item_name': 'test_list3', 'item_type': 'list', 'item_default': ["one","two","three"],
- 'list_item_spec' : { 'item_name': 'number', 'item_type': 'string' } },
- { 'item_name': 'test_map3', 'item_type': 'map', 'item_default': {'a':'one','b':'two','c':'three'},
- 'map_item_spec' : [ { 'item_name': 'a', 'item_type': 'string'},
- { 'item_name': 'b', 'item_type': 'string'},
- { 'item_name': 'c', 'item_type': 'string'} ] },
- {
- 'item_name': 'test_named_set',
- 'item_type': 'named_set',
- 'item_default': { },
- 'named_set_item_spec': {
- 'item_name': 'name',
- 'item_type': 'map',
- 'item_default': { },
- 'map_item_spec': [
- {
- 'item_name': 'number1',
- 'item_type': 'integer'
- },
- {
- 'item_name': 'number2',
- 'item_type': 'integer'
- }
- ]
- }
- }
- ]
-
- def setUp(self):
- self.const_timestamp = 1308730448.965706
- self.const_timetuple = (2011, 6, 22, 8, 14, 8, 2, 173, 0)
- self.const_datetime = '2011-06-22T08:14:08Z'
- stats.time = lambda : self.const_timestamp
- stats.gmtime = lambda : self.const_timetuple
-
- def test_get_spec_defaults(self):
- self.assertEqual(
- stats.get_spec_defaults(self.items), {
- 'test_int1' : 12345 ,
- 'test_real1' : 12345.6789 ,
- 'test_bool1' : True ,
- 'test_str1' : 'ABCD' ,
- 'test_list1' : [1,2,3] ,
- 'test_map1' : {'a':1,'b':2,'c':3},
- 'test_int2' : 0 ,
- 'test_real2' : 0.0,
- 'test_bool2' : False,
- 'test_str2' : "",
- 'test_list2' : [0],
- 'test_map2' : { 'A' : 0, 'B' : 0, 'C' : 0 },
- 'test_none' : None,
- 'test_list3' : [ "one", "two", "three" ],
- 'test_map3' : { 'a' : 'one', 'b' : 'two', 'c' : 'three' },
- 'test_named_set' : {} })
- self.assertEqual(stats.get_spec_defaults(None), {})
- self.assertRaises(KeyError, stats.get_spec_defaults, [{'item_name':'Foo'}])
-
- def test_get_timestamp(self):
- self.assertEqual(stats.get_timestamp(), self.const_timestamp)
-
- def test_get_datetime(self):
- self.assertEqual(stats.get_datetime(), self.const_datetime)
- self.assertNotEqual(stats.get_datetime(
- (2011, 6, 22, 8, 23, 40, 2, 173, 0)), self.const_datetime)
-
- def test__accum(self):
- self.assertEqual(stats._accum(None, None), None)
- self.assertEqual(stats._accum(None, "b"), "b")
- self.assertEqual(stats._accum("a", None), "a")
- self.assertEqual(stats._accum(1, 2), 3)
- self.assertEqual(stats._accum(0.5, 0.3), 0.8)
- self.assertEqual(stats._accum('aa','bb'), 'bb')
- self.assertEqual(stats._accum('1970-01-01T09:00:00Z','2012-08-09T09:33:31Z'),
- '2012-08-09T09:33:31Z')
- self.assertEqual(stats._accum(
- [1, 2, 3], [4, 5]), [5, 7, 3])
- self.assertEqual(stats._accum(
- [4, 5], [1, 2, 3]), [5, 7, 3])
- self.assertEqual(stats._accum(
- [1, 2, 3], [None, 5, 6]), [1, 7, 9])
- self.assertEqual(stats._accum(
- [None, 5, 6], [1, 2, 3]), [1, 7, 9])
- self.assertEqual(stats._accum(
- [1, 2, 3], [None, None, None, None]), [1,2,3,None])
- self.assertEqual(stats._accum(
- [[1,2],3],[[],5,6]), [[1,2],8,6])
- self.assertEqual(stats._accum(
- {'one': 1, 'two': 2, 'three': 3},
- {'one': 4, 'two': 5}),
- {'one': 5, 'two': 7, 'three': 3})
- self.assertEqual(stats._accum(
- {'one': 1, 'two': 2, 'three': 3},
- {'four': 4, 'five': 5}),
- {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
- self.assertEqual(stats._accum(
- {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
- {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
- {'one':[3,2], 'two':[7,5,5], 'three':[None,3,None], 'four': 'FOUR'})
- self.assertEqual(stats._accum(
- [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
- [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
- [ {'one': 1, 'two': 2, 'three': 3}, {'four': 5, 'five': 7, 'six': 9} ])
-
- def test_merge_oldnre(self):
- self.assertEqual(stats.merge_oldnew(1, 2), 2)
- self.assertEqual(stats.merge_oldnew(0.5, 0.3), 0.3)
- self.assertEqual(stats.merge_oldnew('aa','bb'), 'bb')
- self.assertEqual(stats.merge_oldnew(
- [1, 2, 3], [4, 5]), [4, 5, 3])
- self.assertEqual(stats.merge_oldnew(
- [4, 5], [1, 2, 3]), [1, 2, 3])
- self.assertEqual(stats.merge_oldnew(
- [1, 2, 3], [None, 5, 6]), [None, 5, 6])
- self.assertEqual(stats.merge_oldnew(
- [None, 5, 6], [1, 2, 3]), [1, 2, 3])
- self.assertEqual(stats.merge_oldnew(
- [1, 2, 3], [None, None, None, None]), [None, None, None, None])
- self.assertEqual(stats.merge_oldnew(
- [[1,2],3],[[],5,6]), [[1,2],5,6])
- self.assertEqual(stats.merge_oldnew(
- {'one': 1, 'two': 2, 'three': 3},
- {'one': 4, 'two': 5}),
- {'one': 4, 'two': 5, 'three': 3})
- self.assertEqual(stats.merge_oldnew(
- {'one': 1, 'two': 2, 'three': 3},
- {'four': 4, 'five': 5}),
- {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
- self.assertEqual(stats.merge_oldnew(
- {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
- {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
- {'one':[2,2], 'two':[4,5,5], 'three':[None,None,None], 'four': 'FOUR'})
- self.assertEqual(stats.merge_oldnew(
- [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
- [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
- [ {'one': 1, 'two': 2, 'three': 3}, {'four': 1, 'five': 2, 'six': 3} ])
-
-class TestCallback(unittest.TestCase):
- def setUp(self):
- self.dummy_func = lambda *x, **y : (x, y)
- self.dummy_args = (1,2,3)
- self.dummy_kwargs = {'a':1,'b':2,'c':3}
- self.cback1 = stats.Callback(
- command=self.dummy_func,
- args=self.dummy_args,
- kwargs=self.dummy_kwargs
- )
- self.cback2 = stats.Callback(
- args=self.dummy_args,
- kwargs=self.dummy_kwargs
- )
- self.cback3 = stats.Callback(
- command=self.dummy_func,
- kwargs=self.dummy_kwargs
- )
- self.cback4 = stats.Callback(
- command=self.dummy_func,
- args=self.dummy_args
- )
-
- def test_init(self):
- self.assertEqual((self.cback1.command, self.cback1.args, self.cback1.kwargs),
- (self.dummy_func, self.dummy_args, self.dummy_kwargs))
- self.assertEqual((self.cback2.command, self.cback2.args, self.cback2.kwargs),
- (None, self.dummy_args, self.dummy_kwargs))
- self.assertEqual((self.cback3.command, self.cback3.args, self.cback3.kwargs),
- (self.dummy_func, (), self.dummy_kwargs))
- self.assertEqual((self.cback4.command, self.cback4.args, self.cback4.kwargs),
- (self.dummy_func, self.dummy_args, {}))
-
- def test_call(self):
- self.assertEqual(self.cback1(), (self.dummy_args, self.dummy_kwargs))
- self.assertEqual(self.cback1(100, 200), ((100, 200), self.dummy_kwargs))
- self.assertEqual(self.cback1(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
- self.assertEqual(self.cback2(), None)
- self.assertEqual(self.cback3(), ((), self.dummy_kwargs))
- self.assertEqual(self.cback3(100, 200), ((100, 200), self.dummy_kwargs))
- self.assertEqual(self.cback3(a=100, b=200), ((), {'a':100, 'b':200}))
- self.assertEqual(self.cback4(), (self.dummy_args, {}))
- self.assertEqual(self.cback4(100, 200), ((100, 200), {}))
- self.assertEqual(self.cback4(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
-
-class TestStats(unittest.TestCase):
- def setUp(self):
- # set the signal handler for deadlock
- self.sig_handler = SignalHandler(self.fail)
- self.base = BaseModules()
- self.const_timestamp = 1308730448.965706
- self.const_datetime = '2011-06-22T08:14:08Z'
- self.const_default_datetime = '1970-01-01T00:00:00Z'
- # Record original module-defined functions in case we replace them
- self.__orig_timestamp = stats.get_timestamp
- self.__orig_get_datetime = stats.get_datetime
-
- def tearDown(self):
- self.base.shutdown()
- # reset the signal handler
- self.sig_handler.reset()
- # restore the stored original function in case we replaced them
- stats.get_timestamp = self.__orig_timestamp
- stats.get_datetime = self.__orig_get_datetime
-
- def test_init(self):
- self.stats = stats.Stats()
- self.assertEqual(self.stats.module_name, 'Stats')
- self.assertFalse(self.stats.running)
- self.assertTrue('command_show' in self.stats.callbacks)
- self.assertTrue('command_status' in self.stats.callbacks)
- self.assertTrue('command_shutdown' in self.stats.callbacks)
- self.assertTrue('command_show' in self.stats.callbacks)
- self.assertTrue('command_showschema' in self.stats.callbacks)
- self.assertEqual(self.stats.config['poll-interval'], 60)
-
- def test_init_undefcmd(self):
- spec_str = """\
-{
- "module_spec": {
- "module_name": "Stats",
- "module_description": "Stats daemon",
- "config_data": [],
- "commands": [
- {
- "command_name": "_undef_command_",
- "command_description": "a undefined command in stats",
- "command_args": []
- }
- ],
- "statistics": []
- }
-}
-"""
- orig_spec_location = stats.SPECFILE_LOCATION
- stats.SPECFILE_LOCATION = io.StringIO(spec_str)
- self.assertRaises(stats.StatsError, stats.Stats)
- stats.SPECFILE_LOCATION = orig_spec_location
-
- def __send_command(self, stats, command_name, params=None):
- '''Emulate a command arriving to stats by directly calling callback'''
- return isc.config.ccsession.parse_answer(
- stats.command_handler(command_name, params))
-
- def test_start(self):
- # Define a separate exception class so we can be sure that's actually
- # the one raised in __check_start() below
- class CheckException(Exception):
- pass
-
- def __check_start(tested_stats):
- self.assertTrue(tested_stats.running)
- raise CheckException # terminate the loop
-
- # start without err
- stats = SimpleStats()
- self.assertFalse(stats.running)
- stats._check_command = lambda: __check_start(stats)
- # We are going to confirm start() will set running to True, avoiding
- # to fall into a loop with the exception trick.
- self.assertRaises(CheckException, stats.start)
- self.assertEqual(self.__send_command(stats, "status"),
- (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-
- def test_shutdown(self):
- def __check_shutdown(tested_stats):
- self.assertTrue(tested_stats.running)
- self.assertEqual(self.__send_command(tested_stats, "shutdown"),
- (0, None))
- self.assertFalse(tested_stats.running)
- # override get_interval() so it won't go poll statistics
- tested_stats.get_interval = lambda : 0
-
- stats = SimpleStats()
- stats._check_command = lambda: __check_shutdown(stats)
- stats.start()
- self.assertTrue(stats.mccs.stopped)
-
- def test_handlers(self):
- """Test command_handler"""
-
- __stats = SimpleStats()
-
- # 'show' command. We're going to check the expected methods are
- # called in the expected order, and check the resulting response.
- # Details of each method are tested separately.
- call_log = []
- def __steal_method(fn_name, *arg):
- call_log.append((fn_name, arg))
- if fn_name == 'update_stat':
- return False # "no error"
- if fn_name == 'showschema':
- return isc.config.create_answer(0, 'no error')
-
- # Fake some methods and attributes for inspection
- __stats.do_polling = lambda: __steal_method('polling')
- __stats.update_statistics_data = \
- lambda x, y, z: __steal_method('update_stat', x, y, z)
- __stats.update_modules = lambda: __steal_method('update_module')
- __stats.mccs.lname = 'test lname'
- __stats.statistics_data = {'Init': {'boot_time': self.const_datetime}}
-
- # skip initial polling
- stats.get_timestamp = lambda: 0
- __stats._lasttime_poll = 0
-
- stats.get_datetime = lambda: 42 # make the result predictable
-
- # now send the command
- self.assertEqual(
- self.__send_command(
- __stats, 'show',
- params={ 'owner' : 'Init', 'name' : 'boot_time' }),
- (0, {'Init': {'boot_time': self.const_datetime}}))
- # Check if expected methods are called
- self.assertEqual([('update_stat',
- ('Stats', 'test lname',
- {'timestamp': 0,
- 'report_time': 42})),
- ('update_module', ())], call_log)
-
- # Then update faked timestamp so the intial polling will happen, and
- # confirm that.
- call_log = []
- stats.get_timestamp = lambda: 10
- self.assertEqual(
- self.__send_command(
- __stats, 'show',
- params={ 'owner' : 'Init', 'name' : 'boot_time' }),
- (0, {'Init': {'boot_time': self.const_datetime}}))
- self.assertEqual([('polling', ()),
- ('update_stat',
- ('Stats', 'test lname',
- {'timestamp': 10,
- 'report_time': 42})),
- ('update_module', ())], call_log)
-
- # 'status' command. We can confirm the behavior without any fake
- self.assertEqual(
- self.__send_command(__stats, 'status'),
- (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-
- # 'showschema' command. update_modules() will be called, which
- # (implicitly) cofirms the correct method is called; further details
- # are tested seprately.
- call_log = []
- (rcode, value) = self.__send_command(__stats, 'showschema')
- self.assertEqual([('update_module', ())], call_log)
-
- # Unknown command. Error should be returned
- self.assertEqual(
- self.__send_command(__stats, '__UNKNOWN__'),
- (1, "Unknown command: '__UNKNOWN__'"))
-
- def test_update_modules(self):
- """Confirm the behavior of Stats.update_modules().
-
- It checks whether the expected command is sent to ConfigManager,
- and whether the answer from ConfigManager is handled as expected.
-
- """
-
- def __check_rpc_call(command, group):
- self.assertEqual('ConfigManager', group)
- self.assertEqual(command,
- isc.config.ccsession.COMMAND_GET_STATISTICS_SPEC)
- answer_value = {'Init': [{
- "item_name": "boot_time",
- "item_type": "string",
- "item_optional": False,
- # Use a different default so we can check it below
- "item_default": "2013-01-01T00:00:01Z",
- "item_title": "Boot time",
- "item_description": "dummy desc",
- "item_format": "date-time"
- }]}
- return answer_value
-
- self.stats = SimpleStats()
- self.stats.cc_session.rpc_call = __check_rpc_call
-
- self.stats.update_modules()
-
- # Stats is always incorporated. For others, only the ones returned
- # by group_recvmsg() above is available.
- self.assertTrue('Stats' in self.stats.modules)
- self.assertTrue('Init' in self.stats.modules)
- self.assertFalse('Dummy' in self.stats.modules)
-
- my_statistics_data = stats.get_spec_defaults(
- self.stats.modules['Stats'].get_statistics_spec())
- self.assertTrue('report_time' in my_statistics_data)
- self.assertTrue('boot_time' in my_statistics_data)
- self.assertTrue('last_update_time' in my_statistics_data)
- self.assertTrue('timestamp' in my_statistics_data)
- self.assertTrue('lname' in my_statistics_data)
- self.assertEqual(my_statistics_data['report_time'],
- self.const_default_datetime)
- self.assertEqual(my_statistics_data['boot_time'],
- self.const_default_datetime)
- self.assertEqual(my_statistics_data['last_update_time'],
- self.const_default_datetime)
- self.assertEqual(my_statistics_data['timestamp'], 0.0)
- self.assertEqual(my_statistics_data['lname'], "")
- my_statistics_data = stats.get_spec_defaults(
- self.stats.modules['Init'].get_statistics_spec())
- self.assertTrue('boot_time' in my_statistics_data)
- self.assertEqual(my_statistics_data['boot_time'],
- "2013-01-01T00:00:01Z")
-
- # Error case
- def __raise_on_rpc_call(x, y):
- raise isc.config.RPCError(99, 'error')
- orig_parse_answer = stats.isc.config.ccsession.parse_answer
- self.stats.cc_session.rpc_call = __raise_on_rpc_call
- self.assertRaises(stats.StatsError, self.stats.update_modules)
-
- def test_get_statistics_data(self):
- """Confirm the behavior of Stats.get_statistics_data().
-
- It should first call update_modules(), and then retrieve the requested
- data from statistics_data. We confirm this by fake update_modules()
- where we set the expected data in statistics_data.
-
- """
- self.stats = SimpleStats()
- def __faked_update_modules():
- self.stats.statistics_data = { \
- 'Stats': {
- 'report_time': self.const_default_datetime,
- 'boot_time': None,
- 'last_update_time': None,
- 'timestamp': 0.0,
- 'lname': 'dummy name'
- },
- 'Init': { 'boot_time': None }
- }
-
- self.stats.update_modules = __faked_update_modules
-
- my_statistics_data = self.stats.get_statistics_data()
- self.assertTrue('Stats' in my_statistics_data)
- self.assertTrue('Init' in my_statistics_data)
- self.assertTrue('boot_time' in my_statistics_data['Init'])
-
- my_statistics_data = self.stats.get_statistics_data(owner='Stats')
- self.assertTrue('Stats' in my_statistics_data)
- self.assertTrue('report_time' in my_statistics_data['Stats'])
- self.assertTrue('boot_time' in my_statistics_data['Stats'])
- self.assertTrue('last_update_time' in my_statistics_data['Stats'])
- self.assertTrue('timestamp' in my_statistics_data['Stats'])
- self.assertTrue('lname' in my_statistics_data['Stats'])
- self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
- owner='Foo')
-
- my_statistics_data = self.stats.get_statistics_data(
- owner='Stats', name='report_time')
- self.assertEqual(my_statistics_data['Stats']['report_time'],
- self.const_default_datetime)
-
- my_statistics_data = self.stats.get_statistics_data(
- owner='Stats', name='boot_time')
- self.assertTrue('boot_time' in my_statistics_data['Stats'])
-
- my_statistics_data = self.stats.get_statistics_data(
- owner='Stats', name='last_update_time')
- self.assertTrue('last_update_time' in my_statistics_data['Stats'])
-
- my_statistics_data = self.stats.get_statistics_data(
- owner='Stats', name='timestamp')
- self.assertEqual(my_statistics_data['Stats']['timestamp'], 0.0)
-
- my_statistics_data = self.stats.get_statistics_data(
- owner='Stats', name='lname')
- self.assertTrue(len(my_statistics_data['Stats']['lname']) >0)
- self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
- owner='Stats', name='Bar')
- self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
- owner='Foo', name='Bar')
- self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
- name='Bar')
-
- def test_update_statistics_data(self):
- """test for list-type statistics"""
- self.stats = SimpleStats()
- _test_exp1 = {
- 'zonename': 'test1.example',
- 'queries.tcp': 5,
- 'queries.udp': 4
- }
- _test_exp2 = {
- 'zonename': 'test2.example',
- 'queries.tcp': 3,
- 'queries.udp': 2
- }
- _test_exp3 = {}
- _test_exp4 = {
- 'queries.udp': 4
- }
- _test_exp5_1 = {
- 'queries.perzone': [
- { },
- {
- 'queries.udp': 9876
- }
- ]
- }
- _test_exp5_2 = {
- 'queries.perzone[1]/queries.udp':
- isc.cc.data.find(_test_exp5_1,
- 'queries.perzone[1]/queries.udp')
- }
- # Success cases
- self.assertEqual(self.stats.statistics_data['Stats']['lname'],
- self.stats.cc_session.lname)
- self.stats.update_statistics_data(
- 'Stats', self.stats.cc_session.lname,
- {'lname': 'foo at bar'})
- self.assertEqual(self.stats.statistics_data['Stats']['lname'],
- 'foo at bar')
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'queries.perzone': [_test_exp1]}))
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['queries.perzone'],\
- [_test_exp1])
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'queries.perzone': [_test_exp2]}))
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['queries.perzone'],\
- [_test_exp2])
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'queries.perzone': [_test_exp1,_test_exp2]}))
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['queries.perzone'],
- [_test_exp1,_test_exp2])
- # differential update
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'queries.perzone': [_test_exp3,_test_exp4]}))
- _new_data = stats.merge_oldnew(_test_exp2,_test_exp4)
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['queries.perzone'], \
- [_test_exp1,_new_data])
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', _test_exp5_2))
- _new_data = stats.merge_oldnew(_new_data,
- _test_exp5_1['queries.perzone'][1])
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['queries.perzone'], \
- [_test_exp1,_new_data])
- # Error cases
- self.assertEqual(self.stats.update_statistics_data('Stats', None,
- {'lname': 0.0}),
- ['0.0 should be a string'])
- self.assertEqual(self.stats.update_statistics_data('Dummy', None,
- {'foo': 'bar'}),
- ['unknown module name: Dummy'])
- self.assertEqual(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'queries.perzone': [None]}), ['None should be a map'])
-
- def test_update_statistics_data_pt2(self):
- """test for named_set-type statistics"""
- self.stats = SimpleStats()
- _test_exp1 = \
- { 'test10.example': { 'queries.tcp': 5, 'queries.udp': 4 } }
- _test_exp2 = \
- { 'test20.example': { 'queries.tcp': 3, 'queries.udp': 2 } }
- _test_exp3 = {}
- _test_exp4 = { 'test20.example': { 'queries.udp': 4 } }
- _test_exp5_1 = { 'test10.example': { 'queries.udp': 5432 } }
- _test_exp5_2 ={
- 'nds_queries.perzone/test10.example/queries.udp':
- isc.cc.data.find(_test_exp5_1, 'test10.example/queries.udp')
- }
- _test_exp6 = { 'foo/bar': 'brabra' }
- _test_exp7 = { 'foo[100]': 'bar' }
- # Success cases
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'nds_queries.perzone': _test_exp1}))
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['nds_queries.perzone'],\
- _test_exp1)
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'nds_queries.perzone': _test_exp2}))
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['nds_queries.perzone'],\
- dict(_test_exp1,**_test_exp2))
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'nds_queries.perzone':
- dict(_test_exp1, **_test_exp2)}))
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['nds_queries.perzone'],
- dict(_test_exp1, **_test_exp2))
- # differential update
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'nds_queries.perzone':
- dict(_test_exp3, **_test_exp4)}))
- _new_val = dict(_test_exp1,
- **stats.merge_oldnew(_test_exp2,_test_exp4))
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['nds_queries.perzone'],\
- _new_val)
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo1', _test_exp5_2))
- _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['nds_queries.perzone'],\
- _new_val)
- self.assertIsNone(self.stats.update_statistics_data(
- 'Auth', 'foo2', _test_exp5_2))
- _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo2']['nds_queries.perzone'],\
- _test_exp5_1)
- # Error cases
- self.assertEqual(self.stats.update_statistics_data(
- 'Auth', 'foo1', {'nds_queries.perzone': None}),
- ['None should be a map'])
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['nds_queries.perzone'],\
- _new_val)
- self.assertEqual(self.stats.update_statistics_data(
- 'Auth', 'foo1', _test_exp6), ['unknown item foo'])
- self.assertEqual(self.stats.statistics_data_bymid['Auth']\
- ['foo1']['nds_queries.perzone'],\
- _new_val)
- self.assertEqual(self.stats.update_statistics_data(
- 'Init', 'bar1', _test_exp7), ["KeyError: 'foo'"])
- self.assertEqual(self.stats.update_statistics_data(
- 'Foo', 'foo1', _test_exp6), ['unknown module name: Foo'])
-
- def test_update_statistics_data_withmid(self):
- self.stats = SimpleStats()
-
- # This test relies on existing statistics data at the Stats object.
- # This version of test prepares the data using the do_polling() method;
- # that's a bad practice because a unittest for a method
- # (update_statistics_data) would heavily depend on details of another
- # method (do_polling). However, there's currently no direct test
- # for do_polling (which is also bad), so we still keep that approach,
- # partly for testing do_polling indirectly. #2781 should provide
- # direct test for do_polling, with which this test scenario should
- # also be changed to be more stand-alone.
-
- # We use the knowledge of what kind of messages are sent via
- # do_polling, and return the following faked answer directly.
- create_answer = isc.config.ccsession.create_answer # shortcut
- self.stats._answers = [\
- # Answer for "show_processes"
- (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
- [1035, 'b10-auth-2', 'Auth']]), None),
- # Answers for "getstats". 2 for Auth instances and 1 for Init.
- # we return some bogus values for Init, but the rest of the test
- # doesn't need it, so it's okay.
- (create_answer(0, self.stats._auth_sdata), {'from': 'auth1'}),
- (create_answer(0, self.stats._auth_sdata), {'from': 'auth2'}),
- (create_answer(0, self.stats._auth_sdata), {'from': 'auth3'})
- ]
- # do_polling calls update_modules internally; in our scenario there's
- # no change in modules, so we make it no-op.
- self.stats.update_modules = lambda: None
- # Now call do_polling.
- self.stats.do_polling()
-
- # samples of query number
- bar1_tcp = 1001
- bar2_tcp = 2001
- bar3_tcp = 1002
- bar3_udp = 1003
- # two auth instances invoked, so we double the pre-set stat values
- sum_qtcp = self.stats._queries_tcp * 2
- sum_qudp = self.stats._queries_udp * 2
- self.stats.update_statistics_data('Auth', "bar1 at foo",
- {'queries.tcp': bar1_tcp})
- self.assertTrue('Auth' in self.stats.statistics_data)
- self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
- self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
- bar1_tcp + sum_qtcp)
- self.assertTrue('Auth' in self.stats.statistics_data_bymid)
- self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
- self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid
- ['Auth']['bar1 at foo'])
- self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
- {'queries.tcp': bar1_tcp})
- # check consolidation of statistics data even if there is
- # non-existent mid of Auth
- self.stats.update_statistics_data('Auth', "bar2 at foo",
- {'queries.tcp': bar2_tcp})
- self.assertTrue('Auth' in self.stats.statistics_data)
- self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
- self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
- bar1_tcp + bar2_tcp + sum_qtcp)
- self.assertTrue('Auth' in self.stats.statistics_data_bymid)
- self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
- self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
- self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
- {'queries.tcp': bar1_tcp})
- self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar2 at foo'],
- {'queries.tcp': bar2_tcp})
- # kill running Auth but the statistics data doesn't change
- self.base.auth2.server.shutdown()
- self.stats.update_statistics_data()
- self.assertTrue('Auth' in self.stats.statistics_data)
- self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
- self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
- self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
- bar1_tcp + bar2_tcp + sum_qtcp)
- self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
- sum_qudp)
- self.assertTrue('Auth' in self.stats.statistics_data_bymid)
- # restore statistics data of killed auth
- # self.base.b10_init.server.pid_list = [ killed ] + self.base.b10_init.server.pid_list[:]
- self.stats.update_statistics_data('Auth',
- "bar1 at foo",
- {'queries.tcp': bar1_tcp})
- # set another mid of Auth
- self.stats.update_statistics_data('Auth',
- "bar3 at foo",
- {'queries.tcp':bar3_tcp,
- 'queries.udp':bar3_udp})
- self.assertTrue('Auth' in self.stats.statistics_data)
- self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
- self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
- self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
- bar1_tcp + bar2_tcp + bar3_tcp + sum_qtcp)
- self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
- bar3_udp + sum_qudp)
- self.assertTrue('Auth' in self.stats.statistics_data_bymid)
- self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
- self.assertTrue('bar3 at foo' in self.stats.statistics_data_bymid['Auth'])
- self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
- self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
- self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
- self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo']['queries.tcp'], bar1_tcp)
- self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.tcp'], bar3_tcp)
- self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.udp'], bar3_udp)
-
- def test_config(self):
- orig_get_timestamp = stats.get_timestamp
- stats.get_timestamp = lambda : self.const_timestamp
- stat = SimpleStats()
-
- # test updating poll-interval
- self.assertEqual(stat.config['poll-interval'], 60)
- self.assertEqual(stat.get_interval(), 60)
- self.assertEqual(stat.next_polltime, self.const_timestamp + 60)
- self.assertEqual(stat.config_handler({'poll-interval': 120}),
- isc.config.create_answer(0))
- self.assertEqual(stat.config['poll-interval'], 120)
- self.assertEqual(stat.get_interval(), 120)
- self.assertEqual(stat.next_polltime, self.const_timestamp + 120)
- stats.get_timestamp = orig_get_timestamp
- self.assertEqual(stat.config_handler({'poll-interval': "foo"}),
- isc.config.create_answer(1, 'foo should be an integer'))
- self.assertEqual(stat.config_handler({'poll-interval': -1}),
- isc.config.create_answer(1, 'Negative integer ignored'))
- # unknown item
- self.assertEqual(
- stat.config_handler({'_UNKNOWN_KEY_': None}),
- isc.config.ccsession.create_answer(
- 1, "unknown item _UNKNOWN_KEY_"))
- # test no change if zero interval time
- self.assertEqual(stat.config_handler({'poll-interval': 0}),
- isc.config.create_answer(0))
- self.assertEqual(stat.config['poll-interval'], 0)
-
- # see the comment for test_update_statistics_data_withmid. We abuse
- # do_polling here, too. With #2781 we should make it more direct.
- create_answer = isc.config.ccsession.create_answer # shortcut
- stat._answers = [\
- # Answer for "show_processes"
- (create_answer(0, []), None),
- # Answers for "getstats" for Init (the other one for Auth, but
- # that doesn't matter for this test)
- (create_answer(0, stat._init_sdata), {'from': 'init'}),
- (create_answer(0, stat._init_sdata), {'from': 'init'})
- ]
- stat.update_modules = lambda: None
-
- self.assertEqual(
- self.__send_command(
- stat, 'show',
- params={ 'owner' : 'Init', 'name' : 'boot_time' }),
- (0, {'Init': {'boot_time': self.const_datetime}}))
-
- def test_commands(self):
- self.stats = stats.Stats()
-
- # status
- self.assertEqual(self.stats.command_status(),
- isc.config.create_answer(
- 0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-
- # shutdown
- self.stats.running = True
- self.assertEqual(self.stats.command_shutdown(),
- isc.config.create_answer(0))
- self.assertFalse(self.stats.running)
-
- @unittest.skipIf(sys.version_info >= (3, 3), "Unsupported in Python 3.3 or higher")
- def test_command_show(self):
- # two auth instances invoked
- list_auth = [ self.base.auth.server,
- self.base.auth2.server ]
- sum_qtcp = 0
- sum_qudp = 0
- sum_qtcp_perzone1 = 0
- sum_qudp_perzone1 = 0
- sum_qtcp_perzone2 = 4 * len(list_auth)
- sum_qudp_perzone2 = 3 * len(list_auth)
- sum_qtcp_nds_perzone10 = 0
- sum_qudp_nds_perzone10 = 0
- sum_qtcp_nds_perzone20 = 4 * len(list_auth)
- sum_qudp_nds_perzone20 = 3 * len(list_auth)
- self.stats = stats.Stats()
- self.assertEqual(self.stats.command_show(owner='Foo', name=None),
- isc.config.create_answer(
- 1, "specified arguments are incorrect: owner: Foo, name: None"))
- self.assertEqual(self.stats.command_show(owner='Foo', name='_bar_'),
- isc.config.create_answer(
- 1, "specified arguments are incorrect: owner: Foo, name: _bar_"))
- self.assertEqual(self.stats.command_show(owner='Foo', name='bar'),
- isc.config.create_answer(
- 1, "specified arguments are incorrect: owner: Foo, name: bar"))
-
- for a in list_auth:
- sum_qtcp += a.queries_tcp
- sum_qudp += a.queries_udp
- sum_qtcp_perzone1 += a.queries_per_zone[0]['queries.tcp']
- sum_qudp_perzone1 += a.queries_per_zone[0]['queries.udp']
- sum_qtcp_nds_perzone10 += a.nds_queries_per_zone['test10.example']['queries.tcp']
- sum_qudp_nds_perzone10 += a.nds_queries_per_zone['test10.example']['queries.udp']
-
- self.assertEqual(self.stats.command_show(owner='Auth'),
- isc.config.create_answer(
- 0, {'Auth':{ 'queries.udp': sum_qudp,
- 'queries.tcp': sum_qtcp,
- 'queries.perzone': [{ 'zonename': 'test1.example',
- 'queries.udp': sum_qudp_perzone1,
- 'queries.tcp': sum_qtcp_perzone1 },
- { 'zonename': 'test2.example',
- 'queries.udp': sum_qudp_perzone2,
- 'queries.tcp': sum_qtcp_perzone2 }
- ],
- 'nds_queries.perzone': { 'test10.example' : {
- 'queries.udp': sum_qudp_nds_perzone10,
- 'queries.tcp': sum_qtcp_nds_perzone10 },
- 'test20.example' : {
- 'queries.udp': sum_qudp_nds_perzone20,
- 'queries.tcp': sum_qtcp_nds_perzone20 }
- }}}))
- self.assertEqual(self.stats.command_show(owner='Auth', name='queries.udp'),
- isc.config.create_answer(
- 0, {'Auth': {'queries.udp': sum_qudp}}))
- self.assertEqual(self.stats.command_show(owner='Auth', name='queries.perzone'),
- isc.config.create_answer(
- 0, {'Auth': {'queries.perzone': [
- { 'zonename': 'test1.example',
- 'queries.udp': sum_qudp_perzone1,
- 'queries.tcp': sum_qtcp_perzone1 },
- { 'zonename': 'test2.example',
- 'queries.udp': sum_qudp_perzone2,
- 'queries.tcp': sum_qtcp_perzone2 }]}}))
- self.assertEqual(self.stats.command_show(owner='Auth', name='nds_queries.perzone'),
- isc.config.create_answer(
- 0, {'Auth': {'nds_queries.perzone': {
- 'test10.example': {
- 'queries.udp': sum_qudp_nds_perzone10,
- 'queries.tcp': sum_qtcp_nds_perzone10 },
- 'test20.example': {
- 'queries.udp': sum_qudp_nds_perzone20,
- 'queries.tcp': sum_qtcp_nds_perzone20 }}}}))
- orig_get_datetime = stats.get_datetime
- orig_get_timestamp = stats.get_timestamp
- stats.get_datetime = lambda x=None: self.const_datetime
- stats.get_timestamp = lambda : self.const_timestamp
- self.assertEqual(self.stats.command_show(owner='Stats', name='report_time'),
- isc.config.create_answer(
- 0, {'Stats': {'report_time':self.const_datetime}}))
- self.assertEqual(self.stats.command_show(owner='Stats', name='timestamp'),
- isc.config.create_answer(
- 0, {'Stats': {'timestamp':self.const_timestamp}}))
- stats.get_datetime = orig_get_datetime
- stats.get_timestamp = orig_get_timestamp
- self.stats.modules[self.stats.module_name] = isc.config.module_spec.ModuleSpec(
- { "module_name": self.stats.module_name,
- "statistics": [] } )
- self.assertRaises(
- stats.StatsError, self.stats.command_show, owner=self.stats.module_name, name='bar')
-
- def test_command_showchema(self):
- self.stats = stats.Stats()
- (rcode, value) = isc.config.ccsession.parse_answer(
- self.stats.command_showschema())
- self.assertEqual(rcode, 0)
- self.assertEqual(len(value), 3)
- self.assertTrue('Stats' in value)
- self.assertTrue('Init' in value)
- self.assertTrue('Auth' in value)
- self.assertFalse('__Dummy__' in value)
- schema = value['Stats']
- self.assertEqual(len(schema), 5)
- for item in schema:
- self.assertTrue(len(item) == 6 or len(item) == 7)
- self.assertTrue('item_name' in item)
- self.assertTrue('item_type' in item)
- self.assertTrue('item_optional' in item)
- self.assertTrue('item_default' in item)
- self.assertTrue('item_title' in item)
- self.assertTrue('item_description' in item)
- if len(item) == 7:
- self.assertTrue('item_format' in item)
-
- schema = value['Init']
- self.assertEqual(len(schema), 1)
- for item in schema:
- self.assertTrue(len(item) == 7)
- self.assertTrue('item_name' in item)
- self.assertTrue('item_type' in item)
- self.assertTrue('item_optional' in item)
- self.assertTrue('item_default' in item)
- self.assertTrue('item_title' in item)
- self.assertTrue('item_description' in item)
- self.assertTrue('item_format' in item)
-
- schema = value['Auth']
- self.assertEqual(len(schema), 4)
- for item in schema:
- if item['item_type'] == 'list' or item['item_type'] == 'named_set':
- self.assertEqual(len(item), 7)
- else:
- self.assertEqual(len(item), 6)
- self.assertTrue('item_name' in item)
- self.assertTrue('item_type' in item)
- self.assertTrue('item_optional' in item)
- self.assertTrue('item_default' in item)
- self.assertTrue('item_title' in item)
- self.assertTrue('item_description' in item)
-
- (rcode, value) = isc.config.ccsession.parse_answer(
- self.stats.command_showschema(owner='Stats'))
- self.assertEqual(rcode, 0)
- self.assertTrue('Stats' in value)
- self.assertFalse('Init' in value)
- self.assertFalse('Auth' in value)
- for item in value['Stats']:
- self.assertTrue(len(item) == 6 or len(item) == 7)
- self.assertTrue('item_name' in item)
- self.assertTrue('item_type' in item)
- self.assertTrue('item_optional' in item)
- self.assertTrue('item_default' in item)
- self.assertTrue('item_title' in item)
- self.assertTrue('item_description' in item)
- if len(item) == 7:
- self.assertTrue('item_format' in item)
-
- (rcode, value) = isc.config.ccsession.parse_answer(
- self.stats.command_showschema(owner='Stats', name='report_time'))
- self.assertEqual(rcode, 0)
- self.assertTrue('Stats' in value)
- self.assertFalse('Init' in value)
- self.assertFalse('Auth' in value)
- self.assertEqual(len(value['Stats'][0]), 7)
- self.assertTrue('item_name' in value['Stats'][0])
- self.assertTrue('item_type' in value['Stats'][0])
- self.assertTrue('item_optional' in value['Stats'][0])
- self.assertTrue('item_default' in value['Stats'][0])
- self.assertTrue('item_title' in value['Stats'][0])
- self.assertTrue('item_description' in value['Stats'][0])
- self.assertTrue('item_format' in value['Stats'][0])
- self.assertEqual(value['Stats'][0]['item_name'], 'report_time')
- self.assertEqual(value['Stats'][0]['item_format'], 'date-time')
-
- self.assertEqual(self.stats.command_showschema(owner='Foo'),
- isc.config.create_answer(
- 1, "specified arguments are incorrect: owner: Foo, name: None"))
- self.assertEqual(self.stats.command_showschema(owner='Foo', name='bar'),
- isc.config.create_answer(
- 1, "specified arguments are incorrect: owner: Foo, name: bar"))
- self.assertEqual(self.stats.command_showschema(owner='Auth'),
- isc.config.create_answer(
- 0, {'Auth': [{
- "item_default": 0,
- "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
- "item_name": "queries.tcp",
- "item_optional": False,
- "item_title": "Queries TCP",
- "item_type": "integer"
- },
- {
- "item_default": 0,
- "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially",
- "item_name": "queries.udp",
- "item_optional": False,
- "item_title": "Queries UDP",
- "item_type": "integer"
- },
- {
- "item_name": "queries.perzone",
- "item_type": "list",
- "item_optional": False,
- "item_default": [
- {
- "zonename" : "test1.example",
- "queries.udp" : 1,
- "queries.tcp" : 2
- },
- {
- "zonename" : "test2.example",
- "queries.udp" : 3,
- "queries.tcp" : 4
- }
- ],
- "item_title": "Queries per zone",
- "item_description": "Queries per zone",
- "list_item_spec": {
- "item_name": "zones",
- "item_type": "map",
- "item_optional": False,
- "item_default": {},
- "map_item_spec": [
- {
- "item_name": "zonename",
- "item_type": "string",
- "item_optional": False,
- "item_default": "",
- "item_title": "Zonename",
- "item_description": "Zonename"
- },
- {
- "item_name": "queries.udp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries UDP per zone",
- "item_description": "A number of UDP query counts per zone"
- },
- {
- "item_name": "queries.tcp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries TCP per zone",
- "item_description": "A number of TCP query counts per zone"
- }
- ]
- }
- },
- {
- "item_name": "nds_queries.perzone",
- "item_type": "named_set",
- "item_optional": False,
- "item_default": {
- "test10.example" : {
- "queries.udp" : 1,
- "queries.tcp" : 2
- },
- "test20.example" : {
- "queries.udp" : 3,
- "queries.tcp" : 4
- }
- },
- "item_title": "Queries per zone",
- "item_description": "Queries per zone",
- "named_set_item_spec": {
- "item_name": "zonename",
- "item_type": "map",
- "item_optional": False,
- "item_default": {},
- "item_title": "Zonename",
- "item_description": "Zonename",
- "map_item_spec": [
- {
- "item_name": "queries.udp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries UDP per zone",
- "item_description": "A number of UDP query counts per zone"
- },
- {
- "item_name": "queries.tcp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries TCP per zone",
- "item_description": "A number of TCP query counts per zone"
- }
- ]
- }
- }]}))
- self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.tcp'),
- isc.config.create_answer(
- 0, {'Auth': [{
- "item_default": 0,
- "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
- "item_name": "queries.tcp",
- "item_optional": False,
- "item_title": "Queries TCP",
- "item_type": "integer"
- }]}))
- self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.perzone'),
- isc.config.create_answer(
- 0, {'Auth':[{
- "item_name": "queries.perzone",
- "item_type": "list",
- "item_optional": False,
- "item_default": [
- {
- "zonename" : "test1.example",
- "queries.udp" : 1,
- "queries.tcp" : 2
- },
- {
- "zonename" : "test2.example",
- "queries.udp" : 3,
- "queries.tcp" : 4
- }
- ],
- "item_title": "Queries per zone",
- "item_description": "Queries per zone",
- "list_item_spec": {
- "item_name": "zones",
- "item_type": "map",
- "item_optional": False,
- "item_default": {},
- "map_item_spec": [
- {
- "item_name": "zonename",
- "item_type": "string",
- "item_optional": False,
- "item_default": "",
- "item_title": "Zonename",
- "item_description": "Zonename"
- },
- {
- "item_name": "queries.udp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries UDP per zone",
- "item_description": "A number of UDP query counts per zone"
- },
- {
- "item_name": "queries.tcp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries TCP per zone",
- "item_description": "A number of TCP query counts per zone"
- }
- ]
- }
- }]}))
- self.assertEqual(self.stats.command_showschema(owner='Auth', name='nds_queries.perzone'),
- isc.config.create_answer(
- 0, {'Auth':[{
- "item_name": "nds_queries.perzone",
- "item_type": "named_set",
- "item_optional": False,
- "item_default": {
- "test10.example" : {
- "queries.udp" : 1,
- "queries.tcp" : 2
- },
- "test20.example" : {
- "queries.udp" : 3,
- "queries.tcp" : 4
- }
- },
- "item_title": "Queries per zone",
- "item_description": "Queries per zone",
- "named_set_item_spec": {
- "item_name": "zonename",
- "item_type": "map",
- "item_optional": False,
- "item_default": {},
- "item_title": "Zonename",
- "item_description": "Zonename",
- "map_item_spec": [
- {
- "item_name": "queries.udp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries UDP per zone",
- "item_description": "A number of UDP query counts per zone"
- },
- {
- "item_name": "queries.tcp",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "Queries TCP per zone",
- "item_description": "A number of TCP query counts per zone"
- }
- ]
- }
- }]}))
-
- self.assertEqual(self.stats.command_showschema(owner='Stats', name='bar'),
- isc.config.create_answer(
- 1, "specified arguments are incorrect: owner: Stats, name: bar"))
- self.assertEqual(self.stats.command_showschema(name='bar'),
- isc.config.create_answer(
- 1, "module name is not specified"))
-
- @unittest.skipIf(sys.version_info >= (3, 3), "Unsupported in Python 3.3 or higher")
- def test_polling(self):
- stats_server = ThreadingServerManager(MyStats)
- stat = stats_server.server
- stats_server.run()
- self.assertEqual(
- send_command('show', 'Stats'),
- (0, stat.statistics_data))
- # check statistics data of 'Init'
- b10_init = self.base.b10_init.server
- self.assertEqual(
- stat.statistics_data_bymid['Init'][b10_init.cc_session.lname],
- {'boot_time': self.const_datetime})
- self.assertEqual(
- len(stat.statistics_data_bymid['Init']), 1)
- self.assertEqual(
- stat.statistics_data['Init'],
- {'boot_time': self.const_datetime})
- # check statistics data of each 'Auth' instances
- list_auth = ['', '2']
- for i in list_auth:
- auth = getattr(self.base,"auth"+i).server
- for s in stat.statistics_data_bymid['Auth'].values():
- self.assertEqual(
- s, {'queries.perzone': auth.queries_per_zone,
- 'nds_queries.perzone': auth.nds_queries_per_zone,
- 'queries.tcp': auth.queries_tcp,
- 'queries.udp': auth.queries_udp})
- n = len(stat.statistics_data_bymid['Auth'])
- self.assertEqual(n, len(list_auth))
- # check consolidation of statistics data of the auth
- # instances
- self.assertEqual(
- stat.statistics_data['Auth'],
- {'queries.perzone': [
- {'zonename':
- auth.queries_per_zone[0]['zonename'],
- 'queries.tcp':
- auth.queries_per_zone[0]['queries.tcp']*n,
- 'queries.udp':
- auth.queries_per_zone[0]['queries.udp']*n},
- {'zonename': "test2.example",
- 'queries.tcp': 4*n,
- 'queries.udp': 3*n },
- ],
- 'nds_queries.perzone': {
- 'test10.example': {
- 'queries.tcp':
- auth.nds_queries_per_zone['test10.example']['queries.tcp']*n,
- 'queries.udp':
- auth.nds_queries_per_zone['test10.example']['queries.udp']*n},
- 'test20.example': {
- 'queries.tcp':
- 4*n,
- 'queries.udp':
- 3*n},
- },
- 'queries.tcp': auth.queries_tcp*n,
- 'queries.udp': auth.queries_udp*n})
- # check statistics data of 'Stats'
- self.assertEqual(
- len(stat.statistics_data['Stats']), 5)
- self.assertTrue('boot_time' in
- stat.statistics_data['Stats'])
- self.assertTrue('last_update_time' in
- stat.statistics_data['Stats'])
- self.assertTrue('report_time' in
- stat.statistics_data['Stats'])
- self.assertTrue('timestamp' in
- stat.statistics_data['Stats'])
- self.assertEqual(
- stat.statistics_data['Stats']['lname'],
- stat.mccs._session.lname)
- stats_server.shutdown()
-
- def test_polling2(self):
- # set invalid statistics
- b10_init = self.base.b10_init.server
- b10_init.statistics_data = {'boot_time':1}
- stats_server = ThreadingServerManager(MyStats)
- stat = stats_server.server
- stats_server.run()
- self.assertEqual(
- send_command('status', 'Stats'),
- (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
- # check default statistics data of 'Init'
- self.assertEqual(
- stat.statistics_data['Init'],
- {'boot_time': self.const_default_datetime})
- stats_server.shutdown()
-
-class TestOSEnv(unittest.TestCase):
- def test_osenv(self):
- """
- test for the environ variable "B10_FROM_SOURCE"
- "B10_FROM_SOURCE" is set in Makefile
- """
- # test case having B10_FROM_SOURCE
- self.assertTrue("B10_FROM_SOURCE" in os.environ)
- self.assertEqual(stats.SPECFILE_LOCATION, \
- os.environ["B10_FROM_SOURCE"] + os.sep + \
- "src" + os.sep + "bin" + os.sep + "stats" + \
- os.sep + "stats.spec")
- # test case not having B10_FROM_SOURCE
- path = os.environ["B10_FROM_SOURCE"]
- os.environ.pop("B10_FROM_SOURCE")
- self.assertFalse("B10_FROM_SOURCE" in os.environ)
- # import stats again
- imp.reload(stats)
- # revert the changes
- os.environ["B10_FROM_SOURCE"] = path
- imp.reload(stats)
-
-if __name__ == "__main__":
- isc.log.resetUnitTestRootLogger()
- unittest.main()
diff --git a/src/bin/stats/tests/stats-httpd_test.py b/src/bin/stats/tests/stats-httpd_test.py
new file mode 100644
index 0000000..a37302d
--- /dev/null
+++ b/src/bin/stats/tests/stats-httpd_test.py
@@ -0,0 +1,1128 @@
+# Copyright (C) 2011-2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+In each of these tests we start several virtual components. They are
+not the real components, no external processes are started. They are
+just simple mock objects running each in its own thread and pretending
+to be bind10 modules. This helps testing the stats http server in a
+close to real environment.
+"""
+
+import unittest
+import os
+import imp
+import socket
+import errno
+import select
+import string
+import time
+import threading
+import http.client
+import xml.etree.ElementTree
+import random
+import urllib.parse
+import sys
+# load this module for xml validation with xsd. For this test, an
+# installation of lxml is required in advance. See http://lxml.de/.
+try:
+ from lxml import etree as lxml_etree
+except ImportError:
+ lxml_etree = None
+
+import isc
+import isc.log
+import stats_httpd
+import stats
+from test_utils import ThreadingServerManager, SignalHandler, \
+ MyStatsHttpd, CONST_BASETIME
+from isc.testutils.ccsession_mock import MockModuleCCSession
+from isc.config import RPCRecipientMissing, RPCError
+
+# This test suite uses xml.etree.ElementTree.XMLParser via
+# xml.etree.ElementTree.parse. On the platform where expat isn't
+# installed, ImportError is raised and it's failed. Check expat is
+# available before the test invocation. Skip this test if it's
+# unavailable.
+try:
+ # ImportError raised if xpat is unavailable
+ xml_parser = xml.etree.ElementTree.XMLParser()
+except ImportError:
+ xml_parser = None
+
+# set XML Namespaces for testing
+XMLNS_XSL = "http://www.w3.org/1999/XSL/Transform"
+XMLNS_XHTML = "http://www.w3.org/1999/xhtml"
+XMLNS_XSD = "http://www.w3.org/2001/XMLSchema"
+XMLNS_XSI = stats_httpd.XMLNS_XSI
+
+DUMMY_DATA = {
+ 'Init' : {
+ "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME)
+ },
+ 'Auth' : {
+ "queries.tcp": 6,
+ "queries.udp": 4,
+ "queries.perzone": [{
+ "zonename": "test1.example",
+ "queries.tcp": 10,
+ "queries.udp": 8
+ }, {
+ "zonename": "test2.example",
+ "queries.tcp": 8,
+ "queries.udp": 6
+ }],
+ "nds_queries.perzone": {
+ "test10.example": {
+ "queries.tcp": 10,
+ "queries.udp": 8
+ },
+ "test20.example": {
+ "queries.tcp": 8,
+ "queries.udp": 6
+ }
+ }
+ },
+ 'Stats' : {
+ "report_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+ "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+ "last_update_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+ "lname": "4d70d40a_c at host",
+ "timestamp": time.mktime(CONST_BASETIME)
+ }
+ }
+
+# Bad practice: this should be localized
+stats._BASETIME = CONST_BASETIME
+stats.get_timestamp = lambda: time.mktime(CONST_BASETIME)
+stats.get_datetime = lambda x=None: time.strftime("%Y-%m-%dT%H:%M:%SZ", CONST_BASETIME)
+
+def get_availaddr(address='127.0.0.1', port=8001):
+ """returns a tuple of address and port which is available to
+ listen on the platform. The first argument is a address for
+ search. The second argument is a port for search. If a set of
+ address and port is failed on the search for the availability, the
+ port number is increased and it goes on the next trial until the
+ available set of address and port is looked up. If the port number
+ reaches over 65535, it may stop the search and raise a
+ OverflowError exception."""
+ while True:
+ for addr in socket.getaddrinfo(
+ address, port, 0,
+ socket.SOCK_STREAM, socket.IPPROTO_TCP):
+ sock = socket.socket(addr[0], socket.SOCK_STREAM)
+ try:
+ sock.bind((address, port))
+ return (address, port)
+ except socket.error:
+ continue
+ finally:
+ if sock: sock.close()
+ # This address and port number are already in use.
+ # next port number is added
+ port = port + 1
+
+def is_ipv6_enabled(address='::1', port=8001):
+ """checks IPv6 enabled on the platform. address for check is '::1'
+ and port for check is random number between 8001 and
+ 65535. Retrying is 3 times even if it fails. The built-in socket
+ module provides a 'has_ipv6' parameter, but it's not used here
+ because there may be a situation where the value is True on an
+ environment where the IPv6 config is disabled."""
+ for p in random.sample(range(port, 65535), 3):
+ try:
+ sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+ sock.bind((address, p))
+ return True
+ except socket.error:
+ continue
+ finally:
+ if sock: sock.close()
+ return False
+
+class TestItemNameList(unittest.TestCase):
+
+ def test_item_name_list(self):
+ # for a one-element list
+ self.assertEqual(['a'],
+ stats_httpd.item_name_list({'a':1}, 'a'))
+ # for a dict under a dict
+ self.assertEqual(['a','a/b'],
+ stats_httpd.item_name_list({'a':{'b':1}}, 'a'))
+ self.assertEqual(['a/b'],
+ stats_httpd.item_name_list({'a':{'b':1}}, 'a/b'))
+ self.assertEqual(['a','a/b','a/b/c'],
+ stats_httpd.item_name_list({'a':{'b':{'c':1}}}, 'a'))
+ self.assertEqual(['a/b','a/b/c'],
+ stats_httpd.item_name_list({'a':{'b':{'c':1}}},
+ 'a/b'))
+ self.assertEqual(['a/b/c'],
+ stats_httpd.item_name_list({'a':{'b':{'c':1}}},
+ 'a/b/c'))
+ # for a list under a dict
+ self.assertEqual(['a[2]'],
+ stats_httpd.item_name_list({'a':[1,2,3]}, 'a[2]'))
+ self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
+ stats_httpd.item_name_list({'a':[1,2,3]}, 'a'))
+ self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
+ stats_httpd.item_name_list({'a':[1,2,3]}, ''))
+ # for a list under a dict under a dict
+ self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+ stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a'))
+ self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+ stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, ''))
+ self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+ stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a/b'))
+ # for a mixed case of the above
+ self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]', 'a/c'],
+ stats_httpd.item_name_list(
+ {'a':{'b':[1,2,3], 'c':1}}, 'a'))
+ self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+ stats_httpd.item_name_list(
+ {'a':{'b':[1,2,3], 'c':1}}, 'a/b'))
+ self.assertEqual(['a/c'],
+ stats_httpd.item_name_list(
+ {'a':{'b':[1,2,3], 'c':1}}, 'a/c'))
+ # for specifying a wrong identifier which is not found in
+ # element
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ stats_httpd.item_name_list, {'x':1}, 'a')
+ # for specifying a string in element and an empty string in
+ # identifier
+ self.assertEqual([],
+ stats_httpd.item_name_list('a', ''))
+ # for specifying empty strings in element and identifier
+ self.assertEqual([],
+ stats_httpd.item_name_list('', ''))
+ # for specifying wrong element, which is an non-empty string,
+ # and an non-empty string in identifier
+ self.assertRaises(isc.cc.data.DataTypeError,
+ stats_httpd.item_name_list, 'a', 'a')
+ # for specifying None in element and identifier
+ self.assertRaises(isc.cc.data.DataTypeError,
+ stats_httpd.item_name_list, None, None)
+ # for specifying non-dict in element
+ self.assertRaises(isc.cc.data.DataTypeError,
+ stats_httpd.item_name_list, [1,2,3], 'a')
+ self.assertRaises(isc.cc.data.DataTypeError,
+ stats_httpd.item_name_list, [1,2,3], '')
+ # for checking key names sorted which consist of element
+ num = 11
+ keys = [ 'a', 'aa', 'b' ]
+ keys.sort(reverse=True)
+ dictlist = dict([ (k, list(range(num))) for k in keys ])
+ keys.sort()
+ ans = []
+ for k in keys:
+ ans += [k] + [ '%s[%d]' % (k, i) for i in range(num) ]
+ self.assertEqual(ans,
+ stats_httpd.item_name_list(dictlist, ''))
+
+class TestHttpHandler(unittest.TestCase):
+ """Tests for HttpHandler class"""
+ def setUp(self):
+ # set the signal handler for deadlock
+ self.sig_handler = SignalHandler(self.fail)
+ DUMMY_DATA['Stats']['lname'] = 'test-lname'
+ (self.address, self.port) = get_availaddr()
+ self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd,
+ (self.address,
+ self.port))
+ self.stats_httpd = self.stats_httpd_server.server
+ self.stats_httpd_server.run()
+ self.client = http.client.HTTPConnection(self.address, self.port)
+ self.client._http_vsn_str = 'HTTP/1.0\n'
+ self.client.connect()
+
+ def tearDown(self):
+ self.client.close()
+ self.stats_httpd_server.shutdown()
+ # reset the signal handler
+ self.sig_handler.reset()
+
+ @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+ def test_do_GET(self):
+ self.assertTrue(type(self.stats_httpd.httpd) is list)
+ self.assertEqual(len(self.stats_httpd.httpd), 1)
+ self.assertEqual((self.address, self.port), self.stats_httpd.http_addrs[0])
+
+ def check_XML_URL_PATH(path=''):
+ url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
+ url_path = urllib.parse.quote(url_path)
+ self.client.putrequest('GET', url_path)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.getheader("Content-type"), "text/xml")
+ self.assertGreater(int(response.getheader("Content-Length")), 0)
+ self.assertEqual(response.status, 200)
+ xml_doctype = response.readline().decode()
+ xsl_doctype = response.readline().decode()
+ self.assertGreater(len(xml_doctype), 0)
+ self.assertGreater(len(xsl_doctype), 0)
+ root = xml.etree.ElementTree.parse(response).getroot()
+ self.assertGreater(root.tag.find('statistics'), 0)
+ schema_loc = '{%s}schemaLocation' % XMLNS_XSI
+ # check the path of XSD
+ self.assertEqual(root.attrib[schema_loc],
+ stats_httpd.XSD_NAMESPACE + ' '
+ + stats_httpd.XSD_URL_PATH)
+ # check the path of XSL
+ self.assertTrue(xsl_doctype.startswith(
+ '<?xml-stylesheet type="text/xsl" href="' +
+ stats_httpd.XSL_URL_PATH
+ + '"?>'))
+ # check whether the list of 'identifier' attributes in
+ # root is same as the list of item names in DUMMY_DATA
+ id_list = [ elm.attrib['identifier'] for elm in root ]
+ item_list = [ it for it in \
+ stats_httpd.item_name_list(DUMMY_DATA, path) \
+ if len(it.split('/')) > 1 ]
+ self.assertEqual(id_list, item_list)
+ for elem in root:
+ attr = elem.attrib
+ value = isc.cc.data.find(DUMMY_DATA, attr['identifier'])
+ # No 'value' attribute should be found in the 'item'
+ # element when datatype of the value is list or dict.
+ if type(value) is list or type(value) is dict:
+ self.assertFalse('value' in attr)
+ # The value of the 'value' attribute should be checked
+ # after casting it to string type if datatype of the
+ # value is int or float. Because attr['value'] returns
+ # string type even if its value is int or float.
+ elif type(value) is int or type(value) is float:
+ self.assertEqual(attr['value'], str(value))
+ else:
+ self.assertEqual(attr['value'], value)
+
+ # URL is '/bind10/statistics/xml'
+ check_XML_URL_PATH()
+ for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
+ check_XML_URL_PATH(path)
+
+ def check_XSD_URL_PATH():
+ url_path = stats_httpd.XSD_URL_PATH
+ url_path = urllib.parse.quote(url_path)
+ self.client.putrequest('GET', url_path)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.getheader("Content-type"), "text/xml")
+ self.assertGreater(int(response.getheader("Content-Length")), 0)
+ self.assertEqual(response.status, 200)
+ root = xml.etree.ElementTree.parse(response).getroot()
+ url_xmlschema = '{%s}' % XMLNS_XSD
+ self.assertGreater(root.tag.find('schema'), 0)
+ self.assertTrue(hasattr(root, 'attrib'))
+ self.assertTrue('targetNamespace' in root.attrib)
+ self.assertEqual(root.attrib['targetNamespace'],
+ stats_httpd.XSD_NAMESPACE)
+
+ # URL is '/bind10/statistics/xsd'
+ check_XSD_URL_PATH()
+
+ def check_XSL_URL_PATH():
+ url_path = stats_httpd.XSL_URL_PATH
+ url_path = urllib.parse.quote(url_path)
+ self.client.putrequest('GET', url_path)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.getheader("Content-type"), "text/xml")
+ self.assertGreater(int(response.getheader("Content-Length")), 0)
+ self.assertEqual(response.status, 200)
+ root = xml.etree.ElementTree.parse(response).getroot()
+ url_trans = '{%s}' % XMLNS_XSL
+ url_xhtml = '{%s}' % XMLNS_XHTML
+ self.assertEqual(root.tag, url_trans + 'stylesheet')
+
+ # URL is '/bind10/statistics/xsl'
+ check_XSL_URL_PATH()
+
+ # 302 redirect
+ self.client._http_vsn_str = 'HTTP/1.1'
+ self.client.putrequest('GET', '/')
+ self.client.putheader('Host', self.address)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 302)
+ self.assertEqual(response.getheader('Location'),
+ "http://%s:%d%s/" % (self.address, self.port, stats_httpd.XML_URL_PATH))
+
+ # 404 NotFound (random path)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', '/path/to/foo/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', '/bind10/foo')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', '/bind10/statistics/foo')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + 'Auth') # with no slash
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+
+ # 200 ok
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/#foo')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/?foo=bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+
+ # 404 NotFound (too long path)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Init/boot_time/a')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+
+ # 404 NotFound (nonexistent module name)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+
+ # 404 NotFound (nonexistent item name)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+
+ # 404 NotFound (existent module but nonexistent item name)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Auth/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Auth/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+ self.client._http_vsn_str = 'HTTP/1.0'
+ self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Auth/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+
+ def test_do_GET_failed1(self):
+ # failure case (Stats is down, so rpc_call() results in an exception)
+ # Note: this should eventually be RPCRecipientMissing.
+ self.stats_httpd._rpc_answers.append(
+ isc.cc.session.SessionTimeout('timeout'))
+
+ # request XML
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 500)
+
+ # request XSD
+ self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+
+ # request XSL
+ self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+
+ def test_do_GET_failed2(self):
+ # failure case(Stats replies an error)
+ self.stats_httpd._rpc_answers.append(
+ RPCError(1, "specified arguments are incorrect: I have an error."))
+
+ # request XML
+ self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+
+ # request XSD
+ self.stats_httpd._rpc_answers.append(
+ RPCError(1, "specified arguments are incorrect: I have an error."))
+ self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+
+ # request XSL
+ self.stats_httpd._rpc_answers.append(
+ RPCError(1, "specified arguments are incorrect: I have an error."))
+ self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+
+ def test_do_HEAD(self):
+ self.client.putrequest('HEAD', stats_httpd.XML_URL_PATH + '/')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 200)
+
+ self.client.putrequest('HEAD', '/path/to/foo/bar')
+ self.client.endheaders()
+ response = self.client.getresponse()
+ self.assertEqual(response.status, 404)
+
+ @unittest.skipUnless(lxml_etree, "skipping XML validation with XSD")
+ def test_xml_validation_with_xsd(self):
+ """Tests for XML validation with XSD. If lxml is not
+ installed, this tests would be skipped."""
+ def request_xsd():
+ url_path = stats_httpd.XSD_URL_PATH
+ url_path = urllib.parse.quote(url_path)
+ self.client.putrequest('GET', url_path)
+ self.client.endheaders()
+ xsd_doc = self.client.getresponse()
+ xsd_doc = lxml_etree.parse(xsd_doc)
+ return lxml_etree.XMLSchema(xsd_doc)
+
+ def request_xmldoc(path=''):
+ url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
+ url_path = urllib.parse.quote(url_path)
+ self.client.putrequest('GET', url_path)
+ self.client.endheaders()
+ xml_doc = self.client.getresponse()
+ return lxml_etree.parse(xml_doc)
+
+ # request XSD and XML
+ xsd = request_xsd()
+ xml_doc = request_xmldoc()
+ # do validation
+ self.assertTrue(xsd.validate(xml_doc))
+
+ # validate each paths in DUMMY_DATA
+ for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
+ # request XML
+ xml_doc = request_xmldoc(path)
+ # do validation
+ self.assertTrue(xsd.validate(xml_doc))
+
+class TestHttpServerError(unittest.TestCase):
+ """Tests for HttpServerError exception"""
+ def test_raises(self):
+ try:
+ raise stats_httpd.HttpServerError('Nothing')
+ except stats_httpd.HttpServerError as err:
+ self.assertEqual(str(err), 'Nothing')
+
+class TestHttpServer(unittest.TestCase):
+ """Tests for HttpServer class"""
+ def setUp(self):
+ # set the signal handler for deadlock
+ self.sig_handler = SignalHandler(self.fail)
+
+ def tearDown(self):
+ if hasattr(self, "stats_httpd"):
+ self.stats_httpd.stop()
+ # reset the signal handler
+ self.sig_handler.reset()
+
+ def test_httpserver(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.assertEqual(type(self.stats_httpd.httpd), list)
+ self.assertEqual(len(self.stats_httpd.httpd), 1)
+ for httpd in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(httpd, stats_httpd.HttpServer))
+
+class TestStatsHttpdError(unittest.TestCase):
+ """Tests for StatsHttpdError exception"""
+
+ def test_raises1(self):
+ try:
+ raise stats_httpd.StatsHttpdError('Nothing')
+ except stats_httpd.StatsHttpdError as err:
+ self.assertEqual(str(err), 'Nothing')
+
+ def test_raises2(self):
+ try:
+ raise stats_httpd.StatsHttpdDataError('Nothing')
+ except stats_httpd.StatsHttpdDataError as err:
+ self.assertEqual(str(err), 'Nothing')
+
+class TestStatsHttpd(unittest.TestCase):
+ """Tests for StatsHttpd class"""
+
+ def setUp(self):
+ # set the signal handler for deadlock
+ self.sig_handler = SignalHandler(self.fail)
+ # checking IPv6 enabled on this platform
+ self.ipv6_enabled = is_ipv6_enabled()
+ # instantiation of StatsHttpd indirectly calls gethostbyaddr(), which
+ # can block for an uncontrollable period, leading many undesirable
+ # results. We should rather eliminate the reliance, but until we
+ # can make such fundamental cleanup we replace it with a faked method;
+ # in our test scenario the return value doesn't matter.
+ self.__gethostbyaddr_orig = socket.gethostbyaddr
+ socket.gethostbyaddr = lambda x: ('test.example.', [], None)
+
+ # Some tests replace this library function. Keep the original for
+ # restor
+ self.__orig_select_select = select.select
+
+ def tearDown(self):
+ socket.gethostbyaddr = self.__gethostbyaddr_orig
+ if hasattr(self, "stats_httpd"):
+ self.stats_httpd.stop()
+ # reset the signal handler
+ self.sig_handler.reset()
+
+ # restore original of replaced library
+ select.select = self.__orig_select_select
+
+ def test_init(self):
+ server_address = get_availaddr()
+ self.stats_httpd = MyStatsHttpd(server_address)
+ self.assertEqual(self.stats_httpd.running, False)
+ self.assertEqual(self.stats_httpd.poll_intval, 0.5)
+ self.assertNotEqual(len(self.stats_httpd.httpd), 0)
+ self.assertIsNotNone(self.stats_httpd.mccs)
+ self.assertIsNotNone(self.stats_httpd.cc_session)
+ # The real CfgMgr would return 'version', but our test mock omits it,
+ # so the len(config) should be 1
+ self.assertEqual(len(self.stats_httpd.config), 1)
+ self.assertTrue('listen_on' in self.stats_httpd.config)
+ self.assertEqual(len(self.stats_httpd.config['listen_on']), 1)
+ self.assertTrue('address' in self.stats_httpd.config['listen_on'][0])
+ self.assertTrue('port' in self.stats_httpd.config['listen_on'][0])
+ self.assertTrue(server_address in set(self.stats_httpd.http_addrs))
+ self.assertEqual('StatsHttpd', self.stats_httpd.mccs.\
+ get_module_spec().get_module_name())
+
+ def test_init_hterr(self):
+ """Test the behavior of StatsHttpd constructor when open_httpd fails.
+
+ We specifically check the following two:
+ - close_mccs() is called (so stats-httpd tells ConfigMgr it's shutting
+ down)
+ - the constructor results in HttpServerError exception.
+
+ """
+ self.__mccs_closed = False
+ def call_checker():
+ self.__mccs_closed = True
+ class FailingStatsHttpd(MyStatsHttpd):
+ def open_httpd(self):
+ raise stats_httpd.HttpServerError
+ def close_mccs(self):
+ call_checker()
+ self.assertRaises(stats_httpd.HttpServerError, FailingStatsHttpd)
+ self.assertTrue(self.__mccs_closed)
+
+ def test_openclose_mccs(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ mccs = self.stats_httpd.mccs
+ self.assertFalse(self.stats_httpd.mccs.stopped)
+ self.assertFalse(self.stats_httpd.mccs.closed)
+ self.stats_httpd.close_mccs()
+ self.assertTrue(mccs.stopped)
+ self.assertTrue(mccs.closed)
+ self.assertIsNone(self.stats_httpd.mccs)
+ self.stats_httpd.open_mccs()
+ self.assertIsNotNone(self.stats_httpd.mccs)
+ self.stats_httpd.mccs = None
+ self.assertIsNone(self.stats_httpd.mccs)
+ self.assertIsNone(self.stats_httpd.close_mccs())
+
+ def test_mccs(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.assertIsNotNone(self.stats_httpd.mccs.get_socket())
+ self.assertTrue(
+ isinstance(self.stats_httpd.mccs.get_socket(), socket.socket))
+ self.assertIsNotNone(self.stats_httpd.cc_session)
+ statistics_spec = self.stats_httpd.get_stats_spec()
+ for mod in DUMMY_DATA:
+ self.assertTrue(mod in statistics_spec)
+ for cfg in statistics_spec[mod]:
+ self.assertTrue('item_name' in cfg)
+ self.assertTrue(cfg['item_name'] in DUMMY_DATA[mod])
+ self.assertTrue(len(statistics_spec[mod]), len(DUMMY_DATA[mod]))
+ self.stats_httpd.close_mccs()
+ self.assertIsNone(self.stats_httpd.mccs)
+
+ def test_httpd(self):
+ # dual stack (addresses is ipv4 and ipv6)
+ if self.ipv6_enabled:
+ server_addresses = (get_availaddr('::1'), get_availaddr())
+ self.stats_httpd = MyStatsHttpd(*server_addresses)
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+ self.assertTrue(ht.address_family in set([socket.AF_INET,
+ socket.AF_INET6]))
+ self.assertTrue(isinstance(ht.socket, socket.socket))
+ ht.socket.close() # to silence warning about resource leak
+ self.stats_httpd.close_mccs() # ditto
+
+ # dual stack (address is ipv6)
+ if self.ipv6_enabled:
+ server_addresses = get_availaddr('::1')
+ self.stats_httpd = MyStatsHttpd(server_addresses)
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+ self.assertEqual(ht.address_family, socket.AF_INET6)
+ self.assertTrue(isinstance(ht.socket, socket.socket))
+ ht.socket.close()
+ self.stats_httpd.close_mccs() # ditto
+
+ # dual/single stack (address is ipv4)
+ server_addresses = get_availaddr()
+ self.stats_httpd = MyStatsHttpd(server_addresses)
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+ self.assertEqual(ht.address_family, socket.AF_INET)
+ self.assertTrue(isinstance(ht.socket, socket.socket))
+ ht.socket.close()
+ self.stats_httpd.close_mccs()
+
+ def test_httpd_anyIPv4(self):
+ # any address (IPv4)
+ server_addresses = get_availaddr(address='0.0.0.0')
+ self.stats_httpd = MyStatsHttpd(server_addresses)
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+ self.assertEqual(ht.address_family,socket.AF_INET)
+ self.assertTrue(isinstance(ht.socket, socket.socket))
+
+ def test_httpd_anyIPv6(self):
+ # any address (IPv6)
+ if self.ipv6_enabled:
+ server_addresses = get_availaddr(address='::')
+ self.stats_httpd = MyStatsHttpd(server_addresses)
+ for ht in self.stats_httpd.httpd:
+ self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+ self.assertEqual(ht.address_family,socket.AF_INET6)
+ self.assertTrue(isinstance(ht.socket, socket.socket))
+
+ def test_httpd_failed(self):
+ # existent hostname
+ self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+ get_availaddr(address='localhost'))
+
+ # nonexistent hostname
+ self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+ ('my.host.domain', 8000))
+
+ # over flow of port number
+ self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+ ('127.0.0.1', 80000))
+
+ # negative
+ self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+ ('127.0.0.1', -8000))
+
+ # alphabet
+ self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+ ('127.0.0.1', 'ABCDE'))
+
+ # Address already in use
+ server_addresses = get_availaddr()
+ server = MyStatsHttpd(server_addresses)
+ self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+ server_addresses)
+
+ def __faked_select(self, ex=None):
+ """A helper subroutine for tests using faked select.select.
+
+ See test_running() for basic features. If ex is not None,
+ it's assumed to be an exception object and will be raised on the
+ first call.
+
+ """
+ self.assertTrue(self.stats_httpd.running)
+ self.__call_count += 1
+ if ex is not None and self.__call_count == 1:
+ raise ex
+ if self.__call_count == 2:
+ self.stats_httpd.running = False
+ assert self.__call_count <= 2 # safety net to avoid infinite loop
+ return ([], [], [])
+
+ def test_running(self):
+ # Previous version of this test checks the result of "status" and
+ # "shutdown" commands; however, they are more explicitly tested
+ # in specific tests. In this test we only have to check:
+ # - start() will set 'running' to True
+ # - as long as 'running' is True, it keeps calling select.select
+ # - when running becomes False, it exists from the loop and calls
+ # stop()
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.assertFalse(self.stats_httpd.running)
+
+ # In this test we'll call select.select() 2 times: on the first call
+ # stats_httpd.running should be True; on the second call the faked
+ # select() will set it to False.
+ self.__call_count = 0
+ select.select = lambda r, w, x, t: self.__faked_select()
+ self.stats_httpd.start()
+ self.assertFalse(self.stats_httpd.running)
+ self.assertIsNone(self.stats_httpd.mccs) # stop() clears .mccs
+
+ def test_running_fail(self):
+ # A failure case of start(): we close the (real but dummy) socket for
+ # the CC session. This breaks the select-loop due to exception
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.stats_httpd.mccs.get_socket().close()
+ self.assertRaises(ValueError, self.stats_httpd.start)
+
+ def test_failure_with_a_select_error (self):
+ """checks select.error is raised if the exception except
+ errno.EINTR is raised while it's selecting"""
+ def raise_select_except(*args):
+ raise select.error('dummy error')
+ select.select = raise_select_except
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.assertRaises(select.error, self.stats_httpd.start)
+
+ def test_nofailure_with_errno_EINTR(self):
+ """checks no exception is raised if errno.EINTR is raised
+ while it's selecting"""
+ self.__call_count = 0
+ select.select = lambda r, w, x, t: self.__faked_select(
+ select.error(errno.EINTR))
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.stats_httpd.start() # shouldn't leak the exception
+ self.assertFalse(self.stats_httpd.running)
+ self.assertIsNone(self.stats_httpd.mccs)
+
+ def test_open_template(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ # successful conditions
+ tmpl = self.stats_httpd.open_template(
+ stats_httpd.XML_TEMPLATE_LOCATION)
+ self.assertTrue(isinstance(tmpl, string.Template))
+ opts = dict(
+ xml_string="<dummy></dummy>",
+ xsl_url_path="/path/to/")
+ lines = tmpl.substitute(opts)
+ for n in opts:
+ self.assertGreater(lines.find(opts[n]), 0)
+ tmpl = self.stats_httpd.open_template(
+ stats_httpd.XSD_TEMPLATE_LOCATION)
+ self.assertTrue(isinstance(tmpl, string.Template))
+ opts = dict(xsd_namespace="http://host/path/to/")
+ lines = tmpl.substitute(opts)
+ for n in opts:
+ self.assertGreater(lines.find(opts[n]), 0)
+ tmpl = self.stats_httpd.open_template(
+ stats_httpd.XSL_TEMPLATE_LOCATION)
+ self.assertTrue(isinstance(tmpl, string.Template))
+ opts = dict(xsd_namespace="http://host/path/to/")
+ lines = tmpl.substitute(opts)
+ for n in opts:
+ self.assertGreater(lines.find(opts[n]), 0)
+ # unsuccessful condition
+ self.assertRaises(
+ stats_httpd.StatsHttpdDataError,
+ self.stats_httpd.open_template, '/path/to/foo/bar')
+
+ def test_commands(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.assertEqual(self.stats_httpd.command_handler("status", None),
+ isc.config.ccsession.create_answer(
+ 0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
+ self.stats_httpd.running = True
+ self.assertEqual(self.stats_httpd.command_handler("shutdown", None),
+ isc.config.ccsession.create_answer(0))
+ self.assertFalse(self.stats_httpd.running)
+ self.assertEqual(
+ self.stats_httpd.command_handler("__UNKNOWN_COMMAND__", None),
+ isc.config.ccsession.create_answer(
+ 1, "Unknown command: __UNKNOWN_COMMAND__"))
+
+ def test_config(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ self.assertEqual(
+ self.stats_httpd.config_handler(dict(_UNKNOWN_KEY_=None)),
+ isc.config.ccsession.create_answer(
+ 1, "unknown item _UNKNOWN_KEY_"))
+
+ addresses = get_availaddr()
+ self.assertEqual(
+ self.stats_httpd.config_handler(
+ dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
+ isc.config.ccsession.create_answer(0))
+ self.assertTrue("listen_on" in self.stats_httpd.config)
+ for addr in self.stats_httpd.config["listen_on"]:
+ self.assertTrue("address" in addr)
+ self.assertTrue("port" in addr)
+ self.assertTrue(addr["address"] == addresses[0])
+ self.assertTrue(addr["port"] == addresses[1])
+
+ if self.ipv6_enabled:
+ addresses = get_availaddr("::1")
+ self.assertEqual(
+ self.stats_httpd.config_handler(
+ dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
+ isc.config.ccsession.create_answer(0))
+ self.assertTrue("listen_on" in self.stats_httpd.config)
+ for addr in self.stats_httpd.config["listen_on"]:
+ self.assertTrue("address" in addr)
+ self.assertTrue("port" in addr)
+ self.assertTrue(addr["address"] == addresses[0])
+ self.assertTrue(addr["port"] == addresses[1])
+
+ addresses = get_availaddr()
+ self.assertEqual(
+ self.stats_httpd.config_handler(
+ dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
+ isc.config.ccsession.create_answer(0))
+ self.assertTrue("listen_on" in self.stats_httpd.config)
+ for addr in self.stats_httpd.config["listen_on"]:
+ self.assertTrue("address" in addr)
+ self.assertTrue("port" in addr)
+ self.assertTrue(addr["address"] == addresses[0])
+ self.assertTrue(addr["port"] == addresses[1])
+ (ret, arg) = isc.config.ccsession.parse_answer(
+ self.stats_httpd.config_handler(
+ dict(listen_on=[dict(address="1.2.3.4",port=543210)]))
+ )
+ self.assertEqual(ret, 1)
+
+ @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+ def test_xml_handler(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ module_name = 'Dummy'
+ stats_spec = \
+ { module_name :
+ [{
+ "item_name": "foo",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "bar",
+ "item_description": "foo is bar",
+ "item_title": "Foo"
+ },
+ {
+ "item_name": "foo2",
+ "item_type": "list",
+ "item_optional": False,
+ "item_default": [
+ {
+ "zonename" : "test1",
+ "queries.udp" : 1,
+ "queries.tcp" : 2
+ },
+ {
+ "zonename" : "test2",
+ "queries.udp" : 3,
+ "queries.tcp" : 4
+ }
+ ],
+ "item_title": "Foo bar",
+ "item_description": "Foo bar",
+ "list_item_spec": {
+ "item_name": "foo2-1",
+ "item_type": "map",
+ "item_optional": False,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "foo2-1-1",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "",
+ "item_title": "Foo2 1 1",
+ "item_description": "Foo bar"
+ },
+ {
+ "item_name": "foo2-1-2",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Foo2 1 2",
+ "item_description": "Foo bar"
+ },
+ {
+ "item_name": "foo2-1-3",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Foo2 1 3",
+ "item_description": "Foo bar"
+ }
+ ]
+ }
+ }]
+ }
+ stats_data = \
+ { module_name : { 'foo':'bar',
+ 'foo2': [
+ {
+ "foo2-1-1" : "bar1",
+ "foo2-1-2" : 10,
+ "foo2-1-3" : 9
+ },
+ {
+ "foo2-1-1" : "bar2",
+ "foo2-1-2" : 8,
+ "foo2-1-3" : 7
+ }
+ ] } }
+ self.stats_httpd.get_stats_spec = lambda x,y: stats_spec
+ self.stats_httpd.get_stats_data = lambda x,y: stats_data
+ xml_string = self.stats_httpd.xml_handler()
+ stats_xml = xml.etree.ElementTree.fromstring(xml_string)
+ schema_loc = '{%s}schemaLocation' % XMLNS_XSI
+ self.assertEqual(stats_xml.attrib[schema_loc],
+ stats_httpd.XML_ROOT_ATTRIB['xsi:schemaLocation'])
+ stats_data = stats_data[module_name]
+ stats_spec = stats_spec[module_name]
+ names = stats_httpd.item_name_list(stats_data, '')
+ for i in range(0, len(names)):
+ self.assertEqual('%s/%s' % (module_name, names[i]), stats_xml[i].attrib['identifier'])
+ value = isc.cc.data.find(stats_data, names[i])
+ if type(value) is int:
+ value = str(value)
+ if type(value) is dict or type(value) is list:
+ self.assertFalse('value' in stats_xml[i].attrib)
+ else:
+ self.assertEqual(value, stats_xml[i].attrib['value'])
+ self.assertEqual(module_name, stats_xml[i].attrib['owner'])
+ self.assertEqual(urllib.parse.quote('%s/%s/%s' % (stats_httpd.XML_URL_PATH,
+ module_name, names[i])),
+ stats_xml[i].attrib['uri'])
+ spec = isc.config.find_spec_part(stats_spec, names[i])
+ self.assertEqual(spec['item_name'], stats_xml[i].attrib['name'])
+ self.assertEqual(spec['item_type'], stats_xml[i].attrib['type'])
+ self.assertEqual(spec['item_description'], stats_xml[i].attrib['description'])
+ self.assertEqual(spec['item_title'], stats_xml[i].attrib['title'])
+ self.assertEqual(str(spec['item_optional']).lower(), stats_xml[i].attrib['optional'])
+ default = spec['item_default']
+ if type(default) is int:
+ default = str(default)
+ if type(default) is dict or type(default) is list:
+ self.assertFalse('default' in stats_xml[i].attrib)
+ else:
+ self.assertEqual(default, stats_xml[i].attrib['default'])
+ self.assertFalse('item_format' in spec)
+ self.assertFalse('format' in stats_xml[i].attrib)
+
+ @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+ def test_xsd_handler(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ xsd_string = self.stats_httpd.xsd_handler()
+ stats_xsd = xml.etree.ElementTree.fromstring(xsd_string)
+ ns = '{%s}' % XMLNS_XSD
+ stats_xsd = stats_xsd[1].find('%scomplexType/%ssequence/%selement' % ((ns,)*3))
+ self.assertEqual('item', stats_xsd.attrib['name'])
+ stats_xsd = stats_xsd.find('%scomplexType' % ns)
+ type_types = ('boolean', 'integer', 'real', 'string', 'map', \
+ 'list', 'named_set', 'any')
+ attribs = [('identifier', 'string', 'required'),
+ ('value', 'string', 'optional'),
+ ('owner', 'string', 'required'),
+ ('uri', 'anyURI', 'required'),
+ ('name', 'string', 'required'),
+ ('type', type_types, 'required'),
+ ('description', 'string', 'optional'),
+ ('title', 'string', 'optional'),
+ ('optional', 'boolean', 'optional'),
+ ('default', 'string', 'optional'),
+ ('format', 'string', 'optional')]
+ for i in range(0, len(attribs)):
+ self.assertEqual(attribs[i][0], stats_xsd[i].attrib['name'])
+ if attribs[i][0] == 'type':
+ stats_xsd_types = \
+ stats_xsd[i].find('%ssimpleType/%srestriction' % \
+ ((ns,)*2))
+ for j in range(0, len(attribs[i][1])):
+ self.assertEqual(attribs[i][1][j], \
+ stats_xsd_types[j].attrib['value'])
+ else:
+ self.assertEqual(attribs[i][1], stats_xsd[i].attrib['type'])
+ self.assertEqual(attribs[i][2], stats_xsd[i].attrib['use'])
+
+ @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+ def test_xsl_handler(self):
+ self.stats_httpd = MyStatsHttpd(get_availaddr())
+ xsl_string = self.stats_httpd.xsl_handler()
+ stats_xsl = xml.etree.ElementTree.fromstring(xsl_string)
+ nst = '{%s}' % XMLNS_XSL
+ nsx = '{%s}' % XMLNS_XHTML
+ self.assertEqual("bind10:statistics", stats_xsl[2].attrib['match'])
+ stats_xsl = stats_xsl[2].find('%stable' % nsx)
+ self.assertEqual('item', stats_xsl[1].attrib['select'])
+ stats_xsl = stats_xsl[1].find('%str' % nsx)
+ self.assertEqual('@uri', stats_xsl[0].find(
+ '%selement/%sattribute/%svalue-of' % ((nst,)*3)).attrib['select'])
+ self.assertEqual('@identifier', stats_xsl[0].find(
+ '%selement/%svalue-of' % ((nst,)*2)).attrib['select'])
+ self.assertEqual('@value', stats_xsl[1].find('%sif' % nst).attrib['test'])
+ self.assertEqual('@value', stats_xsl[1].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
+ self.assertEqual('@description', stats_xsl[2].find('%sif' % nst).attrib['test'])
+ self.assertEqual('@description', stats_xsl[2].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
+
+class Z_TestOSEnv(unittest.TestCase):
+ def test_for_without_B10_FROM_SOURCE(self):
+ # Note: this test is sensitive due to its substantial side effect of
+ # reloading. For exmaple, it affects tests that tweak module
+ # attributes (such as test_init_hterr). It also breaks logging
+ # setting for unit tests. To minimize these effects, we use
+ # workaround: make it very likely to run at the end of the tests
+ # by naming the test class "Z_".
+
+ # just lets it go through the code without B10_FROM_SOURCE env
+ # variable
+ if "B10_FROM_SOURCE" in os.environ:
+ tmppath = os.environ["B10_FROM_SOURCE"]
+ os.environ.pop("B10_FROM_SOURCE")
+ imp.reload(stats_httpd)
+ os.environ["B10_FROM_SOURCE"] = tmppath
+ imp.reload(stats_httpd)
+
+if __name__ == "__main__":
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/bin/stats/tests/stats_test.py b/src/bin/stats/tests/stats_test.py
new file mode 100644
index 0000000..14e49f9
--- /dev/null
+++ b/src/bin/stats/tests/stats_test.py
@@ -0,0 +1,1668 @@
+# Copyright (C) 2010-2013 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.
+
+"""
+In each of these tests we start several virtual components. They are
+not the real components, no external processes are started. They are
+just simple mock objects running each in its own thread and pretending
+to be bind10 modules. This helps testing the stats module in a close
+to real environment.
+"""
+
+import unittest
+import os
+import io
+import time
+import imp
+import sys
+
+import stats
+import isc.log
+from test_utils import MyStats
+from isc.config.ccsession import create_answer
+
+class TestUtilties(unittest.TestCase):
+ items = [
+ { 'item_name': 'test_int1', 'item_type': 'integer', 'item_default': 12345 },
+ { 'item_name': 'test_real1', 'item_type': 'real', 'item_default': 12345.6789 },
+ { 'item_name': 'test_bool1', 'item_type': 'boolean', 'item_default': True },
+ { 'item_name': 'test_str1', 'item_type': 'string', 'item_default': 'ABCD' },
+ { 'item_name': 'test_list1', 'item_type': 'list', 'item_default': [1,2,3],
+ 'list_item_spec' : { 'item_name': 'number', 'item_type': 'integer' } },
+ { 'item_name': 'test_map1', 'item_type': 'map', 'item_default': {'a':1,'b':2,'c':3},
+ 'map_item_spec' : [ { 'item_name': 'a', 'item_type': 'integer'},
+ { 'item_name': 'b', 'item_type': 'integer'},
+ { 'item_name': 'c', 'item_type': 'integer'} ] },
+ { 'item_name': 'test_int2', 'item_type': 'integer' },
+ { 'item_name': 'test_real2', 'item_type': 'real' },
+ { 'item_name': 'test_bool2', 'item_type': 'boolean' },
+ { 'item_name': 'test_str2', 'item_type': 'string' },
+ { 'item_name': 'test_list2', 'item_type': 'list',
+ 'list_item_spec' : { 'item_name': 'number', 'item_type': 'integer' } },
+ { 'item_name': 'test_map2', 'item_type': 'map',
+ 'map_item_spec' : [ { 'item_name': 'A', 'item_type': 'integer'},
+ { 'item_name': 'B', 'item_type': 'integer'},
+ { 'item_name': 'C', 'item_type': 'integer'} ] },
+ { 'item_name': 'test_none', 'item_type': 'none' },
+ { 'item_name': 'test_list3', 'item_type': 'list', 'item_default': ["one","two","three"],
+ 'list_item_spec' : { 'item_name': 'number', 'item_type': 'string' } },
+ { 'item_name': 'test_map3', 'item_type': 'map', 'item_default': {'a':'one','b':'two','c':'three'},
+ 'map_item_spec' : [ { 'item_name': 'a', 'item_type': 'string'},
+ { 'item_name': 'b', 'item_type': 'string'},
+ { 'item_name': 'c', 'item_type': 'string'} ] },
+ {
+ 'item_name': 'test_named_set',
+ 'item_type': 'named_set',
+ 'item_default': { },
+ 'named_set_item_spec': {
+ 'item_name': 'name',
+ 'item_type': 'map',
+ 'item_default': { },
+ 'map_item_spec': [
+ {
+ 'item_name': 'number1',
+ 'item_type': 'integer'
+ },
+ {
+ 'item_name': 'number2',
+ 'item_type': 'integer'
+ }
+ ]
+ }
+ }
+ ]
+
+ def setUp(self):
+ self.const_timestamp = 1308730448.965706
+ self.const_timetuple = (2011, 6, 22, 8, 14, 8, 2, 173, 0)
+ self.const_datetime = '2011-06-22T08:14:08Z'
+ self.__orig_time = stats.time
+ self.__orig_gmtime = stats.gmtime
+ stats.time = lambda : self.const_timestamp
+ stats.gmtime = lambda : self.const_timetuple
+
+ def tearDown(self):
+ stats.time = self.__orig_time
+ stats.gmtime = self.__orig_gmtime
+
+ def test_get_spec_defaults(self):
+ self.assertEqual(
+ stats.get_spec_defaults(self.items), {
+ 'test_int1' : 12345 ,
+ 'test_real1' : 12345.6789 ,
+ 'test_bool1' : True ,
+ 'test_str1' : 'ABCD' ,
+ 'test_list1' : [1,2,3] ,
+ 'test_map1' : {'a':1,'b':2,'c':3},
+ 'test_int2' : 0 ,
+ 'test_real2' : 0.0,
+ 'test_bool2' : False,
+ 'test_str2' : "",
+ 'test_list2' : [0],
+ 'test_map2' : { 'A' : 0, 'B' : 0, 'C' : 0 },
+ 'test_none' : None,
+ 'test_list3' : [ "one", "two", "three" ],
+ 'test_map3' : { 'a' : 'one', 'b' : 'two', 'c' : 'three' },
+ 'test_named_set' : {} })
+ self.assertEqual(stats.get_spec_defaults(None), {})
+ self.assertRaises(KeyError, stats.get_spec_defaults, [{'item_name':'Foo'}])
+
+ def test_get_timestamp(self):
+ self.assertEqual(stats.get_timestamp(), self.const_timestamp)
+
+ def test_get_datetime(self):
+ self.assertEqual(stats.get_datetime(), self.const_datetime)
+ self.assertNotEqual(stats.get_datetime(
+ (2011, 6, 22, 8, 23, 40, 2, 173, 0)), self.const_datetime)
+
+ def test__accum(self):
+ self.assertEqual(stats._accum(None, None), None)
+ self.assertEqual(stats._accum(None, "b"), "b")
+ self.assertEqual(stats._accum("a", None), "a")
+ self.assertEqual(stats._accum(1, 2), 3)
+ self.assertEqual(stats._accum(0.5, 0.3), 0.8)
+ self.assertEqual(stats._accum(1, 0.3), 1.3)
+ self.assertEqual(stats._accum(0.5, 2), 2.5)
+ self.assertEqual(stats._accum('aa','bb'), 'bb')
+ self.assertEqual(stats._accum('1970-01-01T09:00:00Z','2012-08-09T09:33:31Z'),
+ '2012-08-09T09:33:31Z')
+ self.assertEqual(stats._accum(
+ [1, 2, 3], [4, 5]), [5, 7, 3])
+ self.assertEqual(stats._accum(
+ [4, 5], [1, 2, 3]), [5, 7, 3])
+ self.assertEqual(stats._accum(
+ [1, 2, 3], [None, 5, 6]), [1, 7, 9])
+ self.assertEqual(stats._accum(
+ [None, 5, 6], [1, 2, 3]), [1, 7, 9])
+ self.assertEqual(stats._accum(
+ [1, 2, 3], [None, None, None, None]), [1,2,3,None])
+ self.assertEqual(stats._accum(
+ [[1,2],3],[[],5,6]), [[1,2],8,6])
+ self.assertEqual(stats._accum(
+ {'one': 1, 'two': 2, 'three': 3},
+ {'one': 4, 'two': 5}),
+ {'one': 5, 'two': 7, 'three': 3})
+ self.assertEqual(stats._accum(
+ {'one': 1, 'two': 2, 'three': 3},
+ {'four': 4, 'five': 5}),
+ {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
+ self.assertEqual(stats._accum(
+ {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
+ {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
+ {'one':[3,2], 'two':[7,5,5], 'three':[None,3,None], 'four': 'FOUR'})
+ self.assertEqual(stats._accum(
+ [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
+ [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
+ [ {'one': 1, 'two': 2, 'three': 3}, {'four': 5, 'five': 7, 'six': 9} ])
+
+ def test_merge_oldnre(self):
+ self.assertEqual(stats.merge_oldnew(1, 2), 2)
+ self.assertEqual(stats.merge_oldnew(0.5, 0.3), 0.3)
+ self.assertEqual(stats.merge_oldnew('aa','bb'), 'bb')
+ self.assertEqual(stats.merge_oldnew(
+ [1, 2, 3], [4, 5]), [4, 5, 3])
+ self.assertEqual(stats.merge_oldnew(
+ [4, 5], [1, 2, 3]), [1, 2, 3])
+ self.assertEqual(stats.merge_oldnew(
+ [1, 2, 3], [None, 5, 6]), [None, 5, 6])
+ self.assertEqual(stats.merge_oldnew(
+ [None, 5, 6], [1, 2, 3]), [1, 2, 3])
+ self.assertEqual(stats.merge_oldnew(
+ [1, 2, 3], [None, None, None, None]), [None, None, None, None])
+ self.assertEqual(stats.merge_oldnew(
+ [[1,2],3],[[],5,6]), [[1,2],5,6])
+ self.assertEqual(stats.merge_oldnew(
+ {'one': 1, 'two': 2, 'three': 3},
+ {'one': 4, 'two': 5}),
+ {'one': 4, 'two': 5, 'three': 3})
+ self.assertEqual(stats.merge_oldnew(
+ {'one': 1, 'two': 2, 'three': 3},
+ {'four': 4, 'five': 5}),
+ {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
+ self.assertEqual(stats.merge_oldnew(
+ {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
+ {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
+ {'one':[2,2], 'two':[4,5,5], 'three':[None,None,None], 'four': 'FOUR'})
+ self.assertEqual(stats.merge_oldnew(
+ [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
+ [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
+ [ {'one': 1, 'two': 2, 'three': 3}, {'four': 1, 'five': 2, 'six': 3} ])
+
+class TestCallback(unittest.TestCase):
+ def setUp(self):
+ self.dummy_func = lambda *x, **y : (x, y)
+ self.dummy_args = (1,2,3)
+ self.dummy_kwargs = {'a':1,'b':2,'c':3}
+ self.cback1 = stats.Callback(
+ command=self.dummy_func,
+ args=self.dummy_args,
+ kwargs=self.dummy_kwargs
+ )
+ self.cback2 = stats.Callback(
+ args=self.dummy_args,
+ kwargs=self.dummy_kwargs
+ )
+ self.cback3 = stats.Callback(
+ command=self.dummy_func,
+ kwargs=self.dummy_kwargs
+ )
+ self.cback4 = stats.Callback(
+ command=self.dummy_func,
+ args=self.dummy_args
+ )
+
+ def test_init(self):
+ self.assertEqual((self.cback1.command, self.cback1.args, self.cback1.kwargs),
+ (self.dummy_func, self.dummy_args, self.dummy_kwargs))
+ self.assertEqual((self.cback2.command, self.cback2.args, self.cback2.kwargs),
+ (None, self.dummy_args, self.dummy_kwargs))
+ self.assertEqual((self.cback3.command, self.cback3.args, self.cback3.kwargs),
+ (self.dummy_func, (), self.dummy_kwargs))
+ self.assertEqual((self.cback4.command, self.cback4.args, self.cback4.kwargs),
+ (self.dummy_func, self.dummy_args, {}))
+
+ def test_call(self):
+ self.assertEqual(self.cback1(), (self.dummy_args, self.dummy_kwargs))
+ self.assertEqual(self.cback1(100, 200), ((100, 200), self.dummy_kwargs))
+ self.assertEqual(self.cback1(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
+ self.assertEqual(self.cback2(), None)
+ self.assertEqual(self.cback3(), ((), self.dummy_kwargs))
+ self.assertEqual(self.cback3(100, 200), ((100, 200), self.dummy_kwargs))
+ self.assertEqual(self.cback3(a=100, b=200), ((), {'a':100, 'b':200}))
+ self.assertEqual(self.cback4(), (self.dummy_args, {}))
+ self.assertEqual(self.cback4(100, 200), ((100, 200), {}))
+ self.assertEqual(self.cback4(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
+
+class TestStats(unittest.TestCase):
+ def setUp(self):
+ # set the signal handler for deadlock
+ self.const_timestamp = 1308730448.965706
+ self.const_datetime = '2011-06-22T08:14:08Z'
+ self.const_default_datetime = '1970-01-01T00:00:00Z'
+ # Record original module-defined functions in case we replace them
+ self.__orig_timestamp = stats.get_timestamp
+ self.__orig_get_datetime = stats.get_datetime
+
+ def tearDown(self):
+ # restore the stored original function in case we replaced them
+ stats.get_timestamp = self.__orig_timestamp
+ stats.get_datetime = self.__orig_get_datetime
+
+ def test_init(self):
+ self.stats = MyStats()
+ self.assertEqual(self.stats.module_name, 'Stats')
+ self.assertFalse(self.stats.running)
+ self.assertTrue('command_show' in self.stats.callbacks)
+ self.assertTrue('command_status' in self.stats.callbacks)
+ self.assertTrue('command_shutdown' in self.stats.callbacks)
+ self.assertTrue('command_show' in self.stats.callbacks)
+ self.assertTrue('command_showschema' in self.stats.callbacks)
+ self.assertEqual(self.stats.config['poll-interval'], 60)
+
+ def test_init_undefcmd(self):
+ spec_str = """\
+{
+ "module_spec": {
+ "module_name": "Stats",
+ "module_description": "Stats daemon",
+ "config_data": [],
+ "commands": [
+ {
+ "command_name": "_undef_command_",
+ "command_description": "a undefined command in stats",
+ "command_args": []
+ }
+ ],
+ "statistics": []
+ }
+}
+"""
+ orig_spec_location = stats.SPECFILE_LOCATION
+ stats.SPECFILE_LOCATION = io.StringIO(spec_str)
+ self.assertRaises(stats.StatsError, MyStats)
+ stats.SPECFILE_LOCATION = orig_spec_location
+
+ def __send_command(self, stats, command_name, params=None):
+ '''Emulate a command arriving to stats by directly calling callback'''
+ return isc.config.ccsession.parse_answer(
+ stats.command_handler(command_name, params))
+
+ def test_check_command(self):
+ """Test _check_command sets proper timeout values and sets proper values
+ returned from group_recvmsg"""
+ stat = MyStats()
+ set_timeouts = []
+ orig_timeout = 1
+ stat.cc_session.get_timeout = lambda: orig_timeout
+ stat.cc_session.set_timeout = lambda x: set_timeouts.append(x)
+ msg = create_answer(0, 'msg')
+ env = {'from': 'frominit'}
+ stat._answers = [(msg, env)]
+ stat._check_command()
+ self.assertListEqual([500, orig_timeout], set_timeouts)
+ self.assertEqual(msg, stat.mccs._msg)
+ self.assertEqual(env, stat.mccs._env)
+
+ def test_check_command_sessiontimeout(self):
+ """Test _check_command doesn't perform but sets proper timeout values in
+ case that a SesstionTimeout exception is caught while doing
+ group_recvmsg()"""
+ stat = MyStats()
+ set_timeouts = []
+ orig_timeout = 1
+ stat.cc_session.get_timeout = lambda: orig_timeout
+ stat.cc_session.set_timeout = lambda x: set_timeouts.append(x)
+ msg = create_answer(0, 'msg')
+ env = {'from': 'frominit'}
+ stat._answers = [(msg, env)]
+ # SessionTimeout is raised while doing group_recvmsg()
+ ex = isc.cc.session.SessionTimeout
+ def __raise(*x): raise ex(*x)
+ stat.cc_session.group_recvmsg = lambda x: __raise()
+ stat._check_command()
+ self.assertListEqual([500, orig_timeout], set_timeouts)
+ self.assertEqual(None, stat.mccs._msg)
+ self.assertEqual(None, stat.mccs._env)
+
+ def test_start(self):
+ # Define a separate exception class so we can be sure that's actually
+ # the one raised in __check_start() below
+ class CheckException(Exception):
+ pass
+
+ def __check_start(tested_stats):
+ self.assertTrue(tested_stats.running)
+ raise CheckException # terminate the loop
+
+ # start without err
+ self.stats = MyStats()
+ self.assertFalse(self.stats.running)
+ self.stats._check_command = lambda: __check_start(self.stats)
+ # We are going to confirm start() will set running to True, avoiding
+ # to fall into a loop with the exception trick.
+ self.assertRaises(CheckException, self.stats.start)
+ self.assertEqual(self.__send_command(self.stats, "status"),
+ (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
+ self.assertTrue(self.stats.mccs.stopped)
+
+ def test_start_set_next_polltime(self):
+ """Test start() properly sets the time next_polltime to do_poll() next
+ time"""
+ orig_get_timestamp = stats.get_timestamp
+ stats.get_timestamp = lambda : self.const_timestamp
+ stat = MyStats()
+ # manupilate next_polltime to go it through the inner if-condition
+ stat.next_polltime = self.const_timestamp - stat.get_interval() - 1
+ # stop an infinity loop at once
+ def __stop_running(): stat.running = False
+ stat.do_polling = __stop_running
+ # do nothing in _check_command()
+ stat._check_command = lambda: None
+ stat.start()
+ # check stat.next_polltime reassigned
+ self.assertEqual(self.const_timestamp, stat.next_polltime)
+ stats.get_timestamp = orig_get_timestamp
+
+ def test_shutdown(self):
+ def __check_shutdown(tested_stats):
+ self.assertTrue(tested_stats.running)
+ self.assertEqual(self.__send_command(tested_stats, "shutdown"),
+ (0, None))
+ self.assertFalse(tested_stats.running)
+ # override get_interval() so it won't go poll statistics
+ tested_stats.get_interval = lambda : 0
+
+ self.stats = MyStats()
+ self.stats._check_command = lambda: __check_shutdown(self.stats)
+ self.stats.start()
+ self.assertTrue(self.stats.mccs.stopped)
+
+ def test_handlers(self):
+ """Test command_handler"""
+
+ __stats = MyStats()
+
+ # 'show' command. We're going to check the expected methods are
+ # called in the expected order, and check the resulting response.
+ # Details of each method are tested separately.
+ call_log = []
+ def __steal_method(fn_name, *arg):
+ call_log.append((fn_name, arg))
+ if fn_name == 'update_stat':
+ return False # "no error"
+ if fn_name == 'showschema':
+ return isc.config.create_answer(0, 'no error')
+
+ # Fake some methods and attributes for inspection
+ __stats.do_polling = lambda: __steal_method('polling')
+ __stats.update_statistics_data = \
+ lambda x, y, z: __steal_method('update_stat', x, y, z)
+ __stats.update_modules = lambda: __steal_method('update_module')
+ __stats.mccs.lname = 'test lname'
+ __stats.statistics_data = {'Init': {'boot_time': self.const_datetime}}
+
+ # skip initial polling
+ stats.get_timestamp = lambda: 0
+ __stats._lasttime_poll = 0
+
+ stats.get_datetime = lambda: 42 # make the result predictable
+
+ # now send the command
+ self.assertEqual(
+ self.__send_command(
+ __stats, 'show',
+ params={ 'owner' : 'Init', 'name' : 'boot_time' }),
+ (0, {'Init': {'boot_time': self.const_datetime}}))
+ # Check if expected methods are called
+ self.assertEqual([('update_stat',
+ ('Stats', 'test lname',
+ {'timestamp': 0,
+ 'report_time': 42})),
+ ('update_module', ())], call_log)
+
+ # Then update faked timestamp so the initial polling will happen, and
+ # confirm that.
+ call_log = []
+ stats.get_timestamp = lambda: 10
+ self.assertEqual(
+ self.__send_command(
+ __stats, 'show',
+ params={ 'owner' : 'Init', 'name' : 'boot_time' }),
+ (0, {'Init': {'boot_time': self.const_datetime}}))
+ self.assertEqual([('polling', ()),
+ ('update_stat',
+ ('Stats', 'test lname',
+ {'timestamp': 10,
+ 'report_time': 42})),
+ ('update_module', ())], call_log)
+
+ # 'status' command. We can confirm the behavior without any fake
+ self.assertEqual(
+ self.__send_command(__stats, 'status'),
+ (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
+
+ # 'showschema' command. update_modules() will be called, which
+ # (implicitly) confirms the correct method is called; further details
+ # are tested separately.
+ call_log = []
+ (rcode, value) = self.__send_command(__stats, 'showschema')
+ self.assertEqual([('update_module', ())], call_log)
+
+ # Unknown command. Error should be returned
+ self.assertEqual(
+ self.__send_command(__stats, '__UNKNOWN__'),
+ (1, "Unknown command: '__UNKNOWN__'"))
+
+ def test_update_modules(self):
+ """Confirm the behavior of Stats.update_modules().
+
+ It checks whether the expected command is sent to ConfigManager,
+ and whether the answer from ConfigManager is handled as expected.
+
+ """
+
+ def __check_rpc_call(command, group):
+ self.assertEqual('ConfigManager', group)
+ self.assertEqual(command,
+ isc.config.ccsession.COMMAND_GET_STATISTICS_SPEC)
+ answer_value = {'Init': [{
+ "item_name": "boot_time",
+ "item_type": "string",
+ "item_optional": False,
+ # Use a different default so we can check it below
+ "item_default": "2013-01-01T00:00:01Z",
+ "item_title": "Boot time",
+ "item_description": "dummy desc",
+ "item_format": "date-time"
+ }]}
+ return answer_value
+
+ self.stats = MyStats()
+ self.stats.cc_session.rpc_call = __check_rpc_call
+
+ self.stats.update_modules()
+
+ # Stats is always incorporated. For others, only the ones returned
+ # by group_recvmsg() above is available.
+ self.assertTrue('Stats' in self.stats.modules)
+ self.assertTrue('Init' in self.stats.modules)
+ self.assertFalse('Dummy' in self.stats.modules)
+
+ my_statistics_data = stats.get_spec_defaults(
+ self.stats.modules['Stats'].get_statistics_spec())
+ self.assertTrue('report_time' in my_statistics_data)
+ self.assertTrue('boot_time' in my_statistics_data)
+ self.assertTrue('last_update_time' in my_statistics_data)
+ self.assertTrue('timestamp' in my_statistics_data)
+ self.assertTrue('lname' in my_statistics_data)
+ self.assertEqual(my_statistics_data['report_time'],
+ self.const_default_datetime)
+ self.assertEqual(my_statistics_data['boot_time'],
+ self.const_default_datetime)
+ self.assertEqual(my_statistics_data['last_update_time'],
+ self.const_default_datetime)
+ self.assertEqual(my_statistics_data['timestamp'], 0.0)
+ self.assertEqual(my_statistics_data['lname'], "")
+ my_statistics_data = stats.get_spec_defaults(
+ self.stats.modules['Init'].get_statistics_spec())
+ self.assertTrue('boot_time' in my_statistics_data)
+ self.assertEqual(my_statistics_data['boot_time'],
+ "2013-01-01T00:00:01Z")
+
+ # Error case
+ def __raise_on_rpc_call(x, y):
+ raise isc.config.RPCError(99, 'error')
+ orig_parse_answer = stats.isc.config.ccsession.parse_answer
+ self.stats.cc_session.rpc_call = __raise_on_rpc_call
+ self.assertRaises(stats.StatsError, self.stats.update_modules)
+
+ def test_get_statistics_data(self):
+ """Confirm the behavior of Stats.get_statistics_data().
+
+ It should first call update_modules(), and then retrieve the requested
+ data from statistics_data. We confirm this by fake update_modules()
+ where we set the expected data in statistics_data.
+
+ """
+ self.stats = MyStats()
+ def __faked_update_modules():
+ self.stats.statistics_data = { \
+ 'Stats': {
+ 'report_time': self.const_default_datetime,
+ 'boot_time': None,
+ 'last_update_time': None,
+ 'timestamp': 0.0,
+ 'lname': 'dummy name'
+ },
+ 'Init': { 'boot_time': None }
+ }
+
+ self.stats.update_modules = __faked_update_modules
+
+ my_statistics_data = self.stats.get_statistics_data()
+ self.assertTrue('Stats' in my_statistics_data)
+ self.assertTrue('Init' in my_statistics_data)
+ self.assertTrue('boot_time' in my_statistics_data['Init'])
+
+ my_statistics_data = self.stats.get_statistics_data(owner='Stats')
+ self.assertTrue('Stats' in my_statistics_data)
+ self.assertTrue('report_time' in my_statistics_data['Stats'])
+ self.assertTrue('boot_time' in my_statistics_data['Stats'])
+ self.assertTrue('last_update_time' in my_statistics_data['Stats'])
+ self.assertTrue('timestamp' in my_statistics_data['Stats'])
+ self.assertTrue('lname' in my_statistics_data['Stats'])
+ self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+ owner='Foo')
+
+ my_statistics_data = self.stats.get_statistics_data(
+ owner='Stats', name='report_time')
+ self.assertEqual(my_statistics_data['Stats']['report_time'],
+ self.const_default_datetime)
+
+ my_statistics_data = self.stats.get_statistics_data(
+ owner='Stats', name='boot_time')
+ self.assertTrue('boot_time' in my_statistics_data['Stats'])
+
+ my_statistics_data = self.stats.get_statistics_data(
+ owner='Stats', name='last_update_time')
+ self.assertTrue('last_update_time' in my_statistics_data['Stats'])
+
+ my_statistics_data = self.stats.get_statistics_data(
+ owner='Stats', name='timestamp')
+ self.assertEqual(my_statistics_data['Stats']['timestamp'], 0.0)
+
+ my_statistics_data = self.stats.get_statistics_data(
+ owner='Stats', name='lname')
+ self.assertTrue(len(my_statistics_data['Stats']['lname']) >0)
+ self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+ owner='Stats', name='Bar')
+ self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+ owner='Foo', name='Bar')
+ self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+ name='Bar')
+
+ def test_update_statistics_data(self):
+ """test for list-type statistics"""
+ self.stats = MyStats()
+ _test_exp1 = {
+ 'zonename': 'test1.example',
+ 'queries.tcp': 5,
+ 'queries.udp': 4
+ }
+ _test_exp2 = {
+ 'zonename': 'test2.example',
+ 'queries.tcp': 3,
+ 'queries.udp': 2
+ }
+ _test_exp3 = {}
+ _test_exp4 = {
+ 'queries.udp': 4
+ }
+ _test_exp5_1 = {
+ 'queries.perzone': [
+ { },
+ {
+ 'queries.udp': 9876
+ }
+ ]
+ }
+ _test_exp5_2 = {
+ 'queries.perzone[1]/queries.udp':
+ isc.cc.data.find(_test_exp5_1,
+ 'queries.perzone[1]/queries.udp')
+ }
+ # Success cases
+ self.assertEqual(self.stats.statistics_data['Stats']['lname'],
+ self.stats.cc_session.lname)
+ self.stats.update_statistics_data(
+ 'Stats', self.stats.cc_session.lname,
+ {'lname': 'foo at bar'})
+ self.assertEqual(self.stats.statistics_data['Stats']['lname'],
+ 'foo at bar')
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'queries.perzone': [_test_exp1]}))
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['queries.perzone'],\
+ [_test_exp1])
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'queries.perzone': [_test_exp2]}))
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['queries.perzone'],\
+ [_test_exp2])
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'queries.perzone': [_test_exp1,_test_exp2]}))
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['queries.perzone'],
+ [_test_exp1,_test_exp2])
+ # differential update
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'queries.perzone': [_test_exp3,_test_exp4]}))
+ _new_data = stats.merge_oldnew(_test_exp2,_test_exp4)
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['queries.perzone'], \
+ [_test_exp1,_new_data])
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', _test_exp5_2))
+ _new_data = stats.merge_oldnew(_new_data,
+ _test_exp5_1['queries.perzone'][1])
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['queries.perzone'], \
+ [_test_exp1,_new_data])
+ # Error cases
+ self.assertEqual(self.stats.update_statistics_data('Stats', None,
+ {'lname': 0.0}),
+ ['0.0 should be a string'])
+ self.assertEqual(self.stats.update_statistics_data('Dummy', None,
+ {'foo': 'bar'}),
+ ['unknown module name: Dummy'])
+ self.assertEqual(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'queries.perzone': [None]}), ['None should be a map'])
+
+ def test_update_statistics_data_pt2(self):
+ """test for named_set-type statistics"""
+ self.stats = MyStats()
+ _test_exp1 = \
+ { 'test10.example': { 'queries.tcp': 5, 'queries.udp': 4 } }
+ _test_exp2 = \
+ { 'test20.example': { 'queries.tcp': 3, 'queries.udp': 2 } }
+ _test_exp3 = {}
+ _test_exp4 = { 'test20.example': { 'queries.udp': 4 } }
+ _test_exp5_1 = { 'test10.example': { 'queries.udp': 5432 } }
+ _test_exp5_2 ={
+ 'nds_queries.perzone/test10.example/queries.udp':
+ isc.cc.data.find(_test_exp5_1, 'test10.example/queries.udp')
+ }
+ _test_exp6 = { 'foo/bar': 'brabra' }
+ _test_exp7 = { 'foo[100]': 'bar' }
+ # Success cases
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'nds_queries.perzone': _test_exp1}))
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['nds_queries.perzone'],\
+ _test_exp1)
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'nds_queries.perzone': _test_exp2}))
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['nds_queries.perzone'],\
+ dict(_test_exp1,**_test_exp2))
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'nds_queries.perzone':
+ dict(_test_exp1, **_test_exp2)}))
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['nds_queries.perzone'],
+ dict(_test_exp1, **_test_exp2))
+ # differential update
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'nds_queries.perzone':
+ dict(_test_exp3, **_test_exp4)}))
+ _new_val = dict(_test_exp1,
+ **stats.merge_oldnew(_test_exp2,_test_exp4))
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['nds_queries.perzone'],\
+ _new_val)
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo1', _test_exp5_2))
+ _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['nds_queries.perzone'],\
+ _new_val)
+ self.assertIsNone(self.stats.update_statistics_data(
+ 'Auth', 'foo2', _test_exp5_2))
+ _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo2']['nds_queries.perzone'],\
+ _test_exp5_1)
+ # Error cases
+ self.assertEqual(self.stats.update_statistics_data(
+ 'Auth', 'foo1', {'nds_queries.perzone': None}),
+ ['None should be a map'])
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['nds_queries.perzone'],\
+ _new_val)
+ self.assertEqual(self.stats.update_statistics_data(
+ 'Auth', 'foo1', _test_exp6), ['unknown item foo'])
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+ ['foo1']['nds_queries.perzone'],\
+ _new_val)
+ self.assertEqual(self.stats.update_statistics_data(
+ 'Init', 'bar1', _test_exp7), ["KeyError: 'foo'"])
+ self.assertEqual(self.stats.update_statistics_data(
+ 'Foo', 'foo1', _test_exp6), ['unknown module name: Foo'])
+
+ def test_update_statistics_data_withmid(self):
+ self.stats = MyStats()
+
+ # This test relies on existing statistics data at the Stats object.
+ # This version of test prepares the data using the do_polling() method;
+ # that's a bad practice because a unittest for a method
+ # (update_statistics_data) would heavily depend on details of another
+ # method (do_polling). However, there's currently no direct test
+ # for do_polling (which is also bad), so we still keep that approach,
+ # partly for testing do_polling indirectly. #2781 should provide
+ # direct test for do_polling, with which this test scenario should
+ # also be changed to be more stand-alone.
+
+ # We use the knowledge of what kind of messages are sent via
+ # do_polling, and return the following faked answer directly.
+ self.stats._answers = [
+ # Answer for "show_processes"
+ (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
+ [1035, 'b10-auth-2', 'Auth']]), None),
+ # Answers for "getstats". 2 for Auth instances and 1 for Init.
+ # we return some bogus values for Init, but the rest of the test
+ # doesn't need it, so it's okay.
+ (create_answer(0, self.stats._auth_sdata), {'from': 'auth1'}),
+ (create_answer(0, self.stats._auth_sdata), {'from': 'auth2'}),
+ (create_answer(0, self.stats._auth_sdata), {'from': 'auth3'})
+ ]
+ # do_polling calls update_modules internally; in our scenario there's
+ # no change in modules, so we make it no-op.
+ self.stats.update_modules = lambda: None
+ # Now call do_polling.
+ self.stats.do_polling()
+
+ # samples of query number
+ bar1_tcp = 1001
+ bar2_tcp = 2001
+ bar3_tcp = 1002
+ bar3_udp = 1003
+ # two auth instances invoked, so we double the pre-set stat values
+ sum_qtcp = self.stats._queries_tcp * 2
+ sum_qudp = self.stats._queries_udp * 2
+ self.stats.update_statistics_data('Auth', "bar1 at foo",
+ {'queries.tcp': bar1_tcp})
+ self.assertTrue('Auth' in self.stats.statistics_data)
+ self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+ bar1_tcp + sum_qtcp)
+ self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+ self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
+ self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid
+ ['Auth']['bar1 at foo'])
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
+ {'queries.tcp': bar1_tcp})
+ # check consolidation of statistics data even if there is
+ # non-existent mid of Auth
+ self.stats.update_statistics_data('Auth', "bar2 at foo",
+ {'queries.tcp': bar2_tcp})
+ self.assertTrue('Auth' in self.stats.statistics_data)
+ self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+ bar1_tcp + bar2_tcp + sum_qtcp)
+ self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+ self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
+ self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
+ {'queries.tcp': bar1_tcp})
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar2 at foo'],
+ {'queries.tcp': bar2_tcp})
+ # kill running Auth but the statistics data doesn't change
+ self.stats.update_statistics_data()
+ self.assertTrue('Auth' in self.stats.statistics_data)
+ self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+ self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+ bar1_tcp + bar2_tcp + sum_qtcp)
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
+ sum_qudp)
+ self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+ # restore statistics data of killed auth
+ self.stats.update_statistics_data('Auth',
+ "bar1 at foo",
+ {'queries.tcp': bar1_tcp})
+ # set another mid of Auth
+ self.stats.update_statistics_data('Auth',
+ "bar3 at foo",
+ {'queries.tcp':bar3_tcp,
+ 'queries.udp':bar3_udp})
+ self.assertTrue('Auth' in self.stats.statistics_data)
+ self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+ self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+ bar1_tcp + bar2_tcp + bar3_tcp + sum_qtcp)
+ self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
+ bar3_udp + sum_qudp)
+ self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+ self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
+ self.assertTrue('bar3 at foo' in self.stats.statistics_data_bymid['Auth'])
+ self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
+ self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
+ self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo']['queries.tcp'], bar1_tcp)
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.tcp'], bar3_tcp)
+ self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.udp'], bar3_udp)
+
+ def test_config(self):
+ orig_get_timestamp = stats.get_timestamp
+ stats.get_timestamp = lambda : self.const_timestamp
+ stat = MyStats()
+
+ # test updating poll-interval
+ self.assertEqual(stat.config['poll-interval'], 60)
+ self.assertEqual(stat.get_interval(), 60)
+ self.assertEqual(stat.next_polltime, self.const_timestamp + 60)
+ self.assertEqual(stat.config_handler({'poll-interval': 120}),
+ isc.config.create_answer(0))
+ self.assertEqual(stat.config['poll-interval'], 120)
+ self.assertEqual(stat.get_interval(), 120)
+ self.assertEqual(stat.next_polltime, self.const_timestamp + 120)
+ stats.get_timestamp = orig_get_timestamp
+ self.assertEqual(stat.config_handler({'poll-interval': "foo"}),
+ isc.config.create_answer(1, 'foo should be an integer'))
+ self.assertEqual(stat.config_handler({'poll-interval': -1}),
+ isc.config.create_answer(1, 'Negative integer ignored'))
+ # unknown item
+ self.assertEqual(
+ stat.config_handler({'_UNKNOWN_KEY_': None}),
+ isc.config.ccsession.create_answer(
+ 1, "unknown item _UNKNOWN_KEY_"))
+ # test no change if zero interval time
+ self.assertEqual(stat.config_handler({'poll-interval': 0}),
+ isc.config.create_answer(0))
+ self.assertEqual(stat.config['poll-interval'], 0)
+
+ # see the comment for test_update_statistics_data_withmid. We abuse
+ # do_polling here, too. With #2781 we should make it more direct.
+ stat._answers = [\
+ # Answer for "show_processes"
+ (create_answer(0, []), None),
+ # Answers for "getstats" for Init (the other one for Auth, but
+ # that doesn't matter for this test)
+ (create_answer(0, stat._init_sdata), {'from': 'init'}),
+ (create_answer(0, stat._init_sdata), {'from': 'init'})
+ ]
+ stat.update_modules = lambda: None
+
+ self.assertEqual(
+ self.__send_command(
+ stat, 'show',
+ params={ 'owner' : 'Init', 'name' : 'boot_time' }),
+ (0, {'Init': {'boot_time': self.const_datetime}}))
+
+ def test_commands(self):
+ self.stats = MyStats()
+
+ # status
+ self.assertEqual(self.stats.command_status(),
+ isc.config.create_answer(
+ 0, "Stats is up. (PID " + str(os.getpid()) + ")"))
+
+ # shutdown
+ self.stats.running = True
+ self.assertEqual(self.stats.command_shutdown(),
+ isc.config.create_answer(0))
+ self.assertFalse(self.stats.running)
+
+ def test_command_show_error(self):
+ self.stats = MyStats()
+ self.assertEqual(self.stats.command_show(owner='Foo', name=None),
+ isc.config.create_answer(
+ 1,
+ "specified arguments are incorrect: owner: Foo, name: None"))
+ self.assertEqual(self.stats.command_show(owner='Foo', name='_bar_'),
+ isc.config.create_answer(
+ 1,
+ "specified arguments are incorrect: owner: Foo, name: _bar_"))
+ self.assertEqual(self.stats.command_show(owner='Foo', name='bar'),
+ isc.config.create_answer(
+ 1,
+ "specified arguments are incorrect: owner: Foo, name: bar"))
+
+ def test_command_show_auth(self):
+ self.stats = MyStats()
+ self.stats.update_modules = lambda: None
+
+ # Test data borrowed from test_update_statistics_data_withmid
+ self.stats._answers = [
+ (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
+ [1035, 'b10-auth-2', 'Auth']]), None),
+ (create_answer(0, self.stats._auth_sdata), {'from': 'auth1'}),
+ (create_answer(0, self.stats._auth_sdata), {'from': 'auth2'}),
+ (create_answer(0, self.stats._auth_sdata), {'from': 'auth3'})
+ ]
+
+ num_instances = 2
+ sum_qtcp = 0
+ sum_qudp = 0
+ sum_qtcp_perzone1 = 0
+ sum_qudp_perzone1 = 0
+ sum_qtcp_perzone2 = 4 * num_instances
+ sum_qudp_perzone2 = 3 * num_instances
+ sum_qtcp_nds_perzone10 = 0
+ sum_qudp_nds_perzone10 = 0
+ sum_qtcp_nds_perzone20 = 4 * num_instances
+ sum_qudp_nds_perzone20 = 3 * num_instances
+
+ self.maxDiff = None
+ for a in (0, num_instances):
+ sum_qtcp += self.stats._queries_tcp
+ sum_qudp += self.stats._queries_udp
+ sum_qtcp_perzone1 += self.stats._queries_per_zone[0]['queries.tcp']
+ sum_qudp_perzone1 += self.stats._queries_per_zone[0]['queries.udp']
+ sum_qtcp_nds_perzone10 += \
+ self.stats._nds_queries_per_zone['test10.example']['queries.tcp']
+ sum_qudp_nds_perzone10 += \
+ self.stats._nds_queries_per_zone['test10.example']['queries.udp']
+
+ self.assertEqual(self.stats.command_show(owner='Auth'),
+ isc.config.create_answer(
+ 0, {'Auth':{ 'queries.udp': sum_qudp,
+ 'queries.tcp': sum_qtcp,
+ 'queries.perzone': [{ 'zonename': 'test1.example',
+ 'queries.udp': sum_qudp_perzone1,
+ 'queries.tcp': sum_qtcp_perzone1 },
+ { 'zonename': 'test2.example',
+ 'queries.udp': sum_qudp_perzone2,
+ 'queries.tcp': sum_qtcp_perzone2 }
+ ],
+ 'nds_queries.perzone': { 'test10.example' : {
+ 'queries.udp': sum_qudp_nds_perzone10,
+ 'queries.tcp': sum_qtcp_nds_perzone10 },
+ 'test20.example' : {
+ 'queries.udp': sum_qudp_nds_perzone20,
+ 'queries.tcp': sum_qtcp_nds_perzone20 }
+ }}}))
+ self.assertEqual(self.stats.command_show(owner='Auth', name='queries.udp'),
+ isc.config.create_answer(
+ 0, {'Auth': {'queries.udp': sum_qudp}}))
+ self.assertEqual(self.stats.command_show(owner='Auth', name='queries.perzone'),
+ isc.config.create_answer(
+ 0, {'Auth': {'queries.perzone': [
+ { 'zonename': 'test1.example',
+ 'queries.udp': sum_qudp_perzone1,
+ 'queries.tcp': sum_qtcp_perzone1 },
+ { 'zonename': 'test2.example',
+ 'queries.udp': sum_qudp_perzone2,
+ 'queries.tcp': sum_qtcp_perzone2 }]}}))
+ self.assertEqual(self.stats.command_show(owner='Auth', name='nds_queries.perzone'),
+ isc.config.create_answer(
+ 0, {'Auth': {'nds_queries.perzone': {
+ 'test10.example': {
+ 'queries.udp': sum_qudp_nds_perzone10,
+ 'queries.tcp': sum_qtcp_nds_perzone10 },
+ 'test20.example': {
+ 'queries.udp': sum_qudp_nds_perzone20,
+ 'queries.tcp': sum_qtcp_nds_perzone20 }}}}))
+
+ def test_command_show_stats(self):
+ self.stats = MyStats()
+ orig_get_datetime = stats.get_datetime
+ orig_get_timestamp = stats.get_timestamp
+ stats.get_datetime = lambda x=None: self.const_datetime
+ stats.get_timestamp = lambda : self.const_timestamp
+ self.assertEqual(self.stats.command_show(owner='Stats',
+ name='report_time'),
+ isc.config.create_answer(
+ 0, {'Stats': {'report_time':self.const_datetime}}))
+ self.assertEqual(self.stats.command_show(owner='Stats',
+ name='timestamp'),
+ isc.config.create_answer(
+ 0, {'Stats': {'timestamp':self.const_timestamp}}))
+ stats.get_datetime = orig_get_datetime
+ stats.get_timestamp = orig_get_timestamp
+ self.stats.do_polling = lambda : None
+ self.stats.modules[self.stats.module_name] = \
+ isc.config.module_spec.ModuleSpec(
+ { "module_name": self.stats.module_name, "statistics": [] } )
+ self.assertRaises(
+ stats.StatsError, self.stats.command_show,
+ owner=self.stats.module_name, name='bar')
+
+ def test_command_showchema(self):
+ self.stats = MyStats()
+ (rcode, value) = isc.config.ccsession.parse_answer(
+ self.stats.command_showschema())
+ self.assertEqual(rcode, 0)
+ self.assertEqual(len(value), 3)
+ self.assertTrue('Stats' in value)
+ self.assertTrue('Init' in value)
+ self.assertTrue('Auth' in value)
+ self.assertFalse('__Dummy__' in value)
+ schema = value['Stats']
+ self.assertEqual(len(schema), 5)
+ for item in schema:
+ self.assertTrue(len(item) == 6 or len(item) == 7)
+ self.assertTrue('item_name' in item)
+ self.assertTrue('item_type' in item)
+ self.assertTrue('item_optional' in item)
+ self.assertTrue('item_default' in item)
+ self.assertTrue('item_title' in item)
+ self.assertTrue('item_description' in item)
+ if len(item) == 7:
+ self.assertTrue('item_format' in item)
+
+ schema = value['Init']
+ self.assertEqual(len(schema), 1)
+ for item in schema:
+ self.assertTrue(len(item) == 7)
+ self.assertTrue('item_name' in item)
+ self.assertTrue('item_type' in item)
+ self.assertTrue('item_optional' in item)
+ self.assertTrue('item_default' in item)
+ self.assertTrue('item_title' in item)
+ self.assertTrue('item_description' in item)
+ self.assertTrue('item_format' in item)
+
+ schema = value['Auth']
+ self.assertEqual(len(schema), 4)
+ for item in schema:
+ if item['item_type'] == 'list' or item['item_type'] == 'named_set':
+ self.assertEqual(len(item), 7)
+ else:
+ self.assertEqual(len(item), 6)
+ self.assertTrue('item_name' in item)
+ self.assertTrue('item_type' in item)
+ self.assertTrue('item_optional' in item)
+ self.assertTrue('item_default' in item)
+ self.assertTrue('item_title' in item)
+ self.assertTrue('item_description' in item)
+
+ (rcode, value) = isc.config.ccsession.parse_answer(
+ self.stats.command_showschema(owner='Stats'))
+ self.assertEqual(rcode, 0)
+ self.assertTrue('Stats' in value)
+ self.assertFalse('Init' in value)
+ self.assertFalse('Auth' in value)
+ for item in value['Stats']:
+ self.assertTrue(len(item) == 6 or len(item) == 7)
+ self.assertTrue('item_name' in item)
+ self.assertTrue('item_type' in item)
+ self.assertTrue('item_optional' in item)
+ self.assertTrue('item_default' in item)
+ self.assertTrue('item_title' in item)
+ self.assertTrue('item_description' in item)
+ if len(item) == 7:
+ self.assertTrue('item_format' in item)
+
+ (rcode, value) = isc.config.ccsession.parse_answer(
+ self.stats.command_showschema(owner='Stats', name='report_time'))
+ self.assertEqual(rcode, 0)
+ self.assertTrue('Stats' in value)
+ self.assertFalse('Init' in value)
+ self.assertFalse('Auth' in value)
+ self.assertEqual(len(value['Stats'][0]), 7)
+ self.assertTrue('item_name' in value['Stats'][0])
+ self.assertTrue('item_type' in value['Stats'][0])
+ self.assertTrue('item_optional' in value['Stats'][0])
+ self.assertTrue('item_default' in value['Stats'][0])
+ self.assertTrue('item_title' in value['Stats'][0])
+ self.assertTrue('item_description' in value['Stats'][0])
+ self.assertTrue('item_format' in value['Stats'][0])
+ self.assertEqual(value['Stats'][0]['item_name'], 'report_time')
+ self.assertEqual(value['Stats'][0]['item_format'], 'date-time')
+
+ self.assertEqual(self.stats.command_showschema(owner='Foo'),
+ isc.config.create_answer(
+ 1, "specified arguments are incorrect: owner: Foo, name: None"))
+ self.assertEqual(self.stats.command_showschema(owner='Foo', name='bar'),
+ isc.config.create_answer(
+ 1, "specified arguments are incorrect: owner: Foo, name: bar"))
+ self.assertEqual(self.stats.command_showschema(owner='Auth'),
+ isc.config.create_answer(
+ 0, {'Auth': [{
+ "item_default": 0,
+ "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
+ "item_name": "queries.tcp",
+ "item_optional": False,
+ "item_title": "Queries TCP",
+ "item_type": "integer"
+ },
+ {
+ "item_default": 0,
+ "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially",
+ "item_name": "queries.udp",
+ "item_optional": False,
+ "item_title": "Queries UDP",
+ "item_type": "integer"
+ },
+ {
+ "item_name": "queries.perzone",
+ "item_type": "list",
+ "item_optional": False,
+ "item_default": [
+ {
+ "zonename" : "test1.example",
+ "queries.udp" : 1,
+ "queries.tcp" : 2
+ },
+ {
+ "zonename" : "test2.example",
+ "queries.udp" : 3,
+ "queries.tcp" : 4
+ }
+ ],
+ "item_title": "Queries per zone",
+ "item_description": "Queries per zone",
+ "list_item_spec": {
+ "item_name": "zones",
+ "item_type": "map",
+ "item_optional": False,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "zonename",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "",
+ "item_title": "Zonename",
+ "item_description": "Zonename"
+ },
+ {
+ "item_name": "queries.udp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries UDP per zone",
+ "item_description": "A number of UDP query counts per zone"
+ },
+ {
+ "item_name": "queries.tcp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries TCP per zone",
+ "item_description": "A number of TCP query counts per zone"
+ }
+ ]
+ }
+ },
+ {
+ "item_name": "nds_queries.perzone",
+ "item_type": "named_set",
+ "item_optional": False,
+ "item_default": {
+ "test10.example" : {
+ "queries.udp" : 1,
+ "queries.tcp" : 2
+ },
+ "test20.example" : {
+ "queries.udp" : 3,
+ "queries.tcp" : 4
+ }
+ },
+ "item_title": "Queries per zone",
+ "item_description": "Queries per zone",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": False,
+ "item_default": {},
+ "item_title": "Zonename",
+ "item_description": "Zonename",
+ "map_item_spec": [
+ {
+ "item_name": "queries.udp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries UDP per zone",
+ "item_description": "A number of UDP query counts per zone"
+ },
+ {
+ "item_name": "queries.tcp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries TCP per zone",
+ "item_description": "A number of TCP query counts per zone"
+ }
+ ]
+ }
+ }]}))
+ self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.tcp'),
+ isc.config.create_answer(
+ 0, {'Auth': [{
+ "item_default": 0,
+ "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
+ "item_name": "queries.tcp",
+ "item_optional": False,
+ "item_title": "Queries TCP",
+ "item_type": "integer"
+ }]}))
+ self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.perzone'),
+ isc.config.create_answer(
+ 0, {'Auth':[{
+ "item_name": "queries.perzone",
+ "item_type": "list",
+ "item_optional": False,
+ "item_default": [
+ {
+ "zonename" : "test1.example",
+ "queries.udp" : 1,
+ "queries.tcp" : 2
+ },
+ {
+ "zonename" : "test2.example",
+ "queries.udp" : 3,
+ "queries.tcp" : 4
+ }
+ ],
+ "item_title": "Queries per zone",
+ "item_description": "Queries per zone",
+ "list_item_spec": {
+ "item_name": "zones",
+ "item_type": "map",
+ "item_optional": False,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "zonename",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "",
+ "item_title": "Zonename",
+ "item_description": "Zonename"
+ },
+ {
+ "item_name": "queries.udp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries UDP per zone",
+ "item_description": "A number of UDP query counts per zone"
+ },
+ {
+ "item_name": "queries.tcp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries TCP per zone",
+ "item_description": "A number of TCP query counts per zone"
+ }
+ ]
+ }
+ }]}))
+ self.assertEqual(self.stats.command_showschema(owner='Auth', name='nds_queries.perzone'),
+ isc.config.create_answer(
+ 0, {'Auth':[{
+ "item_name": "nds_queries.perzone",
+ "item_type": "named_set",
+ "item_optional": False,
+ "item_default": {
+ "test10.example" : {
+ "queries.udp" : 1,
+ "queries.tcp" : 2
+ },
+ "test20.example" : {
+ "queries.udp" : 3,
+ "queries.tcp" : 4
+ }
+ },
+ "item_title": "Queries per zone",
+ "item_description": "Queries per zone",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": False,
+ "item_default": {},
+ "item_title": "Zonename",
+ "item_description": "Zonename",
+ "map_item_spec": [
+ {
+ "item_name": "queries.udp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries UDP per zone",
+ "item_description": "A number of UDP query counts per zone"
+ },
+ {
+ "item_name": "queries.tcp",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "Queries TCP per zone",
+ "item_description": "A number of TCP query counts per zone"
+ }
+ ]
+ }
+ }]}))
+
+ self.assertEqual(self.stats.command_showschema(owner='Stats', name='bar'),
+ isc.config.create_answer(
+ 1, "specified arguments are incorrect: owner: Stats, name: bar"))
+ self.assertEqual(self.stats.command_showschema(name='bar'),
+ isc.config.create_answer(
+ 1, "module name is not specified"))
+
+ def test_get_multi_module_list(self):
+ """Test _get_multi_module_list() returns a module list which is running
+ as multiple modules."""
+ stat = MyStats()
+ # no answer
+ self.assertListEqual([], stat._get_multi_module_list())
+ # proc list returned
+ proc_list = [
+ [29317, 'b10-xfrout', 'Xfrout'],
+ [29318, 'b10-xfrin', 'Xfrin'],
+ [20061, 'b10-auth','Auth'],
+ [20103, 'b10-auth-2', 'Auth']]
+ mod_list = [ a[2] for a in proc_list ]
+ stat._answers = [
+ # Answer for "show_processes"
+ (create_answer(0, proc_list), {'from': 'init'})
+ ]
+ self.assertListEqual(mod_list, stat._get_multi_module_list())
+ # invalid proc list
+ stat._answers = [
+ # Answer for "show_processes"
+ (create_answer(0, [[999, 'invalid', 'Invalid'], 'invalid']),
+ {'from': 'init'})
+ ]
+ self.assertListEqual(['Invalid', None], stat._get_multi_module_list())
+
+ def test_get_multi_module_list_rpcrecipientmissing(self):
+ """Test _get_multi_module_list() raises an RPCRecipientMissing exception
+ if rcp_call() raise the exception"""
+ # RPCRecipientMissing case
+ stat = MyStats()
+ ex = isc.config.RPCRecipientMissing
+ def __raise(*x): raise ex(*x)
+ stat.mccs.rpc_call = lambda x,y: __raise('Error')
+ self.assertRaises(ex, stat._get_multi_module_list)
+
+ def test_get_multi_module_list_rpcerror(self):
+ """Test _get_multi_module_list() returns an empty list if rcp_call()
+ raise an RPCError exception"""
+ # RPCError case
+ stat = MyStats()
+ ex = isc.config.RPCError
+ def __raise(*x): raise ex(*x)
+ stat.mccs.rpc_call = lambda x,y: __raise(99, 'Error')
+ self.assertListEqual([], stat._get_multi_module_list())
+
+ def test_get_multi_module_list_initsessiontimeout(self):
+ """Test _get_multi_module_list() raises an InitSeeionTimeout exception
+ if a CC session times out in rcp_call()"""
+ # InitSeeionTimeout case
+ stat = MyStats()
+ ex = isc.cc.session.SessionTimeout
+ def __raise(*x): raise ex(*x)
+ stat.mccs.rpc_call = lambda x,y: __raise()
+ self.assertRaises(stats.InitSessionTimeout, stat._get_multi_module_list)
+
+ def test_query_statistics(self):
+ """Test _query_statistics returns a list of pairs of module and
+ sequences from group_sendmsg()"""
+ stat = MyStats()
+ # imitate stat.get_statistics_data().items. The order of the array
+ # returned by items() should be preserved.
+ class DummyDict:
+ def items(self):
+ return [('Init', 'dummy'), ('Stats', 'dummy'), ('Auth', 'dummy')]
+ stat.get_statistics_data = lambda: DummyDict()
+ mod = ('Init', 'Auth', 'Auth')
+ seq = [('Init', stat._seq + 1),
+ ('Auth', stat._seq + 2),
+ ('Auth', stat._seq + 2) ]
+ self.assertListEqual(seq, stat._query_statistics(mod))
+
+ def test_collect_statistics(self):
+ """Test _collect_statistics() collects statistics data from each module
+ based on the sequences which is a list of values returned from
+ group_sendmsg()"""
+ stat = MyStats()
+ seq = [ ('Init', stat._seq + 1),
+ ('Auth', stat._seq + 2),
+ ('Auth', stat._seq + 2) ]
+ ret = [('Init', 'frominit', {'boot_time': '2013-01-01T00:00:00Z'}),
+ ('Auth', 'fromauth1', {'queries.tcp': 100}),
+ ('Auth', 'fromauth2', {'queries.udp': 200})]
+ stat._answers = [
+ (create_answer(0, r[2]), {'from': r[1]}) for r in ret
+ ]
+ self.assertListEqual(ret, stat._collect_statistics(seq))
+
+ def test_collect_statistics_nodata(self):
+ """Test _collect_statistics() returns empty statistics data if
+ a module returns an empty list"""
+ stat = MyStats()
+ seq = []
+ stat._answers = []
+ ret = []
+ self.assertListEqual(ret, stat._collect_statistics(seq))
+
+ def test_collect_statistics_nonzero_rcode(self):
+ """Test _collect_statistics() returns empty statistics data if
+ a module returns non-zero rcode"""
+ stat = MyStats()
+ seq = [('Init', stat._seq + 1)]
+ stat._answers = [
+ (create_answer(1, 'error'), {'from': 'frominit'})
+ ]
+ ret = []
+ self.assertListEqual(ret, stat._collect_statistics(seq))
+
+ def test_collect_statistics_sessiontimeout(self):
+ """Test _collect_statistics() collects statistics data from each module
+ based on the sequences which is a list of values returned from
+ group_sendmsg(). In this test case, SessionTimeout exceptions are raised
+ while collecting from Auth. This tests _collect_statistics skips
+ collecting from Auth."""
+ # SessionTimeout case
+ stat = MyStats()
+ ex = isc.cc.session.SessionTimeout
+ def __raise(*x): raise ex(*x)
+ # SessionTimeout is raised when asking to Auth
+ stat.cc_session.group_recvmsg = lambda x,seq: \
+ __raise() if seq == stat._seq + 2 else stat._answers.pop(0)
+ seq = [ ('Init', stat._seq + 1),
+ ('Auth', stat._seq + 2),
+ ('Auth', stat._seq + 2) ]
+ ret = [('Init', 'frominit', {'boot_time': '2013-01-01T00:00:00Z'})]
+ stat._answers = [
+ (create_answer(0, r[2]), {'from': r[1]}) for r in ret
+ ]
+ self.assertListEqual(ret, stat._collect_statistics(seq))
+
+ def test_refresh_statistics(self):
+ """Test _refresh_statistics() refreshes statistics data from given data
+ """
+ stat = MyStats()
+ self.assertEqual(self.const_default_datetime,
+ stat.statistics_data['Init']['boot_time'])
+ self.assertEqual(0,
+ stat.statistics_data['Auth']['queries.tcp'])
+ self.assertEqual(0,
+ stat.statistics_data['Auth']['queries.udp'])
+ # change stats.get_datetime() for testing 'last_update_time'
+ orig_get_datetime = stats.get_datetime
+ stats.get_datetime = lambda : self.const_datetime
+ arg = [('Init', 'frominit', {'boot_time': '2013-01-01T00:00:00Z'}),
+ ('Auth', 'fromauth1', {'queries.tcp': 100}),
+ ('Auth', 'fromauth2', {'queries.udp': 200})]
+ stat._refresh_statistics(arg)
+ self.assertEqual('2013-01-01T00:00:00Z',
+ stat.statistics_data['Init']['boot_time'])
+ self.assertEqual(100,
+ stat.statistics_data['Auth']['queries.tcp'])
+ self.assertEqual(200,
+ stat.statistics_data['Auth']['queries.udp'])
+ self.assertEqual(self.const_datetime,
+ stat.statistics_data['Stats']['last_update_time'])
+ stats.get_datetime = orig_get_datetime
+
+ def test_polling_init(self):
+ """check statistics data of 'Init'."""
+
+ stat = MyStats()
+ # At this point 'stat' is initialized with statistics for Stats,
+ # Init and Auth modules. In this test, we only need to check for Init
+ # statistics, while do_polling() can ask for module statistics in an
+ # unpredictable order (if hash randomization is enabled, which is
+ # the case by default for Python 3.3). To make it predictable and
+ # ensure the prepared answer doesn't cause disruption, we remove the
+ # information for the Auth module for this test.
+ del stat.statistics_data['Auth']
+
+ stat.update_modules = lambda: None
+
+ stat._answers = [
+ # Answer for "show_processes"
+ (create_answer(0, []), None),
+ # Answers for "getstats" for Init (type of boot_time is invalid)
+ (create_answer(0, {'boot_time': self.const_datetime}),
+ {'from': 'init'}),
+ ]
+
+ stat.do_polling()
+ self.assertEqual(
+ stat.statistics_data_bymid['Init']['init'],
+ {'boot_time': self.const_datetime})
+
+ def test_polling_consolidate(self):
+ """check statistics data of multiple instances of same module."""
+ stat = MyStats()
+ stat.update_modules = lambda: None
+
+ # Test data borrowed from test_update_statistics_data_withmid
+ stat._answers = [
+ (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
+ [1035, 'b10-auth-2', 'Auth']]), None),
+ (create_answer(0, stat._auth_sdata), {'from': 'auth1'}),
+ (create_answer(0, stat._auth_sdata), {'from': 'auth2'}),
+ (create_answer(0, stat._auth_sdata), {'from': 'auth3'})
+ ]
+
+ stat.do_polling()
+
+ # check statistics data of each 'Auth' instances. expected data
+ # for 'nds_queries.perzone' is special as it needs data merge.
+ self.assertEqual(2, len(stat.statistics_data_bymid['Auth'].values()))
+ for s in stat.statistics_data_bymid['Auth'].values():
+ self.assertEqual(
+ s, {'queries.perzone': stat._auth_sdata['queries.perzone'],
+ 'nds_queries.perzone': stat._nds_queries_per_zone,
+ 'queries.tcp': stat._auth_sdata['queries.tcp'],
+ 'queries.udp': stat._auth_sdata['queries.udp']})
+
+ # check consolidation of statistics data of the auth instances.
+ # it's union of the reported data and the spec default.
+ n = len(stat.statistics_data_bymid['Auth'].values())
+ self.maxDiff = None
+ self.assertEqual(
+ stat.statistics_data['Auth'],
+ {'queries.perzone': [
+ {'zonename': 'test1.example',
+ 'queries.tcp': 5 * n,
+ 'queries.udp': 4 * n},
+ {'zonename': 'test2.example',
+ 'queries.tcp': 4 * n,
+ 'queries.udp': 3 * n},
+ ],
+ 'nds_queries.perzone': {
+ 'test10.example': {
+ 'queries.tcp': 5 * n,
+ 'queries.udp': 4 * n
+ },
+ 'test20.example': {
+ 'queries.tcp': 4 * n,
+ 'queries.udp': 3 * n
+ },
+ },
+ 'queries.tcp': 3 * n,
+ 'queries.udp': 2 * n})
+
+ def test_polling_stats(self):
+ """Check statistics data of 'Stats'
+
+ This is actually irrelevant to do_polling(), but provided to
+ compatibility of older tests.
+
+ """
+ stat = MyStats()
+ self.assertEqual(len(stat.statistics_data['Stats']), 5)
+ self.assertTrue('boot_time' in stat.statistics_data['Stats'])
+ self.assertTrue('last_update_time' in stat.statistics_data['Stats'])
+ self.assertTrue('report_time' in stat.statistics_data['Stats'])
+ self.assertTrue('timestamp' in stat.statistics_data['Stats'])
+ self.assertEqual(stat.statistics_data['Stats']['lname'],
+ stat.mccs._session.lname)
+
+ def test_refresh_statistics_broken_statistics_data(self):
+ """Test _refresh_statistics() doesn't incorporate broken statistics data
+ """
+ stat = MyStats()
+ # check default statistics data of 'Init'
+ self.assertEqual(
+ {'boot_time': self.const_default_datetime},
+ stat.statistics_data['Init'])
+ last_update_time = stat.statistics_data['Stats']['last_update_time']
+ # _refresh_statistics() should ignore the invalid statistics_data(type
+ # of boot_time is invalid); default data shouldn't be replaced.
+ arg = [('Init', 'lname', {'boot_time': 1})]
+ stat._refresh_statistics(arg)
+ self.assertEqual(
+ {'boot_time': self.const_default_datetime},
+ stat.statistics_data['Init'])
+ # 'last_update_time' doesn't change
+ self.assertEqual(
+ last_update_time,
+ stat.statistics_data['Stats']['last_update_time'])
+
+ def test_polling_update_lasttime_poll(self):
+ """Test _lasttime_poll is updated after do_polling()
+ """
+ orig_get_timestamp = stats.get_timestamp
+ stats.get_timestamp = lambda : self.const_timestamp
+ stat = MyStats()
+ self.assertEqual(0.0, stat._lasttime_poll)
+ stat.do_polling()
+ self.assertEqual(self.const_timestamp, stat._lasttime_poll)
+ stats.get_timestamp = orig_get_timestamp
+
+ def test_polling_initsessiontimeout(self):
+ """Test _lasttime_poll is updated after do_polling() in case that it catches
+ InitSesionTimeout at _get_multi_module_list()
+ """
+ orig_get_timestamp = stats.get_timestamp
+ stats.get_timestamp = lambda : self.const_timestamp
+ ex = stats.InitSessionTimeout
+ def __raise(*x): raise ex(*x)
+ stat = MyStats()
+ self.assertEqual(0.0, stat._lasttime_poll)
+ stat._get_multi_module_list = lambda: __raise()
+ stat.do_polling()
+ self.assertEqual(self.const_timestamp, stat._lasttime_poll)
+ stats.get_timestamp = orig_get_timestamp
+
+class Z_TestOSEnv(unittest.TestCase):
+ # Running this test would break logging setting. To prevent it from
+ # affecting other tests we use the same workaround as Z_TestOSEnv in
+ # stats-httpd_test.py.
+ def test_osenv(self):
+ """
+ test for the environ variable "B10_FROM_SOURCE"
+ "B10_FROM_SOURCE" is set in Makefile
+ """
+ # test case having B10_FROM_SOURCE
+ self.assertTrue("B10_FROM_SOURCE" in os.environ)
+ self.assertEqual(stats.SPECFILE_LOCATION, \
+ os.environ["B10_FROM_SOURCE"] + os.sep + \
+ "src" + os.sep + "bin" + os.sep + "stats" + \
+ os.sep + "stats.spec")
+ # test case not having B10_FROM_SOURCE
+ path = os.environ["B10_FROM_SOURCE"]
+ os.environ.pop("B10_FROM_SOURCE")
+ self.assertFalse("B10_FROM_SOURCE" in os.environ)
+ # import stats again
+ imp.reload(stats)
+ # revert the changes
+ os.environ["B10_FROM_SOURCE"] = path
+ imp.reload(stats)
+
+if __name__ == "__main__":
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/bin/stats/tests/test_utils.py b/src/bin/stats/tests/test_utils.py
index bfabc13..99cc980 100644
--- a/src/bin/stats/tests/test_utils.py
+++ b/src/bin/stats/tests/test_utils.py
@@ -20,13 +20,11 @@ Utilities and mock modules for unittests of statistics modules
import os
import io
import time
-import sys
import threading
-import tempfile
import json
import signal
+import socket
-import msgq
import isc.config.cfgmgr
import stats
import stats_httpd
@@ -48,22 +46,9 @@ class SignalHandler():
signal.signal(signal.SIGALRM, self.orig_handler)
def sig_handler(self, signal, frame):
- """envokes unittest.TestCase.fail as a signal handler"""
+ """invokes unittest.TestCase.fail as a signal handler"""
self.fail_handler("A deadlock might be detected")
-def send_command(command_name, module_name, params=None):
- cc_session = isc.cc.Session()
- command = isc.config.ccsession.create_command(command_name, params)
- seq = cc_session.group_sendmsg(command, module_name)
- try:
- (answer, env) = cc_session.group_recvmsg(False, seq)
- if answer:
- return isc.config.ccsession.parse_answer(answer)
- except isc.cc.SessionTimeout:
- pass
- finally:
- cc_session.close()
-
class ThreadingServerManager:
def __init__(self, server, *args, **kwargs):
self.server = server(*args, **kwargs)
@@ -91,45 +76,7 @@ class ThreadingServerManager:
else:
self.server._thread.join(0) # timeout is 0
-class MockMsgq:
- def __init__(self):
- self._started = threading.Event()
- self.msgq = msgq.MsgQ(verbose=False)
- result = self.msgq.setup()
- if result:
- sys.exit("Error on Msgq startup: %s" % result)
-
- def run(self):
- self._started.set()
- try:
- self.msgq.run()
- finally:
- # Make sure all the sockets, etc, are removed once it stops.
- self.msgq.shutdown()
-
- def shutdown(self):
- # Ask it to terminate nicely
- self.msgq.stop()
-
-class MockCfgmgr:
- def __init__(self):
- self._started = threading.Event()
- self.cfgmgr = isc.config.cfgmgr.ConfigManager(
- os.environ['CONFIG_TESTDATA_PATH'], "b10-config.db")
- self.cfgmgr.read_config()
-
- def run(self):
- self._started.set()
- try:
- self.cfgmgr.run()
- except Exception:
- pass
-
- def shutdown(self):
- self.cfgmgr.running = False
-
-class MockInit:
- spec_str = """\
+INIT_SPEC_STR = """\
{
"module_spec": {
"module_name": "Init",
@@ -221,56 +168,12 @@ class MockInit:
}
}
"""
- _BASETIME = CONST_BASETIME
- def __init__(self):
- self._started = threading.Event()
- self.running = False
- self.spec_file = io.StringIO(self.spec_str)
- # create ModuleCCSession object
- self.mccs = isc.config.ModuleCCSession(
- self.spec_file,
- self.config_handler,
- self.command_handler)
- self.spec_file.close()
- self.cc_session = self.mccs._session
- self.got_command_name = ''
- self.pid_list = [[ 9999, "b10-auth", "Auth" ],
- [ 9998, "b10-auth-2", "Auth" ]]
- self.statistics_data = {
- 'boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', self._BASETIME)
- }
-
- def run(self):
- self.mccs.start()
- self.running = True
- self._started.set()
- try:
- while self.running:
- self.mccs.check_command(False)
- except Exception:
- pass
-
- def shutdown(self):
- self.running = False
-
- def config_handler(self, new_config):
- return isc.config.create_answer(0)
-
- def command_handler(self, command, *args, **kwargs):
- self._started.set()
- self.got_command_name = command
- sdata = self.statistics_data
- if command == 'getstats':
- return isc.config.create_answer(0, sdata)
- elif command == 'show_processes':
- # Return dummy pids
- return isc.config.create_answer(
- 0, self.pid_list)
- return isc.config.create_answer(1, "Unknown Command")
-
-class MockAuth:
- spec_str = """\
+# Note: this is derived of the spec for the DNS authoritative server, but
+# for the purpose of this test, it's completely irrelevant to DNS.
+# Some statisittics specs do not make sense for practical sense but used
+# just cover various types of statistics data (list, map/dict, etc).
+AUTH_SPEC_STR = """\
{
"module_spec": {
"module_name": "Auth",
@@ -392,68 +295,6 @@ class MockAuth:
}
}
"""
- def __init__(self):
- self._started = threading.Event()
- self.running = False
- self.spec_file = io.StringIO(self.spec_str)
- # create ModuleCCSession object
- self.mccs = isc.config.ModuleCCSession(
- self.spec_file,
- self.config_handler,
- self.command_handler)
- self.spec_file.close()
- self.cc_session = self.mccs._session
- self.got_command_name = ''
- self.queries_tcp = 3
- self.queries_udp = 2
- self.queries_per_zone = [{
- 'zonename': 'test1.example',
- 'queries.tcp': 5,
- 'queries.udp': 4
- }]
- self.nds_queries_per_zone = {
- 'test10.example': {
- 'queries.tcp': 5,
- 'queries.udp': 4
- }
- }
-
- def run(self):
- self.mccs.start()
- self.running = True
- self._started.set()
- try:
- while self.running:
- self.mccs.check_command(False)
- except Exception:
- pass
-
- def shutdown(self):
- self.running = False
-
- def config_handler(self, new_config):
- return isc.config.create_answer(0)
-
- def command_handler(self, command, *args, **kwargs):
- self.got_command_name = command
- sdata = { 'queries.tcp': self.queries_tcp,
- 'queries.udp': self.queries_udp,
- 'queries.perzone' : self.queries_per_zone,
- 'nds_queries.perzone' : {
- 'test10.example': {
- 'queries.tcp': \
- isc.cc.data.find(
- self.nds_queries_per_zone,
- 'test10.example/queries.tcp')
- }
- },
- 'nds_queries.perzone/test10.example/queries.udp' :
- isc.cc.data.find(self.nds_queries_per_zone,
- 'test10.example/queries.udp')
- }
- if command == 'getstats':
- return isc.config.create_answer(0, sdata)
- return isc.config.create_answer(1, "Unknown Command")
class MyModuleCCSession(isc.config.ConfigData):
"""Mocked ModuleCCSession class.
@@ -468,7 +309,10 @@ class MyModuleCCSession(isc.config.ConfigData):
isc.config.ConfigData.__init__(self, module_spec)
self._session = self
self.stopped = False
+ self.closed = False
self.lname = 'mock_mod_ccs'
+ self._msg = None
+ self._env = None
def start(self):
pass
@@ -476,10 +320,17 @@ class MyModuleCCSession(isc.config.ConfigData):
def send_stopping(self):
self.stopped = True # just record it's called to inspect it later
-class SimpleStats(stats.Stats):
+ def close(self):
+ self.closed = True
+
+ def check_command_without_recvmsg(self, msg, env):
+ self._msg = msg
+ self._env = env
+
+class MyStats(stats.Stats):
"""A faked Stats class for unit tests.
- This class inherits most of the real Stats class, but replace the
+ This class inherits most of the real Stats class, but replaces the
ModuleCCSession with a fake one so we can avoid network I/O in tests,
and can also inspect or tweak messages via the session more easily.
This class also maintains some faked module information and statistics
@@ -493,16 +344,16 @@ class SimpleStats(stats.Stats):
# may want to inspect or tweak them.
# initial seq num for faked group_sendmsg, arbitrary choice.
- self.__seq = 4200
+ self._seq = 4200
# if set, use them as faked response to group_recvmsg (see below).
# it's a list of tuples, each of which is of (answer, envelope).
self._answers = []
# the default answer from faked recvmsg if _answers is empty
self.__default_answer = isc.config.ccsession.create_answer(
0, {'Init':
- json.loads(MockInit.spec_str)['module_spec']['statistics'],
+ json.loads(INIT_SPEC_STR)['module_spec']['statistics'],
'Auth':
- json.loads(MockAuth.spec_str)['module_spec']['statistics']
+ json.loads(AUTH_SPEC_STR)['module_spec']['statistics']
})
# setup faked auth statistics
self.__init_auth_stat()
@@ -530,24 +381,24 @@ class SimpleStats(stats.Stats):
def __init_auth_stat(self):
self._queries_tcp = 3
self._queries_udp = 2
- self.__queries_per_zone = [{
+ self._queries_per_zone = [{
'zonename': 'test1.example', 'queries.tcp': 5, 'queries.udp': 4
}]
- self.__nds_queries_per_zone = \
+ self._nds_queries_per_zone = \
{ 'test10.example': { 'queries.tcp': 5, 'queries.udp': 4 } }
self._auth_sdata = \
{ 'queries.tcp': self._queries_tcp,
'queries.udp': self._queries_udp,
- 'queries.perzone' : self.__queries_per_zone,
+ 'queries.perzone' : self._queries_per_zone,
'nds_queries.perzone' : {
'test10.example': {
'queries.tcp': isc.cc.data.find(
- self.__nds_queries_per_zone,
+ self._nds_queries_per_zone,
'test10.example/queries.tcp')
}
},
'nds_queries.perzone/test10.example/queries.udp' :
- isc.cc.data.find(self.__nds_queries_per_zone,
+ isc.cc.data.find(self._nds_queries_per_zone,
'test10.example/queries.udp')
}
@@ -563,10 +414,10 @@ class SimpleStats(stats.Stats):
generated sequence number.
"""
- self.__seq += 1
- return self.__seq
+ self._seq += 1
+ return self._seq
- def __group_recvmsg(self, nonblocking, seq):
+ def __group_recvmsg(self, nonblocking = True, seq = None):
"""Faked ModuleCCSession.group_recvmsg for tests.
Skipping actual network communication, and returning an internally
@@ -589,32 +440,62 @@ class SimpleStats(stats.Stats):
answer, _ = self.__group_recvmsg(None, None)
return isc.config.ccsession.parse_answer(answer)[1]
-class MyStats(stats.Stats):
-
- stats._BASETIME = CONST_BASETIME
- stats.get_timestamp = lambda: time.mktime(CONST_BASETIME)
- stats.get_datetime = lambda x=None: time.strftime("%Y-%m-%dT%H:%M:%SZ", CONST_BASETIME)
-
- def __init__(self):
- self._started = threading.Event()
- stats.Stats.__init__(self)
+class MyStatsHttpd(stats_httpd.StatsHttpd):
+ """A faked StatsHttpd class for unit tests.
- def run(self):
- self._started.set()
- try:
- self.start()
- except Exception:
- pass
+ This class inherits most of the real StatsHttpd class, but replaces the
+ ModuleCCSession with a fake one so we can avoid network I/O in tests,
+ and can also inspect or tweak messages via the session more easily.
- def shutdown(self):
- self.command_shutdown()
+ """
-class MyStatsHttpd(stats_httpd.StatsHttpd):
ORIG_SPECFILE_LOCATION = stats_httpd.SPECFILE_LOCATION
def __init__(self, *server_address):
self._started = threading.Event()
+ self.__dummy_sock = None # see below
+
+ # Prepare commonly used statistics schema and data requested in
+ # stats-httpd tests. For the purpose of these tests, the content of
+ # statistics data is not so important (they don't test whther the
+ # counter values are correct, etc), so hardcoding the common case
+ # should suffice. Note also that some of the statistics values and
+ # specs don't make sense in practice (see also comments on
+ # AUTH_SPEC_STR).
+ with open(stats.SPECFILE_LOCATION) as f:
+ stat_spec_str = f.read()
+ self.__default_spec_answer = {
+ 'Init': json.loads(INIT_SPEC_STR)['module_spec']['statistics'],
+ 'Auth': json.loads(AUTH_SPEC_STR)['module_spec']['statistics'],
+ 'Stats': json.loads(stat_spec_str)['module_spec']['statistics']
+ }
+ self.__default_data_answer = {
+ 'Init': {'boot_time':
+ time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME)},
+ 'Stats': {'last_update_time':
+ time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+ 'report_time':
+ time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+ 'lname': 'test-lname',
+ 'boot_time':
+ time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+ 'timestamp': time.mktime(CONST_BASETIME)},
+ 'Auth': {'queries.udp': 4, 'queries.tcp': 6,
+ 'queries.perzone': [
+ {'queries.udp': 8, 'queries.tcp': 10,
+ 'zonename': 'test1.example'},
+ {'queries.udp': 6, 'queries.tcp': 8,
+ 'zonename': 'test2.example'}],
+ 'nds_queries.perzone': {
+ 'test10.example': {'queries.udp': 8, 'queries.tcp': 10},
+ 'test20.example': {'queries.udp': 6, 'queries.tcp': 8}}}}
+
+ # if set, use them as faked response to rpc_call (see below).
+ # it's a list of answer data of rpc_call.
+ self._rpc_answers = []
+
if server_address:
- stats_httpd.SPECFILE_LOCATION = self.create_specfile(*server_address)
+ stats_httpd.SPECFILE_LOCATION = \
+ self.__create_specfile(*server_address)
try:
stats_httpd.StatsHttpd.__init__(self)
finally:
@@ -624,7 +505,59 @@ class MyStatsHttpd(stats_httpd.StatsHttpd):
else:
stats_httpd.StatsHttpd.__init__(self)
- def create_specfile(self, *server_address):
+ # replace some (faked) ModuleCCSession methods so we can inspect/fake.
+ # in order to satisfy select.select() we need some real socket. We
+ # use an unusable AF_UNIX socket; we won't actually use it for
+ # communication.
+ self.cc_session.rpc_call = self.__rpc_call
+ self.__dummy_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.mccs.get_socket = lambda: self.__dummy_sock
+
+ def open_mccs(self):
+ self.mccs = MyModuleCCSession(stats_httpd.SPECFILE_LOCATION,
+ self.config_handler,
+ self.command_handler)
+ self.cc_session = self.mccs._session
+ self.mccs.start = self.load_config # force reload
+
+ # check_command could be called from the main select() loop due to
+ # Linux's bug of spurious wakeup. We don't need the actual behavior
+ # of check_command in our tests, so we can basically replace it with a
+ # no-op mock function.
+ def mock_check_command(nonblock):
+ pass
+ self.mccs.check_command = mock_check_command
+
+ def close_mccs(self):
+ super().close_mccs()
+ if self.__dummy_sock is not None:
+ self.__dummy_sock.close()
+ self.__dummy_sock = None
+
+ def __rpc_call(self, command, group, params={}):
+ """Faked ModuleCCSession.rpc_call for tests.
+
+ The stats httpd module only issues two commands: 'showschema' and
+ 'show'. In most cases we can simply use the prepared default
+ answer. If customization is needed, the test case can add a
+ faked answer by appending it to _rpc_answers. If the added object
+ is of Exception type this method raises it instead of return it,
+ emulating the situation where rpc_call() results in an exception.
+
+ """
+ if len(self._rpc_answers) == 0:
+ if command == 'showschema':
+ return self.__default_spec_answer
+ elif command == 'show':
+ return self.__default_data_answer
+ assert False, "unexpected command for faked rpc_call: " + command
+
+ answer = self._rpc_answers.pop(0)
+ if issubclass(type(answer), Exception):
+ raise answer
+ return answer
+
+ def __create_specfile(self, *server_address):
spec_io = open(self.ORIG_SPECFILE_LOCATION)
try:
spec = json.load(spec_io)
@@ -633,7 +566,8 @@ class MyStatsHttpd(stats_httpd.StatsHttpd):
for i in range(len(config)):
if config[i]['item_name'] == 'listen_on':
config[i]['item_default'] = \
- [ dict(address=a[0], port=a[1]) for a in server_address ]
+ [ dict(address=a[0], port=a[1])
+ for a in server_address ]
break
return io.StringIO(json.dumps(spec))
finally:
@@ -641,53 +575,7 @@ class MyStatsHttpd(stats_httpd.StatsHttpd):
def run(self):
self._started.set()
- try:
- self.start()
- except Exception:
- pass
+ self.start()
def shutdown(self):
self.command_handler('shutdown', None)
-
-class BaseModules:
- def __init__(self):
- # MockMsgq
- self.msgq = ThreadingServerManager(MockMsgq)
- self.msgq.run()
- # Check whether msgq is ready. A SessionTimeout is raised here if not.
- isc.cc.session.Session().close()
- # MockCfgmgr
- self.cfgmgr = ThreadingServerManager(MockCfgmgr)
- self.cfgmgr.run()
- # MockInit
- self.b10_init = ThreadingServerManager(MockInit)
- self.b10_init.run()
- # MockAuth
- self.auth = ThreadingServerManager(MockAuth)
- self.auth.run()
- self.auth2 = ThreadingServerManager(MockAuth)
- self.auth2.run()
-
-
- def shutdown(self):
- # MockMsgq. We need to wait (blocking) for it, otherwise it'll wipe out
- # a socket for another test during its shutdown.
- self.msgq.shutdown(True)
-
- # We also wait for the others, but these are just so we don't create
- # too many threads in parallel.
-
- # MockAuth
- self.auth2.shutdown(True)
- self.auth.shutdown(True)
- # MockInit
- self.b10_init.shutdown(True)
- # MockCfgmgr
- self.cfgmgr.shutdown(True)
- # remove the unused socket file
- socket_file = self.msgq.server.msgq.socket_file
- try:
- if os.path.exists(socket_file):
- os.remove(socket_file)
- except OSError:
- pass
diff --git a/src/bin/tests/Makefile.am b/src/bin/tests/Makefile.am
index 41b497f..535257c 100644
--- a/src/bin/tests/Makefile.am
+++ b/src/bin/tests/Makefile.am
@@ -8,7 +8,7 @@ noinst_SCRIPTS = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/tests/process_rename_test.py.in b/src/bin/tests/process_rename_test.py.in
index ea8ad87..7e6a9c9 100644
--- a/src/bin/tests/process_rename_test.py.in
+++ b/src/bin/tests/process_rename_test.py.in
@@ -25,6 +25,11 @@ class TestRename(unittest.TestCase):
def __scan(self, directory, script, fun):
# Scan one script if it contains call to the renaming function
filename = os.path.join(directory, script)
+ if not os.path.exists(filename):
+ # We either didn't compile it yet or it is not compiled based
+ # on some environmental condition. That is OK, we don't want
+ # to break on that.
+ return
with open(filename) as f:
data = ''.join(f.readlines())
prettyname = 'src' + filename[filename.rfind('../') + 2:]
diff --git a/src/bin/usermgr/Makefile.am b/src/bin/usermgr/Makefile.am
index ce7977f..d356d20 100644
--- a/src/bin/usermgr/Makefile.am
+++ b/src/bin/usermgr/Makefile.am
@@ -1,8 +1,11 @@
+SUBDIRS = tests
+
sbin_SCRIPTS = b10-cmdctl-usermgr
+noinst_SCRIPTS = run_b10-cmdctl-usermgr.sh
b10_cmdctl_usermgrdir = $(pkgdatadir)
-CLEANFILES= b10-cmdctl-usermgr
+CLEANFILES= b10-cmdctl-usermgr b10-cmdctl-usermgr.pyc
man_MANS = b10-cmdctl-usermgr.8
DISTCLEANFILES = $(man_MANS)
@@ -25,3 +28,7 @@ endif
b10-cmdctl-usermgr: b10-cmdctl-usermgr.py
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" b10-cmdctl-usermgr.py >$@
chmod a+x $@
+
+CLEANDIRS = __pycache__
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/bin/usermgr/b10-cmdctl-usermgr.py.in b/src/bin/usermgr/b10-cmdctl-usermgr.py.in
old mode 100644
new mode 100755
index d62ad72..b0fd30f
--- a/src/bin/usermgr/b10-cmdctl-usermgr.py.in
+++ b/src/bin/usermgr/b10-cmdctl-usermgr.py.in
@@ -11,115 +11,234 @@
# 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
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN COMMAND OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS COMMAND, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
'''
-This file implements user management program. The user name and
-its password is appended to csv file.
+This tool implements user management for b10-cmdctl. It is used to
+add and remove users from the accounts file.
'''
+import sys; sys.path.append ('@@PYTHONPATH@@')
+from bind10_config import SYSCONFPATH
+from collections import OrderedDict
import random
from hashlib import sha1
import csv
import getpass
-import getopt
-import sys; sys.path.append ('@@PYTHONPATH@@')
+from optparse import OptionParser, OptionValueError
+import os
import isc.util.process
isc.util.process.rename()
-VERSION_NUMBER = 'bind10'
-DEFAULT_FILE = 'cmdctl-accounts.csv'
-
-def gen_password_hash(password):
- salt = "".join(chr(random.randint(33, 127)) for x in range(64))
- saltedpwd = sha1((password + salt).encode()).hexdigest()
- return salt, saltedpwd
-
-def username_exist(name, filename):
- # The file may doesn't exist.
- exist = False
- csvfile = None
- try:
- csvfile = open(filename)
- reader = csv.reader(csvfile)
- for row in reader:
- if name == row[0]:
- exist = True
- break
- except Exception:
- pass
-
- if csvfile:
- csvfile.close()
- return exist
-
-def save_userinfo(username, pw, salt, filename):
- csvfile = open(filename, 'a')
- writer = csv.writer(csvfile)
- writer.writerow([username, pw, salt])
- csvfile.close()
- print("\n create new account successfully! \n")
-
-def usage():
- print('''Usage: usermgr [options]
- -h, --help \t Show this help message and exit
- -f, --file \t Specify the file to append user name and password
- -v, --version\t Get version number
- ''')
+VERSION_STRING = "b10-cmdctl-usermgr @PACKAGE_VERSION@"
+DEFAULT_FILE = SYSCONFPATH + "/cmdctl-accounts.csv"
-def main():
- filename = DEFAULT_FILE
- try:
- opts, args = getopt.getopt(sys.argv[1:], 'f:hv',
- ['file=', 'help', 'version'])
- except getopt.GetoptError as err:
- print(err)
- usage()
- sys.exit(2)
- for op, param in opts:
- if op in ('-h', '--help'):
- usage()
- sys.exit()
- elif op in ('-v', '--version'):
- print(VERSION_NUMBER)
- sys.exit()
- elif op in ('-f', "--file"):
- filename = param
- else:
- assert False, 'unknown option'
- usage()
-
- try:
- while True :
- name = input("Desired Login Name:")
- if name == '':
- print("error, user name can't be empty")
+# Actions that can be performed (used for argument parsing,
+# code paths, and output)
+COMMAND_ADD = "add"
+COMMAND_DELETE = "delete"
+
+# Non-zero return codes, used in tests
+BAD_ARGUMENTS = 1
+FILE_ERROR = 2
+USER_EXISTS = 3
+USER_DOES_NOT_EXIST = 4
+
+class UserManager:
+ def __init__(self, options, args):
+ self.options = options
+ self.args = args
+
+ def __print(self, msg):
+ if not self.options.quiet:
+ print(msg)
+
+ def __gen_password_hash(self, password):
+ salt = "".join(chr(random.randint(ord('!'), ord('~')))\
+ for x in range(64))
+ saltedpwd = sha1((password + salt).encode()).hexdigest()
+ return salt, saltedpwd
+
+ def __read_user_info(self):
+ """
+ Read the existing user info
+ Raises an IOError if the file can't be read
+ """
+ # Currently, this is done quite naively (there is no
+ # check that the file is modified between read and write)
+ # But taking multiple simultaneous users of this tool on the
+ # same file seems unnecessary at this point.
+ self.user_info = OrderedDict()
+ if os.path.exists(self.options.output_file):
+ # Just let any file read error bubble up; it will
+ # be caught in the run() method
+ with open(self.options.output_file, newline='') as csvfile:
+ reader = csv.reader(csvfile, strict=True)
+ for row in reader:
+ self.user_info[row[0]] = row
+
+ def __save_user_info(self):
+ """
+ Write out the (modified) user info
+ Raises an IOError if the file can't be written
+ """
+ # Just let any file write error bubble up; it will
+ # be caught in the run() method
+ with open(self.options.output_file, 'w',
+ newline='') as csvfile:
+ writer = csv.writer(csvfile)
+ for row in self.user_info.values():
+ writer.writerow(row)
+
+ def __add_user(self, name, password):
+ """
+ Add the given username/password combination to the stored user_info.
+ First checks if the username exists, and returns False if so.
+ If not, it is added, and this method returns True.
+ """
+ if name in self.user_info:
+ return False
+ salt, pw = self.__gen_password_hash(password)
+ self.user_info[name] = [name, pw, salt]
+ return True
+
+ def __delete_user(self, name):
+ """
+ Removes the row with the given name from the stored user_info
+ First checks if the username exists, and returns False if not.
+ Otherwise, it is removed, and this mehtod returns True
+ """
+ if name not in self.user_info:
+ return False
+ del self.user_info[name]
+ return True
+
+ # overridable input() call, used in testing
+ def _input(self, prompt):
+ return input(prompt)
+
+ # in essence this is private, but made 'protected' for ease
+ # of testing
+ def _prompt_for_username(self, command):
+ # Note, direct prints here are intentional
+ while True:
+ name = self._input("Username to " + command + ": ")
+ if name == "":
+ print("Error username can't be empty")
continue
- if username_exist(name, filename):
- print("user name already exists!")
+ if command == COMMAND_ADD and name in self.user_info:
+ print("user already exists")
+ continue
+ elif command == COMMAND_DELETE and name not in self.user_info:
+ print("user does not exist")
continue
- while True:
- pwd1 = getpass.getpass("Choose a password:")
- pwd2 = getpass.getpass("Re-enter password:")
- if pwd1 != pwd2:
- print("password is not same, please input again")
+ return name
+
+ # in essence this is private, but made 'protected' for ease
+ # of testing
+ def _prompt_for_password(self):
+ # Note, direct prints here are intentional
+ while True:
+ pwd1 = getpass.getpass("Choose a password: ")
+ if pwd1 == "":
+ print("Error: password cannot be empty")
+ continue
+ pwd2 = getpass.getpass("Re-enter password: ")
+ if pwd1 != pwd2:
+ print("passwords do not match, try again")
+ continue
+ return pwd1
+
+ def __verify_options_and_args(self):
+ """
+ Basic sanity checks on command line arguments.
+ Returns False if there is a problem, True if everything seems OK.
+ """
+ if len(self.args) < 1:
+ self.__print("Error: no command specified")
+ return False
+ if len(self.args) > 3:
+ self.__print("Error: extraneous arguments")
+ return False
+ if self.args[0] not in [ COMMAND_ADD, COMMAND_DELETE ]:
+ self.__print("Error: command must be either add or delete")
+ return False
+ if self.args[0] == COMMAND_DELETE and len(self.args) > 2:
+ self.__print("Error: delete only needs username, not a password")
+ return False
+ return True
+
+ def run(self):
+ if not self.__verify_options_and_args():
+ return BAD_ARGUMENTS
+
+ try:
+ self.__print("Using accounts file: " + self.options.output_file)
+ self.__read_user_info()
+
+ command = self.args[0]
+
+ if len(self.args) > 1:
+ username = self.args[1]
+ else:
+ username = self._prompt_for_username(command)
+
+ if command == COMMAND_ADD:
+ if len(self.args) > 2:
+ password = self.args[2]
else:
- break;
-
- salt, pw = gen_password_hash(pwd1)
- save_userinfo(name, pw, salt, filename)
- inputdata = input('continue to create new account by input \'y\' or \'Y\':')
- if inputdata not in ['y', 'Y']:
- break
-
- except KeyboardInterrupt:
- pass
+ password = self._prompt_for_password()
+ if not self.__add_user(username, password):
+ print("Error: username exists")
+ return USER_EXISTS
+ elif command == COMMAND_DELETE:
+ if not self.__delete_user(username):
+ print("Error: username does not exist")
+ return USER_DOES_NOT_EXIST
+
+ self.__save_user_info()
+ return 0
+ except IOError as ioe:
+ self.__print("Error accessing " + ioe.filename +\
+ ": " + str(ioe.strerror))
+ return FILE_ERROR
+ except csv.Error as csve:
+ self.__print("Error parsing csv file: " + str(csve))
+ return FILE_ERROR
+
+def set_options(parser):
+ parser.add_option("-f", "--file",
+ dest="output_file", default=DEFAULT_FILE,
+ help="Accounts file to modify"
+ )
+ parser.add_option("-q", "--quiet",
+ dest="quiet", action="store_true", default=False,
+ help="Quiet mode, don't print any output"
+ )
+
+def main():
+ usage = "usage: %prog [options] <command> [username] [password]\n\n"\
+ "Arguments:\n"\
+ " command\t\teither 'add' or 'delete'\n"\
+ " username\t\tthe username to add or delete\n"\
+ " password\t\tthe password to set for the added user\n"\
+ "\n"\
+ "If username or password are not specified, %prog will\n"\
+ "prompt for them. It is recommended practice to let the\n"\
+ "tool prompt for the password, as command-line\n"\
+ "arguments can be visible through history or process\n"\
+ "viewers."
+ parser = OptionParser(usage=usage, version=VERSION_STRING)
+ set_options(parser)
+ (options, args) = parser.parse_args()
+ usermgr = UserManager(options, args)
+ return usermgr.run()
if __name__ == '__main__':
- main()
+ sys.exit(main())
diff --git a/src/bin/usermgr/b10-cmdctl-usermgr.xml b/src/bin/usermgr/b10-cmdctl-usermgr.xml
index 529a8db..26940d7 100644
--- a/src/bin/usermgr/b10-cmdctl-usermgr.xml
+++ b/src/bin/usermgr/b10-cmdctl-usermgr.xml
@@ -12,8 +12,8 @@
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN COMMAND OF CONTRACT, NEGLIGENCE
+ - OR OTHER TORTIOUS COMMAND, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- PERFORMANCE OF THIS SOFTWARE.
-->
@@ -47,10 +47,12 @@
<arg><option>-f <replaceable>filename</replaceable></option></arg>
<arg><option>-h</option></arg>
- <arg><option>-v</option></arg>
- <arg><option>--file <replaceable>filename</replaceable></option></arg>
+ <arg><option>--file=<replaceable>filename</replaceable></option></arg>
<arg><option>--help</option></arg>
<arg><option>--version</option></arg>
+ <arg choice="plain"><replaceable>command</replaceable></arg>
+ <arg><option>username</option></arg>
+ <arg><option>password</option></arg>
</cmdsynopsis>
</refsynopsisdiv>
@@ -58,24 +60,22 @@
<refsect1>
<title>DESCRIPTION</title>
<para>The <command>b10-cmdctl-usermgr</command> tool may be used
- to add accounts with passwords for the
+ to add and remove accounts with passwords for the
<citerefentry><refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>
daemon.
</para>
<para>
By default, the accounts are saved in the
- <filename>cmdctl-accounts.csv</filename> file in the current directory,
- unless the <option>--filename</option> switch is used.
- The entry is appended to the file.
-<!-- TODO: default should have full path? -->
+ <filename>cmdctl-accounts.csv</filename> file in the system config
+ directory, unless the <option>--filename</option> switch is used.
+ The entry is appended to or removed from the file.
</para>
<para>
- The tool can't remove or replace existing entries.
+ The tool can't replace existing entries, but this can easily be
+ accomplished by removing the entry and adding a new one.
</para>
-<!-- TODO: the tool can't remove or replace existing entries -->
-
</refsect1>
<refsect1>
@@ -83,6 +83,18 @@
<para>The arguments are as follows:</para>
+ <para>
+ command is either 'add' or 'delete', respectively to add or delete users.
+ </para>
+
+ <para>
+ If a username and password are given (or just a username in case of
+ deletion), these are used. Otherwise, the tool shall prompt for a
+ username and/or password. It is recommended practice to let the
+ tool prompt for the password, as command-line arguments can be
+ visible through history or process viewers.
+ </para>
+
<variablelist>
<varlistentry>
@@ -97,14 +109,13 @@
<term><option>-f <replaceable>filename</replaceable></option></term>
<term><option>--file <replaceable>filename</replaceable></option></term>
<listitem><para>
- Define the filename to append the account to. The default
- is <filename>cmdctl-accounts.csv</filename> in the current directory.
-<!-- TODO: default should have full path? -->
+ Specify the accounts file to update. The default is
+ <filename>cmdctl-accounts.csv</filename> in the system config
+ directory.
</para></listitem>
</varlistentry>
<varlistentry>
- <term><option>-v</option></term>
<term><option>--version</option></term>
<listitem><para>
Report the version and exit.
diff --git a/src/bin/usermgr/run_b10-cmdctl-usermgr.sh.in b/src/bin/usermgr/run_b10-cmdctl-usermgr.sh.in
old mode 100644
new mode 100755
index 5989205..8e8e743
--- a/src/bin/usermgr/run_b10-cmdctl-usermgr.sh.in
+++ b/src/bin/usermgr/run_b10-cmdctl-usermgr.sh.in
@@ -18,6 +18,9 @@
PYTHON_EXEC=${PYTHON_EXEC:- at PYTHON@}
export PYTHON_EXEC
+PYTHONPATH=@abs_top_builddir@/src/lib/python
+export PYTHONPATH
+
MYPATH_PATH=@abs_top_builddir@/src/bin/usermgr
BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
diff --git a/src/bin/usermgr/tests/Makefile.am b/src/bin/usermgr/tests/Makefile.am
new file mode 100644
index 0000000..230f164
--- /dev/null
+++ b/src/bin/usermgr/tests/Makefile.am
@@ -0,0 +1,22 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
+PYTESTS = b10-cmdctl-usermgr_test.py
+EXTRA_DIST = $(PYTESTS)
+
+CLEANFILES = *.csv
+
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/cmdctl \
+ CMDCTL_BUILD_PATH=$(abs_top_builddir)/src/bin/cmdctl \
+ CMDCTL_SRC_PATH=$(abs_top_srcdir)/src/bin/cmdctl \
+ B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
diff --git a/src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py b/src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py
new file mode 100644
index 0000000..2da418d
--- /dev/null
+++ b/src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py
@@ -0,0 +1,511 @@
+# Copyright (C) 2013 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 COMMAND OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS COMMAND, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import csv
+from hashlib import sha1
+import getpass
+import imp
+import os
+import subprocess
+import stat
+import sys
+import unittest
+from bind10_config import SYSCONFPATH
+
+class PrintCatcher:
+ def __init__(self):
+ self.stdout_lines = []
+
+ def __enter__(self):
+ self.__orig_stdout_write = sys.stdout.write
+ def new_write(line):
+ self.stdout_lines.append(line)
+
+ sys.stdout.write = new_write
+ return self
+
+ def __exit__(self, type, value, traceback):
+ sys.stdout.write = self.__orig_stdout_write
+
+class OverrideGetpass:
+ def __init__(self, new_getpass):
+ self.__new_getpass = new_getpass
+ self.__orig_getpass = getpass.getpass
+
+ def __enter__(self):
+ getpass.getpass = self.__new_getpass
+ return self
+
+ def __exit__(self, type, value, traceback):
+ getpass.getpass = self.__orig_getpass
+
+# input() is a built-in function and not easily overridable
+# so this one uses usermgr for that
+class OverrideInput:
+ def __init__(self, usermgr, new_getpass):
+ self.__usermgr = usermgr
+ self.__new_input = new_getpass
+ self.__orig_input = usermgr._input
+
+ def __enter__(self):
+ self.__usermgr._input = self.__new_input
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.__usermgr._input = self.__orig_input
+
+def run(command):
+ """
+ Small helper function that returns a tuple of (rcode, stdout, stderr)
+ after running the given command (an array of command and arguments, as
+ passed on to subprocess).
+ Parameters:
+ command: an array of command and argument strings, which will be
+ passed to subprocess.Popen()
+ """
+ subp = subprocess.Popen(command, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ (stdout, stderr) = subp.communicate()
+ return (subp.returncode, stdout, stderr)
+
+class TestUserMgr(unittest.TestCase):
+ TOOL = '../b10-cmdctl-usermgr'
+ OUTPUT_FILE = 'test_users.csv'
+
+ def setUp(self):
+ self.delete_output_file()
+ # For access to the actual module, we load it directly
+ self.usermgr_module = imp.load_source('usermgr',
+ '../b10-cmdctl-usermgr.py')
+ # And instantiate 1 instance (with fake options/args)
+ self.usermgr = self.usermgr_module.UserManager(object(), object())
+
+ def tearDown(self):
+ self.delete_output_file()
+
+ def delete_output_file(self):
+ if os.path.exists(self.OUTPUT_FILE):
+ os.remove(self.OUTPUT_FILE)
+
+ def check_output_file(self, expected_content):
+ self.assertTrue(os.path.exists(self.OUTPUT_FILE))
+
+ csv_entries = []
+ with open(self.OUTPUT_FILE, newline='') as csvfile:
+ reader = csv.reader(csvfile)
+ csv_entries = [row for row in reader]
+
+ self.assertEqual(len(expected_content), len(csv_entries))
+ csv_entries.reverse()
+ for expected_entry in expected_content:
+ expected_name = expected_entry[0]
+ expected_pass = expected_entry[1]
+
+ csv_entry = csv_entries.pop()
+ entry_name = csv_entry[0]
+ entry_salt = csv_entry[2]
+ entry_hash = csv_entry[1]
+
+ self.assertEqual(expected_name, entry_name)
+ expected_hash =\
+ sha1((expected_pass + entry_salt).encode()).hexdigest()
+ self.assertEqual(expected_hash, entry_hash)
+
+ def run_check(self, expected_returncode, expected_stdout, expected_stderr,
+ command):
+ """
+ Runs the given command, and checks return code, and outputs (if provided).
+ Arguments:
+ expected_returncode, return code of the command
+ expected_stdout, (multiline) string that is checked against stdout.
+ May be None, in which case the check is skipped.
+ expected_stderr, (multiline) string that is checked against stderr.
+ May be None, in which case the check is skipped.
+
+ Returns the standard output and error captured to a string.
+ """
+ (returncode, stdout, stderr) = run(command)
+ if expected_stderr is not None:
+ self.assertEqual(expected_stderr, stderr.decode())
+ if expected_stdout is not None:
+ self.assertEqual(expected_stdout, stdout.decode())
+ self.assertEqual(expected_returncode, returncode, " ".join(command))
+ return (stdout.decode(), stderr.decode())
+
+ def test_help(self):
+ self.run_check(0,
+'''Usage: b10-cmdctl-usermgr [options] <command> [username] [password]
+
+Arguments:
+ command either 'add' or 'delete'
+ username the username to add or delete
+ password the password to set for the added user
+
+If username or password are not specified, b10-cmdctl-usermgr will
+prompt for them. It is recommended practice to let the
+tool prompt for the password, as command-line
+arguments can be visible through history or process
+viewers.
+
+Options:
+ --version show program's version number and exit
+ -h, --help show this help message and exit
+ -f OUTPUT_FILE, --file=OUTPUT_FILE
+ Accounts file to modify
+ -q, --quiet Quiet mode, don't print any output
+''',
+ '',
+ [self.TOOL, '-h'])
+
+ def test_add_delete_users_ok(self):
+ """
+ Test that a file is created, and users are added.
+ Also tests quiet mode for adding a user to an existing file.
+ """
+ # content is a list of (user, pass) tuples
+ expected_content = []
+
+ # Creating a file
+ self.run_check(0,
+ 'Using accounts file: test_users.csv\n',
+ '',
+ [ self.TOOL,
+ '-f', self.OUTPUT_FILE,
+ 'add', 'user1', 'pass1'
+ ])
+ expected_content.append(('user1', 'pass1'))
+ self.check_output_file(expected_content)
+
+ # Add to existing file
+ self.run_check(0,
+ 'Using accounts file: test_users.csv\n',
+ '',
+ [ self.TOOL,
+ '-f', self.OUTPUT_FILE,
+ 'add', 'user2', 'pass2'
+ ])
+ expected_content.append(('user2', 'pass2'))
+ self.check_output_file(expected_content)
+
+ # Quiet mode
+ self.run_check(0,
+ '',
+ '',
+ [ self.TOOL, '-q',
+ '-f', self.OUTPUT_FILE,
+ 'add', 'user3', 'pass3'
+ ])
+ expected_content.append(('user3', 'pass3'))
+ self.check_output_file(expected_content)
+
+ # Delete a user (let's pick the middle one)
+ self.run_check(0,
+ '',
+ '',
+ [ self.TOOL, '-q',
+ '-f', self.OUTPUT_FILE,
+ 'delete', 'user2'
+ ])
+ del expected_content[1]
+ self.check_output_file(expected_content)
+
+ def test_add_delete_users_bad(self):
+ """
+ More add/delete tests, this time for some error scenarios
+ """
+ # content is a list of (user, pass) tuples
+ expected_content = []
+ # First add one
+ self.run_check(0, None, None,
+ [ self.TOOL,
+ '-f', self.OUTPUT_FILE,
+ 'add', 'user', 'pass'
+ ])
+ expected_content.append(('user', 'pass'))
+ self.check_output_file(expected_content)
+
+ # Adding it again should error
+ self.run_check(3,
+ 'Using accounts file: test_users.csv\n'
+ 'Error: username exists\n',
+ '',
+ [ self.TOOL,
+ '-f', self.OUTPUT_FILE,
+ 'add', 'user', 'pass'
+ ])
+ self.check_output_file(expected_content)
+
+ # Deleting a non-existent one should fail too
+ self.run_check(4,
+ 'Using accounts file: test_users.csv\n'
+ 'Error: username does not exist\n',
+ '',
+ [ self.TOOL,
+ '-f', self.OUTPUT_FILE,
+ 'delete', 'nosuchuser'
+ ])
+ self.check_output_file(expected_content)
+
+ def test_bad_arguments(self):
+ """
+ Assorted tests with bad command-line arguments
+ """
+ self.run_check(1,
+ 'Error: no command specified\n',
+ '',
+ [ self.TOOL ])
+ self.run_check(1,
+ 'Error: command must be either add or delete\n',
+ '',
+ [ self.TOOL, 'foo' ])
+ self.run_check(1,
+ 'Error: extraneous arguments\n',
+ '',
+ [ self.TOOL, 'add', 'user', 'pass', 'toomuch' ])
+ self.run_check(1,
+ 'Error: delete only needs username, not a password\n',
+ '',
+ [ self.TOOL, 'delete', 'user', 'pass' ])
+
+ def test_default_file(self):
+ """
+ Check the default file is the correct one.
+ """
+ # Hardcoded path .. should be ok since this is run from make check
+ self.assertEqual(SYSCONFPATH + '/cmdctl-accounts.csv',
+ self.usermgr_module.DEFAULT_FILE)
+
+ def test_prompt_for_password_different(self):
+ """
+ Check that the method that prompts for a password verifies that
+ the same value is entered twice
+ """
+ # returns a different string (the representation of the number
+ # of times it has been called), until it has been called
+ # over 10 times, in which case it will always return "11"
+ getpass_different_called = 0
+ def getpass_different(question):
+ nonlocal getpass_different_called
+ getpass_different_called += 1
+ if getpass_different_called > 10:
+ return "11"
+ else:
+ return str(getpass_different_called)
+
+ with PrintCatcher() as pc:
+ with OverrideGetpass(getpass_different):
+ pwd = self.usermgr._prompt_for_password()
+ self.assertEqual(12, getpass_different_called)
+ self.assertEqual("11", pwd)
+ # stdout should be 5 times the no match string;
+ expected_output = "passwords do not match, try again\n"*5
+ self.assertEqual(expected_output, ''.join(pc.stdout_lines))
+
+ def test_prompt_for_password_empty(self):
+ """
+ Check that the method that prompts for a password verifies that
+ the value entered is not empty
+ """
+ # returns an empty string until it has been called over 10
+ # times
+ getpass_empty_called = 0
+ def getpass_empty(prompt):
+ nonlocal getpass_empty_called
+ getpass_empty_called += 1
+ if getpass_empty_called > 10:
+ return "nonempty"
+ else:
+ return ""
+
+ with PrintCatcher() as pc:
+ with OverrideGetpass(getpass_empty):
+ pwd = self.usermgr._prompt_for_password()
+ self.assertEqual("nonempty", pwd)
+ self.assertEqual(12, getpass_empty_called)
+ # stdout should be 10 times the 'cannot be empty' string
+ expected_output = "Error: password cannot be empty\n"*10
+ self.assertEqual(expected_output, ''.join(pc.stdout_lines))
+
+ def test_prompt_for_user(self):
+ """
+ Test that the method that prompts for a username verifies that
+ is not empty, and that it exists (or does not, depending on the
+ action that is specified)
+ """
+ new_input_called = 0
+ input_results = [ '', '', 'existinguser', 'nonexistinguser',
+ '', '', 'nonexistinguser', 'existinguser' ]
+ def new_input(prompt):
+ nonlocal new_input_called
+
+ if new_input_called < len(input_results):
+ result = input_results[new_input_called]
+ else:
+ result = 'empty'
+ new_input_called += 1
+ return result
+
+ # add fake user (value doesn't matter, method only checks for key)
+ self.usermgr.user_info = { 'existinguser': None }
+
+ expected_output = ''
+
+ with PrintCatcher() as pc:
+ with OverrideInput(self.usermgr, new_input):
+ # should skip the first three since empty or existing
+ # are not allowed, then return 'nonexistinguser'
+ username = self.usermgr._prompt_for_username(
+ self.usermgr_module.COMMAND_ADD)
+ self.assertEqual('nonexistinguser', username)
+ expected_output += "Error username can't be empty\n"*2
+ expected_output += "user already exists\n"
+ self.assertEqual(expected_output, ''.join(pc.stdout_lines))
+
+ # For delete, should again not accept empty (in a while true
+ # loop), and this time should not accept nonexisting users
+ username = self.usermgr._prompt_for_username(
+ self.usermgr_module.COMMAND_DELETE)
+ self.assertEqual('existinguser', username)
+ expected_output += "Error username can't be empty\n"*2
+ expected_output += "user does not exist\n"
+ self.assertEqual(expected_output, ''.join(pc.stdout_lines))
+
+ def test_bad_file(self):
+ """
+ Check for graceful handling of bad file argument
+ """
+ self.run_check(2,
+ 'Using accounts file: /\n'
+ 'Error accessing /: Is a directory\n',
+ '',
+ [ self.TOOL, '-f', '/', 'add', 'user', 'pass' ])
+
+ # Make sure we can initially write to the test file
+ self.run_check(0, None, None,
+ [ self.TOOL,
+ '-f', self.OUTPUT_FILE,
+ 'add', 'user1', 'pass1'
+ ])
+
+ @unittest.skipIf(os.getuid() == 0,
+ 'test cannot be run as root user')
+ def test_bad_file_permissions(self):
+ """
+ Check for graceful handling of bad file argument
+ """
+ # Create the test file
+ self.run_check(0, None, None,
+ [ self.TOOL,
+ '-f', self.OUTPUT_FILE,
+ 'add', 'user1', 'pass1'
+ ])
+
+ # Make it non-writable (don't worry about cleanup, the
+ # file should be deleted after each test anyway
+ os.chmod(self.OUTPUT_FILE, stat.S_IRUSR)
+ self.run_check(2,
+ 'Using accounts file: test_users.csv\n'
+ 'Error accessing test_users.csv: Permission denied\n',
+ '',
+ [ self.TOOL,
+ '-f', self.OUTPUT_FILE,
+ 'add', 'user2', 'pass1'
+ ])
+
+ self.run_check(2,
+ 'Using accounts file: test_users.csv\n'
+ 'Error accessing test_users.csv: Permission denied\n',
+ '',
+ [ self.TOOL,
+ '-f', self.OUTPUT_FILE,
+ 'delete', 'user1'
+ ])
+
+ # Making it write-only should have the same effect
+ os.chmod(self.OUTPUT_FILE, stat.S_IWUSR)
+ self.run_check(2,
+ 'Using accounts file: test_users.csv\n'
+ 'Error accessing test_users.csv: Permission denied\n',
+ '',
+ [ self.TOOL,
+ '-f', self.OUTPUT_FILE,
+ 'add', 'user2', 'pass1'
+ ])
+
+ self.run_check(2,
+ 'Using accounts file: test_users.csv\n'
+ 'Error accessing test_users.csv: Permission denied\n',
+ '',
+ [ self.TOOL,
+ '-f', self.OUTPUT_FILE,
+ 'delete', 'user1'
+ ])
+
+ def test_missing_fields(self):
+ """
+ Test that an invalid csv file is handled gracefully
+ """
+ # Valid but incomplete csv; should be handled
+ # correctly
+ with open(self.OUTPUT_FILE, 'w', newline='') as f:
+ f.write('onlyuserfield\n')
+ f.write('userfield,saltfield\n')
+ f.write(',emptyuserfield,passwordfield\n')
+
+ self.run_check(0, None, None,
+ [ self.TOOL,
+ '-f', self.OUTPUT_FILE,
+ 'add', 'user1', 'pass1'
+ ])
+ self.run_check(0, None, None,
+ [ self.TOOL,
+ '-f', self.OUTPUT_FILE,
+ 'delete', 'onlyuserfield'
+ ])
+ self.run_check(0, None, None,
+ [ self.TOOL,
+ '-f', self.OUTPUT_FILE,
+ 'delete', ''
+ ])
+
+ def test_bad_data(self):
+ # I can only think of one invalid format, an unclosed string
+ with open(self.OUTPUT_FILE, 'w', newline='') as f:
+ f.write('a,"\n')
+ # Different versions of the csv library return different errors.
+ # So we need to check the output in a little more complex way.
+ # We ask the run_check not to check the output and check it
+ # ourselves.
+ (stdout, stderr) = self.run_check(2, None,
+ '',
+ [ self.TOOL,
+ '-f', self.OUTPUT_FILE,
+ 'add', 'user1', 'pass1'
+ ])
+ # This looks little bit awkward, but is probably easiest with
+ # just 2 known possibilities. If there are more, we'll have to
+ # think of something else.
+ self.assertTrue(stdout ==
+ 'Using accounts file: test_users.csv\n'
+ 'Error parsing csv file: newline inside string\n' or
+ stdout ==
+ 'Using accounts file: test_users.csv\n'
+ 'Error parsing csv file: unexpected end of data\n')
+
+
+
+if __name__== '__main__':
+ unittest.main()
+
diff --git a/src/bin/xfrin/b10-xfrin.xml b/src/bin/xfrin/b10-xfrin.xml
index 591a85a..49b05ed 100644
--- a/src/bin/xfrin/b10-xfrin.xml
+++ b/src/bin/xfrin/b10-xfrin.xml
@@ -111,7 +111,7 @@ in separate zonemgr process.
<varname>class</varname> (defaults to <quote>IN</quote>),
<varname>master_addr</varname> (the zone master to transfer from),
<varname>master_port</varname> (defaults to 53),
- <varname>use_ixfr</varname> (defaults to false), and
+ <varname>request_ixfr</varname> (defaults to yes), and
<varname>tsig_key</varname> (optional TSIG key name to use).
The <varname>tsig_key</varname> is specified using a name that
corresponds to one of the TSIG keys configured in the global
@@ -231,127 +231,113 @@ operation
<variablelist>
<varlistentry>
- <term><replaceable>zonename</replaceable></term>
+ <term><replaceable>classname</replaceable></term>
<listitem><simpara>
- A actual zone name or special zone name <quote>_SERVER_</quote>
- representing an entire server
+ An actual RR class name of the zone, e.g. IN, CH, and HS
</simpara>
<variablelist>
<varlistentry>
- <term>soaoutv4</term>
+ <term><replaceable>zonename</replaceable></term>
<listitem><simpara>
- Number of IPv4 SOA queries sent from Xfrin
- </simpara></listitem>
- </varlistentry>
+ An actual zone name or special zone name
+ <quote>_SERVER_</quote> representing the entire server
+ </simpara>
+ <variablelist>
- <varlistentry>
- <term>soaoutv6</term>
- <listitem><simpara>
- Number of IPv6 SOA queries sent from Xfrin
- </simpara></listitem>
- </varlistentry>
+ <varlistentry>
+ <term>soaoutv4</term>
+ <listitem><simpara>
+ Number of IPv4 SOA queries sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
- <varlistentry>
- <term>axfrreqv4</term>
- <listitem><simpara>
- Number of IPv4 AXFR requests sent from Xfrin
- </simpara></listitem>
- </varlistentry>
+ <varlistentry>
+ <term>soaoutv6</term>
+ <listitem><simpara>
+ Number of IPv6 SOA queries sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
- <varlistentry>
- <term>axfrreqv6</term>
- <listitem><simpara>
- Number of IPv6 AXFR requests sent from Xfrin
- </simpara></listitem>
- </varlistentry>
+ <varlistentry>
+ <term>axfrreqv4</term>
+ <listitem><simpara>
+ Number of IPv4 AXFR requests sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
- <varlistentry>
- <term>ixfrreqv4</term>
- <listitem><simpara>
- Number of IPv4 IXFR requests sent from Xfrin
- </simpara></listitem>
- </varlistentry>
+ <varlistentry>
+ <term>axfrreqv6</term>
+ <listitem><simpara>
+ Number of IPv6 AXFR requests sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
- <varlistentry>
- <term>ixfrreqv6</term>
- <listitem><simpara>
- Number of IPv6 IXFR requests sent from Xfrin
- </simpara></listitem>
- </varlistentry>
+ <varlistentry>
+ <term>ixfrreqv4</term>
+ <listitem><simpara>
+ Number of IPv4 IXFR requests sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
- <varlistentry>
- <term>xfrsuccess</term>
- <listitem><simpara>
- Number of zone transfer requests succeeded
- </simpara></listitem>
- </varlistentry>
+ <varlistentry>
+ <term>ixfrreqv6</term>
+ <listitem><simpara>
+ Number of IPv6 IXFR requests sent from Xfrin
+ </simpara></listitem>
+ </varlistentry>
- <varlistentry>
- <term>xfrfail</term>
- <listitem><simpara>
- Number of zone transfer requests failed
- </simpara></listitem>
- </varlistentry>
+ <varlistentry>
+ <term>xfrsuccess</term>
+ <listitem><simpara>
+ Number of zone transfer requests succeeded.
+ These include the case where the zone turns
+ out to be the latest as a result of an
+ initial SOA query (and there is actually no
+ AXFR or IXFR transaction).
+ </simpara></listitem>
+ </varlistentry>
- <varlistentry>
- <term>time_to_ixfr</term>
- <listitem><simpara>
- Elapsed time in second to do the last IXFR
- </simpara></listitem>
- </varlistentry>
+ <varlistentry>
+ <term>xfrfail</term>
+ <listitem><simpara>
+ Number of zone transfer requests failed
+ </simpara></listitem>
+ </varlistentry>
- <varlistentry>
- <term>time_to_axfr</term>
- <listitem><simpara>
- Elapsed time in second to do the last AXFR
- </simpara></listitem>
- </varlistentry>
+ <varlistentry>
+ <term>last_axfr_duration</term>
+ <listitem><simpara>
+ Duration in seconds of the last successful AXFR. 0.0
+ means no successful AXFR done or means a successful AXFR
+ done in less than a microsecond. If an AXFR is aborted
+ due to some failure, this duration won't be updated.
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>last_ixfr_duration</term>
+ <listitem><simpara>
+ Duration in seconds of the last successful IXFR. 0.0
+ means no successful IXFR done or means a successful IXFR
+ done in less than a microsecond. If an IXFR is aborted
+ due to some failure, this duration won't be updated.
+ </simpara></listitem>
+ </varlistentry>
+
+ </variablelist>
+ </listitem>
+ </varlistentry><!-- end of zonename -->
</variablelist>
</listitem>
- </varlistentry><!-- end of zonename -->
+ </varlistentry><!-- end of classname -->
</variablelist>
</listitem>
</varlistentry><!-- end of zones -->
<varlistentry>
- <term>ixfr_running</term>
- <listitem><simpara>
- Number of IXFRs in progress
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>axfr_running</term>
- <listitem><simpara>
- Number of AXFRs in progress
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>ixfr_deferred</term>
- <listitem><simpara>
- Number of deferred IXFRs
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>axfr_deferred</term>
- <listitem><simpara>
- Number of deferred AXFRs
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>soa_in_progress</term>
- <listitem><simpara>
- Number of SOA queries in progress
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
<term>socket</term>
<listitem><simpara>
A directory name of socket statistics
@@ -359,9 +345,9 @@ operation
<variablelist>
<varlistentry>
- <term>ipv4 | ipv6</term>
+ <term><replaceable>ipversion</replaceable></term>
<listitem><simpara>
- A directory name of IPv4 or IPv6 statistics
+ A directory name of an IP version as ipv4 or ipv6
</simpara>
<variablelist>
@@ -436,9 +422,11 @@ operation
</variablelist>
<para>
- In per-zone counters the special zone name <quote>_SERVER_</quote> exists.
- It doesn't mean a specific zone. It represents an entire server and its
- value means a total count of all zones.
+ In per-zone counters the special zone name <quote>_SERVER_</quote>
+ exists.
+ It doesn't mean a specific zone. It represents the entire server
+ and the counter value of this special zone is the total of the
+ same counter for all zones.
</para>
</refsect1>
diff --git a/src/bin/xfrin/tests/Makefile.am b/src/bin/xfrin/tests/Makefile.am
index c1ca278..66a13f3 100644
--- a/src/bin/xfrin/tests/Makefile.am
+++ b/src/bin/xfrin/tests/Makefile.am
@@ -8,7 +8,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
else
# Some systems need the ds path even if not all paths are necessary
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index 8de97bf..4dcbb69 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -19,6 +19,7 @@ import shutil
import socket
import sys
import io
+from datetime import datetime
from isc.testutils.tsigctx_mock import MockTSIGContext
from isc.testutils.ccsession_mock import MockModuleCCSession
from isc.testutils.rrset_utils import *
@@ -128,20 +129,17 @@ class XfrinTestException(Exception):
class XfrinTestTimeoutException(Exception):
pass
-class MockCC(MockModuleCCSession):
- def get_default_value(self, identifier):
- # The returned values should be identical to the spec file
- # XXX: these should be retrieved from the spec file
- # (see MyCCSession of xfrout_test.py.in)
- if identifier == "zones/master_port":
- return TEST_MASTER_PORT
- if identifier == "zones/class":
- return TEST_RRCLASS_STR
- if identifier == "zones/use_ixfr":
- return False
+class MockCC(MockModuleCCSession, ConfigData):
+ def __init__(self):
+ super().__init__()
+ module_spec = isc.config.module_spec_from_file(
+ xfrin.SPECFILE_LOCATION)
+ ConfigData.__init__(self, module_spec)
+ # For inspection
+ self.added_remote_modules = []
def add_remote_config_by_name(self, name, callback):
- pass
+ self.added_remote_modules.append((name, callback))
def get_remote_config_value(self, module, identifier):
if module == 'tsig_keys' and identifier == 'keys':
@@ -241,6 +239,37 @@ class MockDataSourceClient():
self.committed_diffs.append(self.diffs)
self.diffs = []
+ def create_zone(self, zone_name):
+ # pretend it just succeeds
+ pass
+
+class MockDataSrcClientsMgr():
+ def __init__(self):
+ # Default faked result of get_client_list, customizable by tests
+ self.found_datasrc_client_list = self
+
+ # Default faked result of find(), customizable by tests
+ self.found_datasrc_client = MockDataSourceClient()
+
+ self.reconfigure_param = [] # for inspection
+
+ def get_client_list(self, rrclass):
+ return self.found_datasrc_client_list
+
+ def reconfigure(self, arg1, arg2):
+ # the only current test simply needs to know this is called with
+ # the expected arguments and exceptions are handled. if we need more
+ # variations in tests, this mock method should be extended.
+ self.reconfigure_param.append((arg1, arg2))
+ raise isc.server_common.datasrc_clients_mgr.ConfigError(
+ 'reconfigure failure')
+
+ def find(self, zone_name, want_exact_match, want_finder):
+ """Pretending find method on the object returned by get_clinet_list"""
+ if issubclass(type(self.found_datasrc_client), Exception):
+ raise self.found_datasrc_client
+ return self.found_datasrc_client, None, None
+
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
@@ -250,35 +279,29 @@ class MockXfrin(Xfrin):
check_command_hook = None
def _cc_setup(self):
- self._tsig_key = None
self._module_cc = MockCC()
- init_keyring(self._module_cc)
- pass
-
- def _get_db_file(self):
- pass
def _cc_check_command(self):
self._shutdown_event.set()
if MockXfrin.check_command_hook:
MockXfrin.check_command_hook()
- def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo,
- tsig_key, request_type, check_soa=True):
+ def xfrin_start(self, zone_name, rrclass, master_addrinfo,
+ tsig_key, request_ixfr, 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]
- self.xfrin_started_request_type = request_type
- return Xfrin.xfrin_start(self, zone_name, rrclass, None,
- master_addrinfo, tsig_key,
- request_type, check_soa)
+ self.xfrin_started_request_ixfr = request_ixfr
+ return Xfrin.xfrin_start(self, zone_name, rrclass, master_addrinfo,
+ tsig_key, request_ixfr, check_soa)
class MockXfrinConnection(XfrinConnection):
def __init__(self, sock_map, zone_name, rrclass, datasrc_client,
shutdown_event, master_addr, tsig_key=None):
super().__init__(sock_map, zone_name, rrclass, MockDataSourceClient(),
- shutdown_event, master_addr, TEST_DB_FILE)
+ shutdown_event, master_addr, begin_soa_rrset,
+ xfrin.Counters(xfrin.SPECFILE_LOCATION))
self.query_data = b''
self.reply_data = b''
self.force_time_out = False
@@ -607,7 +630,7 @@ class TestXfrinIXFRAdd(TestXfrinState):
# signed, rejecting it.
self.assertRaises(xfrin.XfrinProtocolError, self.state.handle_rr,
self.conn, end_soa_rrset)
- # No diffs were commited
+ # No diffs were committed
self.assertEqual([], self.conn._datasrc_client.committed_diffs)
def test_handle_out_of_sync(self):
@@ -717,7 +740,7 @@ class TestXfrinConnection(unittest.TestCase):
self.sock_map = {}
self.conn = MockXfrinConnection(self.sock_map, TEST_ZONE_NAME,
TEST_RRCLASS, None, threading.Event(),
- TEST_MASTER_IPV4_ADDRINFO)
+ self._master_addrinfo)
self.conn.init_socket()
self.soa_response_params = {
'questions': [example_soa_question],
@@ -749,6 +772,10 @@ class TestXfrinConnection(unittest.TestCase):
os.remove(TEST_DB_FILE)
xfrin.check_zone = self.__orig_check_zone
+ @property
+ def _master_addrinfo(self):
+ return TEST_MASTER_IPV4_ADDRINFO
+
def __check_zone(self, name, rrclass, rrsets, callbacks):
'''
A mock function used instead of dns.check_zone.
@@ -841,7 +868,11 @@ class TestXfrinConnection(unittest.TestCase):
'''
self.conn._zone_name = zone_name
- self.conn._zone_soa = self.conn._get_zone_soa()
+ try:
+ self.conn._zone_soa = xfrin._get_zone_soa(
+ self.conn._datasrc_client, zone_name, self.conn._rrclass)
+ except XfrinException: # zone doesn't exist
+ self.conn._zone_soa = None
class TestAXFR(TestXfrinConnection):
def setUp(self):
@@ -853,8 +884,6 @@ class TestAXFR(TestXfrinConnection):
def tearDown(self):
time.time = self.orig_time_time
- # clear all statistics counters after each test
- self.conn._counters.clear_all()
super().tearDown()
def __create_mock_tsig(self, key, error, has_last_signature=True):
@@ -967,7 +996,9 @@ class TestAXFR(TestXfrinConnection):
RRType.IXFR)
self._set_test_zone(Name('dup-soa.example'))
- self.conn._zone_soa = self.conn._get_zone_soa()
+ self.conn._zone_soa = xfrin._get_zone_soa(self.conn._datasrc_client,
+ self.conn._zone_name,
+ self.conn._rrclass)
self.assertRaises(XfrinException, self.conn._create_query,
RRType.IXFR)
@@ -975,7 +1006,7 @@ class TestAXFR(TestXfrinConnection):
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.
+ # tests regarding the TSIG protocol are done in the isc.dns module.
msg = Message(Message.PARSE)
msg.from_wire(data)
return msg.get_tsig_record() is not None
@@ -1068,38 +1099,22 @@ class TestAXFR(TestXfrinConnection):
self.conn._handle_xfrin_responses)
def test_ipver_str(self):
- orig_socket = self.conn.socket
- class FakeSocket(): pass
- self.conn.socket = FakeSocket()
- self.conn.socket.family = socket.AF_INET
- self.assertEqual(self.conn.get_ipver_str(), 'v4')
- self.conn.socket.family = socket.AF_INET6
- self.assertEqual(self.conn.get_ipver_str(), 'v6')
- self.conn.socket.family = None
- self.assertIsNone(self.conn.get_ipver_str())
- self.conn.socket = orig_socket
+ addrs = (((socket.AF_INET, socket.SOCK_STREAM), 'v4'),
+ ((socket.AF_INET6, socket.SOCK_STREAM), 'v6'),
+ ((socket.AF_UNIX, socket.SOCK_STREAM), None))
+ for (info, ver) in addrs:
+ c = MockXfrinConnection({}, TEST_ZONE_NAME, RRClass.CH, None,
+ threading.Event(), info)
+ if ver is not None:
+ self.assertEqual(ver, c._get_ipver_str())
+ else:
+ self.assertRaises(ValueError, c._get_ipver_str)
def test_soacheck(self):
# we need to defer the creation until we know the QID, which is
# determined in _check_soa_serial(), so we use response_generator.
self.conn.response_generator = self._create_soa_response_data
- # check the statistics counters
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'zones',
- TEST_ZONE_NAME_STR, 'soaoutv4')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'zones',
- TEST_ZONE_NAME_STR,'soaoutv6')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'soa_in_progress')
self.assertEqual(self.conn._check_soa_serial(), XFRIN_OK)
- self.assertEqual(1, self.conn._counters.get('zones',
- TEST_ZONE_NAME_STR,
- 'soaoutv4'))
- self.assertEqual(0, self.conn._counters.get('zones',
- TEST_ZONE_NAME_STR,
- 'soaoutv6'))
- self.assertEqual(0, self.conn._counters.get('soa_in_progress'))
def test_soacheck_with_bad_response(self):
self.conn.response_generator = self._create_broken_response_data
@@ -1465,34 +1480,6 @@ class TestAXFR(TestXfrinConnection):
def test_do_xfrin(self):
self.conn.response_generator = self._create_normal_response_data
- # check the statistics counters
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'zones',
- TEST_ZONE_NAME_STR, 'axfrreqv4')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'zones',
- TEST_ZONE_NAME_STR, 'axfrreqv6')
-
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'zones',
- TEST_ZONE_NAME_STR, 'ixfrreqv4')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'zones',
- TEST_ZONE_NAME_STR, 'ixfrreqv6')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'zones',
- TEST_ZONE_NAME_STR, 'xfrsuccess')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'zones',
- TEST_ZONE_NAME_STR, 'time_to_axfr')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'ixfr_running')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'axfr_running')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'axfr_deferred')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'ixfr_deferred')
self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
self.assertFalse(self.conn._datasrc_client._journaling_enabled)
@@ -1503,36 +1490,6 @@ class TestAXFR(TestXfrinConnection):
self.assertEqual(0, self.conn._transfer_stats.ixfr_addition_count)
self.assertEqual(177, self.conn._transfer_stats.byte_count)
self.assertGreater(self.conn._transfer_stats.get_running_time(), 0)
- self.assertEqual(1,
- self.conn._counters.get('zones',
- TEST_ZONE_NAME_STR,
- 'axfrreqv4'))
- self.assertEqual(0,
- self.conn._counters.get('zones',
- TEST_ZONE_NAME_STR,
- 'axfrreqv6'))
- self.assertEqual(0,
- self.conn._counters.get('zones',
- TEST_ZONE_NAME_STR,
- 'ixfrreqv4'))
- self.assertEqual(0,
- self.conn._counters.get('zones',
- TEST_ZONE_NAME_STR,
- 'ixfrreqv6'))
- self.assertEqual(1,
- self.conn._counters.get('zones',
- TEST_ZONE_NAME_STR,
- 'xfrsuccess'))
- self.assertGreaterEqual(self.conn._counters.get('zones',
- TEST_ZONE_NAME_STR,
- 'time_to_axfr'),
- 0.0)
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'ixfr_running')
- self.assertEqual(0, self.conn._counters.get('axfr_running'))
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'ixfr_deferred')
- self.assertEqual(0, self.conn._counters.get('axfr_deferred'))
def test_do_xfrin_with_tsig(self):
# use TSIG with a mock context. we fake all verify results to
@@ -1554,29 +1511,8 @@ class TestAXFR(TestXfrinConnection):
self.conn._tsig_ctx_creator = \
lambda key: self.__create_mock_tsig(key, TSIGError.BAD_SIG)
self.conn.response_generator = self._create_normal_response_data
- # check the statistics counters
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'zones',
- TEST_ZONE_NAME_STR, 'xfrfail')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'ixfr_running')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'axfr_running')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'axfr_deferred')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'ixfr_deferred')
self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
self.assertEqual(1, self.conn._tsig_ctx.verify_called)
- self.assertEqual(1, self.conn._counters.get('zones',
- TEST_ZONE_NAME_STR,
- 'xfrfail'))
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'ixfr_running')
- self.assertEqual(0, self.conn._counters.get('axfr_running'))
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get, 'ixfr_deferred')
- self.assertEqual(0, self.conn._counters.get('axfr_deferred'))
def test_do_xfrin_without_last_tsig(self):
# TSIG verify will succeed, but it will pretend the last message is
@@ -2187,31 +2123,185 @@ class TestXFRSessionWithSQLite3(TestXfrinConnection):
'''
self.axfr_failure_check(RRType.AXFR)
- def test_do_axfrin_nozone_sqlite3(self):
- '''AXFR test with an empty SQLite3 DB file, thus no target zone there.
+class TestStatisticsXfrinConn(TestXfrinConnection):
+ '''Test class based on TestXfrinConnection and including paramters
+ and methods related to statistics tests'''
+ def setUp(self):
+ super().setUp()
+ # fake datetime
+ self.__orig_datetime = isc.statistics.counters.datetime
+ self.__orig_start_timer = isc.statistics.counters._start_timer
+ time1 = datetime(2000, 1, 1, 0, 0, 0, 0)
+ time2 = datetime(2000, 1, 1, 0, 0, 0, 1)
+ class FakeDateTime:
+ @classmethod
+ def now(cls): return time2
+ isc.statistics.counters.datetime = FakeDateTime
+ isc.statistics.counters._start_timer = lambda : time1
+ delta = time2 - time1
+ self._const_sec = round(delta.days * 86400 + delta.seconds +
+ delta.microseconds * 1E-6, 6)
+ # List of statistics counter names and expected initial values
+ self.__name_to_counter = (('axfrreqv4', 0),
+ ('axfrreqv6', 0),
+ ('ixfrreqv4', 0),
+ ('ixfrreqv6', 0),
+ ('last_axfr_duration', 0.0),
+ ('last_ixfr_duration', 0.0),
+ ('soaoutv4', 0),
+ ('soaoutv6', 0),
+ ('xfrfail', 0),
+ ('xfrsuccess', 0))
+ self.__zones = 'zones'
- For now, we provide backward compatible behavior: xfrin will create
- the zone (after even setting up the entire schema) in the zone.
- Note: a future version of this test will make it fail.
+ def tearDown(self):
+ super().tearDown()
+ isc.statistics.counters.datetime = self.__orig_datetime
+ isc.statistics.counters._start_timer = self.__orig_start_timer
+
+ @property
+ def _ipver(self):
+ return 'v4'
+
+ def _check_init_statistics(self):
+ '''checks exception being raised if not incremented statistics
+ counter gotten'''
+ for (name, exp) in self.__name_to_counter:
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.conn._counters.get, self.__zones,
+ TEST_ZONE_NAME_STR, name)
+
+ def _check_updated_statistics(self, overwrite):
+ '''checks getting expect values after updating the pairs of
+ statistics counter name and value on to the "overwrite"
+ dictionary'''
+ name2count = dict(self.__name_to_counter)
+ name2count.update(overwrite)
+ for (name, exp) in name2count.items():
+ act = self.conn._counters.get(self.__zones,
+ TEST_RRCLASS_STR,
+ TEST_ZONE_NAME_STR,
+ name)
+ msg = '%s is expected %s but actually %s' % (name, exp, act)
+ self.assertEqual(exp, act, msg=msg)
+
+class TestStatisticsXfrinAXFRv4(TestStatisticsXfrinConn):
+ '''Xfrin AXFR tests for IPv4 to check statistics counters'''
+ def test_soaout(self):
+ '''tests that an soaoutv4 or soaoutv6 counter is incremented
+ when an soa query succeeds'''
+ self.conn.response_generator = self._create_soa_response_data
+ self._check_init_statistics()
+ self.assertEqual(self.conn._check_soa_serial(), XFRIN_OK)
+ self._check_updated_statistics({'soaout' + self._ipver: 1})
- '''
- self.conn._db_file = self.empty_sqlite3db_obj
- self.conn._datasrc_client = DataSourceClient(
- "sqlite3",
- "{ \"database_file\": \"" + self.empty_sqlite3db_obj + "\"}")
- def create_response():
+ def test_axfrreq_xfrsuccess_last_axfr_duration(self):
+ '''tests that axfrreqv4 or axfrreqv6 and xfrsuccess counters
+ and last_axfr_duration timer are incremented when xfr succeeds'''
+ self.conn.response_generator = self._create_normal_response_data
+ self._check_init_statistics()
+ self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
+ self._check_updated_statistics({'axfrreq' + self._ipver: 1,
+ 'xfrsuccess': 1,
+ 'last_axfr_duration': self._const_sec})
+
+ def test_axfrreq_xfrsuccess_last_axfr_duration2(self):
+ '''tests that axfrreqv4 or axfrreqv6 and xfrsuccess counters
+ and last_axfr_duration timer are incremented when raising
+ XfrinZoneUptodate. The exception is treated as success.'''
+ def exception_raiser():
+ raise XfrinZoneUptodate()
+ self.conn._handle_xfrin_responses = exception_raiser
+ self._check_init_statistics()
+ self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
+ self._check_updated_statistics({'axfrreq' + self._ipver: 1,
+ 'xfrsuccess': 1,
+ 'last_axfr_duration':
+ self._const_sec})
+
+ def test_axfrreq_xfrfail(self):
+ '''tests that axfrreqv4 or axfrreqv6 and xfrfail counters are
+ incremented even if some failure exceptions are expected to be
+ raised inside do_xfrin(): XfrinZoneError, XfrinProtocolError,
+ XfrinException, and Exception'''
+ self._check_init_statistics()
+ count = 0
+ for ex in [XfrinZoneError, XfrinProtocolError, XfrinException,
+ Exception]:
+ def exception_raiser():
+ raise ex()
+ self.conn._handle_xfrin_responses = exception_raiser
+ self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+ count += 1
+ self._check_updated_statistics({'axfrreq' + self._ipver: count,
+ 'xfrfail': count})
+
+class TestStatisticsXfrinIXFRv4(TestStatisticsXfrinConn):
+ '''Xfrin IXFR tests for IPv4 to check statistics counters'''
+ def test_ixfrreq_xfrsuccess_last_ixfr_duration(self):
+ '''tests that ixfrreqv4 or ixfrreqv6 and xfrsuccess counters
+ and last_ixfr_duration timer are incremented when xfr succeeds'''
+ def create_ixfr_response():
self.conn.reply_data = self.conn.create_response_data(
questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
- RRType.AXFR)],
- answers=[soa_rrset, self._create_ns(), soa_rrset])
- self.conn.response_generator = create_response
- self._set_test_zone(Name('example.com'))
- self.assertEqual(XFRIN_OK, self.conn.do_xfrin(False, RRType.AXFR))
- self.assertEqual(type(XfrinAXFREnd()),
- type(self.conn.get_xfrstate()))
- self.assertEqual(1234, self.get_zone_serial().get_value())
- self.assertFalse(self.record_exist(Name('dns01.example.com'),
- RRType.A))
+ RRType.IXFR)],
+ answers=[soa_rrset, begin_soa_rrset, soa_rrset, soa_rrset])
+ self.conn.response_generator = create_ixfr_response
+ self._check_init_statistics()
+ self.assertEqual(XFRIN_OK, self.conn.do_xfrin(False, RRType.IXFR))
+ self._check_updated_statistics({'ixfrreq' + self._ipver: 1,
+ 'xfrsuccess': 1,
+ 'last_ixfr_duration':
+ self._const_sec})
+
+ def test_ixfrreq_xfrsuccess_last_ixfr_duration2(self):
+ '''tests that ixfrreqv4 or ixfrreqv6 and xfrsuccess counters
+ and last_ixfr_duration timer are incremented when raising
+ XfrinZoneUptodate. The exception is treated as success.'''
+ def exception_raiser():
+ raise XfrinZoneUptodate()
+ self.conn._handle_xfrin_responses = exception_raiser
+ self._check_init_statistics()
+ self.assertEqual(self.conn.do_xfrin(False, RRType.IXFR), XFRIN_OK)
+ self._check_updated_statistics({'ixfrreq' + self._ipver: 1,
+ 'xfrsuccess': 1,
+ 'last_ixfr_duration':
+ self._const_sec})
+
+ def test_ixfrreq_xfrfail(self):
+ '''tests that ixfrreqv4 or ixfrreqv6 and xfrfail counters are
+ incremented even if some failure exceptions are expected to be
+ raised inside do_xfrin(): XfrinZoneError, XfrinProtocolError,
+ XfrinException, and Exception'''
+ self._check_init_statistics()
+ count = 0
+ for ex in [XfrinZoneError, XfrinProtocolError, XfrinException,
+ Exception]:
+ def exception_raiser():
+ raise ex()
+ self.conn._handle_xfrin_responses = exception_raiser
+ self.assertEqual(self.conn.do_xfrin(False, RRType.IXFR), XFRIN_FAIL)
+ count += 1
+ self._check_updated_statistics({'ixfrreq' + self._ipver: count,
+ 'xfrfail': count})
+
+class TestStatisticsXfrinAXFRv6(TestStatisticsXfrinAXFRv4):
+ '''Same tests as TestStatisticsXfrinAXFRv4 for IPv6'''
+ @property
+ def _master_addrinfo(self):
+ return TEST_MASTER_IPV6_ADDRINFO
+ @property
+ def _ipver(self):
+ return 'v6'
+
+class TestStatisticsIXFRv6(TestStatisticsXfrinIXFRv4):
+ '''Same tests as TestStatisticsXfrinIXFRv4 for IPv6'''
+ @property
+ def _master_addrinfo(self):
+ return TEST_MASTER_IPV6_ADDRINFO
+ @property
+ def _ipver(self):
+ return 'v6'
class TestXfrinRecorder(unittest.TestCase):
def setUp(self):
@@ -2302,7 +2392,7 @@ class TestXfrinProcess(unittest.TestCase):
master_addrinfo, tsig_key)
# An awkward check that would specifically identify an old bug
- # where initialziation of XfrinConnection._tsig_ctx_creator caused
+ # where initialization of XfrinConnection._tsig_ctx_creator caused
# self reference and subsequently led to reference leak.
orig_ref = sys.getrefcount(conn)
conn._tsig_ctx_creator = None
@@ -2320,16 +2410,16 @@ class TestXfrinProcess(unittest.TestCase):
# Normal, successful case. We only check that things are cleaned up
# at the tearDown time.
process_xfrin(self, self, TEST_ZONE_NAME, TEST_RRCLASS, None, None,
- self.master, False, None, RRType.AXFR,
- self.create_xfrinconn)
+ None, self.master, False, None,
+ ZoneInfo.REQUEST_IXFR_DISABLED, self.create_xfrinconn)
def test_process_xfrin_exception_on_connect(self):
# connect_to_master() will raise an exception. Things must still be
# cleaned up.
self.do_raise_on_connect = True
process_xfrin(self, self, TEST_ZONE_NAME, TEST_RRCLASS, None, None,
- self.master, False, None, RRType.AXFR,
- self.create_xfrinconn)
+ None, self.master, False, None,
+ ZoneInfo.REQUEST_IXFR_DISABLED, self.create_xfrinconn)
def test_process_xfrin_exception_on_close(self):
# connect() will result in exception, and even the cleanup close()
@@ -2338,38 +2428,48 @@ class TestXfrinProcess(unittest.TestCase):
self.do_raise_on_connect = True
self.do_raise_on_close = True
process_xfrin(self, self, TEST_ZONE_NAME, TEST_RRCLASS, None, None,
- self.master, False, None, RRType.AXFR,
- self.create_xfrinconn)
+ None, self.master, False, None,
+ ZoneInfo.REQUEST_IXFR_DISABLED, self.create_xfrinconn)
def test_process_xfrin_exception_on_publish(self):
# xfr succeeds but notifying the zonemgr fails with exception.
# everything must still be cleaned up.
self.do_raise_on_publish = True
process_xfrin(self, self, TEST_ZONE_NAME, TEST_RRCLASS, None, None,
- self.master, False, None, RRType.AXFR,
- self.create_xfrinconn)
+ None, self.master, False, None,
+ ZoneInfo.REQUEST_IXFR_DISABLED, self.create_xfrinconn)
class TestXfrin(unittest.TestCase):
def setUp(self):
# redirect output
self.stderr_backup = sys.stderr
sys.stderr = open(os.devnull, 'w')
+ self.__orig_DataSrcClientsMgr = xfrin.DataSrcClientsMgr
+ xfrin.DataSrcClientsMgr = MockDataSrcClientsMgr
+
self.xfr = MockXfrin()
self.args = {}
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):
+ xfrin.DataSrcClientsMgr = self.__orig_DataSrcClientsMgr
self.assertFalse(self.xfr._module_cc.stopped);
self.xfr.shutdown()
self.assertTrue(self.xfr._module_cc.stopped);
sys.stderr.close()
sys.stderr = self.stderr_backup
+ def test_init(self):
+ """Check some initial configuration after construction"""
+ # data source "module" should have been registrered as a necessary
+ # remote config
+ self.assertEqual([('data_sources', self.xfr._datasrc_config_handler)],
+ self.xfr._module_cc.added_remote_modules)
+
def _do_parse_zone_name_class(self):
return self.xfr._parse_zone_name_and_class(self.args)
@@ -2380,12 +2480,10 @@ class TestXfrin(unittest.TestCase):
def test_parse_cmd_params(self):
name, rrclass = self._do_parse_zone_name_class()
master_addrinfo = self._do_parse_master_port()
- db_file = self.args.get('db_file')
self.assertEqual(master_addrinfo[2][1], int(TEST_MASTER_PORT))
self.assertEqual(name, TEST_ZONE_NAME)
self.assertEqual(rrclass, TEST_RRCLASS)
self.assertEqual(master_addrinfo[2][0], TEST_MASTER_IPV4_ADDRESS)
- self.assertEqual(db_file, TEST_DB_FILE)
def test_parse_cmd_params_default_port(self):
del self.args['port']
@@ -2443,10 +2541,13 @@ 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)
- # By default we use AXFR (for now)
- self.assertEqual(RRType.AXFR, self.xfr.xfrin_started_request_type)
+ self.assertEqual(self.args['master'],
+ self.xfr.xfrin_started_master_addr)
+ self.assertEqual(int(self.args['port']),
+ self.xfr.xfrin_started_master_port)
+ # retransfer always uses AXFR
+ self.assertEqual(ZoneInfo.REQUEST_IXFR_DISABLED,
+ self.xfr.xfrin_started_request_ixfr)
def test_command_handler_retransfer_short_command1(self):
# try it when only specifying the zone name (of unknown zone)
@@ -2530,7 +2631,7 @@ class TestXfrin(unittest.TestCase):
# 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
+ # make sure the # xfrs would exceed the quota
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",
@@ -2541,13 +2642,36 @@ class TestXfrin(unittest.TestCase):
self.assertEqual(self.xfr.command_handler("retransfer",
self.args)['result'][0], 1)
- def test_command_handler_retransfer_nomodule(self):
- dns_module = sys.modules['pydnspp'] # this must exist
- del sys.modules['pydnspp']
- self.assertEqual(self.xfr.command_handler("retransfer",
- self.args)['result'][0], 1)
- # sys.modules is global, so we must recover it
- sys.modules['pydnspp'] = dns_module
+ def test_command_handler_retransfer_datasrc_error(self):
+ # Failure cases due to various errors at the data source (config/data)
+ # level
+
+ # No data source client list for the RR class
+ self.xfr._datasrc_clients_mgr.found_datasrc_client_list = None
+ self.assertEqual(1, self.xfr.command_handler("retransfer",
+ self.args)['result'][0])
+
+ # No data source client for the zone name
+ self.xfr._datasrc_clients_mgr.found_datasrc_client_list = \
+ self.xfr._datasrc_clients_mgr # restore the original
+ self.xfr._datasrc_clients_mgr.found_datasrc_client = None
+ self.assertEqual(1, self.xfr.command_handler("retransfer",
+ self.args)['result'][0])
+
+ # list.find() raises an exception
+ self.xfr._datasrc_clients_mgr.found_datasrc_client = \
+ isc.datasrc.Error('test exception')
+ self.assertEqual(1, self.xfr.command_handler("retransfer",
+ self.args)['result'][0])
+
+ # datasrc.find() raises an exception
+ class RaisingkDataSourceClient(MockDataSourceClient):
+ def find_zone(self, zone_name):
+ raise isc.datasrc.Error('test exception')
+ self.xfr._datasrc_clients_mgr.found_datasrc_client = \
+ RaisingkDataSourceClient()
+ self.assertEqual(1, self.xfr.command_handler("retransfer",
+ self.args)['result'][0])
def test_command_handler_refresh(self):
# at this level, refresh is no different than retransfer.
@@ -2559,8 +2683,9 @@ class TestXfrin(unittest.TestCase):
self.xfr.xfrin_started_master_addr)
self.assertEqual(int(TEST_MASTER_PORT),
self.xfr.xfrin_started_master_port)
- # By default we use AXFR (for now)
- self.assertEqual(RRType.AXFR, self.xfr.xfrin_started_request_type)
+ # By default we use IXFR (with AXFR fallback)
+ self.assertEqual(ZoneInfo.REQUEST_IXFR_FIRST,
+ self.xfr.xfrin_started_request_ixfr)
def test_command_handler_notify(self):
# at this level, refresh is no different than retransfer.
@@ -2627,8 +2752,7 @@ class TestXfrin(unittest.TestCase):
ans = isc.config.parse_answer(
self.xfr.command_handler("getstats", None))
self.assertEqual(0, ans[0])
- self.assertTrue(module_spec.validate_statistics(
- False, ans[1]))
+ self.assertTrue(module_spec.validate_statistics(False, ans[1]))
def _check_zones_config(self, config_given):
if 'transfers_in' in config_given:
@@ -2644,12 +2768,19 @@ class TestXfrin(unittest.TestCase):
Name(zone_config['tsig_key']).to_text())
else:
self.assertIsNone(zone_info.tsig_key_name)
- if 'use_ixfr' in zone_config and\
- zone_config.get('use_ixfr'):
- self.assertTrue(zone_info.use_ixfr)
- else:
- # if not set, should default to False
- self.assertFalse(zone_info.use_ixfr)
+ if ('request_ixfr' in zone_config and
+ zone_config.get('request_ixfr')):
+ cfg_val = zone_config.get('request_ixfr')
+ val = zone_info.request_ixfr
+ if cfg_val == 'yes':
+ self.assertEqual(ZoneInfo.REQUEST_IXFR_FIRST, val)
+ elif cfg_val == 'no':
+ self.assertEqual(ZoneInfo.REQUEST_IXFR_DISABLED, val)
+ elif cfg_val == 'only':
+ self.assertEqual(ZoneInfo.REQUEST_IXFR_ONLY, val)
+ else: # check the default
+ self.assertEqual(ZoneInfo.REQUEST_IXFR_FIRST,
+ zone_info.request_ixfr)
def test_config_handler_zones(self):
# This test passes a number of good and bad configs, and checks whether
@@ -2661,7 +2792,7 @@ class TestXfrin(unittest.TestCase):
{ 'name': 'test.example.',
'master_addr': '192.0.2.1',
'master_port': 53,
- 'use_ixfr': False
+ 'request_ixfr': 'yes'
}
]}
self.assertEqual(self.xfr.config_handler(config1)['result'][0], 0)
@@ -2673,12 +2804,24 @@ class TestXfrin(unittest.TestCase):
'master_addr': '192.0.2.2',
'master_port': 53,
'tsig_key': "example.com:SFuWd/q99SzF8Yzd1QbB9g==",
- 'use_ixfr': True
+ 'request_ixfr': 'no'
}
]}
self.assertEqual(self.xfr.config_handler(config2)['result'][0], 0)
self._check_zones_config(config2)
+ config3 = {'transfers_in': 4,
+ 'zones': [
+ { 'name': 'test.example.',
+ 'master_addr': '192.0.2.2',
+ 'master_port': 53,
+ 'tsig_key': "example.com:SFuWd/q99SzF8Yzd1QbB9g==",
+ 'request_ixfr': 'only'
+ }
+ ]}
+ self.assertEqual(self.xfr.config_handler(config3)['result'][0], 0)
+ self._check_zones_config(config3)
+
# test that configuring the zone multiple times fails
zones = { 'transfers_in': 5,
'zones': [
@@ -2693,7 +2836,7 @@ class TestXfrin(unittest.TestCase):
]}
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)
+ self._check_zones_config(config3)
zones = { 'zones': [
{ 'name': 'test.example.',
@@ -2703,7 +2846,7 @@ class TestXfrin(unittest.TestCase):
}
]}
self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
- self._check_zones_config(config2)
+ self._check_zones_config(config3)
zones = { 'zones': [
{ 'master_addr': '192.0.2.4',
@@ -2712,7 +2855,7 @@ class TestXfrin(unittest.TestCase):
]}
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)
+ self._check_zones_config(config3)
zones = { 'zones': [
{ 'name': 'bad..zone.',
@@ -2722,7 +2865,7 @@ class TestXfrin(unittest.TestCase):
]}
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)
+ self._check_zones_config(config3)
zones = { 'zones': [
{ 'name': '',
@@ -2732,7 +2875,7 @@ class TestXfrin(unittest.TestCase):
]}
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)
+ self._check_zones_config(config3)
zones = { 'zones': [
{ 'name': 'test.example',
@@ -2742,7 +2885,7 @@ class TestXfrin(unittest.TestCase):
]}
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)
+ self._check_zones_config(config3)
zones = { 'zones': [
{ 'name': 'test.example',
@@ -2752,7 +2895,7 @@ class TestXfrin(unittest.TestCase):
]}
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)
+ self._check_zones_config(config3)
zones = { 'zones': [
{ 'name': 'test.example',
@@ -2764,7 +2907,7 @@ class TestXfrin(unittest.TestCase):
]}
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)
+ self._check_zones_config(config3)
# let's also add a zone that is correct too, and make sure
# that the new config is not partially taken
@@ -2781,209 +2924,120 @@ class TestXfrin(unittest.TestCase):
]}
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)
+ self._check_zones_config(config3)
+
+ # invalid request_ixfr value
+ zones = { 'zones': [
+ { 'name': 'test.example',
+ 'master_addr': '192.0.2.7',
+ 'request_ixfr': 'bad value'
+ }
+ ]}
+ 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(config3)
def test_config_handler_zones_default(self):
# Checking it some default config values apply. Using a separate
# test case for a fresh xfr object.
config = { 'zones': [
{ 'name': 'test.example.',
- 'master_addr': '192.0.2.1',
- 'master_port': 53,
+ 'master_addr': '192.0.2.1',
+ 'master_port': 53,
}
]}
self.assertEqual(self.xfr.config_handler(config)['result'][0], 0)
self._check_zones_config(config)
- def common_ixfr_setup(self, xfr_mode, use_ixfr, tsig_key_str = None):
+ def test_config_handler_use_ixfr(self):
+ # use_ixfr was deprecated and explicitly rejected for now.
+ config = { 'zones': [
+ { 'name': 'test.example.',
+ 'master_addr': '192.0.2.1',
+ 'master_port': 53,
+ 'use_ixfr': True
+ }
+ ]}
+ self.assertEqual(self.xfr.config_handler(config)['result'][0], 1)
+
+ def common_ixfr_setup(self, xfr_mode, request_ixfr, tsig_key_str=None):
# This helper method explicitly sets up a zone configuration with
- # use_ixfr, and invokes either retransfer or refresh.
+ # request_ixfr, and invokes either retransfer or refresh.
# Shared by some of the following test cases.
config = {'zones': [
{'name': 'example.com.',
'master_addr': '192.0.2.1',
'tsig_key': tsig_key_str,
- 'use_ixfr': use_ixfr}]}
+ 'request_ixfr': request_ixfr}]}
self.assertEqual(self.xfr.config_handler(config)['result'][0], 0)
self.assertEqual(self.xfr.command_handler(xfr_mode,
self.args)['result'][0], 0)
def test_command_handler_retransfer_ixfr_enabled(self):
- # If IXFR is explicitly enabled in config, IXFR will be used
- self.common_ixfr_setup('retransfer', True)
- self.assertEqual(RRType.IXFR, self.xfr.xfrin_started_request_type)
+ # retransfer always uses AXFR (disabling IXFR), regardless of
+ # request_ixfr value
+ self.common_ixfr_setup('retransfer', 'yes')
+ self.assertEqual(ZoneInfo.REQUEST_IXFR_DISABLED,
+ self.xfr.xfrin_started_request_ixfr)
def test_command_handler_refresh_ixfr_enabled(self):
- # Same for refresh
- self.common_ixfr_setup('refresh', True)
- self.assertEqual(RRType.IXFR, self.xfr.xfrin_started_request_type)
+ # for refresh, it honors zone configuration if defined (the default
+ # case is covered in test_command_handler_refresh
+ self.common_ixfr_setup('refresh', 'no')
+ self.assertEqual(ZoneInfo.REQUEST_IXFR_DISABLED,
+ self.xfr.xfrin_started_request_ixfr)
def test_command_handler_retransfer_with_tsig(self):
- self.common_ixfr_setup('retransfer', False, 'example.com.key')
- self.assertEqual(RRType.AXFR, self.xfr.xfrin_started_request_type)
+ self.common_ixfr_setup('retransfer', 'no', 'example.com.key')
+ self.assertEqual(ZoneInfo.REQUEST_IXFR_DISABLED,
+ self.xfr.xfrin_started_request_ixfr)
def test_command_handler_retransfer_with_tsig_bad_key(self):
# bad keys should not reach xfrin, but should they somehow,
# they are ignored (and result in 'key not found' + error log).
self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
- 'retransfer', False, 'bad.key')
+ 'retransfer', 'no', 'bad.key')
def test_command_handler_retransfer_with_tsig_unknown_key(self):
self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
- 'retransfer', False, 'no.such.key')
+ 'retransfer', 'no', 'no.such.key')
def test_command_handler_refresh_with_tsig(self):
- self.common_ixfr_setup('refresh', False, 'example.com.key')
- self.assertEqual(RRType.AXFR, self.xfr.xfrin_started_request_type)
+ self.common_ixfr_setup('refresh', 'no', 'example.com.key')
+ self.assertEqual(ZoneInfo.REQUEST_IXFR_DISABLED,
+ self.xfr.xfrin_started_request_ixfr)
def test_command_handler_refresh_with_tsig_bad_key(self):
# bad keys should not reach xfrin, but should they somehow,
# they are ignored (and result in 'key not found' + error log).
self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
- 'refresh', False, 'bad.key')
+ 'refresh', 'no', 'bad.key')
def test_command_handler_refresh_with_tsig_unknown_key(self):
self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
- 'refresh', False, 'no.such.key')
+ 'refresh', 'no', 'no.such.key')
def test_command_handler_retransfer_ixfr_disabled(self):
# Similar to the previous case, but explicitly disabled. AXFR should
# be used.
- self.common_ixfr_setup('retransfer', False)
- self.assertEqual(RRType.AXFR, self.xfr.xfrin_started_request_type)
+ self.common_ixfr_setup('retransfer', 'no')
+ self.assertEqual(ZoneInfo.REQUEST_IXFR_DISABLED,
+ self.xfr.xfrin_started_request_ixfr)
def test_command_handler_refresh_ixfr_disabled(self):
# Same for refresh
- self.common_ixfr_setup('refresh', False)
- self.assertEqual(RRType.AXFR, self.xfr.xfrin_started_request_type)
-
-class TestXfrinMemoryZones(unittest.TestCase):
- def setUp(self):
- self.xfr = MockXfrin()
- # Configuration snippet containing 2 memory datasources,
- # one for IN and one for CH. Both contain a zone 'example.com'
- # the IN ds also contains a zone example2.com, and a zone example3.com,
- # which is of file type 'text' (and hence, should be ignored)
- self.config = { 'datasources': [
- { 'type': 'memory',
- 'class': 'IN',
- 'zones': [
- { 'origin': 'example.com',
- 'filetype': 'sqlite3' },
- { 'origin': 'EXAMPLE2.com.',
- 'filetype': 'sqlite3' },
- { 'origin': 'example3.com',
- 'filetype': 'text' }
- ]
- },
- { 'type': 'memory',
- 'class': 'ch',
- 'zones': [
- { 'origin': 'example.com',
- 'filetype': 'sqlite3' }
- ]
- }
- ] }
-
- def test_updates(self):
- self.assertFalse(self.xfr._is_memory_zone("example.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example2.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
-
- # add them all
- self.xfr._set_memory_zones(self.config, None)
- self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
- self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
- self.assertTrue(self.xfr._is_memory_zone("example.com", "CH"))
-
- # Remove the CH data source from the self.config snippet, and update
- del self.config['datasources'][1]
- self.xfr._set_memory_zones(self.config, None)
- self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
- self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
-
- # Remove example2.com from the datasource, and update
- del self.config['datasources'][0]['zones'][1]
- self.xfr._set_memory_zones(self.config, None)
- self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example2.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
-
- # If 'datasources' is not in the self.config update list (i.e. its
- # self.config has not changed), no difference should be found
- self.xfr._set_memory_zones({}, None)
- self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example2.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
-
- # If datasources list becomes empty, everything should be removed
- self.config['datasources'][0]['zones'] = []
- self.xfr._set_memory_zones(self.config, None)
- self.assertFalse(self.xfr._is_memory_zone("example.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example2.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
-
- def test_normalization(self):
- self.xfr._set_memory_zones(self.config, None)
- # make sure it is case insensitive, root-dot-insensitive,
- # and supports CLASSXXX notation
- self.assertTrue(self.xfr._is_memory_zone("EXAMPLE.com", "IN"))
- self.assertTrue(self.xfr._is_memory_zone("example.com", "in"))
- self.assertTrue(self.xfr._is_memory_zone("example2.com.", "IN"))
- self.assertTrue(self.xfr._is_memory_zone("example.com", "CLASS3"))
-
- def test_bad_name(self):
- # First set it to some config
- self.xfr._set_memory_zones(self.config, None)
-
- # Error checking; bad owner name should result in no changes
- self.config['datasources'][1]['zones'][0]['origin'] = ".."
- self.xfr._set_memory_zones(self.config, None)
- self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
- self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
- self.assertTrue(self.xfr._is_memory_zone("example.com", "CH"))
-
- def test_bad_class(self):
- # First set it to some config
- self.xfr._set_memory_zones(self.config, None)
-
- # Error checking; bad owner name should result in no changes
- self.config['datasources'][1]['class'] = "Foo"
- self.xfr._set_memory_zones(self.config, None)
- self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
- self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
- self.assertTrue(self.xfr._is_memory_zone("example.com", "CH"))
-
- def test_no_filetype(self):
- # omitting the filetype should leave that zone out, but not
- # the rest
- del self.config['datasources'][1]['zones'][0]['filetype']
- self.xfr._set_memory_zones(self.config, None)
- self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
- self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example.com", "CH"))
-
- def test_class_filetype(self):
- # omitting the class should have it default to what is in the
- # specfile for Auth.
- AuthConfigData = isc.config.config_data.ConfigData(
- isc.config.module_spec_from_file(xfrin.AUTH_SPECFILE_LOCATION))
- del self.config['datasources'][0]['class']
- self.xfr._set_memory_zones(self.config, AuthConfigData)
- self.assertTrue(self.xfr._is_memory_zone("example.com", "IN"))
- self.assertTrue(self.xfr._is_memory_zone("example2.com", "IN"))
- self.assertFalse(self.xfr._is_memory_zone("example3.com", "IN"))
- self.assertTrue(self.xfr._is_memory_zone("example.com", "CH"))
+ self.common_ixfr_setup('refresh', 'no')
+ self.assertEqual(ZoneInfo.REQUEST_IXFR_DISABLED,
+ self.xfr.xfrin_started_request_ixfr)
+
+ def test_datasrc_config_handler(self):
+ """Check datasrc config handler works expectedly."""
+ # This is a simple wrapper of DataSrcClientsMgr.reconfigure(), so
+ # we just check it's called as expected, and the only possible
+ # exception doesn't cause disruption.
+ self.xfr._datasrc_config_handler(True, False)
+ self.assertEqual([(True, False)],
+ self.xfr._datasrc_clients_mgr.reconfigure_param)
def raise_interrupt():
raise KeyboardInterrupt()
@@ -3067,6 +3121,13 @@ class TestXfrinProcess(unittest.TestCase):
self.__published = []
# How many connections were created.
self.__created_connections = 0
+ # prepare for possible replacement
+ self.__orig_get_zone_soa = xfrin._get_zone_soa
+ xfrin._get_zone_soa = lambda x, y, z: begin_soa_rdata
+
+ def tearDown(self):
+ # restore original value
+ xfrin._get_zone_soa = self.__orig_get_zone_soa
def __get_connection(self, *args):
"""
@@ -3122,7 +3183,8 @@ class TestXfrinProcess(unittest.TestCase):
"""
pass
- def __do_test(self, rets, transfers, request_type):
+ def __do_test(self, rets, transfers, request_ixfr,
+ zone_soa=begin_soa_rrset):
"""
Do the actual test. The request type, prepared sucesses/failures
and expected sequence of transfers is passed to specify what test
@@ -3131,8 +3193,11 @@ class TestXfrinProcess(unittest.TestCase):
self.__rets = rets
published = rets[-1]
xfrin.process_xfrin(self, XfrinRecorder(), Name("example.org."),
- RRClass.IN, None, None, None, True, None,
- request_type, self.__get_connection)
+ RRClass.IN, None, zone_soa, None,
+ TEST_MASTER_IPV4_ADDRINFO, True, None,
+ request_ixfr,
+ xfrin.Counters(xfrin.SPECFILE_LOCATION),
+ self.__get_connection)
self.assertEqual([], self.__rets)
self.assertEqual(transfers, self.__transfers)
# Create a connection for each attempt
@@ -3143,7 +3208,7 @@ class TestXfrinProcess(unittest.TestCase):
"""
Everything OK the first time, over IXFR.
"""
- self.__do_test([XFRIN_OK], [RRType.IXFR], RRType.IXFR)
+ self.__do_test([XFRIN_OK], [RRType.IXFR], ZoneInfo.REQUEST_IXFR_FIRST)
# Check there was loadzone command
self.assertTrue(self._send_cc_session.send_called)
self.assertTrue(self._send_cc_session.send_called_correctly)
@@ -3154,23 +3219,27 @@ class TestXfrinProcess(unittest.TestCase):
"""
Everything OK the first time, over AXFR.
"""
- self.__do_test([XFRIN_OK], [RRType.AXFR], RRType.AXFR)
+ self.__do_test([XFRIN_OK], [RRType.AXFR],
+ ZoneInfo.REQUEST_IXFR_DISABLED)
def test_axfr_fail(self):
"""
The transfer failed over AXFR. Should not be retried (we don't expect
- to fail on AXFR, but succeed on IXFR and we didn't use IXFR in the first
- place for some reason.
+ to fail on AXFR, but succeed on IXFR and we didn't use IXFR in the
+ first place for some reason.
+
"""
- self.__do_test([XFRIN_FAIL], [RRType.AXFR], RRType.AXFR)
+ self.__do_test([XFRIN_FAIL], [RRType.AXFR],
+ ZoneInfo.REQUEST_IXFR_DISABLED)
def test_ixfr_fallback(self):
"""
- The transfer fails over IXFR, but suceeds over AXFR. It should fall back
- to it and say everything is OK.
+ The transfer fails over IXFR, but suceeds over AXFR. It should fall
+ back to it and say everything is OK.
+
"""
self.__do_test([XFRIN_FAIL, XFRIN_OK], [RRType.IXFR, RRType.AXFR],
- RRType.IXFR)
+ ZoneInfo.REQUEST_IXFR_FIRST)
def test_ixfr_fail(self):
"""
@@ -3178,18 +3247,52 @@ class TestXfrinProcess(unittest.TestCase):
(only once) and should try both before giving up.
"""
self.__do_test([XFRIN_FAIL, XFRIN_FAIL],
- [RRType.IXFR, RRType.AXFR], RRType.IXFR)
+ [RRType.IXFR, RRType.AXFR], ZoneInfo.REQUEST_IXFR_FIRST)
+
+ def test_ixfr_only(self):
+ """
+ The transfer fails and IXFR_ONLY is specified. It shouldn't fall
+ back to AXFR and should report failure.
+ """
+ self.__do_test([XFRIN_FAIL], [RRType.IXFR], ZoneInfo.REQUEST_IXFR_ONLY)
def test_send_loadzone(self):
"""
Check the loadzone command is sent after successful transfer.
"""
- self.__do_test([XFRIN_OK], [RRType.IXFR], RRType.IXFR)
+ self.__do_test([XFRIN_OK], [RRType.IXFR],
+ ZoneInfo.REQUEST_IXFR_FIRST)
self.assertTrue(self._send_cc_session.send_called)
self.assertTrue(self._send_cc_session.send_called_correctly)
self.assertTrue(self._send_cc_session.recv_called)
self.assertTrue(self._send_cc_session.recv_called_correctly)
+ def test_initial_request_type(self):
+ """Check initial xfr reuqest type (AXFR or IXFR).
+
+ Varying the policy of use of IXFR and availability of current
+ zone SOA. We are only interested in the initial request type,
+ so won't check the xfr results.
+
+ """
+ for soa in [begin_soa_rdata, None]:
+ for request_ixfr in [ZoneInfo.REQUEST_IXFR_FIRST,
+ ZoneInfo.REQUEST_IXFR_ONLY,
+ ZoneInfo.REQUEST_IXFR_DISABLED]:
+ # Clear all counters
+ self.__transfers = []
+ self.__published = []
+ self.__created_connections = 0
+
+ # Determine the expected type
+ expected_type = RRType.IXFR
+ if (soa is None or
+ request_ixfr == ZoneInfo.REQUEST_IXFR_DISABLED):
+ expected_type = RRType.AXFR
+
+ # perform the test
+ self.__do_test([XFRIN_OK], [expected_type], request_ixfr, soa)
+
class TestFormatting(unittest.TestCase):
# If the formatting functions are moved to a more general library
# (ticket #1379), these tests should be moved with them.
@@ -3307,160 +3410,122 @@ class TestXfrinTransferStats(unittest.TestCase):
class TestXfrinConnectionSocketCounter(unittest.TestCase):
+ @property
+ def _master_addrinfo(self):
+ return TEST_MASTER_IPV4_ADDRINFO
+ @property
+ def _ipver(self):
+ return 'ipv4'
+
def setUp(self):
self.conn = XfrinConnection(
None, TEST_ZONE_NAME, None, MockDataSourceClient(), None,
- TEST_MASTER_IPV4_ADDRINFO, None)
- self.conn._counters.clear_all()
- def raise_expception(*arg): raise Exception
- self.raise_expception = raise_expception
+ self._master_addrinfo, None,
+ xfrin.Counters(xfrin.SPECFILE_LOCATION))
+ self.expception = socket.error
- def tearDown(self):
- self.conn._counters.clear_all()
+ def raise_expception(self, *args):
+ raise self.expception
def test_open(self):
self.assertRaises(isc.cc.data.DataNotFoundError,
self.conn._counters.get,
- 'socket', 'ipv4', 'tcp', 'open')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get,
- 'socket', 'ipv6', 'tcp', 'open')
- self.conn.create_socket(TEST_MASTER_IPV4_ADDRINFO[0],
- TEST_MASTER_IPV4_ADDRINFO[1])
- self.conn.create_socket(TEST_MASTER_IPV6_ADDRINFO[0],
- TEST_MASTER_IPV6_ADDRINFO[1])
- self.assertEqual(1, self.conn._counters.get(
- 'socket', 'ipv4', 'tcp', 'open'))
+ 'socket', self._ipver, 'tcp', 'open')
+ self.conn.create_socket(self._master_addrinfo[0],
+ self._master_addrinfo[1])
self.assertEqual(1, self.conn._counters.get(
- 'socket', 'ipv6', 'tcp', 'open'))
+ 'socket', self._ipver, 'tcp', 'open'))
def test_openfail(self):
self.assertRaises(isc.cc.data.DataNotFoundError,
self.conn._counters.get,
- 'socket', 'ipv4', 'tcp', 'openfail')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get,
- 'socket', 'ipv6', 'tcp', 'openfail')
+ 'socket', self._ipver, 'tcp', 'openfail')
orig_create_socket = xfrin.asyncore.dispatcher.create_socket
xfrin.asyncore.dispatcher.create_socket = self.raise_expception
- self.assertRaises(Exception, self.conn.create_socket,
- TEST_MASTER_IPV4_ADDRINFO[0],
- TEST_MASTER_IPV4_ADDRINFO[1])
- self.assertEqual(1, self.conn._counters.get(
- 'socket', 'ipv4', 'tcp', 'openfail'))
- self.assertRaises(Exception, self.conn.create_socket,
- TEST_MASTER_IPV6_ADDRINFO[0],
- TEST_MASTER_IPV6_ADDRINFO[1])
- self.assertEqual(1, self.conn._counters.get(
- 'socket', 'ipv6', 'tcp', 'openfail'))
- xfrin.asyncore.dispatcher.create_socket = orig_create_socket
+ try:
+ self.assertRaises(self.expception, self.conn.create_socket,
+ self._master_addrinfo[0],
+ self._master_addrinfo[1])
+ self.assertEqual(1, self.conn._counters.get(
+ 'socket', self._ipver, 'tcp', 'openfail'))
+ finally:
+ xfrin.asyncore.dispatcher.create_socket = orig_create_socket
def test_close(self):
self.assertRaises(isc.cc.data.DataNotFoundError,
self.conn._counters.get,
- 'socket', 'ipv4', 'tcp', 'close')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get,
- 'socket', 'ipv6', 'tcp', 'close')
+ 'socket', self._ipver, 'tcp', 'close')
orig_socket_close = xfrin.asyncore.dispatcher.close
xfrin.asyncore.dispatcher.close = lambda x: None
- class FakeSocket(): pass
- self.conn.socket = FakeSocket()
- self.conn.socket.family = socket.AF_INET
- self.conn.close()
- self.assertEqual(1, self.conn._counters.get(
- 'socket', 'ipv4', 'tcp', 'close'))
- self.conn.socket.family = socket.AF_INET6
- self.conn.close()
- self.assertEqual(1, self.conn._counters.get(
- 'socket', 'ipv6', 'tcp', 'close'))
- xfrin.asyncore.dispatcher.close = orig_socket_close
+ try:
+ self.conn.close()
+ self.assertEqual(1, self.conn._counters.get(
+ 'socket', self._ipver, 'tcp', 'close'))
+ finally:
+ xfrin.asyncore.dispatcher.close = orig_socket_close
def test_conn(self):
self.assertRaises(isc.cc.data.DataNotFoundError,
self.conn._counters.get,
- 'socket', 'ipv4', 'tcp', 'conn')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get,
- 'socket', 'ipv6', 'tcp', 'conn')
+ 'socket', self._ipver, 'tcp', 'conn')
orig_socket_connect = xfrin.asyncore.dispatcher.connect
xfrin.asyncore.dispatcher.connect = lambda *a: None
- class FakeSocket(): pass
- self.conn.socket = FakeSocket()
- self.conn.socket.family = socket.AF_INET
- self.conn.connect(TEST_MASTER_IPV4_ADDRINFO[2])
- self.conn.socket.family = socket.AF_INET6
- self.conn.connect(TEST_MASTER_IPV6_ADDRINFO[2])
- self.assertEqual(1, self.conn._counters.get(
- 'socket', 'ipv4', 'tcp', 'conn'))
- self.assertEqual(1, self.conn._counters.get(
- 'socket', 'ipv6', 'tcp', 'conn'))
- xfrin.asyncore.dispatcher.connect = orig_socket_connect
+ try:
+ self.conn.connect(self._master_addrinfo[2])
+ self.assertEqual(1, self.conn._counters.get(
+ 'socket', self._ipver, 'tcp', 'conn'))
+ finally:
+ xfrin.asyncore.dispatcher.connect = orig_socket_connect
def test_connfail(self):
self.assertRaises(isc.cc.data.DataNotFoundError,
self.conn._counters.get,
- 'socket', 'ipv4', 'tcp', 'connfail')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get,
- 'socket', 'ipv6', 'tcp', 'connfail')
+ 'socket', self._ipver, 'tcp', 'connfail')
orig_socket_connect = xfrin.asyncore.dispatcher.connect
xfrin.asyncore.dispatcher.connect = self.raise_expception
- class FakeSocket(): pass
- self.conn.socket = FakeSocket()
- self.conn.socket.family = socket.AF_INET
- self.assertRaises(Exception, self.conn.connect,
- TEST_MASTER_IPV4_ADDRINFO[2])
- self.assertEqual(1, self.conn._counters.get(
- 'socket', 'ipv4', 'tcp', 'connfail'))
- self.conn.socket.family = socket.AF_INET6
- self.assertRaises(Exception, self.conn.connect,
- TEST_MASTER_IPV6_ADDRINFO[2])
- self.assertEqual(1, self.conn._counters.get(
- 'socket', 'ipv6', 'tcp', 'connfail'))
- xfrin.asyncore.dispatcher.connect = orig_socket_connect
+ try:
+ self.assertRaises(self.expception, self.conn.connect,
+ self._master_addrinfo[2])
+ self.assertFalse(self.conn.connect_to_master())
+ self.assertEqual(2, self.conn._counters.get(
+ 'socket', self._ipver, 'tcp', 'connfail'))
+ finally:
+ xfrin.asyncore.dispatcher.connect = orig_socket_connect
def test_senderr(self):
self.assertRaises(isc.cc.data.DataNotFoundError,
self.conn._counters.get,
- 'socket', 'ipv4', 'tcp', 'senderr')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get,
- 'socket', 'ipv6', 'tcp', 'senderr')
+ 'socket', self._ipver, 'tcp', 'senderr')
orig_socket_send = xfrin.asyncore.dispatcher.send
xfrin.asyncore.dispatcher.send = self.raise_expception
- class FakeSocket(): pass
- self.conn.socket = FakeSocket()
- self.conn.socket.family = socket.AF_INET
- self.assertRaises(Exception, self.conn.send, None)
- self.assertEqual(1, self.conn._counters.get(
- 'socket', 'ipv4', 'tcp', 'senderr'))
- self.conn.socket.family = socket.AF_INET6
- self.assertRaises(Exception, self.conn.send, None)
- self.assertEqual(1, self.conn._counters.get(
- 'socket', 'ipv6', 'tcp', 'senderr'))
- xfrin.asyncore.dispatcher.send = orig_socket_send
+ try:
+ self.assertRaises(self.expception, self.conn.send, None)
+ self.assertEqual(1, self.conn._counters.get(
+ 'socket', self._ipver, 'tcp', 'senderr'))
+ finally:
+ xfrin.asyncore.dispatcher.send = orig_socket_send
def test_recverr(self):
self.assertRaises(isc.cc.data.DataNotFoundError,
self.conn._counters.get,
- 'socket', 'ipv4', 'tcp', 'recverr')
- self.assertRaises(isc.cc.data.DataNotFoundError,
- self.conn._counters.get,
- 'socket', 'ipv6', 'tcp', 'recverr')
+ 'socket', self._ipver, 'tcp', 'recverr')
orig_socket_recv = xfrin.asyncore.dispatcher.recv
xfrin.asyncore.dispatcher.recv = self.raise_expception
- class FakeSocket(): pass
- self.conn.socket = FakeSocket()
- self.conn.socket.family = socket.AF_INET
- self.assertRaises(Exception, self.conn.recv, None)
- self.assertEqual(1, self.conn._counters.get(
- 'socket', 'ipv4', 'tcp', 'recverr'))
- self.conn.socket.family = socket.AF_INET6
- self.assertRaises(Exception, self.conn.recv, None)
- self.assertEqual(1, self.conn._counters.get(
- 'socket', 'ipv6', 'tcp', 'recverr'))
- xfrin.asyncore.dispatcher.recv = orig_socket_recv
+ try:
+ self.assertRaises(self.expception, self.conn.recv, None)
+ self.assertEqual(1, self.conn._counters.get(
+ 'socket', self._ipver, 'tcp', 'recverr'))
+ finally:
+ xfrin.asyncore.dispatcher.recv = orig_socket_recv
+
+class TestXfrinConnectionSocketCounterV6(TestXfrinConnectionSocketCounter):
+
+ @property
+ def _master_addrinfo(self):
+ return TEST_MASTER_IPV6_ADDRINFO
+ @property
+ def _ipver(self):
+ return 'ipv6'
if __name__== "__main__":
try:
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index be7b311..2ce6b88 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -28,14 +28,16 @@ import time
from functools import reduce
from optparse import OptionParser, OptionValueError
from isc.config.ccsession import *
-from isc.statistics import Counters
+from isc.statistics.dns import Counters
from isc.notify import notify_out
import isc.util.process
+from isc.util.address_formatter import AddressFormatter
from isc.datasrc import DataSourceClient, ZoneFinder
import isc.net.parse
from isc.xfrin.diff import Diff
from isc.server_common.auth_command import auth_loadzone_command
from isc.server_common.tsig_keyring import init_keyring, get_keyring
+from isc.server_common.datasrc_clients_mgr import DataSrcClientsMgr, ConfigError
from isc.log_messages.xfrin_messages import *
from isc.dns import *
@@ -55,13 +57,9 @@ isc.util.process.rename()
SPECFILE_PATH = "@datadir@/@PACKAGE@"\
.replace("${datarootdir}", "@datarootdir@")\
.replace("${prefix}", "@prefix@")
-AUTH_SPECFILE_PATH = SPECFILE_PATH
if "B10_FROM_SOURCE" in os.environ:
SPECFILE_PATH = os.environ["B10_FROM_SOURCE"] + "/src/bin/xfrin"
-if "B10_FROM_BUILD" in os.environ:
- AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth"
SPECFILE_LOCATION = SPECFILE_PATH + "/xfrin.spec"
-AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec"
AUTH_MODULE_NAME = 'Auth'
XFROUT_MODULE_NAME = 'Xfrout'
@@ -188,7 +186,7 @@ def get_soa_serial(soa_rdata):
class XfrinState:
'''
- The states of the incomding *XFR state machine.
+ The states of the incoming *XFR state machine.
We (will) handle both IXFR and AXFR with a single integrated state
machine because they cannot be distinguished immediately - an AXFR
@@ -270,7 +268,7 @@ class XfrinState:
can be used as singleton objects. For now, however, we always instantiate
a new object for every state transition, partly because the introduction
of singleton will make a code bit complicated, and partly because
- the overhead of object instantiotion wouldn't be significant for xfrin.
+ the overhead of object instantiation wouldn't be significant for xfrin.
'''
def set_xfrstate(self, conn, new_state):
@@ -433,7 +431,7 @@ class XfrinIXFRAdd(XfrinState):
if soa_serial == conn._end_serial:
# The final part is there. Finish the transfer by
# checking the last TSIG (if required), the zone data and
- # commiting.
+ # committing.
conn.finish_transfer()
self.set_xfrstate(conn, XfrinIXFREnd())
return True
@@ -565,18 +563,26 @@ class XfrinConnection(asyncore.dispatcher):
def __init__(self,
sock_map, zone_name, rrclass, datasrc_client,
- shutdown_event, master_addrinfo, db_file, tsig_key=None,
- idle_timeout=60):
- '''Constructor of the XfirnConnection class.
+ shutdown_event, master_addrinfo, zone_soa, counters,
+ tsig_key=None, idle_timeout=60):
+ """Constructor of the XfirnConnection class.
+
+ Parameters:
+ sock_map: empty dict, used with asyncore.
+ zone_name (dns.Name): Zone name.
+ rrclass (dns.RRClass): Zone RR class.
+ datasrc_client (DataSourceClient): the data source client object
+ used for the XFR session.
+ shutdown_event (threading.Event): used for synchronization with
+ parent thread.
+ master_addrinfo (tuple: (sock family, sock type, sockaddr)):
+ address and port of the master server.
+ zone_soa (RRset or None): SOA RRset of zone's current SOA or None
+ if it's not available.
+ counters (Counters): used for statistics counters
+ idle_timeout (int): max idle time for read data from socket.
- db_file: SQLite3 DB file. Unforutnately we still need this for
- temporary workaround in _get_zone_soa(). This should be
- removed when we eliminate the need for the workaround.
- idle_timeout: max idle time for read data from socket.
- datasrc_client: the data source client object used for the XFR session.
- This will eventually replace db_file completely.
-
- '''
+ """
asyncore.dispatcher.__init__(self, map=sock_map)
@@ -595,9 +601,8 @@ class XfrinConnection(asyncore.dispatcher):
self._rrclass = rrclass
# Data source handler
- self._db_file = db_file
self._datasrc_client = datasrc_client
- self._zone_soa = self._get_zone_soa()
+ self._zone_soa = zone_soa
self._sock_map = sock_map
self._soa_rr_count = 0
@@ -605,6 +610,11 @@ class XfrinConnection(asyncore.dispatcher):
self._shutdown_event = shutdown_event
self._master_addrinfo = master_addrinfo
self._tsig_key = tsig_key
+ # self.tsig_key_name is used for outputting an error massage in
+ # connect_to_master().
+ self.tsig_key_name = None
+ if tsig_key:
+ self.tsig_key_name = self._tsig_key.get_key_name()
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)
@@ -613,7 +623,7 @@ class XfrinConnection(asyncore.dispatcher):
# keep a record of this specific transfer to log on success
# (time, rr/s, etc)
self._transfer_stats = XfrinTransferStats()
- self._counters = Counters(SPECFILE_LOCATION)
+ self._counters = counters
def create_socket(self, family, type):
"""create_socket() overridden from the super class for
@@ -622,13 +632,13 @@ class XfrinConnection(asyncore.dispatcher):
ret = super().create_socket(family, type)
# count open
self._counters.inc('socket',
- 'ip' + self.get_ipver_str(family),
+ 'ip' + self._get_ipver_str(),
'tcp', 'open')
return ret
except:
# count openfail
self._counters.inc('socket',
- 'ip' + self.get_ipver_str(family),
+ 'ip' + self._get_ipver_str(),
'tcp', 'openfail')
raise
@@ -638,7 +648,7 @@ class XfrinConnection(asyncore.dispatcher):
ret = super().close()
# count close
self._counters.inc('socket',
- 'ip' + self.get_ipver_str(),
+ 'ip' + self._get_ipver_str(),
'tcp', 'close')
return ret
@@ -649,13 +659,13 @@ class XfrinConnection(asyncore.dispatcher):
ret = super().connect(address)
# count conn
self._counters.inc('socket',
- 'ip' + self.get_ipver_str(),
+ 'ip' + self._get_ipver_str(),
'tcp', 'conn')
return ret
except:
# count connfail
self._counters.inc('socket',
- 'ip' + self.get_ipver_str(),
+ 'ip' + self._get_ipver_str(),
'tcp', 'connfail')
raise
@@ -667,7 +677,7 @@ class XfrinConnection(asyncore.dispatcher):
except:
# count senderr
self._counters.inc('socket',
- 'ip' + self.get_ipver_str(),
+ 'ip' + self._get_ipver_str(),
'tcp', 'senderr')
raise
@@ -679,7 +689,7 @@ class XfrinConnection(asyncore.dispatcher):
except:
# count recverr
self._counters.inc('socket',
- 'ip' + self.get_ipver_str(),
+ 'ip' + self._get_ipver_str(),
'tcp', 'recverr')
raise
@@ -692,55 +702,7 @@ class XfrinConnection(asyncore.dispatcher):
it if the constructor raises an exception after opening the socket.
'''
self.create_socket(self._master_addrinfo[0], self._master_addrinfo[1])
- self.setblocking(1)
-
- def _get_zone_soa(self):
- '''Retrieve the current SOA RR of the zone to be transferred.
-
- It will be used for various purposes in subsequent xfr protocol
- processing. It is validly possible that the zone is currently
- empty and therefore doesn't have an SOA, so this method doesn't
- consider it an error and returns None in such a case. It may or
- may not result in failure in the actual processing depending on
- how the SOA is used.
-
- When the zone has an SOA RR, this method makes sure that it's
- valid, i.e., it has exactly one RDATA; if it is not the case
- this method returns None.
-
- If the underlying data source doesn't even know the zone, this method
- tries to provide backward compatible behavior where xfrin is
- responsible for creating zone in the corresponding DB table.
- For a longer term we should deprecate this behavior by introducing
- more generic zone management framework, but at the moment we try
- to not surprise existing users. (Note also that the part of
- providing the compatible behavior uses the old data source API.
- We'll deprecate this API in a near future, too).
-
- '''
- # get the zone finder. this must be SUCCESS (not even
- # PARTIALMATCH) because we are specifying the zone origin name.
- result, finder = self._datasrc_client.find_zone(self._zone_name)
- if result != DataSourceClient.SUCCESS:
- # The data source doesn't know the zone. For now, we provide
- # backward compatibility and creates a new one ourselves.
- isc.datasrc.sqlite3_ds.load(self._db_file,
- self._zone_name.to_text(),
- lambda : [])
- logger.warn(XFRIN_ZONE_CREATED, self.zone_str())
- # try again
- result, finder = self._datasrc_client.find_zone(self._zone_name)
- if result != DataSourceClient.SUCCESS:
- return None
- result, soa_rrset, _ = finder.find(self._zone_name, RRType.SOA)
- if result != ZoneFinder.SUCCESS:
- logger.info(XFRIN_ZONE_NO_SOA, self.zone_str())
- return None
- if soa_rrset.get_rdata_count() != 1:
- logger.warn(XFRIN_ZONE_MULTIPLE_SOA, self.zone_str(),
- soa_rrset.get_rdata_count())
- return None
- return soa_rrset
+ self.socket.setblocking(1)
def __set_xfrstate(self, new_state):
self.__state = new_state
@@ -764,7 +726,8 @@ class XfrinConnection(asyncore.dispatcher):
self.connect(self._master_addrinfo[2])
return True
except socket.error as e:
- logger.error(XFRIN_CONNECT_MASTER, self._master_addrinfo[2],
+ logger.error(XFRIN_CONNECT_MASTER, self.tsig_key_name,
+ self._master_addrinfo[2],
str(e))
return False
@@ -814,8 +777,9 @@ class XfrinConnection(asyncore.dispatcher):
msg = self._create_query(query_type)
render = MessageRenderer()
- # XXX Currently, python wrapper doesn't accept 'None' parameter in this case,
- # we should remove the if statement and use a universal interface later.
+ # 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)
@@ -878,19 +842,20 @@ class XfrinConnection(asyncore.dispatcher):
'''
Used as error callback below.
'''
- logger.error(XFRIN_ZONE_INVALID, self._zone_name, self._rrclass,
- reason)
+ logger.error(XFRIN_ZONE_INVALID, self._zone_name,
+ self._rrclass, reason)
def __validate_warning(self, reason):
'''
Used as warning callback below.
'''
- logger.warn(XFRIN_ZONE_WARN, self._zone_name, self._rrclass, reason)
+ logger.warn(XFRIN_ZONE_WARN, self._zone_name,
+ self._rrclass, reason)
def finish_transfer(self):
"""
Perform any necessary checks after a transfer. Then complete the
- transfer by commiting the transaction into the data source.
+ transfer by committing the transaction into the data source.
"""
self._check_response_tsig_last()
if not check_zone(self._zone_name, self._rrclass,
@@ -961,15 +926,19 @@ class XfrinConnection(asyncore.dispatcher):
# All okay, return it
return soa
- def get_ipver_str(self, family=None):
+ def _get_ipver_str(self):
"""Returns a 'v4' or 'v6' string representing a IP version
- depending on the socket family"""
- if family is None:
- family = self.socket.family
- if family == socket.AF_INET:
+ depending on the socket family. This is for an internal use
+ only (except for tests). This is supported only for IP sockets.
+ It raises a ValueError exception on other address families.
+
+ """
+ if self._master_addrinfo[0] == socket.AF_INET:
return 'v4'
- elif family == socket.AF_INET6:
+ elif self._master_addrinfo[0] == socket.AF_INET6:
return 'v6'
+ raise ValueError("Invalid address family. "
+ "This is supported only for IP sockets")
def _check_soa_serial(self):
'''Send SOA query and compare the local and remote serials.
@@ -980,17 +949,12 @@ class XfrinConnection(asyncore.dispatcher):
'''
- try:
- # increment SOA queries in progress
- self._counters.inc('soa_in_progress')
- self._send_query(RRType.SOA)
- # count soaoutv4 or soaoutv6 requests
- self._counters.inc('zones', self._zone_name.to_text(),
- 'soaout' + self.get_ipver_str())
- data_len = self._get_request_response(2)
- finally:
- # decrement SOA queries in progress
- self._counters.dec('soa_in_progress')
+ self._send_query(RRType.SOA)
+ # count soaoutv4 or soaoutv6 requests
+ self._counters.inc('zones', self._rrclass.to_text(),
+ self._zone_name.to_text(), 'soaout' +
+ self._get_ipver_str())
+ 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)
msg = Message(Message.PARSE)
@@ -1019,9 +983,7 @@ class XfrinConnection(asyncore.dispatcher):
try:
ret = XFRIN_OK
self._request_type = request_type
- # Right now RRType.[IA]XFR().to_text() is 'TYPExxx', so we need
- # to hardcode here.
- req_str = 'IXFR' if request_type == RRType.IXFR else 'AXFR'
+ req_str = request_type.to_text()
if check_soa:
self._check_soa_serial()
self.close()
@@ -1030,23 +992,21 @@ class XfrinConnection(asyncore.dispatcher):
raise XfrinException('Unable to reconnect to master')
# start statistics timer
- self._counters.start_timer('zones', self._zone_name.to_text(),
- 'time_to_' + req_str.lower())
- try:
- # increment deferred xfers
- self._counters.inc(req_str.lower() + '_deferred')
- logger.info(XFRIN_XFR_TRANSFER_STARTED, req_str, self.zone_str())
- self._send_query(self._request_type)
- finally:
- # decrement deferred xfers
- self._counters.dec(req_str.lower() + '_deferred')
- # count xfer requests for statistics
- self._counters.inc('zones', self._zone_name.to_text(),
+ # Note: If the timer for the zone is already started but
+ # not yet stopped due to some error, the last start time
+ # is overwritten at this point.
+ self._counters.start_timer('zones',
+ self._rrclass.to_text(),
+ self._zone_name.to_text(),
+ 'last_' + req_str.lower() +
+ '_duration')
+ logger.info(XFRIN_XFR_TRANSFER_STARTED, req_str, self.zone_str())
+ # An AXFR or IXFR is being requested.
+ self._counters.inc('zones', self._rrclass.to_text(),
+ self._zone_name.to_text(),
req_str.lower() + 'req' +
- self.get_ipver_str())
- # increment xfers running
- self._counters.inc(req_str.lower() + '_running')
-
+ self._get_ipver_str())
+ self._send_query(self._request_type)
self.__state = XfrinInitialSOA()
self._handle_xfrin_responses()
# Depending what data was found, we log different status reports
@@ -1072,10 +1032,6 @@ class XfrinConnection(asyncore.dispatcher):
"%.3f" % self._transfer_stats.get_running_time(),
"%.f" % self._transfer_stats.get_bytes_per_second()
)
- # stop statistics timer
- self._counters.stop_timer('zones', self._zone_name.to_text(),
- 'time_to_' + req_str.lower())
-
except XfrinZoneUptodate:
# Eventually we'll probably have to treat this case as a trigger
# of trying another primary server, etc, but for now we treat it
@@ -1085,17 +1041,18 @@ class XfrinConnection(asyncore.dispatcher):
# The log message doesn't contain the exception text, since there's
# only one place where the exception is thrown now and it'd be the
# same generic message every time.
- logger.error(XFRIN_INVALID_ZONE_DATA, self.zone_str(),
+ logger.error(XFRIN_INVALID_ZONE_DATA,
+ self.zone_str(),
format_addrinfo(self._master_addrinfo))
ret = XFRIN_FAIL
except XfrinProtocolError as e:
- logger.info(XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION, req_str,
- self.zone_str(),
+ logger.info(XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION,
+ req_str, self.zone_str(),
format_addrinfo(self._master_addrinfo), str(e))
ret = XFRIN_FAIL
except XfrinException as e:
- logger.error(XFRIN_XFR_TRANSFER_FAILURE, req_str,
- self.zone_str(),
+ logger.error(XFRIN_XFR_TRANSFER_FAILURE,
+ req_str, self.zone_str(),
format_addrinfo(self._master_addrinfo), str(e))
ret = XFRIN_FAIL
except Exception as e:
@@ -1111,19 +1068,23 @@ class XfrinConnection(asyncore.dispatcher):
self.zone_str(), str(e))
ret = XFRIN_FAIL
finally:
+ # A xfrsuccess or xfrfail counter is incremented depending on
+ # the result.
+ result = {XFRIN_OK: 'xfrsuccess', XFRIN_FAIL: 'xfrfail'}[ret]
+ self._counters.inc('zones', self._rrclass.to_text(),
+ self._zone_name.to_text(), result)
+ # The started statistics timer is finally stopped only in
+ # a successful case.
+ if ret == XFRIN_OK:
+ self._counters.stop_timer('zones',
+ self._rrclass.to_text(),
+ self._zone_name.to_text(),
+ 'last_' + req_str.lower() +
+ '_duration')
# Make sure any remaining transaction in the diff is closed
# (if not yet - possible in case of xfr-level exception) as soon
# as possible
self._diff = None
- if ret == XFRIN_OK:
- # count successful xfer requests
- self._counters.inc('zones', self._zone_name.to_text(), 'xfrsuccess')
- elif ret == XFRIN_FAIL:
- # count failed xfer requests
- self._counters.inc('zones', self._zone_name.to_text(), 'xfrfail')
- # decrement xfers running
- self._counters.dec(req_str.lower() + '_running')
-
return ret
def _check_response_header(self, msg):
@@ -1195,55 +1156,77 @@ class XfrinConnection(asyncore.dispatcher):
return False
-def __process_xfrin(server, zone_name, rrclass, db_file,
+def __get_initial_xfr_type(zone_soa, request_ixfr, zname, zclass, master_addr):
+ """Determine the initial xfr request type.
+
+ This is a dedicated subroutine of __process_xfrin.
+ """
+ if zone_soa is None:
+ # This is a kind of special case, so we log it at info level.
+ logger.info(XFRIN_INITIAL_AXFR, format_zone_str(zname, zclass),
+ AddressFormatter(master_addr))
+ return RRType.AXFR
+ if request_ixfr == ZoneInfo.REQUEST_IXFR_DISABLED:
+ logger.debug(DBG_XFRIN_TRACE, XFRIN_INITIAL_IXFR_DISABLED,
+ format_zone_str(zname, zclass),
+ AddressFormatter(master_addr))
+ return RRType.AXFR
+
+ assert(request_ixfr == ZoneInfo.REQUEST_IXFR_FIRST or
+ request_ixfr == ZoneInfo.REQUEST_IXFR_ONLY)
+ logger.debug(DBG_XFRIN_TRACE, XFRIN_INITIAL_IXFR,
+ format_zone_str(zname, zclass),
+ AddressFormatter(master_addr))
+ return RRType.IXFR
+
+def __process_xfrin(server, zone_name, rrclass, datasrc_client, zone_soa,
shutdown_event, master_addrinfo, check_soa, tsig_key,
- request_type, conn_class):
+ request_ixfr, counters, conn_class):
conn = None
exception = None
ret = XFRIN_FAIL
try:
- # Create a data source client used in this XFR session. Right now we
- # still assume an sqlite3-based data source, and use both the old and new
- # data source APIs. We also need to use a mock client for tests.
- # For a temporary workaround to deal with these situations, we skip the
- # creation when the given file is none (the test case). Eventually
- # this code will be much cleaner.
- datasrc_client = None
- if db_file is not None:
- # temporary hardcoded sqlite initialization. Once we decide on
- # the config specification, we need to update this (TODO)
- # this may depend on #1207, or any followup ticket created for #1207
- datasrc_type = "sqlite3"
- datasrc_config = "{ \"database_file\": \"" + db_file + "\"}"
- datasrc_client = DataSourceClient(datasrc_type, datasrc_config)
-
- # Create a TCP connection for the XFR session and perform the operation.
+ # Determine the initialreuqest type: AXFR or IXFR.
+ request_type = __get_initial_xfr_type(zone_soa, request_ixfr,
+ zone_name, rrclass,
+ master_addrinfo[2])
+
+ # Create a TCP connection for the XFR session and perform the
+ # operation.
sock_map = {}
- # In case we were asked to do IXFR and that one fails, we try again with
- # AXFR. But only if we could actually connect to the server.
+ # In case we were asked to do IXFR and that one fails, we try again
+ # with AXFR. But only if we could actually connect to the server.
#
- # So we start with retry as True, which is set to false on each attempt.
- # In the case of connected but failed IXFR, we set it to true once again.
+ # So we start with retry as True, which is set to false on each
+ # attempt. In the case of connected but failed IXFR, we set it to true
+ # once again.
retry = True
while retry:
retry = False
conn = conn_class(sock_map, zone_name, rrclass, datasrc_client,
- shutdown_event, master_addrinfo, db_file,
- tsig_key)
+ shutdown_event, master_addrinfo, zone_soa,
+ counters, tsig_key)
conn.init_socket()
ret = XFRIN_FAIL
if conn.connect_to_master():
ret = conn.do_xfrin(check_soa, request_type)
if ret == XFRIN_FAIL and request_type == RRType.IXFR:
- # IXFR failed for some reason. It might mean the server can't
- # handle it, or we don't have the zone or we are out of sync or
- # whatever else. So we retry with with AXFR, as it may succeed
- # in many such cases.
- retry = True
- request_type = RRType.AXFR
- logger.warn(XFRIN_XFR_TRANSFER_FALLBACK, conn.zone_str())
- conn.close()
- conn = None
+ # IXFR failed for some reason. It might mean the server
+ # can't handle it, or we don't have the zone or we are out
+ # of sync or whatever else. So we retry with with AXFR, as
+ # it may succeed in many such cases; if "IXFR only" is
+ # specified in request_ixfr, however, we suppress the
+ # fallback.
+ if request_ixfr == ZoneInfo.REQUEST_IXFR_ONLY:
+ logger.warn(XFRIN_XFR_TRANSFER_FALLBACK_DISABLED,
+ tsig_key, conn.zone_str())
+ else:
+ retry = True
+ request_type = RRType.AXFR
+ logger.warn(XFRIN_XFR_TRANSFER_FALLBACK,
+ tsig_key, conn.zone_str())
+ conn.close()
+ conn = None
except Exception as ex:
# If exception happens, just remember it here so that we can re-raise
@@ -1267,9 +1250,9 @@ def __process_xfrin(server, zone_name, rrclass, db_file,
if exception is not None:
raise exception
-def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file,
- shutdown_event, master_addrinfo, check_soa, tsig_key,
- request_type, conn_class=XfrinConnection):
+def process_xfrin(server, xfrin_recorder, zone_name, rrclass, datasrc_client,
+ zone_soa, shutdown_event, master_addrinfo, check_soa,
+ tsig_key, request_ixfr, counters, conn_class=XfrinConnection):
# Even if it should be rare, the main process of xfrin session can
# raise an exception. In order to make sure the lock in xfrin_recorder
# is released in any cases, we delegate the main part to the helper
@@ -1277,16 +1260,19 @@ def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file,
xfrin_recorder.increment(zone_name)
exception = None
try:
- __process_xfrin(server, zone_name, rrclass, db_file,
+ __process_xfrin(server, zone_name, rrclass, datasrc_client, zone_soa,
shutdown_event, master_addrinfo, check_soa, tsig_key,
- request_type, conn_class)
+ request_ixfr, counters, conn_class)
except Exception as ex:
# don't log it until we complete decrement().
exception = ex
xfrin_recorder.decrement(zone_name)
if exception is not None:
- typestr = "AXFR" if request_type == RRType.AXFR else "IXFR"
+ if request_ixfr == ZoneInfo.REQUEST_IXFR_DISABLED:
+ typestr = "AXFR"
+ else:
+ typestr = "IXFR"
logger.error(XFRIN_XFR_PROCESS_FAILURE, typestr, zone_name.to_text(),
str(rrclass), str(exception))
@@ -1319,10 +1305,26 @@ class XfrinRecorder:
return ret
class ZoneInfo:
+ # Internal values corresponding to request_ixfr
+ REQUEST_IXFR_FIRST = 0 # request_ixfr=yes, use IXFR 1st then AXFR
+ REQUEST_IXFR_ONLY = 1 # request_ixfr=only, use IXFR only
+ REQUEST_IXFR_DISABLED = 2 # request_ixfr=no, AXFR-only
+
+ # Map from configuration values for request_ixfr to internal values
+ # This is a constant; don't modify.
+ REQUEST_IXFR_CFG_TO_VAL = { 'yes': REQUEST_IXFR_FIRST,
+ 'only': REQUEST_IXFR_ONLY,
+ 'no': REQUEST_IXFR_DISABLED }
+
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"""
+ # Handle deprecated config parameter explicitly for the moment.
+ if config_data.get('use_ixfr') is not None:
+ raise XfrinZoneInfoException('"use_ixfr" was deprecated, ' +
+ 'use "request_ixfr"')
+
self._module_cc = module_cc
self.set_name(config_data.get('name'))
self.set_master_addr(config_data.get('master_addr'))
@@ -1330,7 +1332,17 @@ class ZoneInfo:
self.set_master_port(config_data.get('master_port'))
self.set_zone_class(config_data.get('class'))
self.set_tsig_key_name(config_data.get('tsig_key'))
- self.set_use_ixfr(config_data.get('use_ixfr'))
+ self.set_request_ixfr(config_data.get('request_ixfr'))
+
+ @property
+ def request_ixfr(self):
+ """Policy on the use of IXFR.
+
+ Possible values are REQUEST_IXFR_xxx, internally stored in
+ __request_ixfr, read-only outside of the class.
+
+ """
+ return self.__request_ixfr
def set_name(self, name_str):
"""Set the name for this zone given a name string.
@@ -1417,16 +1429,15 @@ class ZoneInfo:
else:
return key
- def set_use_ixfr(self, use_ixfr):
- """Set use_ixfr. If set to True, it will use
- IXFR for incoming transfers. If set to False, it will use AXFR.
- At this moment there is no automatic fallback"""
- # TODO: http://bind10.isc.org/ticket/1279
- if use_ixfr is None:
- self.use_ixfr = \
- self._module_cc.get_default_value("zones/use_ixfr")
- else:
- self.use_ixfr = use_ixfr
+ def set_request_ixfr(self, request_ixfr):
+ if request_ixfr is None:
+ request_ixfr = \
+ self._module_cc.get_default_value("zones/request_ixfr")
+ try:
+ self.__request_ixfr = self.REQUEST_IXFR_CFG_TO_VAL[request_ixfr]
+ except KeyError:
+ raise XfrinZoneInfoException('invalid value for request_ixfr: ' +
+ request_ixfr)
def get_master_addr_info(self):
return (self.master_addr.family, socket.SOCK_STREAM,
@@ -1446,15 +1457,22 @@ class Xfrin:
def __init__(self):
self._max_transfers_in = 10
self._zones = {}
- # This is a set of (zone/class) tuples (both as strings),
- # representing the in-memory zones maintaned by Xfrin. It
- # is used to trigger Auth/in-memory so that it reloads
- # zones when they have been transfered in
- self._memory_zones = set()
- self._cc_setup()
self.recorder = XfrinRecorder()
self._shutdown_event = threading.Event()
self._counters = Counters(SPECFILE_LOCATION)
+ # This is essentially private, but we allow tests to customize it.
+ self._datasrc_clients_mgr = DataSrcClientsMgr()
+
+ # Initial configuration
+ self._cc_setup()
+ config_data = self._module_cc.get_full_config()
+ self.config_handler(config_data)
+ # data_sources configuration should be ready with cfgmgr, so this
+ # shouldn't fail; if it ever does we simply propagate the exception
+ # to terminate the program.
+ self._module_cc.add_remote_config_by_name('data_sources',
+ self._datasrc_config_handler)
+ init_keyring(self._module_cc)
def _cc_setup(self):
'''This method is used only as part of initialization, but is
@@ -1465,14 +1483,9 @@ class Xfrin:
# listening session will block the send operation.
self._send_cc_session = isc.cc.Session()
self._module_cc = isc.config.ModuleCCSession(SPECFILE_LOCATION,
- self.config_handler,
- self.command_handler)
+ self.config_handler,
+ self.command_handler)
self._module_cc.start()
- config_data = self._module_cc.get_full_config()
- self.config_handler(config_data)
- self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION,
- self._auth_config_handler)
- init_keyring(self._module_cc)
def _cc_check_command(self):
'''This is a straightforward wrapper for cc.check_command,
@@ -1504,7 +1517,8 @@ class Xfrin:
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
+ self._max_transfers_in = \
+ new_config.get("transfers_in") or self._max_transfers_in
if 'zones' in new_config:
self._clear_zone_info()
@@ -1519,78 +1533,25 @@ class Xfrin:
return create_answer(0)
- def _auth_config_handler(self, new_config, config_data):
- # Config handler for changes in Auth configuration
- self._set_db_file()
- self._set_memory_zones(new_config, config_data)
-
- def _clear_memory_zones(self):
- """Clears the memory_zones set; called before processing the
- changed list of memory datasource zones that have file type
- sqlite3"""
- self._memory_zones.clear()
-
- def _is_memory_zone(self, zone_name_str, zone_class_str):
- """Returns true if the given zone/class combination is configured
- in the in-memory datasource of the Auth process with file type
- 'sqlite3'.
- Note: this method is not thread-safe. We are considering
- changing the threaded model here, but if we do not, take
- care in accessing and updating the memory zone set (or add
- locks)
- """
- # Normalize them first, if either conversion fails, return false
- # (they won't be in the set anyway)
- try:
- zone_name_str = Name(zone_name_str).to_text().lower()
- zone_class_str = RRClass(zone_class_str).to_text()
- except Exception:
- return False
- return (zone_name_str, zone_class_str) in self._memory_zones
-
- def _set_memory_zones(self, new_config, config_data):
- """Part of the _auth_config_handler function, keeps an internal set
- of zones in the datasources config subset that have 'sqlite3' as
- their file type.
- Note: this method is not thread-safe. We are considering
- changing the threaded model here, but if we do not, take
- care in accessing and updating the memory zone set (or add
- locks)
+ def _datasrc_config_handler(self, new_config, config_data):
+ """Configuration handler of the 'data_sources' module.
+
+ The actual handling is deletegated to the DataSrcClientsMgr class;
+ this method is a simple wrapper.
+
+ This is essentially private, but implemented as 'protected' so tests
+ can refer to it; other external use is prohibited.
+
"""
- # walk through the data and collect the memory zones
- # If this causes any exception, assume we were passed bad data
- # and keep the original set
- new_memory_zones = set()
try:
- if "datasources" in new_config:
- for datasource in new_config["datasources"]:
- if "class" in datasource:
- ds_class = RRClass(datasource["class"])
- else:
- # Get the default
- ds_class = RRClass(config_data.get_default_value(
- "datasources/class"))
- if datasource["type"] == "memory":
- for zone in datasource["zones"]:
- if "filetype" in zone and \
- zone["filetype"] == "sqlite3":
- zone_name = Name(zone["origin"])
- zone_name_str = zone_name.to_text().lower()
- new_memory_zones.add((zone_name_str,
- ds_class.to_text()))
- # Ok, we can use the data, update our list
- self._memory_zones = new_memory_zones
- except Exception:
- # Something is wrong with the data. If this data even reached us,
- # we cannot do more than assume the real module has logged and
- # reported an error. Keep the old set.
- return
+ self._datasrc_clients_mgr.reconfigure(new_config, config_data)
+ except isc.server_common.datasrc_clients_mgr.ConfigError as ex:
+ logger.error(XFRIN_DATASRC_CONFIG_ERROR, ex)
def shutdown(self):
''' shutdown the xfrin process. the thread which is doing xfrin should be
terminated.
'''
- self._module_cc.remove_remote_config(AUTH_SPECFILE_LOCATION)
self._module_cc.send_stopping()
self._shutdown_event.set()
main_thread = threading.currentThread()
@@ -1599,75 +1560,110 @@ class Xfrin:
continue
th.join()
+ def __validate_notify_addr(self, notify_addr, zone_str, zone_info):
+ """Validate notify source as a destination for xfr source.
+
+ This is called from __handle_xfr_command in case xfr is triggered
+ by ZoneMgr either due to incoming Notify or periodic refresh event.
+
+ """
+ if zone_info is None:
+ # TODO what to do? no info known about zone. defaults?
+ errmsg = "Got notification to retransfer unknown zone " + zone_str
+ logger.info(XFRIN_RETRANSFER_UNKNOWN_ZONE, zone_str)
+ return create_answer(1, errmsg)
+ else:
+ master_addr = zone_info.get_master_addr_info()
+ if (notify_addr[0] != master_addr[0] or
+ notify_addr[2] != master_addr[2]):
+ notify_addr_str = format_addrinfo(notify_addr)
+ master_addr_str = format_addrinfo(master_addr)
+ errmsg = "Got notification for " + zone_str\
+ + "from unknown address: " + notify_addr_str;
+ logger.info(XFRIN_NOTIFY_UNKNOWN_MASTER, zone_str,
+ notify_addr_str, master_addr_str)
+ return create_answer(1, errmsg)
+
+ # Notified address is okay
+ return None
+
+ def __get_running_request_ixfr(self, arg_request_ixfr, zone_info):
+ """Determine the request_ixfr policy for a specific transfer.
+
+ This is a dedicated subroutine of __handle_xfr_command.
+
+ """
+ # If explicitly specified, use it.
+ if arg_request_ixfr is not None:
+ return arg_request_ixfr
+ # Otherwise, if zone info is known, use its value.
+ if zone_info is not None:
+ return zone_info.request_ixfr
+ # Otherwise, use the default value for ZoneInfo
+ request_ixfr_def = \
+ self._module_cc.get_default_value("zones/request_ixfr")
+ return ZoneInfo.REQUEST_IXFR_CFG_TO_VAL[request_ixfr_def]
+
+ def __handle_xfr_command(self, args, check_soa, addr_validator,
+ request_ixfr):
+ """Common subroutine for handling transfer commands.
+
+ This helper method unifies both cases of transfer command from
+ ZoneMgr or from a user. Depending on who invokes the transfer,
+ details of validation and parameter selection slightly vary.
+ These conditions are passed through parameters and handled in the
+ unified code of this method accordingly.
+
+ If this is from the ZoneMgr due to incoming notify, zone transfer
+ should start from the notify's source address as long as it's
+ configured as a master address, according to RFC1996. The current
+ implementation conforms to it in a limited way: we can only set one
+ master address. Once we add the ability to have multiple master
+ addresses, we should check if it matches one of them, and then use it.
+
+ In case of transfer command from the user, if the command specifies
+ the master address, use that one; otherwise try to use a configured
+ master address for the zone.
+
+ """
+ (zone_name, rrclass) = self._parse_zone_name_and_class(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 is None else zone_info.get_tsig_key()
+ zone_str = format_zone_str(zone_name, rrclass) # for logging
+ answer = addr_validator(master_addr, zone_str, zone_info)
+ if answer is not None:
+ return answer
+ request_ixfr = self.__get_running_request_ixfr(request_ixfr, zone_info)
+ ret = self.xfrin_start(zone_name, rrclass, master_addr, tsig_key,
+ request_ixfr, check_soa)
+ return create_answer(ret[0], ret[1])
+
def command_handler(self, command, args):
+ logger.debug(DBG_XFRIN_TRACE, XFRIN_RECEIVED_COMMAND, command)
answer = create_answer(0)
try:
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
- # 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)
- zone_str = format_zone_str(zone_name, rrclass)
- zone_info = self._get_zone_info(zone_name, rrclass)
- notify_addr = self._parse_master_and_port(args, 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_str
- logger.info(XFRIN_RETRANSFER_UNKNOWN_ZONE, zone_str)
- answer = create_answer(1, errmsg)
- else:
- request_type = RRType.AXFR
- if zone_info.use_ixfr:
- request_type = RRType.IXFR
- master_addr = zone_info.get_master_addr_info()
- if notify_addr[0] == master_addr[0] and\
- notify_addr[2] == master_addr[2]:
- ret = self.xfrin_start(zone_name,
- rrclass,
- self._get_db_file(),
- master_addr,
- zone_info.get_tsig_key(), request_type,
- True)
- answer = create_answer(ret[0], ret[1])
- else:
- notify_addr_str = format_addrinfo(notify_addr)
- master_addr_str = format_addrinfo(master_addr)
- errmsg = "Got notification for " + zone_str\
- + "from unknown address: " + notify_addr_str;
- logger.info(XFRIN_NOTIFY_UNKNOWN_MASTER, zone_str,
- notify_addr_str, master_addr_str)
- answer = create_answer(1, errmsg)
-
- 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.
- (zone_name, rrclass) = self._parse_zone_name_and_class(args)
- master_addr = self._parse_master_and_port(args, zone_name,
- rrclass)
- zone_info = self._get_zone_info(zone_name, rrclass)
- tsig_key = None
- request_type = RRType.AXFR
- if zone_info:
- tsig_key = zone_info.get_tsig_key()
- if zone_info.use_ixfr:
- request_type = RRType.IXFR
- db_file = args.get('db_file') or self._get_db_file()
- ret = self.xfrin_start(zone_name,
- rrclass,
- db_file,
- master_addr,
- tsig_key, request_type,
- (False if command == 'retransfer' else True))
- answer = create_answer(ret[0], ret[1])
-
+ # refresh/notify command from zone manager.
+ # The address has to be validated and always perform SOA check.
+ addr_validator = \
+ lambda x, y, z: self.__validate_notify_addr(x, y, z)
+ answer = self.__handle_xfr_command(args, True, addr_validator,
+ None)
+ elif command == 'retransfer':
+ # retransfer from cmdctl (sent by bindctl).
+ # No need for address validation, skip SOA check, and always
+ # use AXFR.
+ answer = self.__handle_xfr_command(
+ args, False, lambda x, y, z: None,
+ ZoneInfo.REQUEST_IXFR_DISABLED)
+ elif command == 'refresh':
+ # retransfer from cmdctl (sent by bindctl). similar to
+ # retransfer, but do SOA check, and honor request_ixfr config.
+ answer = self.__handle_xfr_command(
+ args, True, lambda x, y, z: None, None)
# return statistics data to the stats daemon
elif command == "getstats":
# The log level is here set to debug in order to avoid
@@ -1675,9 +1671,6 @@ class Xfrin:
# b10-stats daemon is periodically asking to the
# b10-xfrin daemon.
answer = create_answer(0, self._counters.get_statistics())
- logger.debug(DBG_XFRIN_TRACE, \
- XFRIN_RECEIVED_GETSTATS_COMMAND, \
- str(answer))
else:
answer = create_answer(1, 'unknown command: ' + command)
@@ -1691,7 +1684,8 @@ class Xfrin:
if zone_name_str is None:
raise XfrinException('zone name should be provided')
- return (_check_zone_name(zone_name_str), _check_zone_class(args.get('zone_class')))
+ 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):
"""
@@ -1733,21 +1727,6 @@ class Xfrin:
return (addr.family, socket.SOCK_STREAM, (str(addr), port))
- def _get_db_file(self):
- return self._db_file
-
- def _set_db_file(self):
- db_file, is_default =\
- self._module_cc.get_remote_config_value(AUTH_MODULE_NAME, "database_file")
- if is_default and "B10_FROM_BUILD" in os.environ:
- # override the local database setting if it is default and we
- # are running from the source tree
- # This should be hidden inside the data source library and/or
- # done as a configuration, and this special case should be gone).
- db_file = os.environ["B10_FROM_BUILD"] + os.sep +\
- "bind10_zones.sqlite3"
- self._db_file = db_file
-
def publish_xfrin_news(self, zone_name, zone_class, xfr_result):
'''Send command to xfrout/zone manager module.
If xfrin has finished successfully for one zone, tell the good
@@ -1786,7 +1765,8 @@ class Xfrin:
except isc.cc.session.SessionTimeout:
pass # for now we just ignore the failure
except socket.error as err:
- logger.error(XFRIN_MSGQ_SEND_ERROR, XFROUT_MODULE_NAME, ZONE_MANAGER_MODULE_NAME)
+ logger.error(XFRIN_MSGQ_SEND_ERROR, self.tsig_key_name,
+ XFROUT_MODULE_NAME, ZONE_MANAGER_MODULE_NAME)
else:
msg = create_command(notify_out.ZONE_XFRIN_FAILED, param)
@@ -1800,18 +1780,16 @@ class Xfrin:
except isc.cc.session.SessionTimeout:
pass # for now we just ignore the failure
except socket.error as err:
- logger.error(XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER, ZONE_MANAGER_MODULE_NAME)
+ logger.error(XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER, self.tsig_key_name,
+ ZONE_MANAGER_MODULE_NAME)
def startup(self):
logger.debug(DBG_PROCESS, XFRIN_STARTED)
while not self._shutdown_event.is_set():
self._cc_check_command()
- def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo,
- tsig_key, request_type, check_soa=True):
- if "pydnspp" not in sys.modules:
- return (1, "xfrin failed, can't load dns message python library: 'pydnspp'")
-
+ def xfrin_start(self, zone_name, rrclass, master_addrinfo, tsig_key,
+ request_ixfr, check_soa=True):
# check max_transfer_in, else return quota error
if self.recorder.count() >= self._max_transfers_in:
return (1, 'xfrin quota error')
@@ -1819,19 +1797,83 @@ class Xfrin:
if self.recorder.xfrin_in_progress(zone_name):
return (1, 'zone xfrin is in progress')
- xfrin_thread = threading.Thread(target = process_xfrin,
- args = (self,
- self.recorder,
- zone_name,
- rrclass,
- db_file,
- self._shutdown_event,
- master_addrinfo, check_soa,
- tsig_key, request_type))
+ # Identify the data source to which the zone content is transferred,
+ # and get the current zone SOA from the data source (if available).
+ # Note that we do this before spawning the xfrin session thread.
+ # find() on the client list and use of ZoneFinder (in _get_zone_soa())
+ # should be completed within the same single thread.
+ datasrc_client = None
+ clist = self._datasrc_clients_mgr.get_client_list(rrclass)
+ if clist is None:
+ return (1, 'no data source is configured for class %s' % rrclass)
+
+ try:
+ datasrc_client = clist.find(zone_name, True, False)[0]
+ if datasrc_client is None: # can happen, so log it separately.
+ logger.error(XFRIN_DATASRC_UNKNOWN,
+ format_zone_str(zone_name, rrclass))
+ return (1, 'data source to transfer %s to is unknown' %
+ format_zone_str(zone_name, rrclass))
+ zone_soa = _get_zone_soa(datasrc_client, zone_name, rrclass)
+ except isc.datasrc.Error as ex:
+ # rare case error. re-raise as XfrinException so it'll be logged
+ # in command_handler().
+ raise XfrinException('unexpected failure in datasrc module: ' +
+ str(ex))
+
+ xfrin_thread = threading.Thread(target=process_xfrin,
+ args=(self, self.recorder,
+ zone_name, rrclass,
+ datasrc_client, zone_soa,
+ self._shutdown_event,
+ master_addrinfo, check_soa,
+ tsig_key, request_ixfr,
+ self._counters))
xfrin_thread.start()
return (0, 'zone xfrin is started')
+def _get_zone_soa(datasrc_client, zone_name, zone_class):
+ """Retrieve the current SOA RR of the zone to be transferred.
+
+ This function is essentially private to the module, but will also
+ be called (or tweaked) from tests; no one else should use this
+ function directly.
+
+ The specified zone is expected to exist in the data source referenced
+ by the given datasrc_client at the point of the call to this function.
+ If this is not met XfrinException exception will be raised.
+
+ It will be used for various purposes in subsequent xfr protocol
+ processing. It is validly possible that the zone is currently
+ empty and therefore doesn't have an SOA, so this method doesn't
+ consider it an error and returns None in such a case. It may or
+ may not result in failure in the actual processing depending on
+ how the SOA is used.
+
+ When the zone has an SOA RR, this method makes sure that it's
+ valid, i.e., it has exactly one RDATA; if it is not the case
+ this method returns None.
+
+ """
+ # get the zone finder. this must be SUCCESS (not even
+ # PARTIALMATCH) because we are specifying the zone origin name.
+ result, finder = datasrc_client.find_zone(zone_name)
+ if result != DataSourceClient.SUCCESS:
+ # The data source doesn't know the zone. In the context of this
+ # function is called, this shouldn't happen.
+ raise XfrinException("unexpected result: zone %s doesn't exist" %
+ format_zone_str(zone_name, zone_class))
+ result, soa_rrset, _ = finder.find(zone_name, RRType.SOA)
+ if result != ZoneFinder.SUCCESS:
+ logger.info(XFRIN_ZONE_NO_SOA, format_zone_str(zone_name, zone_class))
+ return None
+ if soa_rrset.get_rdata_count() != 1:
+ logger.warn(XFRIN_ZONE_MULTIPLE_SOA,
+ format_zone_str(zone_name, zone_class),
+ soa_rrset.get_rdata_count())
+ return None
+ return soa_rrset
xfrind = None
diff --git a/src/bin/xfrin/xfrin.spec b/src/bin/xfrin/xfrin.spec
index ac447c8..87bb91e 100644
--- a/src/bin/xfrin/xfrin.spec
+++ b/src/bin/xfrin/xfrin.spec
@@ -48,6 +48,11 @@
"item_type": "boolean",
"item_optional": false,
"item_default": false
+ },
+ { "item_name": "request_ixfr",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "yes"
}
]
}
@@ -56,7 +61,36 @@
"commands": [
{
"command_name": "retransfer",
- "command_description": "retransfer a single zone without checking zone serial number",
+ "command_description": "retransfer a single zone without checking zone serial number, always using AXFR",
+ "command_args": [ {
+ "item_name": "zone_name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ {
+ "item_name": "zone_class",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": "IN"
+ },
+ {
+ "item_name": "master",
+ "item_type": "string",
+ "item_optional": true,
+ "item_default": ""
+ },
+ {
+ "item_name": "port",
+ "item_type": "integer",
+ "item_optional": true,
+ "item_default": 53
+ }
+ ]
+ },
+ {
+ "command_name": "refresh",
+ "command_description": "transfer a single zone with checking zone serial number and honoring the request_ixfr policy",
"command_args": [ {
"item_name": "zone_name",
"item_type": "string",
@@ -101,153 +135,123 @@
"item_type": "named_set",
"item_optional": false,
"item_default": {
- "_SERVER_" : {
- "soaoutv4": 0,
- "soaoutv6": 0,
- "axfrreqv4": 0,
- "axfrreqv6": 0,
- "ixfrreqv4": 0,
- "ixfrreqv6": 0,
- "xfrsuccess": 0,
- "xfrfail": 0,
- "time_to_ixfr": 0.0,
- "time_to_axfr": 0.0
+ "IN" : {
+ "_SERVER_" : {
+ "soaoutv4": 0,
+ "soaoutv6": 0,
+ "axfrreqv4": 0,
+ "axfrreqv6": 0,
+ "ixfrreqv4": 0,
+ "ixfrreqv6": 0,
+ "xfrsuccess": 0,
+ "xfrfail": 0,
+ "last_ixfr_duration": 0.0,
+ "last_axfr_duration": 0.0
+ }
}
},
"item_title": "Zone names",
"item_description": "A directory name of per-zone statistics",
"named_set_item_spec": {
- "item_name": "zonename",
- "item_type": "map",
+ "item_name": "classname",
+ "item_type": "named_set",
"item_optional": false,
"item_default": {},
- "item_title": "Zone name",
- "item_description": "A actual zone name or special zone name _SERVER_ representing an entire server",
- "map_item_spec": [
- {
- "item_name": "soaoutv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "SOAOutv4",
- "item_description": "Number of IPv4 SOA queries sent from Xfrin"
- },
- {
- "item_name": "soaoutv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "SOAOutv6",
- "item_description": "Number of IPv6 SOA queries sent from Xfrin"
- },
- {
- "item_name": "axfrreqv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "AXFRReqv4",
- "item_description": "Number of IPv4 AXFR requests sent from Xfrin"
- },
- {
- "item_name": "axfrreqv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "AXFRReqv6",
- "item_description": "Number of IPv6 AXFR requests sent from Xfrin"
- },
- {
- "item_name": "ixfrreqv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IXFRReqv4",
- "item_description": "Number of IPv4 IXFR requests sent from Xfrin"
- },
- {
- "item_name": "ixfrreqv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IXFRReqv6",
- "item_description": "Number of IPv6 IXFR requests sent from Xfrin"
- },
- {
- "item_name": "xfrsuccess",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "XfrSuccess",
- "item_description": "Number of zone transfer requests succeeded"
- },
- {
- "item_name": "xfrfail",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "XfrFail",
- "item_description": "Number of zone transfer requests failed"
- },
- {
- "item_name": "time_to_ixfr",
- "item_type": "real",
- "item_optional": false,
- "item_default": 0.0,
- "item_title": "Time to IXFR",
- "item_description": "Elapsed time in seconds to do the last IXFR"
- },
- {
- "item_name": "time_to_axfr",
- "item_type": "real",
- "item_optional": false,
- "item_default": 0.0,
- "item_title": "Time to AXFR",
- "item_description": "Elapsed time in seconds to do the last AXFR"
- }
- ]
+ "item_title": "RR class name",
+ "item_description": "An actual RR class name of the zone, e.g. IN, CH, and HS",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "An actual zone name or special zone name _SERVER_ representing the entire server",
+ "map_item_spec": [
+ {
+ "item_name": "soaoutv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOAOutv4",
+ "item_description": "Number of IPv4 SOA queries sent from Xfrin"
+ },
+ {
+ "item_name": "soaoutv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOAOutv6",
+ "item_description": "Number of IPv6 SOA queries sent from Xfrin"
+ },
+ {
+ "item_name": "axfrreqv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRReqv4",
+ "item_description": "Number of IPv4 AXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "axfrreqv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRReqv6",
+ "item_description": "Number of IPv6 AXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "ixfrreqv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRReqv4",
+ "item_description": "Number of IPv4 IXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "ixfrreqv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRReqv6",
+ "item_description": "Number of IPv6 IXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "xfrsuccess",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XfrSuccess",
+ "item_description": "Number of zone transfer requests succeeded. These include the case where the zone turns out to be the latest as a result of an initial SOA query (and there is actually no AXFR or IXFR transaction)."
+ },
+ {
+ "item_name": "xfrfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XfrFail",
+ "item_description": "Number of zone transfer requests failed"
+ },
+ {
+ "item_name": "last_axfr_duration",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 0.0,
+ "item_title": "Last AXFR duration",
+ "item_description": "Duration in seconds of the last successful AXFR. 0.0 means no successful AXFR done or means a successful AXFR done in less than a microsecond. If an AXFR is aborted due to some failure, this duration won't be updated."
+ },
+ {
+ "item_name": "last_ixfr_duration",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 0.0,
+ "item_title": "Last IXFR duration",
+ "item_description": "Duration in seconds of the last successful IXFR. 0.0 means no successful IXFR done or means a successful IXFR done in less than a microsecond. If an IXFR is aborted due to some failure, this duration won't be updated."
+ }
+ ]
+ }
}
},
{
- "item_name": "ixfr_running",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IXFRs running",
- "item_description": "Number of IXFRs in progress"
- },
- {
- "item_name": "axfr_running",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "AXFRs running",
- "item_description": "Number of AXFRs in progress"
- },
- {
- "item_name": "ixfr_deferred",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IXFRs deferred",
- "item_description": "Number of deferred IXFRs"
- },
- {
- "item_name": "axfr_deferred",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "AXFRs deferred",
- "item_description": "Number of deferred AXFRs"
- },
- {
- "item_name": "soa_in_progress",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "SOA queries",
- "item_description": "Number of SOA queries in progress"
- },
- {
"item_name": "socket",
"item_type": "map",
"item_optional": false,
@@ -294,7 +298,7 @@
}
},
"item_title": "IPv4",
- "item_description": "A directory name of IPv4 statistics",
+ "item_description": "A directory name of IPv4",
"map_item_spec": [
{
"item_name": "tcp",
@@ -388,7 +392,7 @@
}
},
"item_title": "IPv6",
- "item_description": "A directory name of IPv6 statistics",
+ "item_description": "A directory name of IPv6",
"map_item_spec": [
{
"item_name": "tcp",
diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes
index 63f40e0..4383324 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -56,10 +56,31 @@ most likely cause is that xfrin the msgq daemon is not running.
There was an error while the given command was being processed. The
error is given in the log message.
-% XFRIN_CONNECT_MASTER error connecting to master at %1: %2
+% XFRIN_CONNECT_MASTER (with TSIG %1) error connecting to master at %2: %3
There was an error opening a connection to the master. The error is
shown in the log message.
+% XFRIN_DATASRC_CONFIG_ERROR failed to update data source configuration: %1
+Configuration for the global data sources is updated, but the update
+cannot be applied to xfrin. The xfrin module will still keep running
+with the previous configuration, but the cause of the failure and
+other log messages must be carefully examined because if only xfrin
+rejects the new configuration then the entire BIND 10 system will have
+inconsistent state among different modules. If other modules accept
+the update but xfrin produces this error, the xfrin module should
+probably be restarted.
+
+% XFRIN_DATASRC_UNKNOWN data source to transfer %1 to is unknown
+The xfrin daemon received a command that would trigger a transfer,
+but could not find a data source where the specified zone belongs.
+There can be several reasons for this error: it may be a simple
+misspelling in the xfrin or zonemgr configuration, or in the user
+supplied parameter if it is triggered by an external command (such as
+from bindctl). Another possibility is that this is the initial transfer
+for a newly setup secondary zone. In this case at least an initial empty zone
+must be created in one of configured data sources. This can be done by
+the -e option of b10-loadzone.
+
% XFRIN_EXITING exiting
The xfrin daemon is exiting.
@@ -80,6 +101,24 @@ is not equal to the requested SOA serial.
There was an error importing the python DNS module pydnspp. The most
likely cause is a PYTHONPATH problem.
+% XFRIN_INITIAL_AXFR no SOA available for %1 yet, requesting AXFR of initial version from %2
+On starting the zone transfer, it's detected that there is no SOA
+record available for the zone. This is always the case for the very
+first transfer or if the administrator has removed the locally copied
+data by hand for some reason. In this case trying IXFR does not make
+sense for the obvious reason, so AXFR will be used from the beginning,
+regardless of the request_ixfr configuration (even if "only" is
+specified).
+
+% XFRIN_INITIAL_IXFR requesting IXFR for %1 from %2
+IXFR will be used for the initial request type for the specified zone
+transfer. It will fall back to AXFR if the initial request fails
+(and unless specified not to do so by configuration).
+
+% XFRIN_INITIAL_IXFR_DISABLED IXFR disabled for %1, requesting AXFR from %2
+The use of IXFR is disabled by configuration for the specified zone,
+so only AXFR will be tried.
+
% XFRIN_INVALID_ZONE_DATA zone %1 received from %2 is broken and unusable
The zone was received, but it failed sanity validation. The previous version
of zone (if any is available) will be used. Look for previous
@@ -120,12 +159,12 @@ the primary server between the SOA and IXFR queries. The client
implementation confirms the whole response is this single SOA, and
aborts the transfer just like a successful case.
-% XFRIN_MSGQ_SEND_ERROR error while contacting %1 and %2
+% XFRIN_MSGQ_SEND_ERROR (with TSIG %1) error while contacting %2 and %3
There was a problem sending a message to the xfrout module or the
zone manager. This most likely means that the msgq daemon has quit or
was killed.
-% XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER error while contacting %1
+% XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER (with TSIG %1) error while contacting %2
There was a problem sending a message to the zone manager. This most
likely means that the msgq daemon has quit or was killed.
@@ -135,6 +174,9 @@ from does not match the master address in the Xfrin configuration. The notify
is ignored. This may indicate that the configuration for the master is wrong,
that a wrong machine is sending notifies, or that fake notifies are being sent.
+% XFRIN_RECEIVED_COMMAND received command: %1
+The xfrin daemon received a command on the command channel.
+
% XFRIN_RETRANSFER_UNKNOWN_ZONE got notification to retransfer unknown zone %1
There was an internal command to retransfer the given zone, but the
zone is not known to the system. This may indicate that the configuration
@@ -203,12 +245,23 @@ often.
The XFR transfer for the given zone has failed due to an internal error.
The error is shown in the log message.
-% XFRIN_XFR_TRANSFER_FALLBACK falling back from IXFR to AXFR for %1
+% XFRIN_XFR_TRANSFER_FALLBACK (with TSIG %1) falling back from IXFR to AXFR for %2
The IXFR transfer of the given zone failed. This might happen in many cases,
such that the remote server doesn't support IXFR, we don't have the SOA record
(or the zone at all), we are out of sync, etc. In many of these situations,
AXFR could still work. Therefore we try that one in case it helps.
+% XFRIN_XFR_TRANSFER_FALLBACK_DISABLED (with TSIG %1) suppressing fallback from IXFR to AXFR for %2
+An IXFR transfer of the given zone failed. By default AXFR will be
+tried next, but this fallback is disabled by configuration, so the
+whole transfer attempt failed at that point. If the reason for the
+failure (which should be logged separately) is temporary, this is
+probably harmless or even desired as another IXFR will take place some
+time later (without falling back to the possibly expensive AXFR). If
+this is a permanent error (e.g., some change at the master server
+completely disables IXFR), the secondary zone will eventually expire,
+so the configuration should be changed to allow AXFR.
+
% XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION %1 transfer of zone %2 with %3 failed: %4
The XFR transfer for the given zone has failed due to a protocol
error, such as an unexpected response from the primary server. The
@@ -223,19 +276,6 @@ is recommended to check the primary server configuration.
A connection to the master server has been made, the serial value in
the SOA record has been checked, and a zone transfer has been started.
-% XFRIN_ZONE_CREATED Zone %1 not found in the given data source, newly created
-On starting an xfrin session, it is identified that the zone to be
-transferred is not found in the data source. This can happen if a
-secondary DNS server first tries to perform AXFR from a primary server
-without creating the zone image beforehand (e.g. by b10-loadzone). As
-of this writing the xfrin process provides backward compatible
-behavior to previous versions: creating a new one in the data source
-not to surprise existing users too much. This is probably not a good
-idea, however, in terms of who should be responsible for managing
-zones at a higher level. In future it is more likely that a separate
-zone management framework is provided, and the situation where the
-given zone isn't found in xfrout will be treated as an error.
-
% XFRIN_ZONE_INVALID Newly received zone %1/%2 fails validation: %3
The zone was received successfully, but it failed validation. The problem
is severe enough that the new version of zone is discarded and the old version,
@@ -247,9 +287,9 @@ On starting an xfrin session, it is identified that the zone to be
transferred has multiple SOA RRs. Such a zone is broken, but could be
accidentally configured especially in a data source using "non
captive" backend database. The implementation ignores entire SOA RRs
-and tries to continue processing as if the zone were empty. This
-means subsequent AXFR can succeed and possibly replace the zone with
-valid content, but an IXFR attempt will fail.
+and tries to continue processing as if the zone were empty. This also
+means AXFR will be used unconditionally, regardless of the configured value
+for request_ixfr of the zone.
% XFRIN_ZONE_NO_SOA Zone %1 does not have SOA
On starting an xfrin session, it is identified that the zone to be
@@ -274,8 +314,3 @@ The zone was received successfully, but when checking it, it was discovered
there's some issue with it. It might be correct, but it should be checked
and possibly fixed on the remote server. The problem is described in the
message. The problem does not stop the zone from being used.
-
-% XFRIN_RECEIVED_GETSTATS_COMMAND received command to send statistics data: %1
-The xfrin daemon received a command on the command channel that
-statistics data should be sent to the stats daemon.
-
diff --git a/src/bin/xfrout/b10-xfrout.xml b/src/bin/xfrout/b10-xfrout.xml
index 5c71e05..468c6af 100644
--- a/src/bin/xfrout/b10-xfrout.xml
+++ b/src/bin/xfrout/b10-xfrout.xml
@@ -171,44 +171,56 @@
<variablelist>
<varlistentry>
- <term><replaceable>zonename</replaceable></term>
+ <term><replaceable>classname</replaceable></term>
<listitem><simpara>
- A actual zone name or special zone name <quote>_SERVER_</quote>
- representing an entire server
+ An actual RR class name of the zone, e.g. IN, CH, and HS
</simpara>
<variablelist>
<varlistentry>
- <term>notifyoutv4</term>
+ <term><replaceable>zonename</replaceable></term>
<listitem><simpara>
- Number of IPv4 notifies per zone name sent out from Xfrout
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>notifyoutv6</term>
- <listitem><simpara>
- Number of IPv6 notifies per zone name sent out from Xfrout
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>xfrrej</term>
- <listitem><simpara>
- Number of XFR requests per zone name rejected by Xfrout
- </simpara></listitem>
- </varlistentry>
-
- <varlistentry>
- <term>xfrreqdone</term>
- <listitem><simpara>
- Number of requested zone transfers per zone name completed
- </simpara></listitem>
- </varlistentry>
+ An actual zone name or special zone
+ name <quote>_SERVER_</quote> representing an entire
+ server
+ </simpara>
+ <variablelist>
+
+ <varlistentry>
+ <term>notifyoutv4</term>
+ <listitem><simpara>
+ Number of IPv4 notifies per zone name sent out from Xfrout
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>notifyoutv6</term>
+ <listitem><simpara>
+ Number of IPv6 notifies per zone name sent out from Xfrout
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>xfrrej</term>
+ <listitem><simpara>
+ Number of XFR requests per zone name rejected by Xfrout
+ </simpara></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>xfrreqdone</term>
+ <listitem><simpara>
+ Number of requested zone transfers per zone name completed
+ </simpara></listitem>
+ </varlistentry>
+
+ </variablelist>
+ </listitem>
+ </varlistentry><!-- end of zonename -->
</variablelist>
</listitem>
- </varlistentry><!-- end of zonename -->
+ </varlistentry><!-- end of classname -->
</variablelist>
</listitem>
diff --git a/src/bin/xfrout/tests/Makefile.am b/src/bin/xfrout/tests/Makefile.am
index ad6d7e6..acaee34 100644
--- a/src/bin/xfrout/tests/Makefile.am
+++ b/src/bin/xfrout/tests/Makefile.am
@@ -10,7 +10,7 @@ EXTRA_DIST += testdata/example.com testdata/creatediff.py
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$$$(ENV_LIBRARY_PATH)
else
# Some systems need the ds path even if not all paths are necessary
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index 2802ace..f7ed1e1 100644
--- a/src/bin/xfrout/tests/xfrout_test.py.in
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -1,4 +1,4 @@
-# Copyright (C) 2010-2012 Internet Systems Consortium.
+# Copyright (C) 2010-2013 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
@@ -18,6 +18,8 @@
import unittest
import os
+import socket
+import fcntl
from isc.testutils.tsigctx_mock import MockTSIGContext
from isc.testutils.ccsession_mock import MockModuleCCSession
from isc.cc.session import *
@@ -39,6 +41,7 @@ TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
TEST_ZONE_NAME_STR = "example.com."
TEST_ZONE_NAME = Name(TEST_ZONE_NAME_STR)
TEST_RRCLASS = RRClass.IN
+TEST_RRCLASS_STR = TEST_RRCLASS.to_text()
IXFR_OK_VERSION = 2011111802
IXFR_NG_VERSION = 2011111803
SOA_CURRENT_VERSION = 2011112001
@@ -190,8 +193,31 @@ class Dbserver:
def decrease_transfers_counter(self):
self.transfer_counter -= 1
+class TestUtility(unittest.TestCase):
+ """Test some utility functions."""
+
+ def test_make_blockign(self):
+ def is_blocking(fd):
+ return (fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_NONBLOCK) == 0
+
+ # socket.socket doesn't support the 'with' statement before Python
+ # 3.2, so we'll close it ourselves (while it's not completely exception
+ # safe, it should be acceptable for a test case)
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM,
+ socket.IPPROTO_TCP)
+ # By default socket is made blocking
+ self.assertTrue(is_blocking(sock.fileno()))
+ # make_blocking(False) makes it non blocking
+ xfrout.make_blocking(sock.fileno(), False)
+ self.assertFalse(is_blocking(sock.fileno()))
+ # make_blocking(True) makes it blocking again
+ xfrout.make_blocking(sock.fileno(), True)
+ self.assertTrue(is_blocking(sock.fileno()))
+
+ sock.close()
+
class TestXfroutSessionBase(unittest.TestCase):
- '''Base classs for tests related to xfrout sessions
+ '''Base class for tests related to xfrout sessions
This class defines common setup/teadown and utility methods. Actual
tests are delegated to subclasses.
@@ -269,6 +295,12 @@ class TestXfroutSessionBase(unittest.TestCase):
self.xfrsess._request_typestr = 'IXFR'
def setUp(self):
+ # xfrout.make_blocking won't work with faked socket, and we'd like to
+ # examine how it's called, so we replace it with our mock.
+ self.__orig_make_blocking = xfrout.make_blocking
+ self._make_blocking_history = []
+ xfrout.make_blocking = lambda x, y: self.__make_blocking(x, y)
+
self.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
self.xfrsess = MyXfroutSession(self.sock, None, Dbserver(),
TSIGKeyRing(),
@@ -277,7 +309,8 @@ class TestXfroutSessionBase(unittest.TestCase):
# When not testing ACLs, simply accept
isc.acl.dns.REQUEST_LOADER.load(
[{"action": "ACCEPT"}]),
- {})
+ {},
+ xfrout.Counters(xfrout.SPECFILE_LOCATION))
self.set_request_type(RRType.AXFR) # test AXFR by default
self.mdata = self.create_request_data()
self.soa_rrset = create_soa(SOA_CURRENT_VERSION)
@@ -285,7 +318,11 @@ class TestXfroutSessionBase(unittest.TestCase):
# original is used elsewhere.
self.orig_get_rrset_len = xfrout.get_rrset_len
+ def __make_blocking(self, fd, on):
+ self._make_blocking_history.append((fd, on))
+
def tearDown(self):
+ xfrout.make_blocking = self.__orig_make_blocking
xfrout.get_rrset_len = self.orig_get_rrset_len
# transfer_counter must be always be reset no matter happens within
# the XfroutSession object. We check the condition here.
@@ -306,7 +343,12 @@ class TestXfroutSession(TestXfroutSessionBase):
def test_quota_ok(self):
'''The default case in terms of the xfrout quota.
+ It also confirms that the socket is made blocking.
+
'''
+ # make_blocking shouldn't be called yet.
+ self.assertEqual([], self._make_blocking_history)
+
# set up a bogus request, which should result in FORMERR. (it only
# has to be something that is different from the previous case)
self.xfrsess._request_data = \
@@ -316,6 +358,26 @@ class TestXfroutSession(TestXfroutSessionBase):
XfroutSession._handle(self.xfrsess)
self.assertEqual(self.sock.read_msg().get_rcode(), Rcode.FORMERR)
+ # make_blocking should have been called in _handle(). Note that
+ # in the test fixture we handle fileno as a faked socket object, not
+ # as integer.
+ self.assertEqual([(self.sock, True)], self._make_blocking_history)
+
+ def test_make_blocking_fail(self):
+ """Check what if make_blocking() raises an exception."""
+
+ # make_blocking() can fail due to OSError. It shouldn't cause
+ # disruption, and xfrout_start shouldn't be called.
+
+ def raise_exception():
+ raise OSError('faked error')
+ xfrout.make_blocking = lambda x, y: raise_exception()
+ self.start_arg = []
+ self.xfrsess.dns_xfrout_start = \
+ lambda s, x, y: self.start_arg.append((x, y))
+ XfroutSession._handle(self.xfrsess)
+ self.assertEqual([], self.start_arg)
+
def test_exception_from_session(self):
'''Test the case where the main processing raises an exception.
@@ -381,7 +443,8 @@ class TestXfroutSession(TestXfroutSessionBase):
# check the 'xfrrej' counter initially
self.assertRaises(isc.cc.data.DataNotFoundError,
self.xfrsess._counters.get, 'zones',
- TEST_ZONE_NAME_STR, 'xfrrej')
+ TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrrej')
# Localhost (the default in this test) is accepted
rcode, msg = self.xfrsess._parse_query_message(self.mdata)
self.assertEqual(rcode.to_text(), "NOERROR")
@@ -397,7 +460,8 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertEqual(rcode.to_text(), "REFUSED")
# check the 'xfrrej' counter after incrementing
self.assertEqual(self.xfrsess._counters.get(
- 'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 1)
+ 'zones', TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrrej'), 1)
# TSIG signed request
request_data = self.create_request_data(with_tsig=True)
@@ -428,7 +492,8 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertEqual(rcode.to_text(), "REFUSED")
# check the 'xfrrej' counter after incrementing
self.assertEqual(self.xfrsess._counters.get(
- 'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 2)
+ 'zones', TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrrej'), 2)
# ACL using TSIG: no TSIG; should be rejected
acl_setter(isc.acl.dns.REQUEST_LOADER.load([
@@ -438,7 +503,8 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertEqual(rcode.to_text(), "REFUSED")
# check the 'xfrrej' counter after incrementing
self.assertEqual(self.xfrsess._counters.get(
- 'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 3)
+ 'zones', TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrrej'), 3)
#
# ACL using IP + TSIG: both should match
@@ -460,7 +526,8 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertEqual(rcode.to_text(), "REFUSED")
# check the 'xfrrej' counter after incrementing
self.assertEqual(self.xfrsess._counters.get(
- 'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 4)
+ 'zones', TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrrej'), 4)
# Address matches, but TSIG doesn't (not included)
self.xfrsess._remote = (socket.AF_INET, socket.SOCK_STREAM,
('192.0.2.1', 12345))
@@ -468,7 +535,8 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertEqual(rcode.to_text(), "REFUSED")
# check the 'xfrrej' counter after incrementing
self.assertEqual(self.xfrsess._counters.get(
- 'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 5)
+ 'zones', TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrrej'), 5)
# Neither address nor TSIG matches
self.xfrsess._remote = (socket.AF_INET, socket.SOCK_STREAM,
('192.0.2.2', 12345))
@@ -476,7 +544,8 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertEqual(rcode.to_text(), "REFUSED")
# check the 'xfrrej' counter after incrementing
self.assertEqual(self.xfrsess._counters.get(
- 'zones', TEST_ZONE_NAME_STR, 'xfrrej'), 6)
+ 'zones', TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrrej'), 6)
def test_transfer_acl(self):
# ACL checks only with the default ACL
@@ -876,12 +945,14 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertRaises(isc.cc.data.DataNotFoundError,
self.xfrsess._counters.get,
- 'zones', TEST_ZONE_NAME_STR, 'xfrreqdone')
+ 'zones', TEST_RRCLASS_STR,
+ TEST_ZONE_NAME_STR, 'xfrreqdone')
self.xfrsess._reply_xfrout_query = myreply
self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
self.assertEqual(self.sock.readsent(), b"success")
self.assertGreater(self.xfrsess._counters.get(
- 'zones', TEST_ZONE_NAME_STR, 'xfrreqdone'), 0)
+ 'zones', TEST_RRCLASS_STR, TEST_ZONE_NAME_STR,
+ 'xfrreqdone'), 0)
def test_reply_xfrout_query_axfr(self):
self.xfrsess._soa = self.soa_rrset
@@ -1253,7 +1324,8 @@ class TestUnixSockServer(unittest.TestCase):
# This would be the handler class, but we just check it is passed
# the right parametes, so function is enough for that.
keys = isc.server_common.tsig_keyring.get_keyring()
- def handler(sock, data, server, keyring, address, acl, config):
+ def handler(sock, data, server, keyring, address, acl, config,
+ counters):
self.assertEqual("sock", sock)
self.assertEqual("data", data)
self.assertEqual(self.unix, server)
@@ -1261,6 +1333,7 @@ class TestUnixSockServer(unittest.TestCase):
self.assertEqual("Address", address)
self.assertEqual("acl", acl)
self.assertEqual("Zone config", config)
+ self.assertIs(self.unix._counters, counters)
self.unix.RequestHandlerClass = handler
self.unix.finish_request("sock", "data")
finally:
@@ -1559,7 +1632,9 @@ class TestUnixSockServerForCounter(unittest.TestCase):
xfrout.ThreadingUnixStreamServer = DummySocketserver
xfrout.super = lambda : DummySocketserver()
xfrout.select.select = lambda x,y,z: ([None],[None],[None])
- self.unix = UnixSockServer(None, None, threading.Event(), None, None)
+ self._counters = xfrout.Counters(xfrout.SPECFILE_LOCATION)
+ self.unix = UnixSockServer(None, None, threading.Event(), None, None,
+ self._counters)
def tearDown(self):
( UnixSockServer._remove_unused_sock_file,
@@ -1589,7 +1664,8 @@ class TestUnixSockServerForCounter(unittest.TestCase):
'socket', 'unixdomain', 'openfail')
xfrout.ThreadingUnixStreamServer = DummySocketserverException
try:
- self.unix = UnixSockServer(None, None, None, None, None)
+ self.unix = UnixSockServer(None, None, None, None, None,
+ self._counters)
except Exception:
pass
else:
@@ -1630,7 +1706,7 @@ class TestUnixSockServerForCounter(unittest.TestCase):
self.unix._counters.get,
'socket', 'unixdomain', 'acceptfail')
xfrout.super = lambda : DummyClassException()
- self.unix = UnixSockServer(None, None, None, None, None)
+ self.unix = UnixSockServer(None, None, None, None, None, self._counters)
self.assertRaises(Exception, self.unix.get_request)
self.assertEqual(
self.unix._counters.get('socket', 'unixdomain', 'acceptfail'), 1)
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index d6ef360..7bf1605 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -27,9 +27,10 @@ from socketserver import *
import os
from isc.config.ccsession import *
from isc.cc import SessionError, SessionTimeout
-from isc.statistics import Counters
+from isc.statistics.dns import Counters
from isc.notify import notify_out
import isc.util.process
+import fcntl
import socket
import select
import errno
@@ -152,9 +153,31 @@ def get_soa_serial(soa_rdata):
'''
return Serial(int(soa_rdata.to_text().split()[2]))
+def make_blocking(filenum, on):
+ """A helper function to change blocking mode of the given socket.
+
+ It sets the mode of blocking I/O for the socket associated with filenum
+ (descriptor of the socket) according to parameter 'on': if it's True the
+ file will be made blocking; otherwise it will be made non-blocking.
+
+ The given filenum must be a descriptor of a socket (not an ordinary file
+ etc), but this function doesn't check that condition.
+
+ filenum(int): file number (descriptor) of the socket to update.
+ on(bool): whether enable (True) or disable (False) blocking I/O.
+
+ """
+ flags = fcntl.fcntl(filenum, fcntl.F_GETFL)
+ if on: # make it blocking
+ flags &= ~os.O_NONBLOCK
+ else: # make it non blocking
+ flags |= os.O_NONBLOCK
+ fcntl.fcntl(filenum, fcntl.F_SETFL, flags)
+
class XfroutSession():
def __init__(self, sock_fd, request_data, server, tsig_key_ring, remote,
- default_acl, zone_config, client_class=DataSourceClient):
+ default_acl, zone_config, counters,
+ client_class=DataSourceClient):
self._sock_fd = sock_fd
self._request_data = request_data
self._server = server
@@ -171,7 +194,7 @@ class XfroutSession():
self._jnl_reader = None # will be set to a reader for IXFR
# Creation of self.counters should be done before of
# invoking self._handle()
- self._counters = Counters(SPECFILE_LOCATION)
+ self._counters = counters
self._handle()
def create_tsig_ctx(self, tsig_record, tsig_key_ring):
@@ -186,10 +209,14 @@ class XfroutSession():
'''
# Check the xfrout quota. We do both increase/decrease in this
- # method so it's clear we always release it once acuired.
+ # method so it's clear we always release it once acquired.
quota_ok = self._server.increase_transfers_counter()
ex = None
try:
+ # Before start, make sure the socket uses blocking I/O because
+ # responses will be sent in the blocking mode; otherwise it could
+ # result in EWOULDBLOCK and disrupt the session.
+ make_blocking(self._sock_fd, True)
self.dns_xfrout_start(self._sock_fd, self._request_data, quota_ok)
except Exception as e:
# To avoid resource leak we need catch all possible exceptions
@@ -275,7 +302,8 @@ class XfroutSession():
return None, None
elif acl_result == REJECT:
# count rejected Xfr request by each zone name
- self._counters.inc('zones', zone_name.to_text(), 'xfrrej')
+ self._counters.inc('zones', zone_class.to_text(),
+ zone_name.to_text(), 'xfrrej')
logger.debug(DBG_XFROUT_TRACE, XFROUT_QUERY_REJECTED,
self._request_type, format_addrinfo(self._remote),
format_zone_str(zone_name, zone_class))
@@ -502,7 +530,7 @@ class XfroutSession():
return self._reply_query_with_error_rcode(msg, sock_fd,
Rcode.FORMERR)
elif not quota_ok:
- logger.warn(XFROUT_QUERY_QUOTA_EXCCEEDED, self._request_typestr,
+ logger.warn(XFROUT_QUERY_QUOTA_EXCEEDED, self._request_typestr,
format_addrinfo(self._remote),
self._server._max_transfers_out)
return self._reply_query_with_error_rcode(msg, sock_fd,
@@ -545,7 +573,8 @@ class XfroutSession():
else:
self._counters.dec('ixfr_running')
# count done Xfr requests by each zone name
- self._counters.inc('zones', zone_name.to_text(), 'xfrreqdone')
+ self._counters.inc('zones', zone_class.to_text(),
+ zone_name.to_text(), 'xfrreqdone')
logger.info(XFROUT_XFR_TRANSFER_DONE, self._request_typestr,
format_addrinfo(self._remote), zone_str)
@@ -655,11 +684,11 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
'''The unix domain socket server which accept xfr query sent from auth server.'''
def __init__(self, sock_file, handle_class, shutdown_event, config_data,
- cc):
+ cc, counters):
self._remove_unused_sock_file(sock_file)
self._sock_file = sock_file
socketserver_mixin.NoPollMixIn.__init__(self)
- self._counters = Counters(SPECFILE_LOCATION)
+ self._counters = counters
try:
ThreadingUnixStreamServer.__init__(self, sock_file, \
handle_class)
@@ -858,7 +887,8 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
self._lock.release()
self.RequestHandlerClass(sock_fd, request_data, self,
isc.server_common.tsig_keyring.get_keyring(),
- self._guess_remote(sock_fd), acl, zone_config)
+ self._guess_remote(sock_fd), acl, zone_config,
+ self._counters)
def _remove_unused_sock_file(self, sock_file):
'''Try to remove the socket file. If the file is being used
@@ -990,17 +1020,19 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
class XfroutServer:
def __init__(self):
+ self._default_notify_address = ''
+ self._default_notify_port = 53
self._unix_socket_server = None
self._listen_sock_file = UNIX_SOCKET_FILE
self._shutdown_event = threading.Event()
self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
self._config_data = self._cc.get_full_config()
+ self._counters = Counters(SPECFILE_LOCATION)
self._cc.start()
self._cc.add_remote_config(AUTH_SPECFILE_LOCATION)
isc.server_common.tsig_keyring.init_keyring(self._cc)
self._start_xfr_query_listener()
self._start_notifier()
- self._counters = Counters(SPECFILE_LOCATION)
def _start_xfr_query_listener(self):
'''Start a new thread to accept xfr query. '''
@@ -1009,16 +1041,22 @@ class XfroutServer:
XfroutSession,
self._shutdown_event,
self._config_data,
- self._cc)
+ self._cc, self._counters)
listener = threading.Thread(target=self._unix_socket_server.serve_forever)
listener.start()
def _start_notifier(self):
datasrc = self._unix_socket_server.get_db_file()
- self._notifier = notify_out.NotifyOut(datasrc)
+ self._notifier = notify_out.NotifyOut(datasrc, counters=self._counters)
if 'also_notify' in self._config_data:
for slave in self._config_data['also_notify']:
- self._notifier.add_slave(slave['address'], slave['port'])
+ address = self._default_notify_address
+ if 'address' in slave:
+ address = slave['address']
+ port = self._default_notify_port
+ if 'port' in slave:
+ port = slave['port']
+ self._notifier.add_slave(address, port)
self._notifier.dispatcher()
def send_notify(self, zone_name, zone_class):
diff --git a/src/bin/xfrout/xfrout.spec.pre.in b/src/bin/xfrout/xfrout.spec.pre.in
index 4277c3b..dd90fe4 100644
--- a/src/bin/xfrout/xfrout.spec.pre.in
+++ b/src/bin/xfrout/xfrout.spec.pre.in
@@ -43,7 +43,7 @@
"item_name": "port",
"item_type": "integer",
"item_optional": false,
- "item_default": 0
+ "item_default": 53
}
]
}
@@ -121,56 +121,66 @@
"item_type": "named_set",
"item_optional": false,
"item_default": {
- "_SERVER_" : {
- "notifyoutv4" : 0,
- "notifyoutv6" : 0,
- "xfrrej" : 0,
- "xfrreqdone" : 0
+ "IN" : {
+ "_SERVER_" : {
+ "notifyoutv4" : 0,
+ "notifyoutv6" : 0,
+ "xfrrej" : 0,
+ "xfrreqdone" : 0
+ }
}
},
"item_title": "Zone names",
"item_description": "A directory name of per-zone statistics",
"named_set_item_spec": {
- "item_name": "zonename",
- "item_type": "map",
+ "item_name": "classname",
+ "item_type": "named_set",
"item_optional": false,
"item_default": {},
- "item_title": "Zone name",
- "item_description": "A actual zone name or special zone name _SERVER_ representing an entire server",
- "map_item_spec": [
- {
- "item_name": "notifyoutv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IPv4 notifies",
- "item_description": "Number of IPv4 notifies per zone name sent out from Xfrout"
- },
- {
- "item_name": "notifyoutv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IPv6 notifies",
- "item_description": "Number of IPv6 notifies per zone name sent out from Xfrout"
- },
- {
- "item_name": "xfrrej",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "XFR rejected requests",
- "item_description": "Number of XFR requests per zone name rejected by Xfrout"
- },
- {
- "item_name": "xfrreqdone",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "Requested zone transfers",
- "item_description": "Number of requested zone transfers completed per zone name"
- }
- ]
+ "item_title": "RR class name",
+ "item_description": "An actual RR class name of the zone, e.g. IN, CH, and HS",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "An actual zone name or special zone name _SERVER_ representing an entire server",
+ "map_item_spec": [
+ {
+ "item_name": "notifyoutv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IPv4 notifies",
+ "item_description": "Number of IPv4 notifies per zone name sent out from Xfrout"
+ },
+ {
+ "item_name": "notifyoutv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IPv6 notifies",
+ "item_description": "Number of IPv6 notifies per zone name sent out from Xfrout"
+ },
+ {
+ "item_name": "xfrrej",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XFR rejected requests",
+ "item_description": "Number of XFR requests per zone name rejected by Xfrout"
+ },
+ {
+ "item_name": "xfrreqdone",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Requested zone transfers",
+ "item_description": "Number of requested zone transfers completed per zone name"
+ }
+ ]
+ }
}
},
{
diff --git a/src/bin/xfrout/xfrout_messages.mes b/src/bin/xfrout/xfrout_messages.mes
index d49981d..505eb7c 100644
--- a/src/bin/xfrout/xfrout_messages.mes
+++ b/src/bin/xfrout/xfrout_messages.mes
@@ -131,7 +131,7 @@ given host. This is required by the ACLs. The %2 represents the IP
address and port of the peer requesting the transfer, and the %3
represents the zone name and class.
-% XFROUT_QUERY_QUOTA_EXCCEEDED %1 client %2: request denied due to quota (%3)
+% XFROUT_QUERY_QUOTA_EXCEEDED %1 client %2: request denied due to quota (%3)
The xfr request was rejected because the server was already handling
the maximum number of allowable transfers as specified in the transfers_out
configuration parameter, which is also shown in the log message. The
diff --git a/src/bin/zonemgr/tests/Makefile.am b/src/bin/zonemgr/tests/Makefile.am
index b60fae7..72b842a 100644
--- a/src/bin/zonemgr/tests/Makefile.am
+++ b/src/bin/zonemgr/tests/Makefile.am
@@ -7,7 +7,7 @@ CLEANFILES = initdb.file
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/zonemgr/tests/zonemgr_test.py b/src/bin/zonemgr/tests/zonemgr_test.py
index 81c5392..5a17476 100644
--- a/src/bin/zonemgr/tests/zonemgr_test.py
+++ b/src/bin/zonemgr/tests/zonemgr_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010 Internet Systems Consortium.
+# Copyright (C) 2010-2013 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
@@ -22,6 +22,7 @@ import tempfile
from zonemgr import *
from isc.testutils.ccsession_mock import MockModuleCCSession
from isc.notify import notify_out
+from isc.datasrc import ZoneFinder
ZONE_NAME_CLASS1_IN = ("example.net.", "IN")
ZONE_NAME_CLASS1_CH = ("example.net.", "CH")
@@ -36,8 +37,6 @@ LOWERBOUND_RETRY = 5
REFRESH_JITTER = 0.10
RELOAD_JITTER = 0.75
-TEST_SQLITE3_DBFILE = os.getenv("TESTDATAOBJDIR") + '/initdb.file'
-
class ZonemgrTestException(Exception):
pass
@@ -46,16 +45,53 @@ class FakeCCSession(isc.config.ConfigData, MockModuleCCSession):
module_spec = isc.config.module_spec_from_file(SPECFILE_LOCATION)
ConfigData.__init__(self, module_spec)
MockModuleCCSession.__init__(self)
+ # For inspection
+ self.added_remote_modules = []
+
+ def add_remote_config_by_name(self, name, callback):
+ self.added_remote_modules.append((name, callback))
def rpc_call(self, command, module, instance="*", to="*", params=None):
if module not in ("Auth", "Xfrin"):
raise ZonemgrTestException("module name not exist")
- def get_remote_config_value(self, module_name, identifier):
- if module_name == "Auth" and identifier == "database_file":
- return TEST_SQLITE3_DBFILE, False
+class MockDataSourceClient():
+ '''A simple mock data source client.'''
+ def __init__(self):
+ self.rdata_net = 'a.example.net. root.example.net. 2009073106 ' + \
+ '7200 3600 2419200 21600'
+ self.rdata_org = 'a.example.org. root.example.org. 2009073112 ' + \
+ '7200 3600 2419200 21600'
+
+ def find_zone(self, zone_name):
+ '''Mock version of DataSourceClient.find_zone().'''
+ return (isc.datasrc.DataSourceClient.SUCCESS, self)
+
+ def find(self, name, rrtype, options=ZoneFinder.FIND_DEFAULT):
+ '''Mock version of ZoneFinder.find().'''
+ if name == Name('example.net'):
+ rdata = Rdata(RRType.SOA, RRClass.IN, self.rdata_net)
+ elif name == 'example.org.':
+ rdata = Rdata(RRType.SOA, RRClass.IN, self.rdata_org)
else:
- return "unknown", False
+ return (ZoneFinder.NXDOMAIN, None, 0)
+ rrset = RRset(name, RRClass.IN, RRType.SOA, RRTTL(3600))
+ rrset.add_rdata(rdata)
+ return (ZoneFinder.SUCCESS, rrset, 0)
+
+class MockDataSrcClientsMgr():
+ '''A simple mock data source client manager.'''
+ def __init__(self):
+ self.datasrc_client = MockDataSourceClient()
+
+ def get_client_list(self, rrclass):
+ return self
+
+ def find(self, zone_name, want_exact_match, want_finder):
+ """Pretending find method on the object returned by get_client_list"""
+ if issubclass(type(self.datasrc_client), Exception):
+ raise self.datasrc_client
+ return self.datasrc_client, None, None
class MyZonemgrRefresh(ZonemgrRefresh):
def __init__(self):
@@ -66,19 +102,8 @@ class MyZonemgrRefresh(ZonemgrRefresh):
self._reload_jitter = 0.75
self._refresh_jitter = 0.25
- def get_zone_soa(zone_name, db_file):
- 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
-
- ZonemgrRefresh.__init__(self, TEST_SQLITE3_DBFILE, self._slave_socket,
- FakeCCSession())
+ ZonemgrRefresh.__init__(self, self._slave_socket, FakeCCSession())
+ self._datasrc_clients_mgr = MockDataSrcClientsMgr()
current_time = time.time()
self._zonemgr_refresh_info = {
('example.net.', 'IN'): {
@@ -95,19 +120,23 @@ class MyZonemgrRefresh(ZonemgrRefresh):
class TestZonemgrRefresh(unittest.TestCase):
def setUp(self):
- if os.path.exists(TEST_SQLITE3_DBFILE):
- os.unlink(TEST_SQLITE3_DBFILE)
self.stderr_backup = sys.stderr
sys.stderr = open(os.devnull, 'w')
self.zone_refresh = MyZonemgrRefresh()
self.cc_session = FakeCCSession()
def tearDown(self):
- if os.path.exists(TEST_SQLITE3_DBFILE):
- os.unlink(TEST_SQLITE3_DBFILE)
sys.stderr.close()
sys.stderr = self.stderr_backup
+ def test_init(self):
+ """Check some initial configuration after construction"""
+ # data source "module" should have been registrered as a necessary
+ # remote config
+ self.assertEqual([('data_sources',
+ self.zone_refresh._datasrc_config_handler)],
+ self.zone_refresh._module_cc.added_remote_modules)
+
def test_random_jitter(self):
max = 100025.120
jitter = 0
@@ -195,16 +224,9 @@ class TestZonemgrRefresh(unittest.TestCase):
def test_zonemgr_reload_zone(self):
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, '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._datasrc_clients_mgr.datasrc_client.rdata_net = soa_rdata
self.zone_refresh.zonemgr_reload_zone(ZONE_NAME_CLASS1_IN)
self.assertEqual(soa_rdata, self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_soa_rdata"])
- sqlite3_ds.get_zone_soa = old_get_zone_soa
def test_get_zone_notifier_master(self):
notify_master = "192.168.1.1"
@@ -275,24 +297,10 @@ class TestZonemgrRefresh(unittest.TestCase):
def test_send_command(self):
self.assertRaises(ZonemgrTestException, self.zone_refresh._send_command, "Unknown", "Notify", None)
- def test_zone_mgr_is_empty(self):
- self.assertFalse(self.zone_refresh._zone_mgr_is_empty())
- self.zone_refresh._zonemgr_refresh_info = {}
- self.assertTrue(self.zone_refresh._zone_mgr_is_empty())
-
def test_zonemgr_add_zone(self):
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
+ self.zone_refresh._datasrc_clients_mgr.datasrc_client.rdata_net = soa_rdata
time1 = time.time()
-
- def get_zone_soa(zone_name, db_file):
- 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_refresh_info = {}
self.zone_refresh.zonemgr_add_zone(ZONE_NAME_CLASS1_IN)
self.assertEqual(1, len(self.zone_refresh._zonemgr_refresh_info))
@@ -306,31 +314,35 @@ class TestZonemgrRefresh(unittest.TestCase):
self.assertTrue((time1 + 900 * (1 - self.zone_refresh._reload_jitter)) <= zone_timeout)
self.assertTrue(zone_timeout <= time2 + 900)
- def get_zone_soa2(zone_name, db_file):
+ old_get_zone_soa = self.zone_refresh._get_zone_soa
+ def get_zone_soa2(zone_name_class):
return None
- sqlite3_ds.get_zone_soa = get_zone_soa2
+ self.zone_refresh._get_zone_soa = get_zone_soa2
self.zone_refresh.zonemgr_add_zone(ZONE_NAME_CLASS2_IN)
self.assertTrue(self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS2_IN]["zone_soa_rdata"] is None)
- sqlite3_ds.get_zone_soa = old_get_zone_soa
+ self.zone_refresh._get_zone_soa = old_get_zone_soa
def test_zone_handle_notify(self):
- self.zone_refresh.zone_handle_notify(ZONE_NAME_CLASS1_IN,"127.0.0.1")
- notify_master = self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["notify_master"]
+ self.assertTrue(self.zone_refresh.zone_handle_notify(
+ ZONE_NAME_CLASS1_IN, "127.0.0.1"))
+ notify_master = self.zone_refresh.\
+ _zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["notify_master"]
self.assertEqual("127.0.0.1", notify_master)
- zone_timeout = self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["next_refresh_time"]
+ zone_timeout = self.zone_refresh.\
+ _zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["next_refresh_time"]
current_time = time.time()
self.assertTrue(zone_timeout <= current_time)
- self.assertRaises(ZonemgrException, self.zone_refresh.zone_handle_notify,\
- 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")
+
+ # If the specified zone does not in the configured secondary list,
+ # it should return False.
+ self.assertFalse(self.zone_refresh.zone_handle_notify(
+ ZONE_NAME_CLASS3_CH, "127.0.0.1"))
+ self.assertFalse(self.zone_refresh.zone_handle_notify(
+ ZONE_NAME_CLASS3_IN, "127.0.0.1"))
def test_zone_refresh_success(self):
soa_rdata = 'a.example.net. root.example.net. 2009073106 1800 900 2419200 21600'
- def get_zone_soa(zone_name, db_file):
- 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._datasrc_clients_mgr.datasrc_client.rdata_net = soa_rdata
time1 = time.time()
self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"] = ZONE_REFRESHING
self.zone_refresh.zone_refresh_success(ZONE_NAME_CLASS1_IN)
@@ -367,14 +379,14 @@ class TestZonemgrRefresh(unittest.TestCase):
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)
- old_get_zone_soa = sqlite3_ds.get_zone_soa
- def get_zone_soa(zone_name, db_file):
+ old_get_zone_soa = self.zone_refresh._get_zone_soa
+ def get_zone_soa(zone_name_class):
return None
- sqlite3_ds.get_zone_soa = get_zone_soa
+ self.zone_refresh._get_zone_soa = get_zone_soa
self.zone_refresh.zone_refresh_fail(ZONE_NAME_CLASS1_IN)
self.assertEqual(self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"],
ZONE_EXPIRED)
- sqlite3_ds.get_zone_soa = old_get_zone_soa
+ self.zone_refresh._get_zone_soa = old_get_zone_soa
def test_find_need_do_refresh_zone(self):
time1 = time.time()
@@ -607,9 +619,21 @@ class TestZonemgrRefresh(unittest.TestCase):
config, self.cc_session)
class MyZonemgr(Zonemgr):
+ class DummySocket:
+ """This dummy class simply steal send() to record any transmitted data.
+
+ """
+ def __init__(self):
+ self.sent_data = []
+
+ def send(self, data):
+ self.sent_data.append(data)
+
+ class DummyLock:
+ def __enter__(self): pass
+ def __exit__(self, type, value, traceback): pass
def __init__(self):
- self._db_file = TEST_SQLITE3_DBFILE
self._zone_refresh = None
self._shutdown_event = threading.Event()
self._module_cc = FakeCCSession()
@@ -621,6 +645,8 @@ class MyZonemgr(Zonemgr):
"reload_jitter" : 0.75,
"secondary_zones": []
}
+ self._lock = self.DummyLock()
+ self._master_socket = self.DummySocket()
def _start_zone_refresh_timer(self):
pass
@@ -628,14 +654,8 @@ class MyZonemgr(Zonemgr):
class TestZonemgr(unittest.TestCase):
def setUp(self):
- if os.path.exists(TEST_SQLITE3_DBFILE):
- os.unlink(TEST_SQLITE3_DBFILE)
self.zonemgr = MyZonemgr()
- def tearDown(self):
- if os.path.exists(TEST_SQLITE3_DBFILE):
- os.unlink(TEST_SQLITE3_DBFILE)
-
def test_config_handler(self):
config_data1 = {
"lowerbound_refresh" : 60,
@@ -655,9 +675,8 @@ class TestZonemgr(unittest.TestCase):
config_data3 = {"refresh_jitter" : 0.7}
self.zonemgr.config_handler(config_data3)
self.assertEqual(0.5, self.zonemgr._config_data.get("refresh_jitter"))
- # The zone doesn't exist in database, simply skip loading soa for it and log an warning
- self.zonemgr._zone_refresh = ZonemgrRefresh(TEST_SQLITE3_DBFILE, None,
- FakeCCSession())
+ # The zone doesn't exist in database, simply skip loading soa for it and log a warning
+ self.zonemgr._zone_refresh = ZonemgrRefresh(None, FakeCCSession())
config_data1["secondary_zones"] = [{"name": "nonexistent.example",
"class": "IN"}]
self.assertEqual(self.zonemgr.config_handler(config_data1),
@@ -668,19 +687,22 @@ class TestZonemgr(unittest.TestCase):
is None)
self.assertEqual(0.1, self.zonemgr._config_data.get("refresh_jitter"))
- def test_get_db_file(self):
- self.assertEqual(TEST_SQLITE3_DBFILE, self.zonemgr.get_db_file())
-
def test_parse_cmd_params(self):
- params1 = {"zone_name" : "example.com.", "zone_class" : "CH", "master" : "127.0.0.1"}
+ 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))
+ self.assertEqual(answer1,
+ self.zonemgr._parse_cmd_params(params1,
+ ZONE_NOTIFY_COMMAND))
params2 = {"zone_name" : "example.com.", "zone_class" : "IN"}
answer2 = ZONE_NAME_CLASS3_IN
- self.assertEqual(answer2, self.zonemgr._parse_cmd_params(params2, notify_out.ZONE_NEW_DATA_READY_CMD))
- self.assertRaises(ZonemgrException, self.zonemgr._parse_cmd_params, params2, ZONE_NOTIFY_COMMAND)
+ self.assertEqual(answer2, self.zonemgr._parse_cmd_params(
+ params2, notify_out.ZONE_NEW_DATA_READY_CMD))
+ self.assertRaises(ZonemgrException, self.zonemgr._parse_cmd_params,
+ params2, ZONE_NOTIFY_COMMAND)
params1 = {"zone_class" : "CH"}
- self.assertRaises(ZonemgrException, self.zonemgr._parse_cmd_params, params2, ZONE_NOTIFY_COMMAND)
+ self.assertRaises(ZonemgrException, self.zonemgr._parse_cmd_params,
+ params2, ZONE_NOTIFY_COMMAND)
def test_config_data_check(self):
# jitter should not be bigger than half of the original value
@@ -697,6 +719,26 @@ class TestZonemgr(unittest.TestCase):
self.zonemgr.run()
self.assertTrue(self.zonemgr._module_cc.stopped)
+ def test_command_handler_notify(self):
+ """Check the result of NOTIFY command."""
+ self.zonemgr._zone_refresh = MyZonemgrRefresh()
+
+ # On successful case, the other thread will be notified via
+ # _master_socket.
+ self.zonemgr._zone_refresh.zone_handle_notify = lambda x, y: True
+ self.zonemgr.command_handler("notify", {"zone_name": "example.",
+ "zone_class": "IN",
+ "master": "192.0.2.1"})
+ self.assertEqual([b" "], self.zonemgr._master_socket.sent_data)
+
+ # If the specified is not found in the secondary list, it doesn't
+ # bother to wake the thread (sent_data shouldn't change)
+ self.zonemgr._zone_refresh.zone_handle_notify = lambda x, y: False
+ self.zonemgr.command_handler("notify", {"zone_name": "example.",
+ "zone_class": "IN",
+ "master": "192.0.2.1"})
+ self.assertEqual([b" "], self.zonemgr._master_socket.sent_data)
+
if __name__== "__main__":
isc.log.resetUnitTestRootLogger()
unittest.main()
diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
index 59900c4..2d5167b 100755
--- a/src/bin/zonemgr/zonemgr.py.in
+++ b/src/bin/zonemgr/zonemgr.py.in
@@ -1,6 +1,6 @@
#!@PYTHON@
-# Copyright (C) 2010 Internet Systems Consortium.
+# Copyright (C) 2010-2013 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
@@ -34,12 +34,14 @@ import threading
import select
import socket
import errno
-from isc.datasrc import sqlite3_ds
from optparse import OptionParser, OptionValueError
from isc.config.ccsession import *
import isc.util.process
from isc.log_messages.zonemgr_messages import *
from isc.notify import notify_out
+from isc.server_common.datasrc_clients_mgr import DataSrcClientsMgr, ConfigError
+from isc.datasrc import DataSourceClient, ZoneFinder
+from isc.dns import *
# Initialize logging for called modules.
isc.log.init("b10-zonemgr", buffer=True)
@@ -66,7 +68,9 @@ if "B10_FROM_BUILD" in os.environ:
else:
PREFIX = "@prefix@"
DATAROOTDIR = "@datarootdir@"
- SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+ SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}",
+ DATAROOTDIR).replace("${prefix}",
+ PREFIX)
AUTH_SPECFILE_PATH = SPECFILE_PATH
SPECFILE_LOCATION = SPECFILE_PATH + "/zonemgr.spec"
@@ -76,7 +80,6 @@ __version__ = "BIND10"
# define module name
XFRIN_MODULE_NAME = 'Xfrin'
-AUTH_MODULE_NAME = 'Auth'
# define command name
ZONE_REFRESH_COMMAND = 'refresh_from_zonemgr'
@@ -103,18 +106,23 @@ class ZonemgrRefresh:
can be stopped by calling shutdown() in another thread.
"""
- def __init__(self, db_file, slave_socket, module_cc_session):
- self._mccs = module_cc_session
+ def __init__(self, slave_socket, module_cc):
+ self._module_cc = module_cc
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(module_cc_session.get_full_config(),
- module_cc_session)
+ # This is essentially private, but we allow tests to customize it.
+ self._datasrc_clients_mgr = DataSrcClientsMgr()
+ # data_sources configuration should be ready with cfgmgr, so this
+ # shouldn't fail; if it ever does we simply propagate the exception
+ # to terminate the program.
+ self._module_cc.add_remote_config_by_name('data_sources',
+ self._datasrc_config_handler)
+ self.update_config_data(module_cc.get_full_config(), module_cc)
self._running = False
def _random_jitter(self, max, jitter):
@@ -133,27 +141,32 @@ class ZonemgrRefresh:
def _set_zone_timer(self, zone_name_class, max, jitter):
"""Set zone next refresh time.
jitter should not be bigger than half the original value."""
- self._set_zone_next_refresh_time(zone_name_class, self._get_current_time() + \
+ self._set_zone_next_refresh_time(zone_name_class,
+ self._get_current_time() +
self._random_jitter(max, jitter))
def _set_zone_refresh_timer(self, zone_name_class):
"""Set zone next refresh time after zone refresh success.
now + refresh - refresh_jitter <= next_refresh_time <= now + refresh
"""
- zone_refresh_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[REFRESH_OFFSET])
+ zone_refresh_time = float(self._get_zone_soa_rdata(zone_name_class).
+ split(" ")[REFRESH_OFFSET])
zone_refresh_time = max(self._lowerbound_refresh, zone_refresh_time)
- self._set_zone_timer(zone_name_class, zone_refresh_time, self._refresh_jitter * zone_refresh_time)
+ self._set_zone_timer(zone_name_class, zone_refresh_time,
+ self._refresh_jitter * zone_refresh_time)
def _set_zone_retry_timer(self, zone_name_class):
"""Set zone next refresh time after zone refresh fail.
now + retry - retry_jitter <= next_refresh_time <= now + retry
"""
- if (self._get_zone_soa_rdata(zone_name_class) is not None):
- zone_retry_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[RETRY_OFFSET])
+ if self._get_zone_soa_rdata(zone_name_class) is not None:
+ zone_retry_time = float(self._get_zone_soa_rdata(zone_name_class).
+ split(" ")[RETRY_OFFSET])
else:
zone_retry_time = 0.0
zone_retry_time = max(self._lowerbound_retry, zone_retry_time)
- self._set_zone_timer(zone_name_class, zone_retry_time, self._refresh_jitter * zone_retry_time)
+ self._set_zone_timer(zone_name_class, zone_retry_time,
+ self._refresh_jitter * zone_retry_time)
def _set_zone_notify_timer(self, zone_name_class):
"""Set zone next refresh time after receiving notify
@@ -167,19 +180,22 @@ class ZonemgrRefresh:
def zone_refresh_success(self, zone_name_class):
"""Update zone info after zone refresh success"""
- if (self._zone_not_exist(zone_name_class)):
- logger.error(ZONEMGR_UNKNOWN_ZONE_SUCCESS, zone_name_class[0], zone_name_class[1])
+ if self._zone_not_exist(zone_name_class):
+ logger.error(ZONEMGR_UNKNOWN_ZONE_SUCCESS, zone_name_class[0],
+ zone_name_class[1])
raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
"belong to zonemgr" % zone_name_class)
self.zonemgr_reload_zone(zone_name_class)
self._set_zone_refresh_timer(zone_name_class)
self._set_zone_state(zone_name_class, ZONE_OK)
- self._set_zone_last_refresh_time(zone_name_class, self._get_current_time())
+ self._set_zone_last_refresh_time(zone_name_class,
+ self._get_current_time())
def zone_refresh_fail(self, zone_name_class):
"""Update zone info after zone refresh fail"""
- if (self._zone_not_exist(zone_name_class)):
- logger.error(ZONEMGR_UNKNOWN_ZONE_FAIL, zone_name_class[0], zone_name_class[1])
+ if self._zone_not_exist(zone_name_class):
+ logger.error(ZONEMGR_UNKNOWN_ZONE_FAIL, zone_name_class[0],
+ zone_name_class[1])
raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
"belong to zonemgr" % zone_name_class)
# Is zone expired?
@@ -191,46 +207,109 @@ class ZonemgrRefresh:
self._set_zone_retry_timer(zone_name_class)
def zone_handle_notify(self, zone_name_class, master):
- """Handle zone notify"""
- if (self._zone_not_exist(zone_name_class)):
- logger.error(ZONEMGR_UNKNOWN_ZONE_NOTIFIED, zone_name_class[0],
- zone_name_class[1], master)
- raise ZonemgrException("[b10-zonemgr] Notified zone (%s, %s) "
- "doesn't belong to zonemgr" % zone_name_class)
+ """Handle an incoming NOTIFY message via the Auth module.
+
+ It returns True if the specified zone matches one of the locally
+ configured list of secondary zones; otherwise returns False.
+ In the latter case it assumes the server is a primary (master) of the
+ zone; the Auth module should have rejected the case where it's not
+ even authoritative for the zone.
+
+ Parameters:
+ zone_name_class (Name, RRClass): the notified zone name and class.
+ master (str): textual address of the NOTIFY sender.
+
+ """
+ if self._zone_not_exist(zone_name_class):
+ logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_ZONE_NOTIFY_NOT_SECONDARY,
+ zone_name_class[0], zone_name_class[1], master)
+ return False
self._set_zone_notifier_master(zone_name_class, master)
self._set_zone_notify_timer(zone_name_class)
+ return True
def zonemgr_reload_zone(self, zone_name_class):
""" Reload a zone."""
- zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]), self._db_file)
- self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"] = zone_soa[7]
+ self._zonemgr_refresh_info[zone_name_class]["zone_soa_rdata"] = \
+ self._get_zone_soa(zone_name_class)
def zonemgr_add_zone(self, zone_name_class):
""" Add a zone into zone manager."""
-
- logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_LOAD_ZONE, zone_name_class[0], zone_name_class[1])
+ logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_LOAD_ZONE, zone_name_class[0],
+ zone_name_class[1])
zone_info = {}
- zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]), self._db_file)
+ zone_soa = self._get_zone_soa(zone_name_class)
if zone_soa is None:
logger.warn(ZONEMGR_NO_SOA, zone_name_class[0], zone_name_class[1])
zone_info["zone_soa_rdata"] = None
zone_reload_time = 0.0
else:
- zone_info["zone_soa_rdata"] = zone_soa[7]
- zone_reload_time = float(zone_soa[7].split(" ")[RETRY_OFFSET])
+ zone_info["zone_soa_rdata"] = zone_soa
+ zone_reload_time = float(zone_soa.split(" ")[RETRY_OFFSET])
zone_info["zone_state"] = ZONE_OK
zone_info["last_refresh_time"] = self._get_current_time()
self._zonemgr_refresh_info[zone_name_class] = zone_info
- # Imposes some random jitters to avoid many zones need to do refresh at the same time.
+ # Imposes some random jitters to avoid many zones need to do refresh
+ # at the same time.
zone_reload_time = max(self._lowerbound_retry, zone_reload_time)
- self._set_zone_timer(zone_name_class, zone_reload_time, self._reload_jitter * zone_reload_time)
+ self._set_zone_timer(zone_name_class, zone_reload_time,
+ self._reload_jitter * zone_reload_time)
+
+ def _get_zone_soa(self, zone_name_class):
+ """Retrieve the current SOA RR of the zone to be transferred."""
+
+ def get_zone_soa_rrset(datasrc_client, zone_name, zone_class):
+ """Retrieve the current SOA RR of the zone to be transferred."""
+ # get the zone finder. this must be SUCCESS (not even
+ # PARTIALMATCH) because we are specifying the zone origin name.
+ result, finder = datasrc_client.find_zone(zone_name)
+ if result != DataSourceClient.SUCCESS:
+ # The data source doesn't know the zone. In the context in
+ # which this function is called, this shouldn't happen.
+ raise ZonemgrException(
+ "unexpected result: zone %s/%s doesn't exist" %
+ (zone_name.to_text(True), str(zone_class)))
+ result, soa_rrset, _ = finder.find(zone_name, RRType.SOA)
+ if result != ZoneFinder.SUCCESS:
+ logger.warn(ZONEMGR_NO_SOA,
+ zone_name.to_text(True), str(zone_class))
+ return None
+ return soa_rrset
+
+ # Identify the data source to which the zone content is transferred,
+ # and get the current zone SOA from the data source (if available).
+ datasrc_client = None
+ clist = self._datasrc_clients_mgr.get_client_list(zone_name_class[1])
+ if clist is None:
+ return None
+ try:
+ datasrc_client = clist.find(zone_name_class[0], True, False)[0]
+ if datasrc_client is None: # can happen, so log it separately.
+ logger.error(ZONEMGR_DATASRC_UNKNOWN,
+ zone_name_class[0] + '/' + zone_name_class[1])
+ return None
+ zone_soa = get_zone_soa_rrset(datasrc_client,
+ Name(zone_name_class[0]),
+ RRClass(zone_name_class[1]))
+ if zone_soa == None:
+ return None
+ else:
+ return zone_soa.get_rdata()[0].to_text()
+ except isc.datasrc.Error as ex:
+ # rare case error. re-raise as ZonemgrException so it'll be logged
+ # in command_handler().
+ raise ZonemgrException('unexpected failure in datasrc module: ' +
+ str(ex))
def _zone_is_expired(self, zone_name_class):
"""Judge whether a zone is expired or not."""
- zone_expired_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[EXPIRED_OFFSET])
- zone_last_refresh_time = self._get_zone_last_refresh_time(zone_name_class)
+ zone_expired_time = float(self._get_zone_soa_rdata(zone_name_class).
+ split(" ")[EXPIRED_OFFSET])
+ zone_last_refresh_time = \
+ self._get_zone_last_refresh_time(zone_name_class)
if (ZONE_EXPIRED == self._get_zone_state(zone_name_class) or
- zone_last_refresh_time + zone_expired_time <= self._get_current_time()):
+ zone_last_refresh_time + zone_expired_time <=
+ self._get_current_time()):
return True
return False
@@ -245,16 +324,19 @@ class ZonemgrRefresh:
self._zonemgr_refresh_info[zone_name_class]["last_refresh_time"] = time
def _get_zone_notifier_master(self, zone_name_class):
- if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()):
+ if ("notify_master" in
+ self._zonemgr_refresh_info[zone_name_class].keys()):
return self._zonemgr_refresh_info[zone_name_class]["notify_master"]
return None
def _set_zone_notifier_master(self, zone_name_class, master_addr):
- self._zonemgr_refresh_info[zone_name_class]["notify_master"] = master_addr
+ self._zonemgr_refresh_info[zone_name_class]["notify_master"] = \
+ master_addr
def _clear_zone_notifier_master(self, zone_name_class):
- if ("notify_master" in self._zonemgr_refresh_info[zone_name_class].keys()):
+ if ("notify_master" in
+ self._zonemgr_refresh_info[zone_name_class].keys()):
del self._zonemgr_refresh_info[zone_name_class]["notify_master"]
def _get_zone_state(self, zone_name_class):
@@ -278,7 +360,7 @@ class ZonemgrRefresh:
def _send_command(self, module_name, command_name, params):
"""Send command between modules."""
try:
- self._mccs.rpc_call(command_name, module_name, params=params)
+ self._module_cc.rpc_call(command_name, module_name, params=params)
except socket.error:
# FIXME: WTF? Where does socket.error come from? And how do we ever
# dare ignore such serious error? It can only be broken link to
@@ -297,7 +379,8 @@ class ZonemgrRefresh:
# If hasn't received refresh response but are within refresh
# timeout, skip the zone
if (ZONE_REFRESHING == zone_state and
- (self._get_zone_refresh_timeout(zone_name_class) > self._get_current_time())):
+ (self._get_zone_refresh_timeout(zone_name_class) >
+ self._get_current_time())):
continue
# Get the zone with minimum next_refresh_time
@@ -307,7 +390,8 @@ class ZonemgrRefresh:
zone_need_refresh = zone_name_class
# Find the zone need do refresh
- if (self._get_zone_next_refresh_time(zone_need_refresh) < self._get_current_time()):
+ if (self._get_zone_next_refresh_time(zone_need_refresh) <
+ self._get_current_time()):
break
return zone_need_refresh
@@ -315,9 +399,12 @@ class ZonemgrRefresh:
def _do_refresh(self, zone_name_class):
"""Do zone refresh."""
- logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_REFRESH_ZONE, zone_name_class[0], zone_name_class[1])
+ logger.debug(DBG_ZONEMGR_BASIC, ZONEMGR_REFRESH_ZONE,
+ zone_name_class[0], zone_name_class[1])
self._set_zone_state(zone_name_class, ZONE_REFRESHING)
- self._set_zone_refresh_timeout(zone_name_class, self._get_current_time() + self._max_transfer_timeout)
+ self._set_zone_refresh_timeout(zone_name_class,
+ self._get_current_time() +
+ self._max_transfer_timeout)
notify_master = self._get_zone_notifier_master(zone_name_class)
# If the zone has notify master, send notify command to xfrin module
if notify_master:
@@ -334,13 +421,6 @@ class ZonemgrRefresh:
}
self._send_command(XFRIN_MODULE_NAME, ZONE_REFRESH_COMMAND, param)
- def _zone_mgr_is_empty(self):
- """Does zone manager has no zone?"""
- if not len(self._zonemgr_refresh_info):
- return True
-
- return False
-
def _run_timer(self, start_event):
while self._running:
# Notify run_timer that we already started and are inside the loop.
@@ -350,24 +430,29 @@ class ZonemgrRefresh:
if start_event:
start_event.set()
start_event = None
- # If zonemgr has no zone, set timer timeout to self._lowerbound_retry.
- if self._zone_mgr_is_empty():
+ # If zonemgr has no zone, set timeout to minimum
+ if not self._zonemgr_refresh_info:
timeout = self._lowerbound_retry
else:
zone_need_refresh = self._find_need_do_refresh_zone()
- # If don't get zone with minimum next refresh time, set timer timeout to self._lowerbound_retry.
+ # If don't get zone with minimum next refresh time, set
+ # timeout to minimum
if not zone_need_refresh:
timeout = self._lowerbound_retry
else:
- timeout = self._get_zone_next_refresh_time(zone_need_refresh) - self._get_current_time()
- if (timeout < 0):
+ timeout = \
+ self._get_zone_next_refresh_time(zone_need_refresh) - \
+ self._get_current_time()
+ if timeout < 0:
self._do_refresh(zone_need_refresh)
continue
""" Wait for the socket notification for a maximum time of timeout
in seconds (as float)."""
try:
- rlist, wlist, xlist = select.select([self._check_sock, self._read_sock], [], [], timeout)
+ rlist, wlist, xlist = \
+ select.select([self._check_sock, self._read_sock],
+ [], [], timeout)
except select.error as e:
if e.args[0] == errno.EINTR:
(rlist, wlist, xlist) = ([], [], [])
@@ -386,8 +471,8 @@ class ZonemgrRefresh:
def run_timer(self, daemon=False):
"""
- Keep track of zone timers. Spawns and starts a thread. The thread object
- is returned.
+ Keep track of zone timers. Spawns and starts a thread. The thread
+ object is returned.
You can stop it by calling shutdown().
"""
@@ -412,6 +497,20 @@ class ZonemgrRefresh:
# Return the thread to anyone interested
return self._thread
+ def _datasrc_config_handler(self, new_config, config_data):
+ """Configuration handler of the 'data_sources' module.
+
+ The actual handling is delegated to the DataSrcClientsMgr class;
+ this method is a simple wrapper.
+
+ This is essentially private, but implemented as 'protected' so tests
+ can refer to it; other external use is prohibited.
+ """
+ try:
+ self._datasrc_clients_mgr.reconfigure(new_config, config_data)
+ except isc.server_common.datasrc_clients_mgr.ConfigError as ex:
+ logger.error(ZONEMGR_DATASRC_CONFIG_ERROR, ex)
+
def shutdown(self):
"""
Stop the run_timer() thread. Block until it finished. This must be
@@ -423,7 +522,7 @@ class ZonemgrRefresh:
# Ask the thread to stop
self._running = False
- self._write_sock.send(b'shutdown') # make self._read_sock readble
+ self._write_sock.send(b'shutdown') # make self._read_sock readable
# Wait for it to actually finnish
self._thread.join()
# Wipe out what we do not need
@@ -433,7 +532,7 @@ class ZonemgrRefresh:
self._read_sock = None
self._write_sock = None
- def update_config_data(self, new_config, module_cc_session):
+ def update_config_data(self, new_config, module_cc):
""" update ZonemgrRefresh config """
# 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
@@ -482,7 +581,7 @@ class ZonemgrRefresh:
# Currently we use an explicit get_default_value call
# in case the class hasn't been set. Alternatively, we
# could use
- # module_cc_session.get_value('secondary_zones[INDEX]/class')
+ # module_cc.get_value('secondary_zones[INDEX]/class')
# To get either the value that was set, or the default if
# it wasn't set.
# But the real solution would be to make new_config a type
@@ -492,7 +591,7 @@ class ZonemgrRefresh:
if 'class' in secondary_zone:
rr_class = secondary_zone['class']
else:
- rr_class = module_cc_session.get_default_value(
+ rr_class = module_cc.get_default_value(
'secondary_zones/class')
# Convert rr_class to and from RRClass to check its value
try:
@@ -504,10 +603,12 @@ class ZonemgrRefresh:
required[name_class] = True
# Add it only if it isn't there already
if not name_class in self._zonemgr_refresh_info:
- # If we are not able to find it in database, log an warning
+ # If we are not able to find it in database, log an
+ # warning
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
+ # 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:
@@ -522,10 +623,11 @@ class Zonemgr:
def __init__(self):
self._zone_refresh = None
self._setup_session()
- self._db_file = self.get_db_file()
- # Create socket pair for communicating between main thread and zonemgr timer thread
- self._master_socket, self._slave_socket = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
- self._zone_refresh = ZonemgrRefresh(self._db_file, self._slave_socket, self._module_cc)
+ # Create socket pair for communicating between main thread and zonemgr
+ # timer thread
+ self._master_socket, self._slave_socket = \
+ socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+ self._zone_refresh = ZonemgrRefresh(self._slave_socket, self._module_cc)
self._zone_refresh.run_timer()
self._lock = threading.Lock()
@@ -533,9 +635,10 @@ class Zonemgr:
self.running = False
def _setup_session(self):
- """Setup two sessions for zonemgr, one(self._module_cc) is used for receiving
- commands and config data sent from other modules, another one (self._cc)
- is used to send commands to proper modules."""
+ """Setup two sessions for zonemgr, one(self._module_cc) is used for
+ receiving commands and config data sent from other modules, another
+ one (self._cc) is used to send commands to proper modules.
+ """
self._module_cc = isc.config.ModuleCCSession(SPECFILE_LOCATION,
self.config_handler,
self.command_handler)
@@ -544,18 +647,9 @@ class Zonemgr:
self._config_data_check(self._config_data)
self._module_cc.start()
- def get_db_file(self):
- db_file, is_default = self._module_cc.get_remote_config_value(AUTH_MODULE_NAME, "database_file")
- # this too should be unnecessary, but currently the
- # 'from build' override isn't stored in the config
- # (and we don't have indirect python access to datasources yet)
- if is_default and "B10_FROM_BUILD" in os.environ:
- db_file = os.environ["B10_FROM_BUILD"] + "/bind10_zones.sqlite3"
- return db_file
-
def shutdown(self):
"""Shutdown the zonemgr process. The thread which is keeping track of
- zone timers should be terminated.
+ zone timers should be terminated.
"""
self._zone_refresh.shutdown()
@@ -579,7 +673,8 @@ class Zonemgr:
self._config_data_check(complete)
if self._zone_refresh is not None:
try:
- self._zone_refresh.update_config_data(complete, self._module_cc)
+ self._zone_refresh.update_config_data(complete,
+ self._module_cc)
except Exception as e:
answer = create_answer(1, str(e))
ok = False
@@ -591,7 +686,8 @@ class Zonemgr:
def _config_data_check(self, config_data):
"""Check whether the new config data is valid or
not. It contains only basic logic, not full check against
- database."""
+ database.
+ """
# jitter should not be bigger than half of the original value
if config_data.get('refresh_jitter') > 0.5:
config_data['refresh_jitter'] = 0.5
@@ -608,7 +704,7 @@ class Zonemgr:
logger.error(ZONEMGR_NO_ZONE_CLASS)
raise ZonemgrException("zone class should be provided")
- if (command != ZONE_NOTIFY_COMMAND):
+ if command != ZONE_NOTIFY_COMMAND:
return (zone_name, zone_class)
master_str = args.get("master")
@@ -624,33 +720,40 @@ class Zonemgr:
ZONE_NOTIFY_COMMAND is issued by Auth process;
ZONE_NEW_DATA_READY_CMD and ZONE_XFRIN_FAILED are issued by
Xfrin process;
- shutdown is issued by a user or Init process. """
+ shutdown is issued by a user or Init process.
+ """
answer = create_answer(0)
if command == ZONE_NOTIFY_COMMAND:
""" Handle Auth notify command"""
# master is the source sender of the notify message.
zone_name_class, master = self._parse_cmd_params(args, command)
- logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_NOTIFY, zone_name_class[0], zone_name_class[1])
+ logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_NOTIFY,
+ zone_name_class[0], zone_name_class[1])
with self._lock:
- self._zone_refresh.zone_handle_notify(zone_name_class, master)
- # Send notification to zonemgr timer thread
- self._master_socket.send(b" ")# make self._slave_socket readble
+ need_refresh = self._zone_refresh.zone_handle_notify(
+ zone_name_class, master)
+ if need_refresh:
+ # Send notification to zonemgr timer thread by making
+ # self._slave_socket readable.
+ self._master_socket.send(b" ")
elif command == notify_out.ZONE_NEW_DATA_READY_CMD:
""" Handle xfrin success command"""
zone_name_class = self._parse_cmd_params(args, command)
- logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_SUCCESS, zone_name_class[0], zone_name_class[1])
+ logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_SUCCESS,
+ zone_name_class[0], zone_name_class[1])
with self._lock:
self._zone_refresh.zone_refresh_success(zone_name_class)
- self._master_socket.send(b" ")# make self._slave_socket readble
+ self._master_socket.send(b" ")# make self._slave_socket readable
elif command == notify_out.ZONE_XFRIN_FAILED:
""" Handle xfrin fail command"""
zone_name_class = self._parse_cmd_params(args, command)
- logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_FAILED, zone_name_class[0], zone_name_class[1])
+ logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_XFRIN_FAILED,
+ zone_name_class[0], zone_name_class[1])
with self._lock:
self._zone_refresh.zone_refresh_fail(zone_name_class)
- self._master_socket.send(b" ")# make self._slave_socket readble
+ self._master_socket.send(b" ")# make self._slave_socket readable
elif command == "shutdown":
logger.debug(DBG_ZONEMGR_COMMAND, ZONEMGR_RECEIVE_SHUTDOWN)
@@ -667,7 +770,20 @@ class Zonemgr:
self.running = True
try:
while not self._shutdown_event.is_set():
- self._module_cc.check_command(False)
+ fileno = self._module_cc.get_socket().fileno()
+ reads = []
+ # Wait with select() until there is something to read,
+ # and then read it using a non-blocking read
+ # This may or may not be relevant data for this loop,
+ # but due to the way the zonemgr does threading, we
+ # can't have a blocking read loop here.
+ try:
+ (reads, _, _) = select.select([fileno], [], [])
+ except select.error as se:
+ if se.args[0] != errno.EINTR:
+ raise
+ if fileno in reads:
+ self._module_cc.check_command(True)
finally:
self._module_cc.send_stopping()
diff --git a/src/bin/zonemgr/zonemgr_messages.mes b/src/bin/zonemgr/zonemgr_messages.mes
index f67b5b9..e749e3b 100644
--- a/src/bin/zonemgr/zonemgr_messages.mes
+++ b/src/bin/zonemgr/zonemgr_messages.mes
@@ -1,4 +1,4 @@
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -19,6 +19,16 @@
An error was encountered on the command channel. The message indicates
the nature of the error.
+% ZONEMGR_DATASRC_CONFIG_ERROR failed to update data source configuration: %1
+Configuration for the global data sources is updated, but the update
+cannot be applied to zonemgr. The zonemgr module will still keep running
+with the previous configuration, but the cause of the failure and
+other log messages must be carefully examined because if only zonemgr
+rejects the new configuration then the entire BIND 10 system will have
+inconsistent state among different modules. If other modules accept
+the update but zonemgr produces this error, the zonemgr module should
+probably be restarted.
+
% ZONEMGR_JITTER_TOO_BIG refresh_jitter is too big, setting to 0.5
The value specified in the configuration for the refresh jitter is too large
so its value has been set to the maximum of 0.5.
@@ -138,14 +148,19 @@ zone, or, if this error appears without the administrator giving transfer
commands, it can indicate an error in the program, as it should not have
initiated transfers of unknown zones on its own.
-% ZONEMGR_UNKNOWN_ZONE_NOTIFIED notified zone %1/%2 from %3 is not known to the zone manager
-A NOTIFY was received but the zone that was the subject of the operation
-is not being managed by the zone manager. This may indicate an error
-in the program (as the operation should not have been initiated if this
-were the case). Please submit a bug report.
-
% ZONEMGR_UNKNOWN_ZONE_SUCCESS zone %1 (class %2) is not known to the zone manager
An XFRIN operation has succeeded but the zone received is not being
managed by the zone manager. This may indicate an error in the program
(as the operation should not have been initiated if this were the case).
Please submit a bug report.
+
+% ZONEMGR_ZONE_NOTIFY_NOT_SECONDARY notify for zone %1/%2 from %3 received but not in secondaries
+A NOTIFY was received but the zone is not listed in the configured
+secondary zones of the zone manager. The most common reason for this
+is that it's simply received by a primary server of the zone. Another
+possibility is a configuration error that it's not configured as a
+secondary while it should be. In either case, the zone manager does
+not take action in terms of zone management, and the authoritative
+server will respond to it like in the secondary case. If this is a
+configuration error, it will be noticed by the fact that the zone
+isn't updated even after a change is made in the primary server.
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index f636c0d..59cb8e1 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -1,3 +1,8 @@
-SUBDIRS = exceptions util log cryptolink dns cc config acl xfr bench \
- asiolink asiodns nsas cache resolve testutils datasrc \
- server_common python dhcp dhcpsrv statistics
+if BUILD_EXPERIMENTAL_RESOLVER
+# Build resolver only with --enable-experimental-resolver
+experimental_resolver = resolve
+endif
+
+SUBDIRS = exceptions util log hooks cryptolink dns cc config acl xfr bench \
+ asiolink asiodns nsas cache $(experimental_resolver) testutils \
+ datasrc server_common python dhcp dhcp_ddns dhcpsrv statistics
diff --git a/src/lib/acl/loader.h b/src/lib/acl/loader.h
index fc69b44..52fdc74 100644
--- a/src/lib/acl/loader.h
+++ b/src/lib/acl/loader.h
@@ -329,7 +329,7 @@ public:
const List &list(description->listValue());
boost::shared_ptr<ACL<Context, Action> > result(
new ACL<Context, Action>(default_action_));
- // Run trough the list of elements
+ // Run through the list of elements
for (List::const_iterator i(list.begin()); i != list.end(); ++i) {
Map map;
try {
@@ -417,7 +417,7 @@ private:
}
default: {
// This is the AND-abbreviated form. We need to create an
- // AND (or "ALL") operator, loop trough the whole map and
+ // AND (or "ALL") operator, loop through the whole map and
// fill it in. We do a small trick - we create bunch of
// single-item maps, call this loader recursively (therefore
// it will get into the "case 1" branch, where there is
diff --git a/src/lib/asiodns/README b/src/lib/asiodns/README
index 596d1df..ad27177 100644
--- a/src/lib/asiodns/README
+++ b/src/lib/asiodns/README
@@ -26,9 +26,6 @@ So, in simplified form, the behavior of a DNS Server is:
if not parent:
break
- # This callback informs the caller that a packet has arrived, and
- # gives it a chance to update configuration, etc
- SimpleCallback(packet)
YIELD answer = DNSLookup(packet, this)
response = DNSAnswer(answer)
YIELD send(response)
@@ -37,7 +34,7 @@ At each "YIELD" point, the coroutine initiates an asynchronous operation,
then pauses and turns over control to some other task on the ASIO service
queue. When the operation completes, the coroutine resumes.
-DNSLookup, DNSAnswer and SimpleCallback define callback methods
+DNSLookup and DNSAnswer define callback methods
used by a DNS Server to communicate with the module that called it.
They are abstract-only classes whose concrete implementations
are supplied by the calling module.
diff --git a/src/lib/asiodns/asiodns_messages.mes b/src/lib/asiodns/asiodns_messages.mes
index db2902e..71aded7 100644
--- a/src/lib/asiodns/asiodns_messages.mes
+++ b/src/lib/asiodns/asiodns_messages.mes
@@ -53,6 +53,84 @@ The asynchronous I/O code encountered an error when trying to send data to
the specified address on the given protocol. The number of the system
error that caused the problem is given in the message.
+% ASIODNS_SYNC_UDP_CLOSE_FAIL failed to close a DNS/UDP socket: %1
+This is the same to ASIODNS_UDP_CLOSE_FAIL but happens on the
+"synchronous UDP server", mainly used for the authoritative DNS server
+daemon.
+
+% ASIODNS_TCP_ACCEPT_FAIL failed to accept TCP DNS connection: %1
+Accepting a TCP connection from a DNS client failed due to an error
+that could happen but should be rare. The reason for the error is
+included in the log message. The server still keeps accepting new
+connections, so unless it happens often it's probably okay to ignore
+this error. If the shown error indicates something like "too many
+open files", it's probably because the run time environment is too
+restrictive on this limitation, so consider adjusing the limit using
+a tool such as ulimit. If you see other types of errors too often,
+there may be something overlooked; please file a bug report in that case.
+
+% ASIODNS_TCP_CLEANUP_CLOSE_FAIL failed to close a DNS/TCP socket on port cleanup: %1
+A TCP DNS server tried to close a TCP socket (one created on accepting
+a new connection or is already unused) as a step of cleaning up the
+corresponding listening port, but it failed to do that. This is
+generally an unexpected event and so is logged as an error.
+See also the description of ASIODNS_TCP_CLOSE_ACCEPTOR_FAIL.
+
+% ASIODNS_TCP_CLOSE_ACCEPTOR_FAIL failed to close listening TCP socket: %1
+A TCP DNS server tried to close a listening TCP socket (for accepting
+new connections) as a step of cleaning up the corresponding listening
+port (e.g., on server shutdown or updating port configuration), but it
+failed to do that. This is generally an unexpected event and so is
+logged as an error. See ASIODNS_TCP_CLOSE_FAIL on the implication of
+related system resources.
+
+% ASIODNS_TCP_CLOSE_FAIL failed to close DNS/TCP socket with a client: %1
+A TCP DNS server tried to close a TCP socket used to communicate with
+a client, but it failed to do that. While closing a socket should
+normally be an error-free operation, there have been known cases where
+this happened with a "connection reset by peer" error. This might be
+because of some odd client behavior, such as sending a TCP RST after
+establishing the connection and before the server closes the socket,
+but how exactly this could happen seems to be system dependent (i.e,
+it's not part of the standard socket API), so it's difficult to
+provide a general explanation. In any case, it is believed that an
+error on closing a socket doesn't mean leaking system resources (the
+kernel should clean up any internal resource related to the socket,
+just reporting an error detected in the close call), but, again, it
+seems to be system dependent. This message is logged at a debug level
+as it's known to happen and could be triggered by a remote node and it
+would be better to not be too verbose, but you might want to increase
+the log level and make sure there's no resource leak or other system
+level troubles when it's logged.
+
+% ASIODNS_TCP_CLOSE_NORESP_FAIL failed to close DNS/TCP socket with a client: %1
+A TCP DNS server tried to close a TCP socket used to communicate with
+a client without returning an answer (which normally happens for zone
+transfer requests), but it failed to do that. See ASIODNS_TCP_CLOSE_FAIL
+for more details.
+
+% ASIODNS_TCP_GETREMOTE_FAIL failed to get remote address of a DNS TCP connection: %1
+A TCP DNS server tried to get the address and port of a remote client
+on a connected socket but failed. It's expected to be rare but can
+still happen. See also ASIODNS_TCP_READLEN_FAIL.
+
+% ASIODNS_TCP_READDATA_FAIL failed to get DNS data on a TCP socket: %1
+A TCP DNS server tried to read a DNS message (that follows a 2-byte
+length field) but failed. It's expected to be rare but can still happen.
+See also ASIODNS_TCP_READLEN_FAIL.
+
+% ASIODNS_TCP_READLEN_FAIL failed to get DNS data length on a TCP socket: %1
+A TCP DNS server tried to get the length field of a DNS message (the first
+2 bytes of a new chunk of data) but failed. This is generally expected to
+be rare but can still happen, e.g, due to an unexpected reset of the
+connection. A specific reason for the failure is included in the log
+message.
+
+% ASIODNS_TCP_WRITE_FAIL failed to send DNS message over a TCP socket: %1
+A TCP DNS server tried to send a DNS message to a remote client but
+failed. It's expected to be rare but can still happen. See also
+ASIODNS_TCP_READLEN_FAIL.
+
% ASIODNS_UDP_ASYNC_SEND_FAIL Error sending UDP packet to %1: %2
The low-level ASIO library reported an error when trying to send a UDP
packet in asynchronous UDP mode. This can be any error reported by
@@ -64,6 +142,23 @@ If you see a single occurrence of this message, it probably does not
indicate any significant problem, but if it is logged often, it is probably
a good idea to inspect your network traffic.
+% ASIODNS_UDP_CLOSE_FAIL failed to close a DNS/UDP socket: %1
+A UDP DNS server tried to close its UDP socket, but failed to do that.
+This is generally an unexpected event and so is logged as an error.
+
+% ASIODNS_UDP_RECEIVE_FAIL failed to receive UDP DNS packet: %1
+Receiving a UDP packet from a DNS client failed due to an error that
+could happen but should be very rare. The server still keeps
+receiving UDP packets on this socket. The reason for the error is
+included in the log message. This log message is basically not
+expected to appear at all in practice; if it does, there may be some
+system level failure and other system logs may have to be checked.
+
+% ASIODNS_UDP_SYNC_RECEIVE_FAIL failed to receive UDP DNS packet: %1
+This is the same to ASIODNS_UDP_RECEIVE_FAIL but happens on the
+"synchronous UDP server", mainly used for the authoritative DNS server
+daemon.
+
% ASIODNS_UDP_SYNC_SEND_FAIL Error sending UDP packet to %1: %2
The low-level ASIO library reported an error when trying to send a UDP
packet in synchronous UDP mode. See ASIODNS_UDP_ASYNC_SEND_FAIL for
diff --git a/src/lib/asiodns/dns_answer.h b/src/lib/asiodns/dns_answer.h
index 4b4576b..89896da 100644
--- a/src/lib/asiodns/dns_answer.h
+++ b/src/lib/asiodns/dns_answer.h
@@ -32,7 +32,7 @@ namespace asiodns {
/// via its constructor.
///
/// A DNS Answer provider function takes answer data that has been obtained
-/// from a DNS Lookup provider functon and readies it to be sent to the
+/// from a DNS Lookup provider function and readies it to be sent to the
/// client. After it has run, the OutputBuffer object passed to it should
/// contain the answer to the query rendered into wire format.
class DNSAnswer {
diff --git a/src/lib/asiodns/dns_server.h b/src/lib/asiodns/dns_server.h
index 3b8758f..35811c0 100644
--- a/src/lib/asiodns/dns_server.h
+++ b/src/lib/asiodns/dns_server.h
@@ -71,7 +71,7 @@ public:
/// without losing access to derived class data.
///
//@{
- /// \brief The funtion operator
+ /// \brief The function operator
virtual void operator()(asio::error_code ec = asio::error_code(),
size_t length = 0)
{
diff --git a/src/lib/asiodns/dns_service.cc b/src/lib/asiodns/dns_service.cc
index f72d24b..58a9918 100644
--- a/src/lib/asiodns/dns_service.cc
+++ b/src/lib/asiodns/dns_service.cc
@@ -37,9 +37,9 @@ class DNSAnswer;
class DNSServiceImpl {
public:
- DNSServiceImpl(IOService& io_service, SimpleCallback* checkin,
+ DNSServiceImpl(IOService& io_service,
DNSLookup* lookup, DNSAnswer* answer) :
- io_service_(io_service), checkin_(checkin), lookup_(lookup),
+ io_service_(io_service), lookup_(lookup),
answer_(answer), tcp_recv_timeout_(5000)
{}
@@ -50,17 +50,23 @@ public:
typedef boost::shared_ptr<TCPServer> TCPServerPtr;
typedef boost::shared_ptr<DNSServer> DNSServerPtr;
std::vector<DNSServerPtr> servers_;
- SimpleCallback* checkin_;
DNSLookup* lookup_;
DNSAnswer* answer_;
size_t tcp_recv_timeout_;
template<class Ptr, class Server> void addServerFromFD(int fd, int af) {
- Ptr server(new Server(io_service_.get_io_service(), fd, af, checkin_,
+ Ptr server(new Server(io_service_.get_io_service(), fd, af,
lookup_, answer_));
- server->setTCPRecvTimeout(tcp_recv_timeout_);
- (*server)();
- servers_.push_back(server);
+ startServer(server);
+ }
+
+ // SyncUDPServer has different constructor signature so it cannot be
+ // templated.
+ void addSyncUDPServerFromFD(int fd, int af) {
+ SyncUDPServerPtr server(SyncUDPServer::create(
+ io_service_.get_io_service(), fd, af,
+ lookup_));
+ startServer(server);
}
void setTCPRecvTimeout(size_t timeout) {
@@ -72,11 +78,18 @@ public:
(*it)->setTCPRecvTimeout(timeout);
}
}
+
+private:
+ void startServer(DNSServerPtr server) {
+ server->setTCPRecvTimeout(tcp_recv_timeout_);
+ (*server)();
+ servers_.push_back(server);
+ }
};
-DNSService::DNSService(IOService& io_service, SimpleCallback* checkin,
+DNSService::DNSService(IOService& io_service,
DNSLookup* lookup, DNSAnswer *answer) :
- impl_(new DNSServiceImpl(io_service, checkin, lookup, answer)),
+ impl_(new DNSServiceImpl(io_service, lookup, answer)),
io_service_(io_service)
{
}
@@ -95,8 +108,7 @@ void DNSService::addServerUDPFromFD(int fd, int af, ServerFlag options) {
<< options);
}
if ((options & SERVER_SYNC_OK) != 0) {
- impl_->addServerFromFD<DNSServiceImpl::SyncUDPServerPtr,
- SyncUDPServer>(fd, af);
+ impl_->addSyncUDPServerFromFD(fd, af);
} else {
impl_->addServerFromFD<DNSServiceImpl::UDPServerPtr, UDPServer>(
fd, af);
diff --git a/src/lib/asiodns/dns_service.h b/src/lib/asiodns/dns_service.h
index 01b6310..00d7158 100644
--- a/src/lib/asiodns/dns_service.h
+++ b/src/lib/asiodns/dns_service.h
@@ -107,8 +107,8 @@ public:
/// DNSService is the service that handles DNS queries and answers with
/// a given IOService. This class is mainly intended to hold all the
/// logic that is shared between the authoritative and the recursive
-/// server implementations. As such, it handles asio, including config
-/// updates (through the 'Checkinprovider'), and listening sockets.
+/// server implementations. As such, it handles asio and listening
+/// sockets.
class DNSService : public DNSServiceBase {
///
/// \name Constructors and Destructor
@@ -132,11 +132,9 @@ public:
/// Use addServerTCPFromFD() or addServerUDPFromFD() to add some servers.
///
/// \param io_service The IOService to work with
- /// \param checkin Provider for cc-channel events (see \c SimpleCallback)
/// \param lookup The lookup provider (see \c DNSLookup)
/// \param answer The answer provider (see \c DNSAnswer)
DNSService(asiolink::IOService& io_service,
- isc::asiolink::SimpleCallback* checkin,
DNSLookup* lookup, DNSAnswer* answer);
/// \brief The destructor.
diff --git a/src/lib/asiodns/io_fetch.cc b/src/lib/asiodns/io_fetch.cc
index eed5fdf..a09d8df 100644
--- a/src/lib/asiodns/io_fetch.cc
+++ b/src/lib/asiodns/io_fetch.cc
@@ -410,10 +410,9 @@ void IOFetch::logIOFailure(asio::error_code ec) {
(data_->origin == ASIODNS_READ_DATA) ||
(data_->origin == ASIODNS_UNKNOWN_ORIGIN));
- static const char* PROTOCOL[2] = {"TCP", "UDP"};
LOG_ERROR(logger, data_->origin).arg(ec.value()).
arg((data_->remote_snd->getProtocol() == IPPROTO_TCP) ?
- PROTOCOL[0] : PROTOCOL[1]).
+ "TCP" : "UDP").
arg(data_->remote_snd->getAddress().toText()).
arg(data_->remote_snd->getPort());
}
diff --git a/src/lib/asiodns/io_fetch.h b/src/lib/asiodns/io_fetch.h
index c31ee09..0a03144 100644
--- a/src/lib/asiodns/io_fetch.h
+++ b/src/lib/asiodns/io_fetch.h
@@ -143,7 +143,7 @@ public:
/// \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
+ /// with above constructor which has only question section. All
/// other parameters are same.
///
/// \param query_message the shared_ptr to a full query message
diff --git a/src/lib/asiodns/sync_udp_server.cc b/src/lib/asiodns/sync_udp_server.cc
index c3f5348..d3e30af 100644
--- a/src/lib/asiodns/sync_udp_server.cc
+++ b/src/lib/asiodns/sync_udp_server.cc
@@ -26,6 +26,8 @@
#include <boost/bind.hpp>
+#include <cassert>
+
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
@@ -38,19 +40,28 @@ using namespace isc::asiolink;
namespace isc {
namespace asiodns {
+SyncUDPServerPtr
+SyncUDPServer::create(asio::io_service& io_service, const int fd,
+ const int af, DNSLookup* lookup)
+{
+ return (SyncUDPServerPtr(new SyncUDPServer(io_service, fd, af, lookup)));
+}
+
SyncUDPServer::SyncUDPServer(asio::io_service& io_service, const int fd,
- const int af, asiolink::SimpleCallback* checkin,
- DNSLookup* lookup, DNSAnswer* answer) :
+ const int af, DNSLookup* lookup) :
output_buffer_(new isc::util::OutputBuffer(0)),
query_(new isc::dns::Message(isc::dns::Message::PARSE)),
- answer_(new isc::dns::Message(isc::dns::Message::RENDER)),
- checkin_callback_(checkin), lookup_callback_(lookup),
- answer_callback_(answer), stopped_(false)
+ udp_endpoint_(sender_), lookup_callback_(lookup),
+ resume_called_(false), done_(false), stopped_(false)
{
if (af != AF_INET && af != AF_INET6) {
isc_throw(InvalidParameter, "Address family must be either AF_INET "
"or AF_INET6, not " << af);
}
+ if (!lookup) {
+ isc_throw(InvalidParameter, "null lookup callback given to "
+ "SyncUDPServer");
+ }
LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_FD_ADD_UDP).arg(fd);
try {
socket_.reset(new asio::ip::udp::socket(io_service));
@@ -61,59 +72,44 @@ SyncUDPServer::SyncUDPServer(asio::io_service& io_service, const int fd,
// convert it
isc_throw(IOError, exception.what());
}
+ udp_socket_.reset(new UDPSocket<DummyIOCallback>(*socket_));
}
void
SyncUDPServer::scheduleRead() {
- socket_->async_receive_from(asio::buffer(data_, MAX_LENGTH), sender_,
- boost::bind(&SyncUDPServer::handleRead, this,
- _1, _2));
+ socket_->async_receive_from(
+ asio::mutable_buffers_1(data_, MAX_LENGTH), sender_,
+ boost::bind(&SyncUDPServer::handleRead, shared_from_this(), _1, _2));
}
void
SyncUDPServer::handleRead(const asio::error_code& ec, const size_t length) {
- // Abort on fatal errors
+ if (stopped_) {
+ // stopped_ can be set to true only after the socket object is closed.
+ // checking this would also detect premature destruction of 'this'
+ // object.
+ assert(socket_ && !socket_->is_open());
+ return;
+ }
if (ec) {
using namespace asio::error;
- if (ec.value() != would_block && ec.value() != try_again &&
- ec.value() != interrupted) {
+ const asio::error_code::value_type err_val = ec.value();
+
+ // See TCPServer::operator() for details on error handling.
+ if (err_val == operation_aborted || err_val == bad_descriptor) {
return;
}
+ if (err_val != would_block && err_val != try_again &&
+ err_val != interrupted) {
+ LOG_ERROR(logger, ASIODNS_UDP_SYNC_RECEIVE_FAIL).arg(ec.message());
+ }
}
- // Some kind of interrupt, spurious wakeup, or like that. Just try reading
- // again.
if (ec || length == 0) {
scheduleRead();
return;
}
// OK, we have a real packet of data. Let's dig into it!
- // XXX: This is taken (and ported) from UDPSocket class. What the hell does
- // it really mean?
-
- // The UDP socket class has been extended with asynchronous functions
- // and takes as a template parameter a completion callback class. As
- // UDPServer does not use these extended functions (only those defined
- // in the IOSocket base class) - but needs a UDPSocket to get hold of
- // the underlying Boost UDP socket - DummyIOCallback is used. This
- // provides the appropriate operator() but is otherwise functionless.
- UDPSocket<DummyIOCallback> socket(*socket_);
- UDPEndpoint endpoint(sender_);
- IOMessage message(data_, length, socket, endpoint);
- if (checkin_callback_ != NULL) {
- (*checkin_callback_)(message);
- if (stopped_) {
- return;
- }
- }
-
- // If we don't have a DNS Lookup provider, there's no point in
- // continuing; we exit the coroutine permanently.
- if (lookup_callback_ == NULL) {
- scheduleRead();
- return;
- }
-
// Make sure the buffers are fresh. Note that we don't touch query_
// because it's supposed to be cleared in lookup_callback_. We should
// eventually even remove this member variable (and remove it from
@@ -121,13 +117,13 @@ SyncUDPServer::handleRead(const asio::error_code& ec, const size_t length) {
// implementation should be careful that it's the responsibility of
// the callback implementation. See also #2239).
output_buffer_->clear();
- answer_->clear(isc::dns::Message::RENDER);
// Mark that we don't have an answer yet.
done_ = false;
resume_called_ = false;
// Call the actual lookup
+ const IOMessage message(data_, length, *udp_socket_, udp_endpoint_);
(*lookup_callback_)(message, query_, answer_, output_buffer_, this);
if (!resume_called_) {
@@ -135,27 +131,14 @@ SyncUDPServer::handleRead(const asio::error_code& ec, const size_t length) {
"No resume called from the lookup callback");
}
- if (stopped_) {
- return;
- }
-
if (done_) {
// Good, there's an answer.
- // Call the answer callback to render it.
- (*answer_callback_)(message, query_, answer_, output_buffer_);
-
- if (stopped_) {
- return;
- }
-
- asio::error_code ec;
- socket_->send_to(asio::buffer(output_buffer_->getData(),
- output_buffer_->getLength()),
- sender_, 0, ec);
- if (ec) {
+ socket_->send_to(asio::const_buffers_1(output_buffer_->getData(),
+ output_buffer_->getLength()),
+ sender_, 0, ec_);
+ if (ec_) {
LOG_ERROR(logger, ASIODNS_UDP_SYNC_SEND_FAIL).
- arg(sender_.address().to_string()).
- arg(ec.message());
+ arg(sender_.address().to_string()).arg(ec_.message());
}
}
@@ -174,20 +157,20 @@ SyncUDPServer::operator()(asio::error_code, size_t) {
void
SyncUDPServer::stop() {
/// Using close instead of cancel, because cancel
- /// will only cancel the asynchornized event already submitted
+ /// will only cancel the asynchronized event already submitted
/// to io service, the events post to io service after
/// cancel still can be scheduled by io service, if
- /// the socket is cloesed, all the asynchronized event
+ /// the socket is closed, all the asynchronized event
/// for it won't be scheduled by io service not matter it is
- /// submit to io serice before or after close call. And we will
- //. get bad_descriptor error
- socket_->close();
+ /// submit to io service before or after close call. And we will
+ /// get bad_descriptor error.
+ socket_->close(ec_);
stopped_ = true;
+ if (ec_) {
+ LOG_ERROR(logger, ASIODNS_SYNC_UDP_CLOSE_FAIL).arg(ec_.message());
+ }
}
-/// Post this coroutine on the ASIO service queue so that it will
-/// resume processing where it left off. The 'done' parameter indicates
-/// whether there is an answer to return to the client.
void
SyncUDPServer::resume(const bool done) {
resume_called_ = true;
diff --git a/src/lib/asiodns/sync_udp_server.h b/src/lib/asiodns/sync_udp_server.h
index 14ec42a..e7315be 100644
--- a/src/lib/asiodns/sync_udp_server.h
+++ b/src/lib/asiodns/sync_udp_server.h
@@ -25,43 +25,77 @@
#include <dns/message.h>
#include <asiolink/simple_callback.h>
+#include <asiolink/dummy_io_cb.h>
+#include <asiolink/udp_socket.h>
#include <util/buffer.h>
#include <exceptions/exceptions.h>
+#include <boost/function.hpp>
#include <boost/noncopyable.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
#include <stdint.h>
namespace isc {
namespace asiodns {
+class SyncUDPServer;
+typedef boost::shared_ptr<SyncUDPServer> SyncUDPServerPtr;
+
/// \brief An UDP server that doesn't asynchronous lookup handlers.
///
/// That means, the lookup handler must provide the answer right away.
/// This allows for implementation with less overhead, compared with
-/// the UDPClass.
-class SyncUDPServer : public DNSServer, public boost::noncopyable {
+/// the \c UDPServer class.
+///
+/// This class inherits from boost::enable_shared_from_this so a shared
+/// pointer of this object can be passed in an ASIO callback and won't be
+/// accidentally destroyed while waiting for events. To enforce this style
+/// of creation, a static factory method is provided, and the constructor is
+/// hidden as a private.
+class SyncUDPServer : public DNSServer,
+ public boost::enable_shared_from_this<SyncUDPServer>,
+ boost::noncopyable
+{
+private:
+ /// \brief Constructor.
+ ///
+ /// This is hidden as private (see the class description).
+ SyncUDPServer(asio::io_service& io_service, const int fd, const int af,
+ DNSLookup* lookup);
+
public:
- /// \brief Constructor
+ /// \brief Factory of SyncUDPServer object in the form of shared_ptr.
+ ///
+ /// Due to the nature of this server, it's meaningless if the lookup
+ /// callback is NULL. So this method explicitly rejects that case
+ /// with an exception. Likewise, it doesn't take "checkin" or "answer"
+ /// callbacks. In fact, calling "checkin" from receive callback does not
+ /// make sense for any of the DNSServer variants (see Trac #2935);
+ /// "answer" callback is simply unnecessary for this class because a
+ /// complete answer is built in the lookup callback (it's the user's
+ /// responsibility to guarantee that condition).
+ ///
/// \param io_service the asio::io_service to work with
/// \param fd the file descriptor of opened UDP socket
/// \param af address family, either AF_INET or AF_INET6
- /// \param checkin the callbackprovider for non-DNS events
- /// \param lookup the callbackprovider for DNS lookup events
- /// \param answer the callbackprovider for DNS answer events
+ /// \param lookup the callbackprovider for DNS lookup events (must not be
+ /// NULL)
+ ///
/// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6
+ /// \throw isc::InvalidParameter lookup is NULL
/// \throw isc::asiolink::IOError when a low-level error happens, like the
/// fd is not a valid descriptor.
- SyncUDPServer(asio::io_service& io_service, const int fd, const int af,
- isc::asiolink::SimpleCallback* checkin = NULL,
- DNSLookup* lookup = NULL, DNSAnswer* answer = NULL);
+ static SyncUDPServerPtr create(asio::io_service& io_service, const int fd,
+ const int af, DNSLookup* lookup);
/// \brief Start the SyncUDPServer.
///
/// This is the function operator to keep interface with other server
/// classes. They need that because they're coroutines.
virtual void operator()(asio::error_code ec = asio::error_code(),
- size_t length = 0);
+ size_t length = 0);
/// \brief Calls the lookup callback
virtual void asyncLookup() {
@@ -114,22 +148,34 @@ private:
// If it was OK to have just a buffer, not the wrapper class,
// we could reuse the data_
isc::util::OutputBufferPtr output_buffer_;
- // Objects to hold the query message and the answer
+ // Objects to hold the query message and the answer. The latter isn't
+ // used and only defined as a placeholder as the callback signature
+ // requires it.
isc::dns::MessagePtr query_, answer_;
// The socket used for the communication
- std::auto_ptr<asio::ip::udp::socket> socket_;
+ boost::scoped_ptr<asio::ip::udp::socket> socket_;
+ // Wrapper of socket_ in the form of asiolink::IOSocket.
+ // "DummyIOCallback" is not necessary for this class, but using the
+ // template is the easiest way to create a UDP instance of IOSocket.
+ boost::scoped_ptr<asiolink::UDPSocket<asiolink::DummyIOCallback> >
+ udp_socket_;
// Place the socket puts the sender of a packet when it is received
asio::ip::udp::endpoint sender_;
- // Callbacks
- const asiolink::SimpleCallback* checkin_callback_;
+ // Wrapper of sender_ in the form of asiolink::IOEndpoint. It's set to
+ // refer to sender_ on initialization, and keeps the reference throughout
+ // this server class.
+ asiolink::UDPEndpoint udp_endpoint_;
+ // Callback
const DNSLookup* lookup_callback_;
- const DNSAnswer* answer_callback_;
// Answers from the lookup callback (not sent directly, but signalled
// through resume()
bool resume_called_, done_;
// This turns true when the server stops. Allows for not sending the
// answer after we closed the socket.
bool stopped_;
+ // Placeholder for error code object. It will be passed to ASIO library
+ // to have it set in case of error.
+ asio::error_code ec_;
// Auxiliary functions
@@ -144,3 +190,7 @@ private:
} // namespace asiodns
} // namespace isc
#endif // SYNC_UDP_SERVER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/asiodns/tcp_server.cc b/src/lib/asiodns/tcp_server.cc
index 397e004..5f01d2f 100644
--- a/src/lib/asiodns/tcp_server.cc
+++ b/src/lib/asiodns/tcp_server.cc
@@ -14,15 +14,6 @@
#include <config.h>
-#include <unistd.h> // for some IPC/network system calls
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <errno.h>
-
-#include <boost/shared_array.hpp>
-
-#include <log/dummylog.h>
-
#include <util/buffer.h>
#include <asio.hpp>
@@ -32,6 +23,14 @@
#include <asiodns/tcp_server.h>
#include <asiodns/logger.h>
+#include <boost/shared_array.hpp>
+
+#include <cassert>
+#include <unistd.h> // for some IPC/network system calls
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <errno.h>
+
using namespace asio;
using asio::ip::udp;
using asio::ip::tcp;
@@ -48,11 +47,10 @@ namespace asiodns {
///
/// The constructor
TCPServer::TCPServer(io_service& io_service, int fd, int af,
- const SimpleCallback* checkin,
const DNSLookup* lookup,
const DNSAnswer* answer) :
io_(io_service), done_(false),
- checkin_callback_(checkin), lookup_callback_(lookup),
+ lookup_callback_(lookup),
answer_callback_(answer)
{
if (af != AF_INET && af != AF_INET6) {
@@ -76,18 +74,18 @@ TCPServer::TCPServer(io_service& io_service, int fd, int af,
}
namespace {
- // Called by the timeout_ deadline timer if the client takes too long.
- // If not aborted, cancels the given socket
- // (in which case TCPServer::operator() will be called to continue,
- // with an 'aborted' error code
- void do_timeout(asio::ip::tcp::socket& socket,
- const asio::error_code& error)
- {
- if (error != asio::error::operation_aborted) {
- socket.cancel();
- }
+// Called by the timeout_ deadline timer if the client takes too long.
+// If not aborted, cancels the given socket
+// (in which case TCPServer::operator() will be called to continue,
+// with an 'aborted' error code.)
+void doTimeOut(boost::shared_ptr<asio::ip::tcp::socket> socket,
+ const asio::error_code& error)
+{
+ if (error != asio::error::operation_aborted) {
+ socket->cancel();
}
}
+}
void
TCPServer::operator()(asio::error_code ec, size_t length) {
@@ -100,73 +98,86 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
CORO_REENTER (this) {
do {
- /// Create a socket to listen for connections
+ /// Create a socket to listen for connections (no-throw operation)
socket_.reset(new tcp::socket(acceptor_->get_io_service()));
/// Wait for new connections. In the event of non-fatal error,
/// try again
do {
CORO_YIELD acceptor_->async_accept(*socket_, *this);
-
- // Abort on fatal errors
- // TODO: Log error?
if (ec) {
using namespace asio::error;
- if (ec.value() != would_block && ec.value() != try_again &&
- ec.value() != connection_aborted &&
- ec.value() != interrupted) {
+ const error_code::value_type err_val = ec.value();
+ // The following two cases can happen when this server is
+ // stopped: operation_aborted in case it's stopped after
+ // starting accept(). bad_descriptor in case it's stopped
+ // even before starting. In these cases we should simply
+ // stop handling events.
+ if (err_val == operation_aborted ||
+ err_val == bad_descriptor) {
return;
}
+ // Other errors should generally be temporary and we should
+ // keep waiting for new connections. We log errors that
+ // should really be rare and would only be caused by an
+ // internal erroneous condition (not an odd remote
+ // behavior).
+ if (err_val != would_block && err_val != try_again &&
+ err_val != connection_aborted &&
+ err_val != interrupted) {
+ LOG_ERROR(logger, ASIODNS_TCP_ACCEPT_FAIL).
+ arg(ec.message());
+ }
}
} while (ec);
/// Fork the coroutine by creating a copy of this one and
/// scheduling it on the ASIO service queue. The parent
- /// will continue listening for DNS connections while the
+ /// will continue listening for DNS connections while the child
/// handles the one that has just arrived.
CORO_FORK io_.post(TCPServer(*this));
} while (is_parent());
+ // From this point, we'll simply return on error, which will
+ // immediately trigger destroying this object, cleaning up all
+ // resources including any open sockets.
+
/// Instantiate the data buffer that will be used by the
/// asynchronous read call.
data_.reset(new char[MAX_LENGTH]);
- /// Start a timer to drop the connection if it is idle
+ /// Start a timer to drop the connection if it is idle. note that
+ // we pass a shared_ptr of the socket object so that it won't be
+ // destroyed at least until the timeout callback (including abort)
+ // is called.
if (*tcp_recv_timeout_ > 0) {
- timeout_.reset(new asio::deadline_timer(io_));
- timeout_->expires_from_now(
+ timeout_.reset(new asio::deadline_timer(io_)); // shouldn't throw
+ timeout_->expires_from_now( // consider any exception fatal.
boost::posix_time::milliseconds(*tcp_recv_timeout_));
- timeout_->async_wait(boost::bind(&do_timeout, boost::ref(*socket_),
- asio::placeholders::error));
+ timeout_->async_wait(boost::bind(&doTimeOut, socket_,
+ asio::placeholders::error));
}
/// Read the message, in two parts. First, the message length:
CORO_YIELD async_read(*socket_, asio::buffer(data_.get(),
TCP_MESSAGE_LENGTHSIZE), *this);
if (ec) {
- socket_->close();
- CORO_YIELD return;
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_TCP_READLEN_FAIL).
+ arg(ec.message());
+ return;
}
/// Now read the message itself. (This is done in a different scope
/// to allow inline variable declarations.)
CORO_YIELD {
InputBuffer dnsbuffer(data_.get(), length);
- uint16_t msglen = dnsbuffer.readUint16();
+ const uint16_t msglen = dnsbuffer.readUint16();
async_read(*socket_, asio::buffer(data_.get(), msglen), *this);
}
-
if (ec) {
- socket_->close();
- CORO_YIELD return;
- }
-
- // Due to possible timeouts and other bad behaviour, after the
- // timely reads are done, there is a chance the socket has
- // been closed already. So before we move on to the actual
- // processing, check that, and stop if so.
- if (!socket_->is_open()) {
- CORO_YIELD return;
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_TCP_READDATA_FAIL).
+ arg(ec.message());
+ return;
}
// Create an \c IOMessage object to store the query.
@@ -174,7 +185,12 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
// (XXX: It would be good to write a factory function
// that would quickly generate an IOMessage object without
// all these calls to "new".)
- peer_.reset(new TCPEndpoint(socket_->remote_endpoint()));
+ peer_.reset(new TCPEndpoint(socket_->remote_endpoint(ec)));
+ if (ec) {
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_TCP_GETREMOTE_FAIL).
+ arg(ec.message());
+ return;
+ }
// The TCP socket class has been extended with asynchronous functions
// and takes as a template parameter a completion callback class. As
@@ -183,23 +199,13 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
// the underlying Boost TCP socket - DummyIOCallback is used. This
// provides the appropriate operator() but is otherwise functionless.
iosock_.reset(new TCPSocket<DummyIOCallback>(*socket_));
- io_message_.reset(new IOMessage(data_.get(), length, *iosock_, *peer_));
-
- // Perform any necessary operations prior to processing the incoming
- // packet (e.g., checking for queued configuration messages).
- //
- // (XXX: it may be a performance issue to have this called for
- // every single incoming packet; we may wish to throttle it somehow
- // in the future.)
- if (checkin_callback_ != NULL) {
- (*checkin_callback_)(*io_message_);
- }
+ io_message_.reset(new IOMessage(data_.get(), length, *iosock_,
+ *peer_));
// If we don't have a DNS Lookup provider, there's no point in
// continuing; we exit the coroutine permanently.
if (lookup_callback_ == NULL) {
- socket_->close();
- CORO_YIELD return;
+ return;
}
// Reset or instantiate objects that will be needed by the
@@ -210,25 +216,31 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
// Schedule a DNS lookup, and yield. When the lookup is
// finished, the coroutine will resume immediately after
- // this point.
+ // this point. On resume, this method should be called with its
+ // default parameter values (because of the signature of post()'s
+ // handler), so ec shouldn't indicate any error.
CORO_YIELD io_.post(AsyncLookup<TCPServer>(*this));
+ assert(!ec);
// The 'done_' flag indicates whether we have an answer
// to send back. If not, exit the coroutine permanently.
if (!done_) {
+ // Explicitly close() isn't necessary for most cases. But for the
+ // very connection, socket_ is shared with the original owner of
+ // the server object and would stay open.
// TODO: should we keep the connection open for a short time
// to see if new requests come in?
- socket_->close();
- CORO_YIELD return;
+ socket_->close(ec);
+ if (ec) {
+ LOG_DEBUG(logger, 0, ASIODNS_TCP_CLOSE_FAIL).arg(ec.message());
+ }
+ return;
}
- if (ec) {
- CORO_YIELD return;
- }
// Call the DNS answer provider to render the answer into
// wire format
- (*answer_callback_)(*io_message_, query_message_,
- answer_message_, respbuf_);
+ (*answer_callback_)(*io_message_, query_message_, answer_message_,
+ respbuf_);
// Set up the response, beginning with two length bytes.
lenbuf.writeUint16(respbuf_->getLength());
@@ -240,13 +252,22 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
// (though we have nothing further to do, so the coroutine
// will simply exit at that time).
CORO_YIELD async_write(*socket_, bufs, *this);
+ if (ec) {
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_TCP_WRITE_FAIL).
+ arg(ec.message());
+ }
- // All done, cancel the timeout timer
+ // All done, cancel the timeout timer. if it throws, consider it fatal.
timeout_->cancel();
// TODO: should we keep the connection open for a short time
// to see if new requests come in?
- socket_->close();
+ socket_->close(ec);
+ if (ec) {
+ // close() should be unlikely to fail, but we've seen it fail once,
+ // so we log the event (at the lowest level of debug).
+ LOG_DEBUG(logger, 0, ASIODNS_TCP_CLOSE_FAIL).arg(ec.message());
+ }
}
}
@@ -259,14 +280,23 @@ TCPServer::asyncLookup() {
}
void TCPServer::stop() {
+ asio::error_code ec;
+
/// we use close instead of cancel, with the same reason
/// with udp server stop, refer to the udp server code
- acceptor_->close();
+ acceptor_->close(ec);
+ if (ec) {
+ LOG_ERROR(logger, ASIODNS_TCP_CLOSE_ACCEPTOR_FAIL).arg(ec.message());
+ }
+
// User may stop the server even when it hasn't started to
- // run, in that that socket_ is empty
+ // run, in that case socket_ is empty
if (socket_) {
- socket_->close();
+ socket_->close(ec);
+ if (ec) {
+ LOG_ERROR(logger, ASIODNS_TCP_CLEANUP_CLOSE_FAIL).arg(ec.message());
+ }
}
}
/// Post this coroutine on the ASIO service queue so that it will
@@ -275,6 +305,10 @@ void TCPServer::stop() {
void
TCPServer::resume(const bool done) {
done_ = done;
+
+ // post() can throw due to memory allocation failure, but as like other
+ // cases of the entire BIND 10 implementation, we consider it fatal and
+ // let the exception be propagated.
io_.post(*this);
}
diff --git a/src/lib/asiodns/tcp_server.h b/src/lib/asiodns/tcp_server.h
index 50e8717..3c01076 100644
--- a/src/lib/asiodns/tcp_server.h
+++ b/src/lib/asiodns/tcp_server.h
@@ -41,14 +41,12 @@ public:
/// \param io_service the asio::io_service to work with
/// \param fd the file descriptor of opened TCP socket
/// \param af address family of the socket, either AF_INET or AF_INET6
- /// \param checkin the callbackprovider for non-DNS events
/// \param lookup the callbackprovider for DNS lookup events
/// \param answer the callbackprovider for DNS answer events
/// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6
/// \throw isc::asiolink::IOError when a low-level error happens, like the
/// fd is not a valid descriptor or it can't be listened on.
TCPServer(asio::io_service& io_service, int fd, int af,
- const isc::asiolink::SimpleCallback* checkin = NULL,
const DNSLookup* lookup = NULL, const DNSAnswer* answer = NULL);
void operator()(asio::error_code ec = asio::error_code(),
@@ -125,7 +123,6 @@ private:
bool done_;
// Callback functions provided by the caller
- const isc::asiolink::SimpleCallback* checkin_callback_;
const DNSLookup* lookup_callback_;
const DNSAnswer* answer_callback_;
diff --git a/src/lib/asiodns/tests/dns_server_unittest.cc b/src/lib/asiodns/tests/dns_server_unittest.cc
index c05a34b..d59bf5e 100644
--- a/src/lib/asiodns/tests/dns_server_unittest.cc
+++ b/src/lib/asiodns/tests/dns_server_unittest.cc
@@ -51,8 +51,8 @@
/// Before the server start to run
/// After we get the query and check whether it's valid
/// After we lookup the query
-/// After we compoisite the answer
-/// After user get the final result.
+/// After we compose the answer
+/// After user gets the final result.
/// The standard about whether we stop the server successfully or not
/// is based on the fact that if the server is still running, the io
@@ -83,14 +83,14 @@ const char* const server_port_str = "5553";
const char* const query_message = "BIND10 is awesome";
// \brief provide capacity to derived class the ability
-// to stop DNSServer at certern point
+// to stop DNSServer at certain point
class ServerStopper {
public:
ServerStopper() : server_to_stop_(NULL) {}
virtual ~ServerStopper(){}
- void setServerToStop(DNSServer* server) {
- server_to_stop_ = server;
+ void setServerToStop(DNSServer& server) {
+ server_to_stop_ = &server;
}
void stopServer() const {
@@ -103,21 +103,13 @@ class ServerStopper {
DNSServer* server_to_stop_;
};
-// \brief no check logic at all,just provide a checkpoint to stop the server
-class DummyChecker : public SimpleCallback, public ServerStopper {
- public:
- virtual void operator()(const IOMessage&) const {
- stopServer();
- }
-};
-
// \brief no lookup logic at all,just provide a checkpoint to stop the server
class DummyLookup : public DNSLookup, public ServerStopper {
public:
DummyLookup() :
allow_resume_(true)
{ }
- void operator()(const IOMessage& io_message,
+ virtual void operator()(const IOMessage& io_message,
isc::dns::MessagePtr message,
isc::dns::MessagePtr answer_message,
isc::util::OutputBufferPtr buffer,
@@ -147,8 +139,26 @@ class SimpleAnswer : public DNSAnswer, public ServerStopper {
};
+/// \brief Mixture of DummyLookup and SimpleAnswer: build the answer in the
+/// lookup callback. Used with SyncUDPServer.
+class SyncDummyLookup : public DummyLookup {
+public:
+ virtual void operator()(const IOMessage& io_message,
+ isc::dns::MessagePtr message,
+ isc::dns::MessagePtr answer_message,
+ isc::util::OutputBufferPtr buffer,
+ DNSServer* server) const
+ {
+ buffer->writeData(io_message.getData(), io_message.getDataSize());
+ stopServer();
+ if (allow_resume_) {
+ server->resume(true);
+ }
+ }
+};
+
// \brief simple client, send one string to server and wait for response
-// in case, server stopped and client cann't get response, there is a timer wait
+// in case, server stopped and client can't get response, there is a timer wait
// for specified seconds (the value is just a estimate since server process logic is quite
// simple, and all the intercommunication is local) then cancel the waiting.
class SimpleClient : public ServerStopper {
@@ -351,47 +361,43 @@ class DNSServerTestBase : public::testing::Test {
protected:
DNSServerTestBase() :
server_address_(ip::address::from_string(server_ip)),
- checker_(new DummyChecker()),
lookup_(new DummyLookup()),
+ sync_lookup_(new SyncDummyLookup()),
answer_(new SimpleAnswer()),
udp_client_(new UDPClient(service,
ip::udp::endpoint(server_address_,
server_port))),
tcp_client_(new TCPClient(service,
ip::tcp::endpoint(server_address_,
- server_port))),
- udp_server_(NULL),
- tcp_server_(NULL)
+ server_port)))
{
current_service = &service;
}
~ DNSServerTestBase() {
- if (udp_server_ != NULL) {
+ if (udp_server_) {
udp_server_->stop();
}
- if (tcp_server_ != NULL) {
+ if (tcp_server_) {
tcp_server_->stop();
}
- delete checker_;
delete lookup_;
+ delete sync_lookup_;
delete answer_;
- delete udp_server_;
delete udp_client_;
- delete tcp_server_;
delete tcp_client_;
// No delete here. The service is not allocated by new, but as our
// member. This only references it, so just cleaning the pointer.
current_service = NULL;
}
- void testStopServerByStopper(DNSServer* server, SimpleClient* client,
- ServerStopper* stopper)
+ void testStopServerByStopper(DNSServer& server, SimpleClient* client,
+ ServerStopper* stopper)
{
static const unsigned int IO_SERVICE_TIME_OUT = 5;
io_service_is_time_out = false;
stopper->setServerToStop(server);
- (*server)();
+ server();
client->sendDataThenWaitForFeedback(query_message);
// Since thread hasn't been introduced into the tool box, using
// signal to make sure run function will eventually return even
@@ -420,13 +426,13 @@ class DNSServerTestBase : public::testing::Test {
asio::io_service service;
const ip::address server_address_;
- DummyChecker* const checker_;
- DummyLookup* const lookup_;
+ DummyLookup* lookup_; // we need to replace it in some cases
+ SyncDummyLookup* const sync_lookup_;
SimpleAnswer* const answer_;
UDPClient* const udp_client_;
TCPClient* const tcp_client_;
- UDPServerClass* udp_server_;
- TCPServer* tcp_server_;
+ boost::shared_ptr<UDPServerClass> udp_server_;
+ boost::shared_ptr<TCPServer> tcp_server_;
// To access them in signal handle function, the following
// variables have to be static.
@@ -482,19 +488,37 @@ private:
protected:
// Using SetUp here so we can ASSERT_*
void SetUp() {
- const int fdUDP(getFd(SOCK_DGRAM));
- ASSERT_NE(-1, fdUDP) << strerror(errno);
- this->udp_server_ = new UDPServerClass(this->service, fdUDP, AF_INET6,
- this->checker_, this->lookup_,
- this->answer_);
- const int fdTCP(getFd(SOCK_STREAM));
- ASSERT_NE(-1, fdTCP) << strerror(errno);
- this->tcp_server_ = new TCPServer(this->service, fdTCP, AF_INET6,
- this->checker_, this->lookup_,
- this->answer_);
+ const int fd_udp(getFd(SOCK_DGRAM));
+ ASSERT_NE(-1, fd_udp) << strerror(errno);
+ this->udp_server_ = createServer(fd_udp, AF_INET6);
+ const int fd_tcp(getFd(SOCK_STREAM));
+ ASSERT_NE(-1, fd_tcp) << strerror(errno);
+ this->tcp_server_ =
+ boost::shared_ptr<TCPServer>(new TCPServer(
+ this->service, fd_tcp, AF_INET6,
+ this->lookup_,
+ this->answer_));
+ }
+
+ // A helper factory of the tested UDP server class: allow customization
+ // by template specialization.
+ boost::shared_ptr<UDPServerClass> createServer(int fd, int af) {
+ return (boost::shared_ptr<UDPServerClass>(
+ new UDPServerClass(this->service, fd, af,
+ this->lookup_,
+ this->answer_)));
}
};
+// Specialization for SyncUDPServer. It needs to use SyncDummyLookup.
+template<>
+boost::shared_ptr<SyncUDPServer>
+FdInit<SyncUDPServer>::createServer(int fd, int af) {
+ delete this->lookup_;
+ this->lookup_ = new SyncDummyLookup;
+ return (SyncUDPServer::create(this->service, fd, af, this->lookup_));
+}
+
// This makes it the template as gtest wants it.
template<class Parent>
class DNSServerTest : public Parent { };
@@ -503,6 +527,11 @@ typedef ::testing::Types<FdInit<UDPServer>, FdInit<SyncUDPServer> >
ServerTypes;
TYPED_TEST_CASE(DNSServerTest, ServerTypes);
+// Some tests work only for SyncUDPServer, some others work only for
+// (non Sync)UDPServer. We specialize these tests.
+typedef FdInit<UDPServer> AsyncServerTest;
+typedef FdInit<SyncUDPServer> SyncServerTest;
+
typedef ::testing::Types<UDPServer, SyncUDPServer> UDPServerTypes;
TYPED_TEST_CASE(DNSServerTestBase, UDPServerTypes);
@@ -513,10 +542,10 @@ asio::io_service* DNSServerTestBase<UDPServerClass>::current_service(NULL);
// Test whether server stopped successfully after client get response
// client will send query and start to wait for response, once client
-// get response, udp server will be stopped, the io service won't quit
+// get response, UDP server will be stopped, the io service won't quit
// if udp server doesn't stop successfully.
TYPED_TEST(DNSServerTest, stopUDPServerAfterOneQuery) {
- this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+ this->testStopServerByStopper(*this->udp_server_, this->udp_client_,
this->udp_client_);
EXPECT_EQ(query_message, this->udp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -525,35 +554,27 @@ TYPED_TEST(DNSServerTest, stopUDPServerAfterOneQuery) {
// Test whether udp server stopped successfully before server start to serve
TYPED_TEST(DNSServerTest, stopUDPServerBeforeItStartServing) {
this->udp_server_->stop();
- this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+ this->testStopServerByStopper(*this->udp_server_, this->udp_client_,
this->udp_client_);
EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
}
-
-// Test whether udp server stopped successfully during message check
-TYPED_TEST(DNSServerTest, stopUDPServerDuringMessageCheck) {
- this->testStopServerByStopper(this->udp_server_, this->udp_client_,
- this->checker_);
- EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
- EXPECT_TRUE(this->serverStopSucceed());
-}
-
// Test whether udp server stopped successfully during query lookup
TYPED_TEST(DNSServerTest, stopUDPServerDuringQueryLookup) {
- this->testStopServerByStopper(this->udp_server_, this->udp_client_,
+ this->testStopServerByStopper(*this->udp_server_, this->udp_client_,
this->lookup_);
EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
}
-// Test whether udp server stopped successfully during composing answer
-TYPED_TEST(DNSServerTest, stopUDPServerDuringPrepareAnswer) {
- this->testStopServerByStopper(this->udp_server_, this->udp_client_,
- this->answer_);
- EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
- EXPECT_TRUE(this->serverStopSucceed());
+// Test whether UDP server stopped successfully during composing answer.
+// Only works for (non-sync) server because SyncUDPServer doesn't use answer
+// callback.
+TEST_F(AsyncServerTest, stopUDPServerDuringPrepareAnswer) {
+ testStopServerByStopper(*udp_server_, udp_client_, answer_);
+ EXPECT_EQ(std::string(""), udp_client_->getReceivedData());
+ EXPECT_TRUE(serverStopSucceed());
}
void
@@ -565,21 +586,20 @@ stopServerManyTimes(DNSServer *server, unsigned int times) {
// Test whether udp server stop interface can be invoked several times without
// throw any exception
-TYPED_TEST(DNSServerTest, stopUDPServeMoreThanOnce) {
+TYPED_TEST(DNSServerTest, stopUDPServerMoreThanOnce) {
ASSERT_NO_THROW({
boost::function<void()> stop_server_3_times
- = boost::bind(stopServerManyTimes, this->udp_server_, 3);
+ = boost::bind(stopServerManyTimes, this->udp_server_.get(), 3);
this->udp_client_->setGetFeedbackCallback(stop_server_3_times);
- this->testStopServerByStopper(this->udp_server_,
+ this->testStopServerByStopper(*this->udp_server_,
this->udp_client_, this->udp_client_);
EXPECT_EQ(query_message, this->udp_client_->getReceivedData());
});
EXPECT_TRUE(this->serverStopSucceed());
}
-
TYPED_TEST(DNSServerTest, stopTCPServerAfterOneQuery) {
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->tcp_client_);
EXPECT_EQ(query_message, this->tcp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -588,7 +608,7 @@ TYPED_TEST(DNSServerTest, stopTCPServerAfterOneQuery) {
TYPED_TEST(DNSServerTest, TCPTimeoutOnLen) {
this->tcp_server_->setTCPRecvTimeout(100);
this->tcp_client_->setSendDataLenDelay(2);
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->tcp_client_);
EXPECT_EQ("", this->tcp_client_->getReceivedData());
EXPECT_FALSE(this->serverStopSucceed());
@@ -598,7 +618,7 @@ TYPED_TEST(DNSServerTest, TCPTimeout) {
// set delay higher than timeout
this->tcp_server_->setTCPRecvTimeout(100);
this->tcp_client_->setSendDataDelay(2);
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->tcp_client_);
EXPECT_EQ("", this->tcp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -608,7 +628,7 @@ TYPED_TEST(DNSServerTest, TCPNoTimeout) {
// set delay lower than timeout
this->tcp_server_->setTCPRecvTimeout(3000);
this->tcp_client_->setSendDataDelay(1);
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->tcp_client_);
EXPECT_EQ("BIND10 is awesome", this->tcp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -617,24 +637,16 @@ TYPED_TEST(DNSServerTest, TCPNoTimeout) {
// Test whether tcp server stopped successfully before server start to serve
TYPED_TEST(DNSServerTest, stopTCPServerBeforeItStartServing) {
this->tcp_server_->stop();
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->tcp_client_);
EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
}
-// Test whether tcp server stopped successfully during message check
-TYPED_TEST(DNSServerTest, stopTCPServerDuringMessageCheck) {
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
- this->checker_);
- EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
- EXPECT_TRUE(this->serverStopSucceed());
-}
-
// Test whether tcp server stopped successfully during query lookup
TYPED_TEST(DNSServerTest, stopTCPServerDuringQueryLookup) {
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->lookup_);
EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -642,7 +654,7 @@ TYPED_TEST(DNSServerTest, stopTCPServerDuringQueryLookup) {
// Test whether tcp server stopped successfully during composing answer
TYPED_TEST(DNSServerTest, stopTCPServerDuringPrepareAnswer) {
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->answer_);
EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
EXPECT_TRUE(this->serverStopSucceed());
@@ -654,9 +666,9 @@ TYPED_TEST(DNSServerTest, stopTCPServerDuringPrepareAnswer) {
TYPED_TEST(DNSServerTest, stopTCPServeMoreThanOnce) {
ASSERT_NO_THROW({
boost::function<void()> stop_server_3_times
- = boost::bind(stopServerManyTimes, this->tcp_server_, 3);
+ = boost::bind(stopServerManyTimes, this->tcp_server_.get(), 3);
this->tcp_client_->setGetFeedbackCallback(stop_server_3_times);
- this->testStopServerByStopper(this->tcp_server_, this->tcp_client_,
+ this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
this->tcp_client_);
EXPECT_EQ(query_message, this->tcp_client_->getReceivedData());
});
@@ -668,14 +680,15 @@ TYPED_TEST(DNSServerTest, stopTCPServeMoreThanOnce) {
TYPED_TEST(DNSServerTestBase, invalidFamily) {
// We abuse DNSServerTestBase for this test, as we don't need the
// initialization.
- EXPECT_THROW(TypeParam(this->service, 0, AF_UNIX, this->checker_,
- this->lookup_, this->answer_),
- isc::InvalidParameter);
- EXPECT_THROW(TCPServer(this->service, 0, AF_UNIX, this->checker_,
+ EXPECT_THROW(TCPServer(this->service, 0, AF_UNIX,
this->lookup_, this->answer_),
isc::InvalidParameter);
}
+TYPED_TEST(DNSServerTest, invalidFamilyUDP) {
+ EXPECT_THROW(this->createServer(0, AF_UNIX), isc::InvalidParameter);
+}
+
// It raises an exception when invalid address family is passed
TYPED_TEST(DNSServerTestBase, invalidTCPFD) {
// We abuse DNSServerTestBase for this test, as we don't need the
@@ -686,15 +699,15 @@ TYPED_TEST(DNSServerTestBase, invalidTCPFD) {
asio backend does fail as it tries to insert it right away, but
not the others, maybe we could make it run this at last on epoll-based
systems).
- EXPECT_THROW(UDPServer(service, -1, AF_INET, checker_, lookup_,
+ EXPECT_THROW(UDPServer(service, -1, AF_INET, lookup_,
answer_), isc::asiolink::IOError);
*/
- EXPECT_THROW(TCPServer(this->service, -1, AF_INET, this->checker_,
+ EXPECT_THROW(TCPServer(this->service, -1, AF_INET,
this->lookup_, this->answer_),
isc::asiolink::IOError);
}
-TYPED_TEST(DNSServerTestBase, DISABLED_invalidUDPFD) {
+TYPED_TEST(DNSServerTest, DISABLED_invalidUDPFD) {
/*
FIXME: The UDP server doesn't fail reliably with an invalid FD.
We need to find a way to trigger it reliably (it seems epoll
@@ -702,14 +715,9 @@ TYPED_TEST(DNSServerTestBase, DISABLED_invalidUDPFD) {
not the others, maybe we could make it run this at least on epoll-based
systems).
*/
- EXPECT_THROW(TypeParam(this->service, -1, AF_INET, this->checker_,
- this->lookup_, this->answer_),
- isc::asiolink::IOError);
+ EXPECT_THROW(this->createServer(-1, AF_INET), isc::asiolink::IOError);
}
-// A specialized test type for SyncUDPServer.
-typedef FdInit<SyncUDPServer> SyncServerTest;
-
// Check it rejects some of the unsupported operations
TEST_F(SyncServerTest, unsupportedOps) {
EXPECT_THROW(udp_server_->clone(), isc::Unexpected);
@@ -719,8 +727,36 @@ TEST_F(SyncServerTest, unsupportedOps) {
// Check it rejects forgotten resume (eg. insists that it is synchronous)
TEST_F(SyncServerTest, mustResume) {
lookup_->allow_resume_ = false;
- ASSERT_THROW(testStopServerByStopper(udp_server_, udp_client_, lookup_),
+ ASSERT_THROW(testStopServerByStopper(*udp_server_, udp_client_, lookup_),
isc::Unexpected);
}
+// SyncUDPServer doesn't allow NULL lookup callback.
+TEST_F(SyncServerTest, nullLookupCallback) {
+ EXPECT_THROW(SyncUDPServer::create(service, 0, AF_INET, NULL),
+ isc::InvalidParameter);
+}
+
+TEST_F(SyncServerTest, resetUDPServerBeforeEvent) {
+ // Reset the UDP server object after starting and before it would get
+ // an event from io_service (in this case abort event). The following
+ // sequence confirms it's shut down immediately, and without any
+ // disruption.
+
+ // Since we'll stop the server run() should immediately return, and
+ // it's very unlikely to cause hangup. But we'll make very sure it
+ // doesn't happen.
+ const unsigned int IO_SERVICE_TIME_OUT = 5;
+ (*udp_server_)();
+ udp_server_->stop();
+ udp_server_.reset();
+ void (*prev_handler)(int) = std::signal(SIGALRM, stopIOService);
+ current_service = &service;
+ alarm(IO_SERVICE_TIME_OUT);
+ service.run();
+ alarm(0);
+ std::signal(SIGALRM, prev_handler);
+ EXPECT_FALSE(io_service_is_time_out);
+}
+
}
diff --git a/src/lib/asiodns/tests/dns_service_unittest.cc b/src/lib/asiodns/tests/dns_service_unittest.cc
index ce8eee9..c35897c 100644
--- a/src/lib/asiodns/tests/dns_service_unittest.cc
+++ b/src/lib/asiodns/tests/dns_service_unittest.cc
@@ -81,7 +81,7 @@ protected:
UDPDNSServiceTest() :
first_buffer_(NULL), second_buffer_(NULL),
lookup(&first_buffer_, &second_buffer_, io_service),
- dns_service(io_service, NULL, &lookup, NULL),
+ dns_service(io_service, &lookup, NULL),
client_socket(io_service.get_io_service(), asio::ip::udp::v6()),
server_ep(asio::ip::address::from_string(TEST_IPV6_ADDR),
lexical_cast<uint16_t>(TEST_SERVER_PORT)),
diff --git a/src/lib/asiodns/udp_server.cc b/src/lib/asiodns/udp_server.cc
index cf4b1c4..649ea92 100644
--- a/src/lib/asiodns/udp_server.cc
+++ b/src/lib/asiodns/udp_server.cc
@@ -21,8 +21,6 @@
#include <config.h>
-#include <log/dummylog.h>
-
#include <asio.hpp>
#include <asio/error.hpp>
#include <asiolink/dummy_io_cb.h>
@@ -35,7 +33,6 @@
using namespace asio;
using asio::ip::udp;
-using isc::log::dlog;
using namespace std;
using namespace isc::dns;
@@ -60,9 +57,9 @@ struct UDPServer::Data {
* first packet. But we do initialize the socket in here.
*/
Data(io_service& io_service, const ip::address& addr, const uint16_t port,
- SimpleCallback* checkin, DNSLookup* lookup, DNSAnswer* answer) :
+ DNSLookup* lookup, DNSAnswer* answer) :
io_(io_service), bytes_(0), done_(false),
- checkin_callback_(checkin),lookup_callback_(lookup),
+ lookup_callback_(lookup),
answer_callback_(answer)
{
// We must use different instantiations for v4 and v6;
@@ -75,15 +72,15 @@ struct UDPServer::Data {
}
socket_->bind(udp::endpoint(addr, port));
}
- Data(io_service& io_service, int fd, int af, SimpleCallback* checkin,
+ Data(io_service& io_service, int fd, int af,
DNSLookup* lookup, DNSAnswer* answer) :
io_(io_service), bytes_(0), done_(false),
- checkin_callback_(checkin),lookup_callback_(lookup),
+ lookup_callback_(lookup),
answer_callback_(answer)
{
if (af != AF_INET && af != AF_INET6) {
- isc_throw(InvalidParameter, "Address family must be either AF_INET "
- "or AF_INET6, not " << af);
+ isc_throw(InvalidParameter, "Address family must be either AF_INET"
+ " or AF_INET6, not " << af);
}
LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, ASIODNS_FD_ADD_UDP).arg(fd);
try {
@@ -105,7 +102,6 @@ struct UDPServer::Data {
*/
Data(const Data& other) :
io_(other.io_), socket_(other.socket_), bytes_(0), done_(false),
- checkin_callback_(other.checkin_callback_),
lookup_callback_(other.lookup_callback_),
answer_callback_(other.answer_callback_)
{
@@ -169,7 +165,6 @@ struct UDPServer::Data {
bool done_;
// Callback functions provided by the caller
- const SimpleCallback* checkin_callback_;
const DNSLookup* lookup_callback_;
const DNSAnswer* answer_callback_;
@@ -182,9 +177,9 @@ struct UDPServer::Data {
/// The constructor. It just creates new internal state object
/// and lets it handle the initialization.
UDPServer::UDPServer(io_service& io_service, int fd, int af,
- SimpleCallback* checkin, DNSLookup* lookup,
+ DNSLookup* lookup,
DNSAnswer* answer) :
- data_(new Data(io_service, fd, af, checkin, lookup, answer))
+ data_(new Data(io_service, fd, af, lookup, answer))
{ }
/// The function operator is implemented with the "stackless coroutine"
@@ -212,14 +207,19 @@ UDPServer::operator()(asio::error_code ec, size_t length) {
buffer(data_->data_.get(), MAX_LENGTH), *data_->sender_,
*this);
- // Abort on fatal errors
- // TODO: add log
+ // See TCPServer::operator() for details on error handling.
if (ec) {
using namespace asio::error;
- if (ec.value() != would_block && ec.value() != try_again &&
- ec.value() != interrupted) {
+ const error_code::value_type err_val = ec.value();
+ if (err_val == operation_aborted ||
+ err_val == bad_descriptor) {
return;
}
+ if (err_val != would_block && err_val != try_again &&
+ err_val != interrupted) {
+ LOG_ERROR(logger, ASIODNS_UDP_RECEIVE_FAIL).
+ arg(ec.message());
+ }
}
} while (ec || length == 0);
@@ -258,19 +258,10 @@ UDPServer::operator()(asio::error_code ec, size_t length) {
data_->io_message_.reset(new IOMessage(data_->data_.get(),
data_->bytes_, *data_->iosock_, *data_->peer_));
- // Perform any necessary operations prior to processing an incoming
- // query (e.g., checking for queued configuration messages).
- //
- // (XXX: it may be a performance issue to check in for every single
- // incoming query; we may wish to throttle this in the future.)
- if (data_->checkin_callback_ != NULL) {
- (*data_->checkin_callback_)(*data_->io_message_);
- }
-
// If we don't have a DNS Lookup provider, there's no point in
// continuing; we exit the coroutine permanently.
if (data_->lookup_callback_ == NULL) {
- CORO_YIELD return;
+ return;
}
// Instantiate objects that will be needed by the
@@ -287,7 +278,7 @@ UDPServer::operator()(asio::error_code ec, size_t length) {
// The 'done_' flag indicates whether we have an answer
// to send back. If not, exit the coroutine permanently.
if (!data_->done_) {
- CORO_YIELD return;
+ return;
}
// Call the DNS answer provider to render the answer into
@@ -322,15 +313,20 @@ UDPServer::asyncLookup() {
/// Stop the UDPServer
void
UDPServer::stop() {
+ asio::error_code ec;
+
/// Using close instead of cancel, because cancel
- /// will only cancel the asynchornized event already submitted
+ /// will only cancel the asynchronized event already submitted
/// to io service, the events post to io service after
/// cancel still can be scheduled by io service, if
- /// the socket is cloesed, all the asynchronized event
+ /// the socket is closed, all the asynchronized event
/// for it won't be scheduled by io service not matter it is
- /// submit to io serice before or after close call. And we will
- //. get bad_descriptor error
- data_->socket_->close();
+ /// submit to io service before or after close call. And we will
+ // get bad_descriptor error.
+ data_->socket_->close(ec);
+ if (ec) {
+ LOG_ERROR(logger, ASIODNS_UDP_CLOSE_FAIL).arg(ec.message());
+ }
}
/// Post this coroutine on the ASIO service queue so that it will
@@ -339,7 +335,7 @@ UDPServer::stop() {
void
UDPServer::resume(const bool done) {
data_->done_ = done;
- data_->io_.post(*this);
+ data_->io_.post(*this); // this can throw, but can be considered fatal.
}
} // namespace asiodns
diff --git a/src/lib/asiodns/udp_server.h b/src/lib/asiodns/udp_server.h
index c2b1b96..1c1dd9f 100644
--- a/src/lib/asiodns/udp_server.h
+++ b/src/lib/asiodns/udp_server.h
@@ -43,14 +43,12 @@ public:
/// \param io_service the asio::io_service to work with
/// \param fd the file descriptor of opened UDP socket
/// \param af address family, either AF_INET or AF_INET6
- /// \param checkin the callbackprovider for non-DNS events
/// \param lookup the callbackprovider for DNS lookup events
/// \param answer the callbackprovider for DNS answer events
/// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6
/// \throw isc::asiolink::IOError when a low-level error happens, like the
/// fd is not a valid descriptor.
UDPServer(asio::io_service& io_service, int fd, int af,
- isc::asiolink::SimpleCallback* checkin = NULL,
DNSLookup* lookup = NULL, DNSAnswer* answer = NULL);
/// \brief The function operator
diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am
index 3505982..8a5bc76 100644
--- a/src/lib/asiolink/Makefile.am
+++ b/src/lib/asiolink/Makefile.am
@@ -32,6 +32,7 @@ libb10_asiolink_la_SOURCES += tcp_endpoint.h
libb10_asiolink_la_SOURCES += tcp_socket.h
libb10_asiolink_la_SOURCES += udp_endpoint.h
libb10_asiolink_la_SOURCES += udp_socket.h
+libb10_asiolink_la_SOURCES += local_socket.h local_socket.cc
# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS)
@@ -41,4 +42,8 @@ if USE_CLANGPP
libb10_asiolink_la_CXXFLAGS += -Wno-error
endif
libb10_asiolink_la_CPPFLAGS = $(AM_CPPFLAGS)
-libb10_asiolink_la_LIBADD = $(top_builddir)/src/lib/log/libb10-log.la
+libb10_asiolink_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+
+# IOAddress is sometimes used in user-library code
+libb10_asiolink_includedir = $(pkgincludedir)/asiolink
+libb10_asiolink_include_HEADERS = io_address.h
diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h
index f6d64a0..a3f3f97 100644
--- a/src/lib/asiolink/io_asio_socket.h
+++ b/src/lib/asiolink/io_asio_socket.h
@@ -158,7 +158,7 @@ public:
/// (The reason there is a need to know is because the call to open() passes
/// in the state of the coroutine at the time the call is made. On an
/// asynchronous I/O, we need to set the state to point to the statement
- /// after the call to open() _before_ we pass the corouine to the open()
+ /// after the call to open() _before_ we pass the coroutine to the open()
/// call. Unfortunately, the macros that set the state of the coroutine
/// also yield control - which we don't want to do if the open is
/// synchronous. Hence we need to know before we make the call to open()
diff --git a/src/lib/asiolink/io_service.cc b/src/lib/asiolink/io_service.cc
index 15fad0c..df08316 100644
--- a/src/lib/asiolink/io_service.cc
+++ b/src/lib/asiolink/io_service.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011,2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -24,6 +24,23 @@
namespace isc {
namespace asiolink {
+namespace {
+// A trivial wrapper for boost::function. SunStudio doesn't seem to be capable
+// of handling a boost::function object if directly passed to
+// io_service::post().
+class CallbackWrapper {
+public:
+ CallbackWrapper(const boost::function<void()>& callback) :
+ callback_(callback)
+ {}
+ void operator()() {
+ callback_();
+ }
+private:
+ boost::function<void()> callback_;
+};
+}
+
class IOServiceImpl {
private:
IOServiceImpl(const IOService& source);
@@ -63,6 +80,10 @@ public:
/// It will eventually be removed once the wrapper interface is
/// generalized.
asio::io_service& get_io_service() { return io_service_; };
+ void post(const boost::function<void ()>& callback) {
+ const CallbackWrapper wrapper(callback);
+ io_service_.post(wrapper);
+ }
private:
asio::io_service io_service_;
asio::io_service::work work_;
@@ -96,5 +117,10 @@ IOService::get_io_service() {
return (io_impl_->get_io_service());
}
+void
+IOService::post(const boost::function<void ()>& callback) {
+ return (io_impl_->post(callback));
+}
+
} // namespace asiolink
} // namespace isc
diff --git a/src/lib/asiolink/io_service.h b/src/lib/asiolink/io_service.h
index e0198dd..e086997 100644
--- a/src/lib/asiolink/io_service.h
+++ b/src/lib/asiolink/io_service.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011,2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -15,6 +15,8 @@
#ifndef ASIOLINK_IO_SERVICE_H
#define ASIOLINK_IO_SERVICE_H 1
+#include <boost/function.hpp>
+
namespace asio {
class io_service;
}
@@ -70,6 +72,17 @@ public:
/// generalized.
asio::io_service& get_io_service();
+ /// \brief Post a callback to the end of the queue.
+ ///
+ /// Requests the callback be called sometime later. It is not guaranteed
+ /// by the underlying asio, but it can reasonably be expected the callback
+ /// is put to the end of the callback queue. It is not called from within
+ /// this function.
+ ///
+ /// It may be used to implement "background" work, for example (doing stuff
+ /// by small bits that are called from time to time).
+ void post(const boost::function<void ()>& callback);
+
private:
IOServiceImpl* io_impl_;
};
diff --git a/src/lib/asiolink/local_socket.cc b/src/lib/asiolink/local_socket.cc
new file mode 100644
index 0000000..f47226e
--- /dev/null
+++ b/src/lib/asiolink/local_socket.cc
@@ -0,0 +1,102 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <asiolink/local_socket.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_error.h>
+
+#include <asio.hpp>
+
+#include <boost/bind.hpp>
+
+#include <string>
+#include <sys/socket.h>
+
+namespace isc {
+namespace asiolink {
+class LocalSocket::Impl {
+public:
+ Impl(IOService& io_service, int fd) :
+ asio_sock_(io_service.get_io_service(),
+ asio::local::stream_protocol(), fd)
+ {
+ // Depending on the underlying demultiplex API, the constructor may or
+ // may not throw in case fd is invalid. To catch such cases sooner,
+ // we try to get the local endpoint (we don't need it in the rest of
+ // this implementation).
+ asio_sock_.local_endpoint(ec_);
+ if (ec_) {
+ isc_throw(IOError, "failed to open local socket with FD " << fd
+ << " (local endpoint unknown): " << ec_.message());
+ }
+ }
+
+ asio::local::stream_protocol::socket asio_sock_;
+ asio::error_code ec_;
+};
+
+LocalSocket::LocalSocket(IOService& io_service, int fd) :
+ impl_(NULL)
+{
+ try {
+ impl_ = new Impl(io_service, fd);
+ } catch (const asio::system_error& error) {
+ // Catch and convert any exception from asio's constructor
+ isc_throw(IOError, "failed to open local socket with FD " << fd
+ << ": " << error.what());
+ }
+}
+
+LocalSocket::~LocalSocket() {
+ delete impl_;
+}
+
+int
+LocalSocket::getNative() const {
+ return (impl_->asio_sock_.native());
+}
+
+int
+LocalSocket::getProtocol() const {
+ return (AF_UNIX);
+}
+
+namespace {
+// Wrapper callback for async_read that simply adjusts asio-native parameters
+// for the LocalSocket interface. Note that this is a free function and
+// doesn't rely on internal member variables of LocalSocket.
+// So it can be called safely even after the LocalSocket object on which
+// asyncRead() was called is destroyed.
+void
+readCompleted(const asio::error_code& ec,
+ LocalSocket::ReadCallback user_callback)
+{
+ // assumption check: we pass non empty string iff ec indicates an error.
+ const std::string err_msg = ec ? ec.message() : std::string();
+ assert(ec || err_msg.empty());
+
+ user_callback(err_msg);
+}
+}
+
+void
+LocalSocket::asyncRead(const ReadCallback& callback, void* buf,
+ size_t buflen)
+{
+ asio::async_read(impl_->asio_sock_, asio::buffer(buf, buflen),
+ boost::bind(readCompleted, _1, callback));
+}
+
+} // namespace asiolink
+} // namespace isc
diff --git a/src/lib/asiolink/local_socket.h b/src/lib/asiolink/local_socket.h
new file mode 100644
index 0000000..6269b7c
--- /dev/null
+++ b/src/lib/asiolink/local_socket.h
@@ -0,0 +1,132 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef LOCAL_SOCKET_H
+#define LOCAL_SOCKET_H 1
+
+#include <asiolink/io_socket.h>
+#include <asiolink/io_service.h>
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace asiolink {
+
+/// \brief A wrapper for ASIO stream socket in the local (AF_UNIX) domain.
+///
+/// This class provides a simple, limited set of wrapper interfaces to an
+/// ASIO stream socket object in the local domain. Unlike other concrete
+/// derived classes of \c IOSocket, this class is intended to be instantiated
+/// directly. Right now it only provides read interface due to the limited
+/// expected usage, but it can be extended as we see need for other operations
+/// on this socket.
+///
+/// Note that in the initial implementation there's even no stop() or cancel()
+/// method; for these cases users are expected to just destroy the socket
+/// object (this may be extended in future, too).
+class LocalSocket : boost::noncopyable, public IOSocket {
+public:
+ /// \brief Constructor from a native file descriptor of AF_UNIX stream
+ /// socket.
+ ///
+ /// Parameter \c fd must be an open stream-type socket of the AF_UNIX
+ /// domain. The constructor tries to detect some invalid cases, but
+ /// it may not reject all invalid cases. It's generally the
+ /// responsibility of the caller.
+ ///
+ /// \throw IOError Failed to create the socket object, most likely because
+ /// the given file descriptor is invalid.
+ ///
+ /// \param io_service The IO service object to handle events on this
+ /// socket.
+ /// \param fd File descriptor of an AF_UNIX socket.
+ LocalSocket(IOService& io_service, int fd);
+
+ /// \brief Destructor.
+ ///
+ /// \throw None.
+ virtual ~LocalSocket();
+
+ /// \brief Local socket version of getNative().
+ ///
+ /// \throw None.
+ virtual int getNative() const;
+
+ /// \brief Local socket version of getProtocol().
+ ///
+ /// It always returns \c AF_UNIX.
+ ///
+ /// \throw None.
+ virtual int getProtocol() const;
+
+ /// \brief The callback functor for the \c asyncRead method.
+ ///
+ /// The callback takes one parameter, \c error. It will be set to
+ /// non empty string if read operation fails and the string explains
+ /// the reason for the failure. On success \c error will be empty.
+ typedef boost::function<void(const std::string& error)> ReadCallback;
+
+ /// \brief Start asynchronous read on the socket.
+ ///
+ /// This method registers an interest on a new read event on the local
+ /// socket for the specified length of data (\c buflen bytes). This
+ /// method returns immediately. When the specified amount of data
+ /// are available for read from the socket or an error happens, the
+ /// specified callback will be called. In the former case the data are
+ /// copied into the given buffer (pointed to by \c buf); in the latter
+ /// case, the \c error parameter of the callback will be set to a non
+ /// empty string.
+ ///
+ /// In the case of error, this socket should be considered
+ /// unusable anymore, because this class doesn't provide a feasible way
+ /// to identify where in the input stream to restart reading. So,
+ /// in practice, the user of this socket should destroy this socket,
+ /// and, if necessary to continue, create a new one.
+ ///
+ /// \c buf must point to a memory region that has at least \c buflen
+ /// bytes of valid space. That region must be kept valid until the
+ /// callback is called or the \c IOService passed to the constructor
+ /// is stopped. This method and class do not check these conditions;
+ /// it's the caller's responsibility to guarantee them.
+ ///
+ /// \note If asyncRead() has been called and hasn't been completed (with
+ /// the callback being called), it's possible that the callback is called
+ /// even after the \c LocalSocket object is destroyed. So the caller
+ /// has to make sure that either \c LocalSocket is valid until the
+ /// callback is called or the callback does not depend on \c LocalSocket;
+ /// alternatively, the caller can stop the \c IOService. This will make
+ /// sure the callback will not be called regardless of when and how
+ /// the \c LocalSocket is destroyed.
+ ///
+ /// \throw None.
+ ///
+ /// \brief callback The callback functor to be called on the completion
+ /// of read.
+ /// \brief buf Buffer to read in data from the socket.
+ /// \brief buflen Length of data to read.
+ void asyncRead(const ReadCallback& callback, void* buf, size_t buflen);
+
+private:
+ class Impl;
+ Impl* impl_;
+};
+
+} // namespace asiolink
+} // namespace isc
+
+#endif // LOCAL_SOCKET_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/asiolink/tcp_endpoint.h b/src/lib/asiolink/tcp_endpoint.h
index 3d6a87a..2fe64f3 100644
--- a/src/lib/asiolink/tcp_endpoint.h
+++ b/src/lib/asiolink/tcp_endpoint.h
@@ -100,7 +100,7 @@ public:
return (asio_endpoint_.protocol().family());
}
- // This is not part of the exosed IOEndpoint API but allows
+ // This is not part of the exposed IOEndpoint API but allows
// direct access to the ASIO implementation of the endpoint
inline const asio::ip::tcp::endpoint& getASIOEndpoint() const {
return (asio_endpoint_);
diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h
index 6b0a43c..7df4a80 100644
--- a/src/lib/asiolink/tcp_socket.h
+++ b/src/lib/asiolink/tcp_socket.h
@@ -19,7 +19,6 @@
#error "asio.hpp must be included before including this, see asiolink.h as to why"
#endif
-#include <log/dummylog.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h> // for some IPC/network system calls
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index b401834..530fe0b 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -33,6 +33,8 @@ run_unittests_SOURCES += tcp_endpoint_unittest.cc
run_unittests_SOURCES += tcp_socket_unittest.cc
run_unittests_SOURCES += udp_endpoint_unittest.cc
run_unittests_SOURCES += udp_socket_unittest.cc
+run_unittests_SOURCES += io_service_unittest.cc
+run_unittests_SOURCES += local_socket_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
diff --git a/src/lib/asiolink/tests/io_service_unittest.cc b/src/lib/asiolink/tests/io_service_unittest.cc
new file mode 100644
index 0000000..2fe4f12
--- /dev/null
+++ b/src/lib/asiolink/tests/io_service_unittest.cc
@@ -0,0 +1,48 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <asiolink/io_service.h>
+
+#include <gtest/gtest.h>
+#include <boost/bind.hpp>
+#include <vector>
+
+using namespace isc::asiolink;
+
+namespace {
+
+void
+postedEvent(std::vector<int>* destination, int value) {
+ destination->push_back(value);
+}
+
+// Check the posted events are called, in the same order they are posted.
+TEST(IOService, post) {
+ std::vector<int> called;
+ IOService service;
+ // Post two events
+ service.post(boost::bind(&postedEvent, &called, 1));
+ service.post(boost::bind(&postedEvent, &called, 2));
+ // They have not yet been called
+ EXPECT_TRUE(called.empty());
+ // Process two events
+ service.run_one();
+ service.run_one();
+ // Both events were called in the right order
+ ASSERT_EQ(2, called.size());
+ EXPECT_EQ(1, called[0]);
+ EXPECT_EQ(2, called[1]);
+}
+
+}
diff --git a/src/lib/asiolink/tests/local_socket_unittest.cc b/src/lib/asiolink/tests/local_socket_unittest.cc
new file mode 100644
index 0000000..72efd6e
--- /dev/null
+++ b/src/lib/asiolink/tests/local_socket_unittest.cc
@@ -0,0 +1,253 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <asiolink/local_socket.h>
+#include <asiolink/io_error.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/bind.hpp>
+
+#include <csignal>
+#include <cstring>
+#include <vector>
+
+#include <sys/socket.h>
+#include <stdint.h>
+#include <unistd.h> // for alarm(3)
+
+using namespace isc::asiolink;
+
+namespace {
+
+// duration (in seconds) until we break possible hangup; value is an
+// arbitrary choice.
+const unsigned IO_TIMEOUT = 10;
+
+// A simple RAII wrapper for a file descriptor so test sockets are safely
+// closed in each test.
+class ScopedSocket : boost::noncopyable {
+public:
+ ScopedSocket() : fd_(-1) {}
+ ~ScopedSocket() {
+ if (fd_ >= 0) {
+ EXPECT_EQ(0, ::close(fd_));
+ }
+ }
+ void set(int fd) {
+ assert(fd_ == -1);
+ fd_ = fd;
+ }
+ int get() { return (fd_); }
+ int release() {
+ const int ret = fd_;
+ fd_ = -1;
+ return (ret);
+ }
+private:
+ int fd_;
+};
+
+class LocalSocketTest : public ::testing::Test {
+protected:
+ LocalSocketTest() {
+ int sock_pair[2];
+ EXPECT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sock_pair));
+ sock_pair_[0].set(sock_pair[0]);
+ sock_pair_[1].set(sock_pair[1]);
+
+ // For tests using actual I/O we use a timer to prevent hangup
+ // due to a bug. Set up the signal handler for the timer here.
+ g_io_service_ = &io_service_;
+ prev_handler_ = std::signal(SIGALRM, stopIOService);
+ }
+
+ ~LocalSocketTest() {
+ alarm(0);
+ // reset the global to NULL to detect any invalid access to freed
+ // io_service (this shouldn't happen, so we don't change stopIOService
+ // itself)
+ g_io_service_ = NULL;
+ std::signal(SIGALRM, prev_handler_);
+ }
+
+ // Common set of tests for async read
+ void checkAsyncRead(size_t data_len);
+
+ IOService io_service_;
+ ScopedSocket sock_pair_[2];
+ std::vector<uint8_t> read_buf_;
+private:
+ static IOService* g_io_service_; // will be set to &io_service_
+ void (*prev_handler_)(int);
+
+ // SIGALRM handler to prevent hangup. This must be a static method
+ // so it can be passed to std::signal().
+ static void stopIOService(int) {
+ g_io_service_->stop();
+ }
+};
+
+IOService* LocalSocketTest::g_io_service_ = NULL;
+
+TEST_F(LocalSocketTest, construct) {
+ const int fd = sock_pair_[0].release();
+ LocalSocket sock(io_service_, fd);
+ EXPECT_EQ(fd, sock.getNative());
+ EXPECT_EQ(AF_UNIX, sock.getProtocol());
+}
+
+TEST_F(LocalSocketTest, constructError) {
+ // try to construct a LocalSocket object with a closed socket. It should
+ // fail.
+ const int fd = sock_pair_[0].release();
+ EXPECT_EQ(0, close(fd));
+ EXPECT_THROW(LocalSocket(io_service_, fd), IOError);
+}
+
+TEST_F(LocalSocketTest, autoClose) {
+ // Confirm that passed FD will be closed on destruction of LocalSocket
+ const int fd = sock_pair_[0].release();
+ {
+ LocalSocket sock(io_service_, fd);
+ }
+ // fd should have been closed, so close() should fail (we assume there's
+ // no other open() call since then)
+ EXPECT_EQ(-1, ::close(fd));
+}
+
+void
+callback(const std::string& error, IOService* io_service, bool* called,
+ bool expect_error)
+{
+ if (expect_error) {
+ EXPECT_NE("", error);
+ } else {
+ EXPECT_EQ("", error);
+ }
+ *called = true;
+ io_service->stop();
+}
+
+void
+LocalSocketTest::checkAsyncRead(size_t data_len) {
+ LocalSocket sock(io_service_, sock_pair_[0].release());
+ bool callback_called = false;
+ read_buf_.resize(data_len);
+ sock.asyncRead(boost::bind(&callback, _1, &io_service_, &callback_called,
+ false), &read_buf_[0], data_len);
+
+ std::vector<uint8_t> expected_data(data_len);
+ for (size_t i = 0; i < data_len; ++i) {
+ expected_data[i] = i & 0xff;
+ }
+ alarm(IO_TIMEOUT);
+ // If write blocks, it will eventually fail due to signal interruption.
+ // Since io_service has been stopped already, run() would immediately
+ // return and test should complete (with failure). But to make very sure
+ // it never cause hangup we rather return from the test at the point of
+ // failure of write. In either case it signals a failure and need for
+ // a fix.
+ ASSERT_EQ(data_len, write(sock_pair_[1].get(), &expected_data[0],
+ data_len));
+ io_service_.run();
+ EXPECT_TRUE(callback_called);
+ EXPECT_EQ(0, std::memcmp(&expected_data[0], &read_buf_[0], data_len));
+
+}
+
+TEST_F(LocalSocketTest, asyncRead) {
+ // A simple case of asynchronous read: wait for 1 byte and successfully
+ // read it in the run() loop.
+ checkAsyncRead(1);
+}
+
+TEST_F(LocalSocketTest, asyncLargeRead) {
+ // Similar to the previous case, but for moderately larger data.
+ // (for the moment) we don't expect to use this interface with much
+ // larger data that could cause blocking write.
+ checkAsyncRead(1024);
+}
+
+TEST_F(LocalSocketTest, asyncPartialRead) {
+ alarm(IO_TIMEOUT);
+
+ // specify reading 4 bytes of data, and send 3 bytes. It shouldn't cause
+ // callback. while we actually don't use the buffer, we'll initialize it
+ // to make valgrind happy.
+ char recv_buf[4];
+ std::memset(recv_buf, 0, sizeof(recv_buf));
+ bool callback_called = false;
+ LocalSocket sock(io_service_, sock_pair_[0].release());
+ sock.asyncRead(boost::bind(&callback, _1, &io_service_, &callback_called,
+ false), recv_buf, sizeof(recv_buf));
+ EXPECT_EQ(3, write(sock_pair_[1].get(), recv_buf, 3));
+
+ // open another pair of sockets so we can stop the IO service after run.
+ int socks[2];
+ char ch = 0;
+ EXPECT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, socks));
+ ScopedSocket aux_sockpair[2];
+ aux_sockpair[0].set(socks[0]);
+ aux_sockpair[1].set(socks[1]);
+ LocalSocket aux_sock(io_service_, aux_sockpair[0].get());
+ aux_sockpair[0].release(); // on successful construction we should release
+ bool aux_callback_called = false;
+ aux_sock.asyncRead(boost::bind(&callback, _1, &io_service_,
+ &aux_callback_called, false), &ch, 1);
+ EXPECT_EQ(1, write(aux_sockpair[1].get(), &ch, 1));
+
+ // run the IO service, it will soon be stopped via the auxiliary callback.
+ // the main callback shouldn't be called.
+ io_service_.run();
+ EXPECT_FALSE(callback_called);
+ EXPECT_TRUE(aux_callback_called);
+}
+
+TEST_F(LocalSocketTest, asyncReadError) {
+ const int sock_fd = sock_pair_[0].release();
+ LocalSocket sock(io_service_, sock_fd);
+ bool callback_called = false;
+ read_buf_.resize(1);
+ read_buf_.at(0) = 53; // dummy data to check it later
+ const char ch = 35; // send different data to the read socket with data
+ EXPECT_EQ(1, write(sock_pair_[1].get(), &ch, 1));
+ close(sock_fd); // invalidate the read socket
+ // we'll get callback with an error (e.g. 'bad file descriptor)
+ sock.asyncRead(boost::bind(&callback, _1, &io_service_, &callback_called,
+ true), &read_buf_[0], 1);
+
+ io_service_.run();
+ EXPECT_TRUE(callback_called);
+ EXPECT_EQ(53, read_buf_.at(0));
+}
+
+TEST_F(LocalSocketTest, asyncReadThenDestroy) {
+ // destroy the socket before running the IO service. we'll still get
+ // callback with an error.
+ boost::scoped_ptr<LocalSocket> sock(
+ new LocalSocket(io_service_, sock_pair_[0].release()));
+ read_buf_.resize(1);
+ bool callback_called = false;
+ sock->asyncRead(boost::bind(&callback, _1, &io_service_, &callback_called,
+ true), &read_buf_[0], 1);
+ sock.reset();
+
+ io_service_.run();
+ EXPECT_TRUE(callback_called);
+}
+
+}
diff --git a/src/lib/asiolink/tests/tcp_socket_unittest.cc b/src/lib/asiolink/tests/tcp_socket_unittest.cc
index 20b6cd8..9de366c 100644
--- a/src/lib/asiolink/tests/tcp_socket_unittest.cc
+++ b/src/lib/asiolink/tests/tcp_socket_unittest.cc
@@ -14,7 +14,7 @@
/// \brief Test of TCPSocket
///
-/// Tests the fuctionality of a TCPSocket by working through an open-send-
+/// Tests the functionality of a TCPSocket by working through an open-send-
/// receive-close sequence and checking that the asynchronous notifications
/// work.
diff --git a/src/lib/asiolink/tests/udp_socket_unittest.cc b/src/lib/asiolink/tests/udp_socket_unittest.cc
index 1ab1a09..3d91874 100644
--- a/src/lib/asiolink/tests/udp_socket_unittest.cc
+++ b/src/lib/asiolink/tests/udp_socket_unittest.cc
@@ -14,7 +14,7 @@
/// \brief Test of UDPSocket
///
-/// Tests the fuctionality of a UDPSocket by working through an open-send-
+/// Tests the functionality of a UDPSocket by working through an open-send-
/// receive-close sequence and checking that the asynchronous notifications
/// work.
diff --git a/src/lib/asiolink/udp_endpoint.h b/src/lib/asiolink/udp_endpoint.h
index 34701b9..02e800d 100644
--- a/src/lib/asiolink/udp_endpoint.h
+++ b/src/lib/asiolink/udp_endpoint.h
@@ -100,7 +100,7 @@ public:
return (asio_endpoint_.protocol().family());
}
- // This is not part of the exosed IOEndpoint API but allows
+ // This is not part of the exposed IOEndpoint API but allows
// direct access to the ASIO implementation of the endpoint
inline const asio::ip::udp::endpoint& getASIOEndpoint() const {
return (asio_endpoint_);
diff --git a/src/lib/asiolink/udp_socket.h b/src/lib/asiolink/udp_socket.h
index 5712957..4ac49c9 100644
--- a/src/lib/asiolink/udp_socket.h
+++ b/src/lib/asiolink/udp_socket.h
@@ -19,7 +19,6 @@
#error "asio.hpp must be included before including this, see asiolink.h as to why"
#endif
-#include <log/dummylog.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h> // for some IPC/network system calls
diff --git a/src/lib/bench/benchmark.h b/src/lib/bench/benchmark.h
index 3e380dc..23dc364 100644
--- a/src/lib/bench/benchmark.h
+++ b/src/lib/bench/benchmark.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010,2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -93,7 +93,7 @@ namespace bench {
/// vector<int>::const_iterator end_key = keys_.end();
/// for (iter = keys_.begin(); iter != end_key; ++iter) {
/// data_.find(*iter);
-/// }
+/// }
/// return (keys_.size());
/// }
/// const set<int>& data_;
diff --git a/src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire b/src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire
index 142d8cf..f1971de 100644
--- a/src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire
+++ b/src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire
@@ -19,7 +19,7 @@ b1fe 8583
## Authority
##
# example.org: type SOA, class IN, mname ns1.example.org
-# TTL: 3 Hourse, 1 second (10801seconds)
+# TTL: 3 Hours, 1 second (10801 seconds)
c0 0e 00 06 00 01 00 00 2a 31 00 22 03 6e 73 31 c0
0e 05 61 64 6d 69 6e c0 0e 00 00 04 d2 00 00 0e
10 00 00 07 08 00 24 ea 00 00 00 2a 31
diff --git a/src/lib/cc/cc_messages.mes b/src/lib/cc/cc_messages.mes
index b561784..9be9c7c 100644
--- a/src/lib/cc/cc_messages.mes
+++ b/src/lib/cc/cc_messages.mes
@@ -37,7 +37,7 @@ socket listed in the output.
This debug message indicates that the connection was successfully made, this
should follow CC_ESTABLISH.
-% CC_GROUP_RECEIVE trying to receive a message
+% CC_GROUP_RECEIVE trying to receive a message with seq %1
Debug message, noting that a message is expected to come over the command
channel.
diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc
index af3602a..3518939 100644
--- a/src/lib/cc/data.cc
+++ b/src/lib/cc/data.cc
@@ -24,8 +24,11 @@
#include <iostream>
#include <string>
#include <sstream>
+#include <cerrno>
+#include <climits>
#include <boost/algorithm/string.hpp> // for iequals
+#include <boost/lexical_cast.hpp>
#include <cmath>
@@ -58,7 +61,7 @@ Element::toWire(std::ostream& ss) const {
}
bool
-Element::getValue(long int&) const {
+Element::getValue(int64_t&) const {
return (false);
}
@@ -88,7 +91,7 @@ Element::getValue(std::map<std::string, ConstElementPtr>&) const {
}
bool
-Element::setValue(const long int) {
+Element::setValue(const long long int) {
return (false);
}
@@ -206,8 +209,8 @@ Element::create() {
}
ElementPtr
-Element::create(const long int i) {
- return (ElementPtr(new IntElement(i)));
+Element::create(const long long int i) {
+ return (ElementPtr(new IntElement(static_cast<int64_t>(i))));
}
ElementPtr
@@ -389,37 +392,24 @@ numberFromStringstream(std::istream& in, int& pos) {
// Should we change from IntElement and DoubleElement to NumberElement
// that can also hold an e value? (and have specific getters if the
// value is larger than an int can handle)
+//
ElementPtr
fromStringstreamNumber(std::istream& in, int& pos) {
- long int i;
- double d = 0.0;
- bool is_double = false;
- char* endptr;
-
std::string number = numberFromStringstream(in, pos);
- i = strtol(number.c_str(), &endptr, 10);
- if (*endptr != '\0') {
- d = strtod(number.c_str(), &endptr);
- is_double = true;
- if (*endptr != '\0') {
- isc_throw(JSONError, std::string("Bad number: ") + number);
- } else {
- if (d == HUGE_VAL || d == -HUGE_VAL) {
- isc_throw(JSONError, std::string("Number overflow: ") + number);
- }
+ if (number.find_first_of(".eE") < number.size()) {
+ try {
+ return (Element::create(boost::lexical_cast<double>(number)));
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(JSONError, std::string("Number overflow: ") + number);
}
} else {
- if (i == LONG_MAX || i == LONG_MIN) {
+ try {
+ return (Element::create(boost::lexical_cast<int64_t>(number)));
+ } catch (const boost::bad_lexical_cast&) {
isc_throw(JSONError, std::string("Number overflow: ") + number);
}
}
-
- if (is_double) {
- return (Element::create(d));
- } else {
- return (Element::create(i));
- }
}
ElementPtr
@@ -688,10 +678,9 @@ NullElement::toJSON(std::ostream& ss) const {
void
StringElement::toJSON(std::ostream& ss) const {
ss << "\"";
- char c;
const std::string& str = stringValue();
for (size_t i = 0; i < str.size(); ++i) {
- c = str[i];
+ const char c = str[i];
// Escape characters as defined in JSON spec
// Note that we do not escape forward slash; this
// is allowed, but not mandatory.
diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h
index db25d9f..8050607 100644
--- a/src/lib/cc/data.h
+++ b/src/lib/cc/data.h
@@ -124,7 +124,7 @@ public:
/// If you want an exception-safe getter method, use
/// getValue() below
//@{
- virtual long int intValue() const
+ virtual int64_t intValue() const
{ isc_throw(TypeError, "intValue() called on non-integer Element"); };
virtual double doubleValue() const
{ isc_throw(TypeError, "doubleValue() called on non-double Element"); };
@@ -151,7 +151,7 @@ public:
/// data to the given reference and returning true
///
//@{
- virtual bool getValue(long int& t) const;
+ virtual bool getValue(int64_t& t) const;
virtual bool getValue(double& t) const;
virtual bool getValue(bool& t) const;
virtual bool getValue(std::string& t) const;
@@ -166,8 +166,12 @@ public:
/// the right type. Set the value and return true if the Elements
/// is of the correct type
///
+ /// Notes: Read notes of IntElement definition about the use of
+ /// long long int, long int and int.
//@{
- virtual bool setValue(const long int v);
+ virtual bool setValue(const long long int v);
+ bool setValue(const long int i) { return (setValue(static_cast<long long int>(i))); };
+ bool setValue(const int i) { return (setValue(static_cast<long long int>(i))); };
virtual bool setValue(const double v);
virtual bool setValue(const bool t);
virtual bool setValue(const std::string& v);
@@ -216,7 +220,7 @@ public:
//@{
/// Returns the ElementPtr at the given key
/// \param name The key of the Element to return
- /// \return The ElementPtr at the given key
+ /// \return The ElementPtr at the given key, or null if not present
virtual ConstElementPtr get(const std::string& name) const;
/// Sets the ElementPtr at the given key
@@ -271,10 +275,14 @@ public:
/// underlying system).
/// (Note that that is different from an NullElement, which
/// represents an empty value, and is created with Element::create())
+ ///
+ /// Notes: Read notes of IntElement definition about the use of
+ /// long long int, long int and int.
//@{
static ElementPtr create();
- static ElementPtr create(const long int i);
- static ElementPtr create(const int i) { return (create(static_cast<long int>(i))); };
+ static ElementPtr create(const long long int i);
+ static ElementPtr create(const int i) { return (create(static_cast<long long int>(i))); };
+ static ElementPtr create(const long int i) { return (create(static_cast<long long int>(i))); };
static ElementPtr create(const double d);
static ElementPtr create(const bool b);
static ElementPtr create(const std::string& s);
@@ -370,16 +378,27 @@ public:
//@}
};
+/// Notes: IntElement type is changed to int64_t.
+/// Due to C++ problems on overloading and automatic type conversion,
+/// (C++ tries to convert integer type values and reference/pointer
+/// if value types do not match exactly)
+/// We decided the storage as int64_t,
+/// three (long long, long, int) override function defintions
+/// and cast int/long/long long to int64_t via long long.
+/// Therefore, call by value methods (create, setValue) have three
+/// (int,long,long long) definitions. Others use int64_t.
+///
class IntElement : public Element {
- long int i;
+ int64_t i;
+private:
public:
- IntElement(long int v) : Element(integer), i(v) { }
- long int intValue() const { return (i); }
+ IntElement(int64_t v) : Element(integer), i(v) { }
+ int64_t intValue() const { return (i); }
using Element::getValue;
- bool getValue(long int& t) const { t = i; return (true); }
+ bool getValue(int64_t& t) const { t = i; return (true); }
using Element::setValue;
- bool setValue(const long int v) { i = v; return (true); }
+ bool setValue(long long int v) { i = v; return (true); }
void toJSON(std::ostream& ss) const;
bool equals(const Element& other) const;
};
@@ -504,7 +523,7 @@ public:
// find the Element at 'id', and store the element pointer in t
// returns true if found, or false if not found (either because
- // it doesnt exist or one of the elements in the path is not
+ // it doesn't exist or one of the elements in the path is not
// a MapElement)
bool find(const std::string& id, ConstElementPtr& t) const;
diff --git a/src/lib/cc/proto_defs.cc b/src/lib/cc/proto_defs.cc
index 5eb8575..2806c2a 100644
--- a/src/lib/cc/proto_defs.cc
+++ b/src/lib/cc/proto_defs.cc
@@ -34,12 +34,25 @@ const char* const CC_HEADER_WANT_ANSWER = "want_answer";
const char* const CC_HEADER_REPLY = "reply";
// The commands in the "type" header
const char* const CC_COMMAND_SEND = "send";
+const char* const CC_COMMAND_SUBSCRIBE = "subscribe";
+const char* const CC_COMMAND_UNSUBSCRIBE = "unsubscribe";
+const char* const CC_COMMAND_GET_LNAME = "getlname";
+const char* const CC_COMMAND_PING = "ping";
+const char* const CC_COMMAND_PONG = "pong";
+const char* const CC_COMMAND_STOP = "stop";
// The wildcards of some headers
const char* const CC_TO_WILDCARD = "*";
const char* const CC_INSTANCE_WILDCARD = "*";
+// Prefixes for groups
+const char* const CC_GROUP_NOTIFICATION_PREFIX = "notifications/";
// Reply codes
-const int CC_REPLY_SUCCESS = 0;
const int CC_REPLY_NO_RECPT = -1;
+const int CC_REPLY_SUCCESS = 0;
+// Payload in the message
+const char *const CC_PAYLOAD_LNAME = "lname";
+const char *const CC_PAYLOAD_RESULT = "result";
+const char *const CC_PAYLOAD_COMMAND = "command";
+const char *const CC_PAYLOAD_NOTIFICATION = "notification";
}
}
diff --git a/src/lib/cc/session.cc b/src/lib/cc/session.cc
index da78bd4..cb1ca39 100644
--- a/src/lib/cc/session.cc
+++ b/src/lib/cc/session.cc
@@ -260,7 +260,7 @@ SessionImpl::getSocketDesc() {
/// @todo boost 1.42 uses native() method, but it is deprecated
/// in 1.49 and native_handle() is recommended instead
if (!socket_.is_open()) {
- isc_throw(InvalidOperation, "Can't return socket desciptor: no socket opened.");
+ isc_throw(InvalidOperation, "Can't return socket descriptor: no socket opened.");
}
return socket_.native();
}
@@ -325,14 +325,14 @@ Session::establish(const char* socket_file) {
//
// send a request for our local name, and wait for a response
//
- ConstElementPtr get_lname_msg =
- Element::fromJSON("{ \"type\": \"getlname\" }");
+ ElementPtr get_lname_msg(Element::createMap());
+ get_lname_msg->set(CC_HEADER_TYPE, Element::create(CC_COMMAND_GET_LNAME));
sendmsg(get_lname_msg);
ConstElementPtr routing, msg;
recvmsg(routing, msg, false);
- impl_->lname_ = msg->get("lname")->stringValue();
+ impl_->lname_ = msg->get(CC_PAYLOAD_LNAME)->stringValue();
LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_LNAME_RECEIVED).arg(impl_->lname_);
// At this point there's no risk of resource leak.
@@ -387,10 +387,10 @@ Session::recvmsg(ConstElementPtr& env, ConstElementPtr& msg,
for (size_t i = 0; i < impl_->queue_->size(); i++) {
q_el = impl_->queue_->get(i);
if (( seq == -1 &&
- !q_el->get(0)->contains("reply")
+ !q_el->get(0)->contains(CC_HEADER_REPLY)
) || (
- q_el->get(0)->contains("reply") &&
- q_el->get(0)->get("reply")->intValue() == seq
+ q_el->get(0)->contains(CC_HEADER_REPLY) &&
+ q_el->get(0)->get(CC_HEADER_REPLY)->intValue() == seq
)
) {
env = q_el->get(0);
@@ -429,10 +429,10 @@ Session::recvmsg(ConstElementPtr& env, ConstElementPtr& msg,
ConstElementPtr l_msg =
Element::fromWire(body_wire_stream, length - header_length);
if ((seq == -1 &&
- !l_env->contains("reply")
+ !l_env->contains(CC_HEADER_REPLY)
) || (
- l_env->contains("reply") &&
- l_env->get("reply")->intValue() == seq
+ l_env->contains(CC_HEADER_REPLY) &&
+ l_env->get(CC_HEADER_REPLY)->intValue() == seq
)
) {
env = l_env;
@@ -453,9 +453,9 @@ Session::subscribe(std::string group, std::string instance) {
LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_SUBSCRIBE).arg(group);
ElementPtr env = Element::createMap();
- env->set("type", Element::create("subscribe"));
- env->set("group", Element::create(group));
- env->set("instance", Element::create(instance));
+ env->set(CC_HEADER_TYPE, Element::create(CC_COMMAND_SUBSCRIBE));
+ env->set(CC_HEADER_GROUP, Element::create(group));
+ env->set(CC_HEADER_INSTANCE, Element::create(instance));
sendmsg(env);
}
@@ -465,9 +465,9 @@ Session::unsubscribe(std::string group, std::string instance) {
LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_UNSUBSCRIBE).arg(group);
ElementPtr env = Element::createMap();
- env->set("type", Element::create("unsubscribe"));
- env->set("group", Element::create(group));
- env->set("instance", Element::create(instance));
+ env->set(CC_HEADER_TYPE, Element::create(CC_COMMAND_UNSUBSCRIBE));
+ env->set(CC_HEADER_GROUP, Element::create(group));
+ env->set(CC_HEADER_INSTANCE, Element::create(instance));
sendmsg(env);
}
@@ -498,7 +498,7 @@ bool
Session::group_recvmsg(ConstElementPtr& envelope, ConstElementPtr& msg,
bool nonblock, int seq)
{
- LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_GROUP_RECEIVE);
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_GROUP_RECEIVE).arg(seq);
bool result(recvmsg(envelope, msg, nonblock, seq));
if (result) {
LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_GROUP_RECEIVED).
@@ -516,13 +516,17 @@ Session::reply(ConstElementPtr envelope, ConstElementPtr newmsg) {
ElementPtr env = Element::createMap();
long int nseq = ++impl_->sequence_;
- env->set("type", Element::create("send"));
- env->set("from", Element::create(impl_->lname_));
- env->set("to", Element::create(envelope->get("from")->stringValue()));
- env->set("group", Element::create(envelope->get("group")->stringValue()));
- env->set("instance", Element::create(envelope->get("instance")->stringValue()));
- env->set("seq", Element::create(nseq));
- env->set("reply", Element::create(envelope->get("seq")->intValue()));
+ env->set(CC_HEADER_TYPE, Element::create(CC_COMMAND_SEND));
+ env->set(CC_HEADER_FROM, Element::create(impl_->lname_));
+ env->set(CC_HEADER_TO,
+ Element::create(envelope->get(CC_HEADER_FROM)->stringValue()));
+ env->set(CC_HEADER_GROUP,
+ Element::create(envelope->get(CC_HEADER_GROUP)->stringValue()));
+ env->set(CC_HEADER_INSTANCE,
+ Element::create(envelope->get(CC_HEADER_INSTANCE)->stringValue()));
+ env->set(CC_HEADER_SEQ, Element::create(nseq));
+ env->set(CC_HEADER_REPLY,
+ Element::create(envelope->get(CC_HEADER_SEQ)->intValue()));
sendmsg(env, newmsg);
diff --git a/src/lib/cc/tests/data_unittests.cc b/src/lib/cc/tests/data_unittests.cc
index 9f015d2..a60c994 100644
--- a/src/lib/cc/tests/data_unittests.cc
+++ b/src/lib/cc/tests/data_unittests.cc
@@ -15,6 +15,7 @@
#include <gtest/gtest.h>
#include <boost/foreach.hpp>
#include <boost/assign/std/vector.hpp>
+#include <climits>
#include <cc/data.h>
@@ -146,6 +147,17 @@ TEST(Element, from_and_to_json) {
EXPECT_EQ("100", Element::fromJSON("1e2")->str());
EXPECT_EQ("100", Element::fromJSON("+1e2")->str());
EXPECT_EQ("-100", Element::fromJSON("-1e2")->str());
+
+ EXPECT_NO_THROW({
+ EXPECT_EQ("9223372036854775807", Element::fromJSON("9223372036854775807")->str());
+ });
+ EXPECT_NO_THROW({
+ EXPECT_EQ("-9223372036854775808", Element::fromJSON("-9223372036854775808")->str());
+ });
+ EXPECT_THROW({
+ EXPECT_NE("9223372036854775808", Element::fromJSON("9223372036854775808")->str());
+ }, JSONError);
+
EXPECT_EQ("0.01", Element::fromJSON("1e-2")->str());
EXPECT_EQ("0.01", Element::fromJSON(".01")->str());
EXPECT_EQ("-0.01", Element::fromJSON("-1e-2")->str());
@@ -173,6 +185,8 @@ TEST(Element, from_and_to_json) {
EXPECT_THROW(Element::fromJSON("-1.1e12345678901234567890")->str(), JSONError);
EXPECT_THROW(Element::fromJSON("1e12345678901234567890")->str(), JSONError);
EXPECT_THROW(Element::fromJSON("1e50000")->str(), JSONError);
+ // number underflow
+ // EXPECT_THROW(Element::fromJSON("1.1e-12345678901234567890")->str(), JSONError);
}
@@ -180,7 +194,10 @@ template <typename T>
void
testGetValueInt() {
T el;
- long int i;
+ int64_t i;
+ int32_t i32;
+ long l;
+ long long ll;
double d;
bool b;
std::string s;
@@ -188,7 +205,9 @@ testGetValueInt() {
std::map<std::string, ConstElementPtr> m;
el = Element::create(1);
- EXPECT_NO_THROW(el->intValue());
+ EXPECT_NO_THROW({
+ EXPECT_EQ(1, el->intValue());
+ });
EXPECT_THROW(el->doubleValue(), TypeError);
EXPECT_THROW(el->boolValue(), TypeError);
EXPECT_THROW(el->stringValue(), TypeError);
@@ -201,13 +220,44 @@ testGetValueInt() {
EXPECT_FALSE(el->getValue(v));
EXPECT_FALSE(el->getValue(m));
EXPECT_EQ(1, i);
+
+ el = Element::create(9223372036854775807LL);
+ EXPECT_NO_THROW({
+ EXPECT_EQ(9223372036854775807LL, el->intValue());
+ });
+ EXPECT_TRUE(el->getValue(i));
+ EXPECT_EQ(9223372036854775807LL, i);
+
+ ll = 9223372036854775807LL;
+ el = Element::create(ll);
+ EXPECT_NO_THROW({
+ EXPECT_EQ(ll, el->intValue());
+ });
+ EXPECT_TRUE(el->getValue(i));
+ EXPECT_EQ(ll, i);
+
+ i32 = 2147483647L;
+ el = Element::create(i32);
+ EXPECT_NO_THROW({
+ EXPECT_EQ(i32, el->intValue());
+ });
+ EXPECT_TRUE(el->getValue(i));
+ EXPECT_EQ(i32, i);
+
+ l = 2147483647L;
+ el = Element::create(l);
+ EXPECT_NO_THROW({
+ EXPECT_EQ(l, el->intValue());
+ });
+ EXPECT_TRUE(el->getValue(i));
+ EXPECT_EQ(l, i);
}
template <typename T>
void
testGetValueDouble() {
T el;
- long int i;
+ int64_t i;
double d;
bool b;
std::string s;
@@ -234,7 +284,7 @@ template <typename T>
void
testGetValueBool() {
T el;
- long int i;
+ int64_t i;
double d;
bool b;
std::string s;
@@ -261,7 +311,7 @@ template <typename T>
void
testGetValueString() {
T el;
- long int i;
+ int64_t i;
double d;
bool b;
std::string s;
@@ -288,7 +338,7 @@ template <typename T>
void
testGetValueList() {
T el;
- long int i;
+ int64_t i;
double d;
bool b;
std::string s;
@@ -315,7 +365,7 @@ template <typename T>
void
testGetValueMap() {
T el;
- long int i;
+ int64_t i;
double d;
bool b;
std::string s;
@@ -343,7 +393,7 @@ TEST(Element, create_and_value_throws) {
// incorrect type is requested
ElementPtr el;
ConstElementPtr cel;
- long int i = 0;
+ int64_t i = 0;
double d = 0.0;
bool b = false;
std::string s("asdf");
diff --git a/src/lib/cc/tests/session_unittests.cc b/src/lib/cc/tests/session_unittests.cc
index fc6e538..2531ce1 100644
--- a/src/lib/cc/tests/session_unittests.cc
+++ b/src/lib/cc/tests/session_unittests.cc
@@ -46,7 +46,7 @@ TEST(AsioSession, establish) {
asio::io_service io_service_;
Session sess(io_service_);
- // can't return socket desciptor before session is established
+ // can't return socket descriptor before session is established
EXPECT_THROW(sess.getSocketDesc(), isc::InvalidOperation);
EXPECT_THROW(
@@ -293,7 +293,7 @@ TEST_F(SessionTest, run_with_handler_timeout) {
msg = isc::data::Element::fromJSON("{ \"a third\": \"message\" }");
tds->sendmsg(env, msg);
- // No followup message, should time out.
+ // No follow-up message, should time out.
ASSERT_THROW(my_io_service.run(), SessionTimeout);
}
diff --git a/src/lib/config/ccsession.cc b/src/lib/config/ccsession.cc
index 378f2b4..780fe95 100644
--- a/src/lib/config/ccsession.cc
+++ b/src/lib/config/ccsession.cc
@@ -32,7 +32,7 @@
#include <boost/foreach.hpp>
#include <cc/data.h>
-#include <module_spec.h>
+#include <config/module_spec.h>
#include <cc/session.h>
#include <exceptions/exceptions.h>
@@ -57,10 +57,10 @@ namespace config {
/// Creates a standard config/command protocol answer message
ConstElementPtr
createAnswer() {
- ElementPtr answer = Element::fromJSON("{\"result\": [] }");
+ ElementPtr answer = Element::createMap();
ElementPtr answer_content = Element::createList();
- answer_content->add(Element::create(0));
- answer->set("result", answer_content);
+ answer_content->add(Element::create(isc::cc::CC_REPLY_SUCCESS));
+ answer->set(isc::cc::CC_PAYLOAD_RESULT, answer_content);
return (answer);
}
@@ -70,22 +70,22 @@ createAnswer(const int rcode, ConstElementPtr arg) {
if (rcode != 0 && (!arg || arg->getType() != Element::string)) {
isc_throw(CCSessionError, "Bad or no argument for rcode != 0");
}
- ElementPtr answer = Element::fromJSON("{\"result\": [] }");
+ ElementPtr answer = Element::createMap();
ElementPtr answer_content = Element::createList();
answer_content->add(Element::create(rcode));
answer_content->add(arg);
- answer->set("result", answer_content);
+ answer->set(isc::cc::CC_PAYLOAD_RESULT, answer_content);
return (answer);
}
ConstElementPtr
createAnswer(const int rcode, const std::string& arg) {
- ElementPtr answer = Element::fromJSON("{\"result\": [] }");
+ ElementPtr answer = Element::createMap();
ElementPtr answer_content = Element::createList();
answer_content->add(Element::create(rcode));
answer_content->add(Element::create(arg));
- answer->set("result", answer_content);
+ answer->set(isc::cc::CC_PAYLOAD_RESULT, answer_content);
return (answer);
}
@@ -94,8 +94,8 @@ ConstElementPtr
parseAnswer(int &rcode, ConstElementPtr msg) {
if (msg &&
msg->getType() == Element::map &&
- msg->contains("result")) {
- ConstElementPtr result = msg->get("result");
+ msg->contains(isc::cc::CC_PAYLOAD_RESULT)) {
+ ConstElementPtr result = msg->get(isc::cc::CC_PAYLOAD_RESULT);
if (result->getType() != Element::list) {
isc_throw(CCSessionError, "Result element in answer message is not a list");
} else if (result->get(0)->getType() != Element::integer) {
@@ -133,7 +133,7 @@ createCommand(const std::string& command, ConstElementPtr arg) {
if (arg) {
cmd_parts->add(arg);
}
- cmd->set("command", cmd_parts);
+ cmd->set(isc::cc::CC_PAYLOAD_COMMAND, cmd_parts);
return (cmd);
}
@@ -141,8 +141,8 @@ std::string
parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
if (command &&
command->getType() == Element::map &&
- command->contains("command")) {
- ConstElementPtr cmd = command->get("command");
+ command->contains(isc::cc::CC_PAYLOAD_COMMAND)) {
+ ConstElementPtr cmd = command->get(isc::cc::CC_PAYLOAD_COMMAND);
if (cmd->getType() == Element::list &&
cmd->size() > 0 &&
cmd->get(0)->getType() == Element::string) {
@@ -165,7 +165,7 @@ namespace {
// getValue() (main problem described in ticket #993)
// This returns either the value set for the given relative id,
// or its default value
-// (intentially defined here so this interface does not get
+// (intentionally defined here so this interface does not get
// included in ConfigData as it is)
ConstElementPtr getValueOrDefault(ConstElementPtr config_part,
const std::string& relative_id,
@@ -463,10 +463,13 @@ ModuleCCSession::ModuleCCSession(
isc_throw(CCSessionInitError, answer->str());
}
- setLocalConfig(Element::fromJSON("{}"));
+ setLocalConfig(Element::createMap());
// get any stored configuration from the manager
if (config_handler_) {
- ConstElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name_ + "\"} ] }");
+ ConstElementPtr cmd =
+ createCommand("get_config",
+ Element::fromJSON("{\"module_name\":\"" +
+ module_name_ + "\"}"));
seq = session_.group_sendmsg(cmd, "ConfigManager");
session_.group_recvmsg(env, answer, false, seq);
ConstElementPtr new_config = parseAnswer(rcode, answer);
@@ -592,6 +595,8 @@ ModuleCCSession::checkModuleCommand(const std::string& cmd_str,
"Command given but no "
"command handler for module"));
}
+ } else if (unhandled_callback_) {
+ unhandled_callback_(cmd_str, target_module, arg);
}
return (ElementPtr());
}
@@ -608,14 +613,16 @@ ModuleCCSession::checkCommand() {
/* ignore result messages (in case we're out of sync, to prevent
* pingpongs */
- if (data->getType() != Element::map || data->contains("result")) {
+ if (data->getType() != Element::map ||
+ data->contains(isc::cc::CC_PAYLOAD_RESULT)) {
return (0);
}
ConstElementPtr arg;
ConstElementPtr answer;
try {
std::string cmd_str = parseCommand(arg, data);
- std::string target_module = routing->get("group")->stringValue();
+ std::string target_module =
+ routing->get(isc::cc::CC_HEADER_GROUP)->stringValue();
if (cmd_str == "config_update") {
answer = checkConfigUpdateCommand(target_module, arg);
} else {
@@ -832,19 +839,19 @@ bool
ModuleCCSession::requestMatch(const AsyncRecvRequest& request,
const ConstElementPtr& envelope) const
{
- if (request.is_reply != envelope->contains("reply")) {
+ if (request.is_reply != envelope->contains(isc::cc::CC_HEADER_REPLY)) {
// Wrong type of message
return (false);
}
if (request.is_reply &&
(request.seq == -1 ||
- request.seq == envelope->get("reply")->intValue())) {
+ request.seq == envelope->get(isc::cc::CC_HEADER_REPLY)->intValue())) {
// This is the correct reply
return (true);
}
if (!request.is_reply &&
- (request.recipient.empty() ||
- request.recipient == envelope->get("group")->stringValue())) {
+ (request.recipient.empty() || request.recipient ==
+ envelope->get(isc::cc::CC_HEADER_GROUP)->stringValue())) {
// This is the correct command
return (true);
}
@@ -857,5 +864,43 @@ ModuleCCSession::cancelAsyncRecv(const AsyncRecvRequestID& id) {
async_recv_requests_.erase(id);
}
+ConstElementPtr
+ModuleCCSession::rpcCall(const std::string &command, const std::string &group,
+ const std::string &instance, const std::string &to,
+ const ConstElementPtr ¶ms)
+{
+ ConstElementPtr command_el(createCommand(command, params));
+ const int seq = groupSendMsg(command_el, group, instance, to, true);
+ ConstElementPtr env, answer;
+ LOG_DEBUG(config_logger, DBGLVL_TRACE_DETAIL, CONFIG_RPC_SEQ).arg(command).
+ arg(group).arg(seq);
+ groupRecvMsg(env, answer, true, seq);
+ int rcode;
+ const ConstElementPtr result(parseAnswer(rcode, answer));
+ if (rcode == isc::cc::CC_REPLY_NO_RECPT) {
+ isc_throw(RPCRecipientMissing, result);
+ } else if (rcode != isc::cc::CC_REPLY_SUCCESS) {
+ isc_throw_1(RPCError, result, rcode);
+ } else {
+ return (result);
+ }
+}
+
+void
+ModuleCCSession::notify(const std::string& group, const std::string& name,
+ const ConstElementPtr& params)
+{
+ const ElementPtr message(Element::createMap());
+ const ElementPtr notification(Element::createList());
+ notification->add(Element::create(name));
+ if (params) {
+ notification->add(params);
+ }
+ message->set(isc::cc::CC_PAYLOAD_NOTIFICATION, notification);
+ groupSendMsg(message, isc::cc::CC_GROUP_NOTIFICATION_PREFIX + group,
+ isc::cc::CC_INSTANCE_WILDCARD,
+ isc::cc::CC_TO_WILDCARD, false);
+}
+
}
}
diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h
index b4a44d0..c536861 100644
--- a/src/lib/config/ccsession.h
+++ b/src/lib/config/ccsession.h
@@ -20,6 +20,7 @@
#include <cc/session.h>
#include <cc/data.h>
+#include <cc/proto_defs.h>
#include <string>
#include <list>
@@ -146,6 +147,34 @@ public:
isc::Exception(file, line, what) {}
};
+/// \brief Exception thrown when there's a problem with the remote call.
+///
+/// This usually means either the command couldn't be called or the remote
+/// side sent an error as a response.
+class RPCError: public CCSessionError {
+public:
+ RPCError(const char* file, size_t line, const char* what, int rcode) :
+ CCSessionError(file, line, what),
+ rcode_(rcode)
+ {}
+
+ /// \brief The error code for the error.
+ int rcode() const {
+ return (rcode_);
+ }
+private:
+ const int rcode_;
+};
+
+/// \brief Specific version of RPCError for the case the recipient of command
+/// doesn't exist.
+class RPCRecipientMissing: public RPCError {
+public:
+ RPCRecipientMissing(const char* file, size_t line, const char* what) :
+ RPCError(file, line, what, isc::cc::CC_REPLY_NO_RECPT)
+ {}
+};
+
///
/// \brief This module keeps a connection to the command channel,
/// holds configuration information, and handles messages from
@@ -284,12 +313,12 @@ public:
* spec_is_filename is true (the default), then a
* filename is assumed, otherwise a module name.
* \param handler The handler functor called whenever there's a change.
- * Called once initally from this function. May be NULL
+ * Called once initially 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 handler should not throw, or it'll fall through 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.
@@ -335,31 +364,106 @@ public:
* \param group see isc::cc::Session::group_sendmsg()
* \param instance see isc::cc::Session::group_sendmsg()
* \param to see isc::cc::Session::group_sendmsg()
+ * \param want_answer see isc::cc::Session::group_sendmsg()
* \return see isc::cc::Session::group_sendmsg()
*/
int groupSendMsg(isc::data::ConstElementPtr msg,
std::string group,
- std::string instance = "*",
- std::string to = "*") {
- return (session_.group_sendmsg(msg, group, instance, to));
+ std::string instance = isc::cc::CC_INSTANCE_WILDCARD,
+ std::string to = isc::cc::CC_TO_WILDCARD,
+ bool want_answer = false) {
+ return (session_.group_sendmsg(msg, group, instance, to, want_answer));
};
- /**
- * Receive a message from the underlying CC session.
- * This has the same interface as isc::cc::Session::group_recvmsg()
- *
- * \param envelope see isc::cc::Session::group_recvmsg()
- * \param msg see isc::cc::Session::group_recvmsg()
- * \param nonblock see isc::cc::Session::group_recvmsg()
- * \param seq see isc::cc::Session::group_recvmsg()
- * \return see isc::cc::Session::group_recvmsg()
- */
+ /// \brief Receive a message from the underlying CC session.
+ /// This has the same interface as isc::cc::Session::group_recvmsg()
+ ///
+ /// NOTE: until #2804 is resolved this method wouldn't work except in
+ /// very limited cases; don't try to use it until then.
+ ///
+ /// \param envelope see isc::cc::Session::group_recvmsg()
+ /// \param msg see isc::cc::Session::group_recvmsg()
+ /// \param nonblock see isc::cc::Session::group_recvmsg()
+ /// \param seq see isc::cc::Session::group_recvmsg()
+ /// \return see isc::cc::Session::group_recvmsg()
bool groupRecvMsg(isc::data::ConstElementPtr& envelope,
isc::data::ConstElementPtr& msg,
bool nonblock = true,
int seq = -1) {
return (session_.group_recvmsg(envelope, msg, nonblock, seq));
- };
+ }
+
+ /// \brief Send a command message and wait for the answer.
+ ///
+ /// NOTE: until #2804 is resolved this method wouldn't work except in
+ /// very limited cases; don't try to use it until then.
+ ///
+ /// This is mostly a convenience wrapper around groupSendMsg
+ /// and groupRecvMsg, with some error handling.
+ ///
+ /// \param command Name of the command to call.
+ /// \param group Name of the remote module to call the command on.
+ /// \param instance Instance part of recipient address.
+ /// \param to The lname to send it to. Can be used to override the
+ /// addressing and use a direct recipient.
+ /// \param params Parameters for the command. Can be left NULL if
+ /// no parameters are needed.
+ /// \return Return value of the successfull remote call. It can be
+ /// NULL if the remote command is void function (returns nothing).
+ /// \throw RPCError if the call fails (for example when the other
+ /// side responds with error code).
+ /// \throw RPCRecipientMissing if the recipient doesn't exist.
+ /// \throw CCSessionError if some lower-level error happens (eg.
+ /// the response was malformed).
+ isc::data::ConstElementPtr rpcCall(const std::string& command,
+ const std::string& group,
+ const std::string& instance =
+ isc::cc::CC_INSTANCE_WILDCARD,
+ const std::string& to =
+ isc::cc::CC_TO_WILDCARD,
+ const isc::data::ConstElementPtr&
+ params =
+ isc::data::ConstElementPtr());
+
+ /// \brief Send a notification to subscribed users
+ ///
+ /// Send a notification message to all users subscribed to the given
+ /// notification group.
+ ///
+ /// This method does not not block.
+ ///
+ /// See docs/design/ipc-high.txt for details about notifications and
+ /// the format of messages sent.
+ ///
+ /// \throw CCSessionError for low-level communication errors.
+ /// \param notification_group This parameter (indirectly) signifies what
+ /// users should receive the notification. Only the users that
+ /// subscribed to notifications on the same group receive it.
+ /// \param name The name of the event to notify about (for example
+ /// `new_group_member`).
+ /// \param params Other parameters that describe the event. This might
+ /// be, for example, the ID of the new member and the name of the
+ /// group. This can be any data element, but it is common for it to be
+ /// map.
+ void notify(const std::string& notification_group,
+ const std::string& name,
+ const isc::data::ConstElementPtr& params =
+ isc::data::ConstElementPtr());
+
+ /// \brief Convenience version of rpcCall
+ ///
+ /// This is exactly the same as the previous version of rpcCall, except
+ /// that the instance and to parameters are at their default. This
+ /// allows to sending a command with parameters to a named module
+ /// without long typing of the parameters.
+ isc::data::ConstElementPtr rpcCall(const std::string& command,
+ const std::string& group,
+ const isc::data::ConstElementPtr&
+ params)
+ {
+ return rpcCall(command, group, isc::cc::CC_INSTANCE_WILDCARD,
+ isc::cc::CC_TO_WILDCARD, params);
+ }
/// \brief Forward declaration of internal data structure.
///
@@ -471,6 +575,50 @@ public:
/// \param id The id of request as returned by groupRecvMsgAsync.
void cancelAsyncRecv(const AsyncRecvRequestID& id);
+ /// \brief Subscribe to a group
+ ///
+ /// Wrapper around the CCSession::subscribe.
+ void subscribe(const std::string& group) {
+ session_.subscribe(group, isc::cc::CC_INSTANCE_WILDCARD);
+ }
+
+ /// \brief Unsubscribe from a group.
+ ///
+ /// Wrapper around the CCSession::unsubscribe.
+ void unsubscribe(const std::string& group) {
+ session_.unsubscribe(group, isc::cc::CC_INSTANCE_WILDCARD);
+ }
+
+ /// \brief Callback type for unhandled commands
+ ///
+ /// The type of functions that are not handled by the ModuleCCSession
+ /// because they are not aimed at the module.
+ ///
+ /// The parameters are:
+ /// - Name of the command.
+ /// - The module it was aimed for (may be empty).
+ /// - The parameters of the command.
+ typedef boost::function<void (const std::string&, const std::string&,
+ const isc::data::ConstElementPtr&)>
+ UnhandledCallback;
+
+ /// \brief Register a callback for messages sent to foreign modules.
+ ///
+ /// Usually, a command aimed at foreign module (or sent directly)
+ /// is discarded. By registering a callback here, these can be
+ /// examined.
+ ///
+ /// \note A callback overwrites the previous one set.
+ /// \todo This is a temporary, unclean, solution. A more generic
+ /// one needs to be designed. Also, a solution that is able
+ /// to send an answer would be great.
+ ///
+ /// \param callback The new callback to use. It may be an empty
+ /// function.
+ void setUnhandledCallback(const UnhandledCallback& callback) {
+ unhandled_callback_ = callback;
+ }
+
private:
ModuleSpec readModuleSpecification(const std::string& filename);
void startCheck();
@@ -520,6 +668,8 @@ private:
isc::data::ConstElementPtr new_config);
ModuleSpec fetchRemoteSpec(const std::string& module, bool is_filename);
+
+ UnhandledCallback unhandled_callback_;
};
/// \brief Default handler for logging config updates
diff --git a/src/lib/config/config_data.h b/src/lib/config/config_data.h
index e40600d..bcc97de 100644
--- a/src/lib/config/config_data.h
+++ b/src/lib/config/config_data.h
@@ -29,7 +29,7 @@ namespace config {
/// point to anything defined in the .spec file)
class DataNotFoundError : public isc::Exception {
public:
- DataNotFoundError(const char* file, size_t line, const std::string& what) :
+ DataNotFoundError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}
};
diff --git a/src/lib/config/config_messages.mes b/src/lib/config/config_messages.mes
index 552256c..6735b16 100644
--- a/src/lib/config/config_messages.mes
+++ b/src/lib/config/config_messages.mes
@@ -94,3 +94,7 @@ manager.
% CONFIG_OPEN_FAIL error opening %1: %2
There was an error opening the given file. The reason for the failure
is included in the message.
+
+% CONFIG_RPC_SEQ RPC call %1 to %2 with seq %3
+Debug message, saying there's a RPC call of given command to given module. It
+has internal sequence number as listed in the message.
diff --git a/src/lib/config/documentation.txt b/src/lib/config/documentation.txt
index 67fe427..a934130 100644
--- a/src/lib/config/documentation.txt
+++ b/src/lib/config/documentation.txt
@@ -18,7 +18,7 @@ In order for this to work, it is important that all modules have a way of dynami
Overview
--------
-Central to the configuration part is the Configuraion Manager b10-cfgmgr. The configuration manager loads any existing configuration, receives configuration updates from user interfaces, and notifies modules about configuration updates.
+Central to the configuration part is the Configuration Manager b10-cfgmgr. The configuration manager loads any existing configuration, receives configuration updates from user interfaces, and notifies modules about configuration updates.
UI <---UIAPI---> Configuration Manager <---ModuleAPI---> Module
<---ModuleAPI---> Module
diff --git a/src/lib/config/tests/ccsession_unittests.cc b/src/lib/config/tests/ccsession_unittests.cc
index 2a5e758..4a04412 100644
--- a/src/lib/config/tests/ccsession_unittests.cc
+++ b/src/lib/config/tests/ccsession_unittests.cc
@@ -53,11 +53,33 @@ protected:
// upon creation of a ModuleCCSession, the class
// sends its specification to the config manager.
- // it expects an ok answer back, so everytime we
+ // it expects an ok answer back, so every time we
// create a ModuleCCSession, we must set an initial
// ok answer.
session.getMessages()->add(createAnswer());
}
+ ConstElementPtr rpcCheck(const std::string& reply) {
+ ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL,
+ false, false);
+ // Prepare the answer beforehand, it'll block until it gets one
+ const ConstElementPtr reply_el(el(reply));
+ session.getMessages()->add(reply_el);
+ const ConstElementPtr
+ result(mccs.rpcCall("test", "Spec2",
+ el("{\"param1\": \"Param 1\","
+ "\"param2\": \"Param 2\"}")));
+ const ConstElementPtr
+ request(el("[\"Spec2\", \"*\", {"
+ " \"command\": [\"test\", {"
+ " \"param1\": \"Param 1\","
+ " \"param2\": \"Param 2\""
+ "}]}, -1, true]"));
+ // The 0th one is from the initialization, to ConfigManager.
+ // our is the 1st.
+ EXPECT_TRUE(request->equals(*session.getMsgQueue()->get(1))) <<
+ session.getMsgQueue()->get(1)->toWire();
+ return (result);
+ }
~CCSessionTest() {
isc::log::setRootLoggerName(root_name);
}
@@ -65,6 +87,87 @@ protected:
const std::string root_name;
};
+// Test we can send an RPC (command) and get an answer. The answer is success
+// in this case.
+TEST_F(CCSessionTest, rpcCallSuccess) {
+ const ConstElementPtr result =
+ rpcCheck("{\"result\": [0, {\"Hello\": \"a\"}]}");
+ EXPECT_TRUE(el("{\"Hello\": \"a\"}")->equals(*result));
+}
+
+// Test success of RPC, but the answer is empty (eg. a void function on the
+// remote side).
+TEST_F(CCSessionTest, rpcCallSuccessNone) {
+ EXPECT_FALSE(rpcCheck("{\"result\": [0]}"));
+}
+
+// Test it successfully raises CCSessionError if the answer is malformed.
+TEST_F(CCSessionTest, rpcCallMalformedAnswer) {
+ EXPECT_THROW(rpcCheck("[\"Nonsense\"]"), CCSessionError);
+}
+
+// Test it raises exception when the remote side reports an error
+TEST_F(CCSessionTest, rpcCallError) {
+ EXPECT_THROW(rpcCheck("{\"result\": [1, \"Error\"]}"), RPCError);
+}
+
+// Test it raises exception when the remote side doesn't exist
+TEST_F(CCSessionTest, rpcNoRecpt) {
+ EXPECT_THROW(rpcCheck("{\"result\": [-1, \"Error\"]}"),
+ RPCRecipientMissing);
+}
+
+// Test sending a notification
+TEST_F(CCSessionTest, notify) {
+ ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL, false,
+ false);
+ mccs.notify("group", "event", el("{\"param\": true}"));
+ const ConstElementPtr notification(el(
+ "["
+ " \"notifications/group\","
+ " \"*\","
+ " {"
+ " \"notification\": ["
+ " \"event\", {"
+ " \"param\": true"
+ " }"
+ " ]"
+ " },"
+ " -1"
+ "]"));
+ EXPECT_TRUE(notification->equals(*session.getMsgQueue()->get(1))) <<
+ session.getMsgQueue()->get(1)->toWire();
+}
+
+// Test sending a notification
+TEST_F(CCSessionTest, notifyNoParams) {
+ ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL, false,
+ false);
+ mccs.notify("group", "event");
+ const ConstElementPtr notification(el(
+ "["
+ " \"notifications/group\","
+ " \"*\","
+ " {"
+ " \"notification\": [\"event\"]"
+ " },"
+ " -1"
+ "]"));
+ EXPECT_TRUE(notification->equals(*session.getMsgQueue()->get(1))) <<
+ session.getMsgQueue()->get(1)->toWire();
+}
+
+// Try to subscribe and unsubscribe once again
+TEST_F(CCSessionTest, subscribe) {
+ ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL, false,
+ false);
+ EXPECT_FALSE(session.haveSubscription("A group", "*"));
+ mccs.subscribe("A group");
+ EXPECT_TRUE(session.haveSubscription("A group", "*"));
+ mccs.unsubscribe("A group");
+ EXPECT_FALSE(session.haveSubscription("A group", "*"));
+}
+
TEST_F(CCSessionTest, createAnswer) {
ConstElementPtr answer;
answer = createAnswer();
@@ -594,6 +697,16 @@ TEST_F(CCSessionTest, remoteConfig) {
}
}
+void
+callback(std::string* command, std::string* target, ConstElementPtr *params,
+ const std::string& command_real, const std::string& target_real,
+ const ConstElementPtr& params_real)
+{
+ *command = command_real;
+ *target = target_real;
+ *params = params_real;
+}
+
TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
// client will ask for config
session.getMessages()->add(createAnswer(0, el("{ }")));
@@ -629,6 +742,22 @@ TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
result = mccs.checkCommand();
EXPECT_EQ(0, session.getMsgQueue()->size());
EXPECT_EQ(0, result);
+
+ // Check that we can get the ignored commands by registering a callback
+ std::string command, target;
+ ConstElementPtr params;
+ mccs.setUnhandledCallback(boost::bind(&callback, &command, &target,
+ ¶ms, _1, _2, _3));
+ session.addMessage(el("{ \"command\": [ \"good_command\","
+ "{\"param\": true} ] }"), "Spec1", "*");
+ EXPECT_EQ(1, session.getMsgQueue()->size());
+ result = mccs.checkCommand();
+ EXPECT_EQ(0, session.getMsgQueue()->size());
+ EXPECT_EQ(0, result);
+
+ EXPECT_EQ("good_command", command);
+ EXPECT_EQ("Spec1", target);
+ EXPECT_TRUE(params && el("{\"param\": true}")->equals(*params));
}
TEST_F(CCSessionTest, initializationFail) {
diff --git a/src/lib/config/tests/fake_session.cc b/src/lib/config/tests/fake_session.cc
index 56a30d4..e6d569e 100644
--- a/src/lib/config/tests/fake_session.cc
+++ b/src/lib/config/tests/fake_session.cc
@@ -97,7 +97,7 @@ bool
FakeSession::recvmsg(ConstElementPtr& msg, bool nonblock, int) {
if (started_ && !nonblock) {
// This would schedule another read for length, leading to
- // corputed data
+ // corrupted data
isc_throw(DoubleRead, "Second read scheduled from recvmsg");
}
@@ -119,7 +119,7 @@ FakeSession::recvmsg(ConstElementPtr& env, ConstElementPtr& msg, bool nonblock,
{
if (started_ && !nonblock) {
// This would schedule another read for length, leading to
- // corputed data
+ // corrupted data
isc_throw(DoubleRead, "Second read scheduled from recvmsg");
}
@@ -183,12 +183,12 @@ FakeSession::unsubscribe(std::string group, std::string instance) {
int
FakeSession::group_sendmsg(ConstElementPtr msg, std::string group,
- std::string to, std::string, bool)
+ std::string to, std::string, bool want_answer)
{
if (throw_on_send_) {
isc_throw(Exception, "Throw on send is set in FakeSession");
}
- addMessage(msg, group, to);
+ addMessage(msg, group, to, -1, want_answer);
return (1);
}
@@ -231,13 +231,16 @@ FakeSession::getFirstMessage(std::string& group, std::string& to) const {
void
FakeSession::addMessage(ConstElementPtr msg, const std::string& group,
- const std::string& to, int seq)
+ const std::string& to, int seq, bool want_answer)
{
ElementPtr m_el = Element::createList();
m_el->add(Element::create(group));
m_el->add(Element::create(to));
m_el->add(msg);
m_el->add(Element::create(seq));
+ if (want_answer) {
+ m_el->add(Element::create(want_answer));
+ }
if (!msg_queue_) {
msg_queue_ = Element::createList();
}
diff --git a/src/lib/config/tests/fake_session.h b/src/lib/config/tests/fake_session.h
index 0dbaadb..8a564a8 100644
--- a/src/lib/config/tests/fake_session.h
+++ b/src/lib/config/tests/fake_session.h
@@ -75,7 +75,8 @@ public:
isc::data::ConstElementPtr getFirstMessage(std::string& group,
std::string& to) const;
void addMessage(isc::data::ConstElementPtr, const std::string& group,
- const std::string& to, int seq = -1);
+ const std::string& to, int seq = -1,
+ bool want_answer = false);
bool haveSubscription(const std::string& group,
const std::string& instance);
bool haveSubscription(const isc::data::ConstElementPtr group,
diff --git a/src/lib/cryptolink/cryptolink.h b/src/lib/cryptolink/cryptolink.h
index 859065b..408ed00 100644
--- a/src/lib/cryptolink/cryptolink.h
+++ b/src/lib/cryptolink/cryptolink.h
@@ -101,7 +101,7 @@ class CryptoLinkImpl;
/// There is only one way to access it, through getCryptoLink(), which
/// returns a reference to the initialized library. On the first call,
/// it will be initialized automatically. You can however initialize it
-/// manually through a call to the initalize(), before your first call
+/// manually through a call to initialize(), before your first call
/// to getCryptoLink. Any subsequent call to initialize() will be a
/// noop.
///
diff --git a/src/lib/datasrc/.gitignore b/src/lib/datasrc/.gitignore
index 206ddca..4b199ed 100644
--- a/src/lib/datasrc/.gitignore
+++ b/src/lib/datasrc/.gitignore
@@ -3,3 +3,5 @@
/datasrc_config.h
/datasrc_config.h.pre
/static.zone
+/sqlite3_datasrc_messages.cc
+/sqlite3_datasrc_messages.h
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index be79557..5422f7d 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -19,12 +19,12 @@ static.zone: static.zone.pre $(top_builddir)/config.h $(top_srcdir)/AUTHORS
$(SED) -e 's/\(.*\)/AUTHORS.BIND. 0 CH TXT "\1"/' $(top_srcdir)/AUTHORS >>$@
CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
+CLEANFILES += sqlite3_datasrc_messages.h sqlite3_datasrc_messages.cc
CLEANFILES += datasrc_config.h
CLEANFILES += static.zone
lib_LTLIBRARIES = libb10-datasrc.la
-libb10_datasrc_la_SOURCES = data_source.h
-libb10_datasrc_la_SOURCES += exceptions.h
+libb10_datasrc_la_SOURCES = exceptions.h
libb10_datasrc_la_SOURCES += zone.h zone_finder.h zone_finder.cc
libb10_datasrc_la_SOURCES += zone_finder_context.cc
libb10_datasrc_la_SOURCES += zone_iterator.h
@@ -38,25 +38,24 @@ libb10_datasrc_la_SOURCES += master_loader_callbacks.h
libb10_datasrc_la_SOURCES += master_loader_callbacks.cc
libb10_datasrc_la_SOURCES += rrset_collection_base.h rrset_collection_base.cc
libb10_datasrc_la_SOURCES += zone_loader.h zone_loader.cc
+libb10_datasrc_la_SOURCES += cache_config.h cache_config.cc
+libb10_datasrc_la_SOURCES += zone_table_accessor.h
+libb10_datasrc_la_SOURCES += zone_table_accessor_cache.h
+libb10_datasrc_la_SOURCES += zone_table_accessor_cache.cc
nodist_libb10_datasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
libb10_datasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
-pkglib_LTLIBRARIES = sqlite3_ds.la static_ds.la
+pkglib_LTLIBRARIES = sqlite3_ds.la
sqlite3_ds_la_SOURCES = sqlite3_accessor.h sqlite3_accessor.cc
sqlite3_ds_la_SOURCES += sqlite3_accessor_link.cc
+nodist_sqlite3_ds_la_SOURCES = sqlite3_datasrc_messages.h sqlite3_datasrc_messages.cc
sqlite3_ds_la_LDFLAGS = -module -avoid-version
sqlite3_ds_la_LDFLAGS += -no-undefined -version-info 1:0:0
sqlite3_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
sqlite3_ds_la_LIBADD += libb10-datasrc.la
sqlite3_ds_la_LIBADD += $(SQLITE_LIBS)
-static_ds_la_SOURCES = static_datasrc_link.cc
-static_ds_la_SOURCES += static_datasrc.h
-static_ds_la_LDFLAGS = -module -avoid-version
-static_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
-static_ds_la_LIBADD += libb10-datasrc.la
-
libb10_datasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
@@ -65,10 +64,13 @@ libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/datasrc/memory/libdatasrc_me
libb10_datasrc_la_LIBADD += $(SQLITE_LIBS)
BUILT_SOURCES = datasrc_config.h datasrc_messages.h datasrc_messages.cc
+BUILT_SOURCES += sqlite3_datasrc_messages.h sqlite3_datasrc_messages.cc
datasrc_messages.h datasrc_messages.cc: Makefile datasrc_messages.mes
$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/datasrc/datasrc_messages.mes
+sqlite3_datasrc_messages.h sqlite3_datasrc_messages.cc: Makefile sqlite3_datasrc_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/datasrc/sqlite3_datasrc_messages.mes
-EXTRA_DIST = datasrc_messages.mes static.zone.pre
+EXTRA_DIST = datasrc_messages.mes sqlite3_datasrc_messages.mes static.zone.pre
zonedir = $(pkgdatadir)
zone_DATA = static.zone
diff --git a/src/lib/datasrc/cache_config.cc b/src/lib/datasrc/cache_config.cc
new file mode 100644
index 0000000..ec3cfeb
--- /dev/null
+++ b/src/lib/datasrc/cache_config.cc
@@ -0,0 +1,198 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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/cache_config.h>
+#include <datasrc/client.h>
+#include <datasrc/memory/load_action.h>
+#include <datasrc/memory/zone_data_loader.h>
+
+#include <util/memory_segment.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/bind.hpp>
+
+#include <cassert>
+#include <map>
+#include <string>
+
+using namespace isc::data;
+
+namespace isc {
+namespace datasrc {
+namespace internal {
+
+namespace {
+bool
+getEnabledFromConf(const Element& conf) {
+ return (conf.contains("cache-enable") &&
+ conf.get("cache-enable")->boolValue());
+}
+
+std::string
+getSegmentTypeFromConf(const Element& conf) {
+ // If cache-type is not explicitly configured, use the default type.
+ // (Ideally we should retrieve the default from the spec).
+ if (!conf.contains("cache-type")) {
+ return ("local");
+ }
+ return (conf.get("cache-type")->stringValue());
+}
+}
+
+CacheConfig::CacheConfig(const std::string& datasrc_type,
+ const DataSourceClient* datasrc_client,
+ const Element& datasrc_conf,
+ bool allowed) :
+ enabled_(allowed && getEnabledFromConf(datasrc_conf)),
+ segment_type_(getSegmentTypeFromConf(datasrc_conf)),
+ datasrc_client_(datasrc_client)
+{
+ ConstElementPtr params = datasrc_conf.get("params");
+ if (!params) {
+ params.reset(new NullElement());
+ }
+ if (datasrc_type == "MasterFiles") {
+ if (datasrc_client_) {
+ isc_throw(InvalidParameter,
+ "data source client is given for MasterFiles");
+ }
+
+ if (!enabled_) {
+ isc_throw(CacheConfigError,
+ "The cache must be enabled for the MasterFiles type");
+ }
+
+ typedef std::map<std::string, ConstElementPtr> ZoneToFile;
+ const ZoneToFile& zone_to_file = params->mapValue();
+ ZoneToFile::const_iterator const it_end = zone_to_file.end();
+ for (ZoneToFile::const_iterator it = zone_to_file.begin();
+ it != it_end;
+ ++it)
+ {
+ zone_config_[dns::Name(it->first)] = it->second->stringValue();
+ }
+ } else {
+ if (!datasrc_client_) {
+ isc_throw(InvalidParameter,
+ "data source client is missing for data source type: "
+ << datasrc_type);
+ }
+ if (!enabled_) {
+ return;
+ }
+
+ if (!datasrc_conf.contains("cache-zones")) {
+ isc_throw(NotImplemented, "Auto-detection of zones "
+ "to cache is not yet implemented, supply "
+ "cache-zones parameter");
+ // TODO: Auto-detect list of all zones in the
+ // data source.
+ }
+
+ const ConstElementPtr zones = datasrc_conf.get("cache-zones");
+ for (size_t i = 0; i < zones->size(); ++i) {
+ const dns::Name zone_name(zones->get(i)->stringValue());
+ if (!zone_config_.insert(Zones::value_type(zone_name,
+ "")).second) {
+ isc_throw(CacheConfigError, "Duplicate cache zone: " <<
+ zone_name);
+ }
+ }
+ }
+}
+
+namespace {
+
+// We would like to use boost::bind for this. However, the loadZoneData takes
+// a reference, while we have a shared pointer to the iterator -- and we need
+// to keep it alive as long as the ZoneWriter is alive. Therefore we can't
+// really just dereference it and pass it, since it would get destroyed once
+// the getCachedZoneWriter would end. This class holds the shared pointer
+// alive, otherwise is mostly simple.
+//
+// It might be doable with nested boost::bind, but it would probably look
+// more awkward and complicated than this.
+class IteratorLoader {
+public:
+ IteratorLoader(const dns::RRClass& rrclass, const dns::Name& name,
+ const ZoneIteratorPtr& iterator) :
+ rrclass_(rrclass),
+ name_(name),
+ iterator_(iterator)
+ {}
+ memory::ZoneData* operator()(util::MemorySegment& segment) {
+ return (memory::loadZoneData(segment, rrclass_, name_, *iterator_));
+ }
+private:
+ const dns::RRClass rrclass_;
+ const dns::Name name_;
+ ZoneIteratorPtr iterator_;
+};
+
+// We can't use the loadZoneData function directly in boost::bind, since
+// it is overloaded and the compiler can't choose the correct version
+// reliably and fails. So we simply wrap it into an unique name.
+memory::ZoneData*
+loadZoneDataFromFile(util::MemorySegment& segment, const dns::RRClass& rrclass,
+ const dns::Name& name, const std::string& filename)
+{
+ return (memory::loadZoneData(segment, rrclass, name, filename));
+}
+
+} // unnamed namespace
+
+memory::LoadAction
+CacheConfig::getLoadAction(const dns::RRClass& rrclass,
+ const dns::Name& zone_name) const
+{
+ // First, check if the specified zone is configured to be cached.
+ Zones::const_iterator found = zone_config_.find(zone_name);
+ if (found == zone_config_.end()) {
+ return (memory::LoadAction());
+ }
+
+ if (!found->second.empty()) {
+ // This is "MasterFiles" data source.
+ return (boost::bind(loadZoneDataFromFile, _1, rrclass, zone_name,
+ found->second));
+ }
+
+ // Otherwise there must be a "source" data source (ensured by constructor)
+ assert(datasrc_client_);
+
+ // If the specified zone name does not exist in our client of the source,
+ // NoSuchZone is thrown, which is exactly the result what we
+ // want, so no need to handle it.
+ ZoneIteratorPtr iterator(datasrc_client_->getIterator(zone_name));
+ if (!iterator) {
+ // This shouldn't happen for a compliant implementation of
+ // DataSourceClient, but we'll protect ourselves from buggy
+ // implementations.
+ isc_throw(Unexpected, "getting LoadAction for " << zone_name
+ << "/" << rrclass << " resulted in Null zone iterator");
+ }
+
+ // Wrap the iterator into the correct functor (which keeps it alive as
+ // long as it is needed).
+ return (IteratorLoader(rrclass, zone_name, iterator));
+}
+
+} // namespace internal
+} // namespace datasrc
+} // namespace isc
diff --git a/src/lib/datasrc/cache_config.h b/src/lib/datasrc/cache_config.h
new file mode 100644
index 0000000..35b5ead
--- /dev/null
+++ b/src/lib/datasrc/cache_config.h
@@ -0,0 +1,219 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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_CACHE_CONFIG_H
+#define DATASRC_CACHE_CONFIG_H
+
+#include <exceptions/exceptions.h>
+
+#include <dns/dns_fwd.h>
+#include <cc/data.h>
+#include <datasrc/memory/load_action.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <map>
+#include <string>
+
+namespace isc {
+namespace datasrc {
+class DataSourceClient;
+
+namespace internal {
+
+/// \brief Exception thrown for configuration error related to in-memory cache.
+class CacheConfigError : public Exception {
+public:
+ CacheConfigError(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ {}
+};
+
+/// \brief Configuration for in-memory cache of a data source.
+///
+/// This class understands and validates the configuration parameters for
+/// \c DataSourceClient related to in-memory cache, and converts it to native,
+/// type-safe objects for the convenience of the user of this class.
+/// Specifically, it allows the user to get the underlying memory segment
+/// type for the cache as a string and to iterate over zone names to be
+/// cached in memory.
+///
+/// It also provides unified interface for getting \c memory::LoadAction
+/// object that can be used for loading zones, regardless of the underlying
+/// data source properties, i.e., whether it's special "MasterFiles" type
+/// or other generic data sources.
+///
+/// This class is publicly defined so it can be tested directly, but
+/// it's essentially private to the \c ConfigurableClientList class.
+/// It's therefore defined in an "internal" namespace, and isn't expected
+/// to be used by other classes or user applications. Likewise, this file
+/// is not expected to be installed with other publicly usable header files.
+///
+/// It's defined as noncopyable, simply because it's not expected to be
+/// copied in the intended usage for \c ConfigurableClientList. Prohibiting
+/// copies will help avoid unexpected disruption due to accidental copy and
+/// sharing internal resources as a result of that.
+class CacheConfig : boost::noncopyable {
+public:
+ /// \brief Constructor.
+ ///
+ /// It performs the following validation on the given configuration:
+ /// - For the "MasterFiles" type
+ /// - datasrc_client_ must not be provided (must be null); throws
+ /// InvalidParameter otherwise.
+ /// - cache must be enabled: "cache-enable" configuration item exists
+ /// and is true, and allowed parameter is true, too; throws
+ /// CacheConfigError otherwise.
+ /// - "params" configuration item must be provided and of a map type,
+ /// and each map entry maps a string to another string; throws
+ /// data::TypeError otherwise.
+ /// - the key string of each map entry must be a valid textual
+ /// representation of a domain name. Otherwise corresponding
+ /// exception from the dns::Name class will be thrown.
+ /// - For other types
+ /// - datasrc_client_ must be provided (must not be null); throws
+ /// InvalidParameter otherwise.
+ /// - (Unless cache is disabled) "cache-zones" configuration item must
+ /// exist and must be a list of strings; throws data::TypeError
+ /// otherwise.
+ /// - Each string value of cache-zones entries must be a valid textual
+ /// representation of a domain name. Otherwise corresponding
+ /// exception from the dns::Name class will be thrown.
+ /// - Names in the list must not have duplicates;
+ /// throws CacheConfigError otherwise.
+ ///
+ /// For other data source types than "MasterFiles", cache can be disabled.
+ /// In this case cache-zones configuration item is simply ignored, even
+ /// it contains an error that would otherwise trigger an exception.
+ ///
+ /// The specified set of zones (directly in "params" in case of
+ /// "MasterFile", and specified in "cache-zones" for others) can be
+ /// empty.
+ ///
+ /// This constructor also identifies the underlying memory segment type
+ /// used for the cache. It's given via the "cache-type" configuration
+ /// item if defined; otherwise it defaults to "local".
+ ///
+ /// \throw InvalidParameter Program error at the caller side rather than
+ /// in the configuration (see above)
+ /// \throw CacheConfigError There is a semantics error in the given
+ /// configuration (see above)
+ /// \throw data::TypeError Invalid type of data is found in the
+ /// configuration (see above)
+ /// \throw Other Exceptions from the dns::Name class when conversion from
+ /// text fails (see above)
+ ///
+ /// \param datasrc_type Type of data source. This must be the "type"
+ /// value of the data source configuration.
+ /// \param datasrc_client Client of the underlying data source for the
+ /// cache, if it's used; for MasterFiles types it's null.
+ /// \param datasrc_conf Configuration element for the data source.
+ /// This must be the value of, e.g., data_sources/classes/IN[0] of
+ /// BIND 10 configuration.
+ /// \param allowed Whether in-memory cache is allowed by the process.
+ /// This must be derived from the allow_cache parameter of
+ /// \c ConfigurableClientList::configure().
+ CacheConfig(const std::string& datasrc_type,
+ const DataSourceClient* datasrc_client,
+ const data::Element& datasrc_conf,
+ bool allowed);
+
+ /// \brief Return if the cache is enabled.
+ ///
+ /// The cache is considered enabled iff the "cache-enable" configuration
+ /// item (given on construction) existed and was set to true, and
+ /// the \c allowed parameter to the constructor was true.
+ ///
+ /// \throw None
+ bool isEnabled() const { return (enabled_); }
+
+ /// \brief Return the memory segment type to be used for the zone table.
+ ///
+ /// \throw None
+ const std::string& getSegmentType() const { return (segment_type_); }
+
+ /// \brief Return a \c LoadAction functor to load zone data into memory.
+ ///
+ /// This method returns an appropriate \c LoadAction functor that can be
+ /// passed to a \c memory::ZoneWriter object to load data of the specified
+ /// zone into memory. The source of the zone data differs depending on
+ /// the cache configuration (either a master file or another data source),
+ /// but this method hides the details and works as a unified interface
+ /// for the caller.
+ ///
+ /// If the specified zone is not configured to be cached, it returns an
+ /// empty functor (which can be evaluated to be \c false as a boolean).
+ /// It doesn't throw an exception in this case because the expected caller
+ /// of this method would handle such a case internally.
+ ///
+ /// \throw NoSuchZone The specified zone doesn't exist in the
+ /// underlying data source storing the original data to be cached.
+ /// \throw DataSourceError Other, unexpected but possible error happens
+ /// in the underlying data source.
+ /// \throw Unexpected Unexpected error happens in the underlying data
+ /// source. This shouldn't happen as long as the data source
+ /// implementation meets the public API requirement.
+ ///
+ /// \param rrclass The RR class of the zone
+ /// \param zone_name The origin name of the zone
+ /// \return A \c LoadAction functor to load zone data or an empty functor
+ /// (see above).
+ memory::LoadAction getLoadAction(const dns::RRClass& rrlcass,
+ const dns::Name& zone_name) const;
+
+ /// \brief Read only iterator type over configured cached zones.
+ ///
+ /// \note This initial version exposes the internal data structure (i.e.
+ /// map from name to string) through this public iterator type for
+ /// simplicity. In terms of data encapsulation it's better to introduce
+ /// a custom iterator type that only goes through the conceptual list
+ /// of zone names, but due to the limitation of the expected user of this
+ /// class that would probably be premature generalization. In future,
+ /// we might want to allow getting the list of zones directly from the
+ /// underlying data source. If and when that happens we should introduce
+ /// a custom type. In any case, the user of this class should only
+ /// use the typedef, not the original map iterator. It should also
+ /// use this iterator as a forward iterator (datasource-based iterator
+ /// wouldn't be able to be bidirectional), and it shouldn't use the
+ /// value of the map entry (a string, specifying a path to master file
+ /// for MasterFiles data source).
+ typedef std::map<dns::Name, std::string>::const_iterator ConstZoneIterator;
+
+ /// \brief Return the beginning of cached zones in the form of iterator.
+ ConstZoneIterator begin() const { return (zone_config_.begin()); }
+
+ /// \brief Return the end of cached zones in the form of iterator.
+ ConstZoneIterator end() const { return (zone_config_.end()); }
+
+private:
+ const bool enabled_; // if the use of in-memory zone table is enabled
+ const std::string segment_type_;
+ // client of underlying data source, will be NULL for MasterFile datasrc
+ const DataSourceClient* datasrc_client_;
+
+ // Maps each of zones to be cached to a string. For "MasterFiles" type
+ // of data source, the string is a path to the master zone file; for
+ // others it's an empty string.
+ typedef std::map<dns::Name, std::string> Zones;
+ Zones zone_config_;
+};
+}
+}
+}
+
+#endif // DATASRC_CACHE_CONFIG_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h
index 9c5d262..4145700 100644
--- a/src/lib/datasrc/client.h
+++ b/src/lib/datasrc/client.h
@@ -132,8 +132,8 @@ public:
/// \brief A helper structure to represent the search result of
/// \c find().
///
- /// This is a straightforward pair of the result code and a share pointer
- /// to the found zone to represent the result of \c find().
+ /// This is a straightforward tuple of the result code/flags and a shared
+ /// pointer to the found zone to represent the result of \c find().
/// We use this in order to avoid overloading the return value for both
/// the result code ("success" or "not found") and the found object,
/// i.e., avoid using \c NULL to mean "not found", etc.
@@ -146,10 +146,13 @@ public:
/// variables.
struct FindResult {
FindResult(result::Result param_code,
- const ZoneFinderPtr param_zone_finder) :
- code(param_code), zone_finder(param_zone_finder)
+ const ZoneFinderPtr param_zone_finder,
+ result::ResultFlags param_flags = result::FLAGS_DEFAULT) :
+ code(param_code), flags(param_flags),
+ zone_finder(param_zone_finder)
{}
const result::Result code;
+ const result::ResultFlags flags;
const ZoneFinderPtr zone_finder;
};
@@ -184,8 +187,12 @@ public:
/// - \c result::PARTIALMATCH: A zone whose origin is a
/// super domain of \c name is found (but there is no exact match)
/// - \c result::NOTFOUND: For all other cases.
+ /// - \c flags: usually FLAGS_DEFAULT, but if the zone data are not
+ /// available (possibly because an error was detected at load time)
+ /// the ZONE_EMPTY flag is set.
/// - \c zone_finder: Pointer to a \c ZoneFinder object for the found zone
- /// if one is found; otherwise \c NULL.
+ /// if one is found and is not empty (flags doesn't have ZONE_EMPTY);
+ /// otherwise \c NULL.
///
/// A specific derived version of this method may throw an exception.
/// This interface does not specify which exceptions can happen (at least
@@ -201,12 +208,9 @@ public:
/// This allows for traversing the whole zone. The returned object can
/// provide the RRsets one by one.
///
- /// This throws DataSourceError when the zone does not exist in the
- /// datasource.
- ///
/// The default implementation throws isc::NotImplemented. This allows
/// for easy and fast deployment of minimal custom data sources, where
- /// the user/implementator doesn't have to care about anything else but
+ /// the user/implementer doesn't have to care about anything else but
/// the actual queries. Also, in some cases, it isn't possible to traverse
/// the zone from logic point of view (eg. dynamically generated zone
/// data).
@@ -214,6 +218,14 @@ public:
/// It is not fixed if a concrete implementation of this method can throw
/// anything else.
///
+ /// \throw NoSuchZone the zone does not exist in the datasource.
+ /// \throw Others Possibly implementation specific exceptions (it is
+ /// not fixed if a concrete implementation of this method can throw
+ /// anything else.)
+ /// \throw EmptyZone the zone is supposed to exist in the data source,
+ /// but its content is not available. This generally means there's an
+ /// error in the content.
+ ///
/// \param name The name of zone apex to be traversed. It doesn't do
/// nearest match as findZone.
/// \param separate_rrs If true, the iterator will return each RR as a
diff --git a/src/lib/datasrc/client_list.cc b/src/lib/datasrc/client_list.cc
index 0750fb6..531821c 100644
--- a/src/lib/datasrc/client_list.cc
+++ b/src/lib/datasrc/client_list.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -13,22 +13,26 @@
// PERFORMANCE OF THIS SOFTWARE.
-#include "client_list.h"
-#include "exceptions.h"
-#include "client.h"
-#include "factory.h"
-#include "memory/memory_client.h"
-#include "memory/zone_table_segment.h"
-#include "memory/zone_writer.h"
-#include "memory/zone_data_loader.h"
-#include "memory/zone_data_updater.h"
-#include "logger.h"
+#include <datasrc/client_list.h>
+#include <datasrc/exceptions.h>
+#include <datasrc/client.h>
+#include <datasrc/factory.h>
+#include <datasrc/cache_config.h>
+#include <datasrc/memory/memory_client.h>
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/zone_writer.h>
+#include <datasrc/memory/zone_data_loader.h>
+#include <datasrc/memory/zone_data_updater.h>
+#include <datasrc/logger.h>
+#include <datasrc/zone_table_accessor_cache.h>
#include <dns/masterload.h>
#include <util/memory_segment_local.h>
#include <memory>
+#include <set>
#include <boost/foreach.hpp>
#include <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
using namespace isc::data;
using namespace isc::dns;
@@ -46,25 +50,18 @@ namespace datasrc {
ConfigurableClientList::DataSourceInfo::DataSourceInfo(
DataSourceClient* data_src_client,
- const DataSourceClientContainerPtr& container, bool has_cache,
- const RRClass& rrclass, const shared_ptr<ZoneTableSegment>& segment) :
+ const DataSourceClientContainerPtr& container,
+ boost::shared_ptr<internal::CacheConfig> cache_conf,
+ const RRClass& rrclass, const string& name) :
data_src_client_(data_src_client),
- container_(container)
+ container_(container),
+ name_(name),
+ cache_conf_(cache_conf)
{
- if (has_cache) {
- cache_.reset(new InMemoryClient(segment, rrclass));
- ztable_segment_ = segment;
- }
-}
-
-ConfigurableClientList::DataSourceInfo::DataSourceInfo(
- const RRClass& rrclass, const shared_ptr<ZoneTableSegment>& segment,
- bool has_cache) :
- data_src_client_(NULL)
-{
- if (has_cache) {
- cache_.reset(new InMemoryClient(segment, rrclass));
- ztable_segment_ = segment;
+ if (cache_conf_ && cache_conf_->isEnabled()) {
+ ztable_segment_.reset(ZoneTableSegment::create(
+ rrclass, cache_conf_->getSegmentType()));
+ cache_.reset(new InMemoryClient(ztable_segment_, rrclass));
}
}
@@ -90,118 +87,103 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
size_t i(0); // Outside of the try to be able to access it in the catch
try {
vector<DataSourceInfo> new_data_sources;
- shared_ptr<ZoneTableSegment> ztable_segment(
- ZoneTableSegment::create(*config, rrclass_));
+ set<string> used_names;
for (; i < config->size(); ++i) {
// Extract the parameters
const ConstElementPtr dconf(config->get(i));
- const ConstElementPtr typeElem(dconf->get("type"));
- if (typeElem == ConstElementPtr()) {
+ const ConstElementPtr type_elem(dconf->get("type"));
+ if (type_elem == ConstElementPtr()) {
isc_throw(ConfigurationError, "Missing the type option in "
"data source no " << i);
}
- const string type(typeElem->stringValue());
- ConstElementPtr paramConf(dconf->get("params"));
- if (paramConf == ConstElementPtr()) {
- paramConf.reset(new NullElement());
+ const string type(type_elem->stringValue());
+ ConstElementPtr param_conf(dconf->get("params"));
+ if (param_conf == ConstElementPtr()) {
+ param_conf.reset(new NullElement());
}
- const bool want_cache(allow_cache &&
- dconf->contains("cache-enable") &&
- dconf->get("cache-enable")->boolValue());
-
- if (type == "MasterFiles") {
- // In case the cache is not allowed, we just skip the master
- // files (at least for now)
- if (!allow_cache) {
- // We're not going to load these zones. Issue warnings about it.
- const map<string, ConstElementPtr>
- zones_files(paramConf->mapValue());
- for (map<string, ConstElementPtr>::const_iterator
- it(zones_files.begin()); it != zones_files.end();
- ++it) {
- LOG_WARN(logger, DATASRC_LIST_NOT_CACHED).
- arg(it->first).arg(rrclass_);
- }
- continue;
- }
- if (!want_cache) {
- isc_throw(ConfigurationError, "The cache must be enabled "
- "for the MasterFiles type");
- }
- new_data_sources.push_back(DataSourceInfo(rrclass_,
- ztable_segment,
- true));
- } else {
- // Ask the factory to create the data source for us
- const DataSourcePair ds(this->getDataSourceClient(type,
- paramConf));
- // And put it into the vector
- new_data_sources.push_back(DataSourceInfo(ds.first, ds.second,
- want_cache, rrclass_,
- ztable_segment));
+ // Get the name (either explicit, or guess)
+ const ConstElementPtr name_elem(dconf->get("name"));
+ const string datasrc_name =
+ name_elem ? name_elem->stringValue() : type;
+ if (!used_names.insert(datasrc_name).second) {
+ isc_throw(ConfigurationError, "Duplicate name in client list: "
+ << datasrc_name);
}
- if (want_cache) {
- if (!dconf->contains("cache-zones") && type != "MasterFiles") {
- isc_throw(isc::NotImplemented, "Auto-detection of zones "
- "to cache is not yet implemented, supply "
- "cache-zones parameter");
- // TODO: Auto-detect list of all zones in the
- // data source.
- }
+ DataSourcePair dsrc_pair;
+ try {
+ // Create a client for the underling data source via
+ // factory. If it's our internal type of data source,
+ // this is essentially no-op. In the latter case, it's
+ // of no use unless cache is allowed; we simply skip
+ // building it in that case.
+ dsrc_pair = getDataSourceClient(type, param_conf);
+ } catch (const DataSourceLibraryError& ex) {
+ LOG_ERROR(logger, DATASRC_LIBRARY_ERROR).
+ arg(datasrc_name).arg(rrclass_).arg(ex.what());
+ continue;
+ }
- // List the zones we are loading
- vector<string> zones_origins;
- if (type == "MasterFiles") {
- const map<string, ConstElementPtr>
- zones_files(paramConf->mapValue());
- for (map<string, ConstElementPtr>::const_iterator
- it(zones_files.begin()); it != zones_files.end();
- ++it) {
- zones_origins.push_back(it->first);
- }
- } else {
- const ConstElementPtr zones(dconf->get("cache-zones"));
- for (size_t i(0); i < zones->size(); ++i) {
- zones_origins.push_back(zones->get(i)->stringValue());
- }
- }
+ if (!allow_cache && !dsrc_pair.first) {
+ LOG_WARN(logger, DATASRC_LIST_NOT_CACHED).
+ arg(datasrc_name).arg(rrclass_);
+ continue;
+ }
+
+ // Build in-memory cache configuration, and create a set of
+ // related objects including the in-memory zone table for the
+ // cache.
+ boost::shared_ptr<internal::CacheConfig> cache_conf(
+ new internal::CacheConfig(type, dsrc_pair.first, *dconf,
+ allow_cache));
+ new_data_sources.push_back(DataSourceInfo(dsrc_pair.first,
+ dsrc_pair.second,
+ cache_conf, rrclass_,
+ datasrc_name));
+
+ // If cache is disabled, or the zone table segment is not (yet)
+ // writable, we are done for this data source.
+ // Otherwise load zones into the in-memory cache.
+ if (!cache_conf->isEnabled()) {
+ continue;
+ }
+ memory::ZoneTableSegment& zt_segment =
+ *new_data_sources.back().ztable_segment_;
+ if (!zt_segment.isWritable()) {
+ LOG_DEBUG(logger, DBGLVL_TRACE_BASIC,
+ DATASRC_LIST_CACHE_PENDING).arg(datasrc_name);
+ continue;
+ }
- const shared_ptr<InMemoryClient>
- cache(new_data_sources.back().cache_);
- const DataSourceClient* const
- client(new_data_sources.back().data_src_client_);
- for (vector<string>::const_iterator it(zones_origins.begin());
- it != zones_origins.end(); ++it) {
- const Name origin(*it);
- if (type == "MasterFiles") {
- try {
- cache->load(origin,
- paramConf->get(*it)->stringValue());
- } catch (const ZoneLoaderException& e) {
- LOG_ERROR(logger, DATASRC_LOAD_FROM_FILE_ERROR)
- .arg(origin).arg(e.what());
- }
- } else {
- ZoneIteratorPtr iterator;
- try {
- iterator = client->getIterator(origin);
- } catch (const DataSourceError&) {
- isc_throw(ConfigurationError, "Unable to "
- "cache non-existent zone "
- << origin);
- }
- if (!iterator) {
- isc_throw(isc::Unexpected, "Got NULL iterator "
- "for zone " << origin);
- }
- try {
- cache->load(origin, *iterator);
- } catch (const ZoneLoaderException& e) {
- LOG_ERROR(logger, DATASRC_LOAD_FROM_ITERATOR_ERROR)
- .arg(origin).arg(e.what());
- }
+ internal::CacheConfig::ConstZoneIterator end_of_zones =
+ cache_conf->end();
+ for (internal::CacheConfig::ConstZoneIterator zone_it =
+ cache_conf->begin();
+ zone_it != end_of_zones;
+ ++zone_it)
+ {
+ const Name& zname = zone_it->first;
+ try {
+ const memory::LoadAction load_action =
+ cache_conf->getLoadAction(rrclass_, zname);
+ // in this loop this should be always true
+ assert(load_action);
+ // For the initial load, we'll let the writer handle
+ // loading error and install an empty zone in the table.
+ memory::ZoneWriter writer(zt_segment, load_action, zname,
+ rrclass_, true);
+
+ std::string error_msg;
+ writer.load(&error_msg);
+ if (!error_msg.empty()) {
+ LOG_ERROR(logger, DATASRC_LOAD_ZONE_ERROR).arg(zname).
+ arg(rrclass_).arg(datasrc_name).arg(error_msg);
}
+ writer.install();
+ writer.cleanup();
+ } catch (const NoSuchZone&) {
+ LOG_ERROR(logger, DATASRC_CACHE_ZONE_NOTFOUND).
+ arg(zname).arg(rrclass_).arg(datasrc_name);
}
}
}
@@ -214,6 +196,9 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
} catch (const TypeError& te) {
isc_throw(ConfigurationError, "Malformed configuration at data source "
"no. " << i << ": " << te.what());
+ } catch (const internal::CacheConfigError& ex) {
+ // convert to the "public" exception type.
+ isc_throw(ConfigurationError, ex.what());
}
}
@@ -345,108 +330,74 @@ ConfigurableClientList::findInternal(MutableResult& candidate,
// and the need_updater parameter is true, get the zone there.
}
-// We still provide this method for backward compatibility. But to not have
-// duplicate code, it is a thin wrapper around getCachedZoneWriter only.
-ConfigurableClientList::ReloadResult
-ConfigurableClientList::reload(const Name& name) {
- const ZoneWriterPair result(getCachedZoneWriter(name));
- if (result.first != ZONE_SUCCESS) {
- return (result.first);
- }
-
- assert(result.second);
- result.second->load();
- result.second->install();
- result.second->cleanup();
-
- return (ZONE_SUCCESS);
-}
-
-namespace {
-
-// We would like to use boost::bind for this. However, the loadZoneData takes
-// a reference, while we have a shared pointer to the iterator -- and we need
-// to keep it alive as long as the ZoneWriter is alive. Therefore we can't
-// really just dereference it and pass it, since it would get destroyed once
-// the getCachedZoneWriter would end. This class holds the shared pointer
-// alive, otherwise is mostly simple.
-//
-// It might be doable with nested boost::bind, but it would probably look
-// more awkward and complicated than this.
-class IteratorLoader {
-public:
- IteratorLoader(const RRClass& rrclass, const Name& name,
- const ZoneIteratorPtr& iterator) :
- rrclass_(rrclass),
- name_(name),
- iterator_(iterator)
- {}
- memory::ZoneData* operator()(util::MemorySegment& segment) {
- return (memory::loadZoneData(segment, rrclass_, name_, *iterator_));
- }
-private:
- const RRClass rrclass_;
- const Name name_;
- ZoneIteratorPtr iterator_;
-};
-
-// We can't use the loadZoneData function directly in boost::bind, since
-// it is overloaded and the compiler can't choose the correct version
-// reliably and fails. So we simply wrap it into an unique name.
-memory::ZoneData*
-loadZoneDataFromFile(util::MemorySegment& segment, const RRClass& rrclass,
- const Name& name, const string& filename)
+bool
+ConfigurableClientList::resetMemorySegment
+ (const std::string& datasrc_name,
+ ZoneTableSegment::MemorySegmentOpenMode mode,
+ ConstElementPtr config_params)
{
- return (memory::loadZoneData(segment, rrclass, name, filename));
-}
-
+ BOOST_FOREACH(DataSourceInfo& info, data_sources_) {
+ if (info.name_ == datasrc_name) {
+ ZoneTableSegment& segment = *info.ztable_segment_;
+ segment.reset(mode, config_params);
+ return true;
+ }
+ }
+ return false;
}
ConfigurableClientList::ZoneWriterPair
-ConfigurableClientList::getCachedZoneWriter(const Name& name) {
+ConfigurableClientList::getCachedZoneWriter(const Name& name,
+ bool catch_load_error,
+ const std::string& datasrc_name)
+{
if (!allow_cache_) {
return (ZoneWriterPair(CACHE_DISABLED, ZoneWriterPtr()));
}
- // Try to find the correct zone.
- MutableResult result;
- findInternal(result, name, true, true);
- if (!result.finder) {
- return (ZoneWriterPair(ZONE_NOT_FOUND, ZoneWriterPtr()));
- }
- // Try to get the in-memory cache for the zone. If there's none,
- // we can't provide the result.
- if (!result.info->cache_) {
- return (ZoneWriterPair(ZONE_NOT_CACHED, ZoneWriterPtr()));
- }
- memory::LoadAction load_action;
- DataSourceClient* client(result.info->data_src_client_);
- if (client != NULL) {
- // Now finally provide the writer.
- // If it does not exist in client,
- // DataSourceError is thrown, which is exactly the result what we
- // want, so no need to handle it.
- ZoneIteratorPtr iterator(client->getIterator(name));
- if (!iterator) {
- isc_throw(isc::Unexpected, "Null iterator from " << name);
+
+ // Find the data source from which the zone to be loaded into memory.
+ // Then get the appropriate load action and create a zone writer.
+ BOOST_FOREACH(const DataSourceInfo& info, data_sources_) {
+ if (!datasrc_name.empty() && datasrc_name != info.name_) {
+ continue;
}
- // And wrap the iterator into the correct functor (which
- // keeps it alive as long as it is needed).
- load_action = IteratorLoader(rrclass_, name, iterator);
- } else {
- // The MasterFiles special case
- const string filename(result.info->cache_->getFileName(name));
- if (filename.empty()) {
- isc_throw(isc::Unexpected, "Confused about missing both filename "
- "and data source");
+ // If there's an underlying "real" data source and it doesn't contain
+ // the given name, obviously we cannot load it. If a specific data
+ // source is given by the name, search should stop here.
+ if (info.data_src_client_ &&
+ info.data_src_client_->findZone(name).code != result::SUCCESS) {
+ if (!datasrc_name.empty()) {
+ return (ZoneWriterPair(ZONE_NOT_FOUND, ZoneWriterPtr()));
+ }
+ continue;
+ }
+ // If the corresponding zone table segment is not (yet) writable,
+ // we cannot load at this time.
+ if (info.ztable_segment_ && !info.ztable_segment_->isWritable()) {
+ return (ZoneWriterPair(CACHE_NOT_WRITABLE, ZoneWriterPtr()));
+ }
+ // Note that getCacheConfig() must return non NULL in this module
+ // (only tests could set it to a bogus value).
+ const memory::LoadAction load_action =
+ info.getCacheConfig()->getLoadAction(rrclass_, name);
+ if (!load_action) {
+ return (ZoneWriterPair(ZONE_NOT_CACHED, ZoneWriterPtr()));
}
- // boost::bind is enough here.
- load_action = boost::bind(loadZoneDataFromFile, _1, rrclass_, name,
- filename);
+ return (ZoneWriterPair(ZONE_SUCCESS,
+ ZoneWriterPtr(
+ new memory::ZoneWriter(
+ *info.ztable_segment_,
+ load_action, name, rrclass_,
+ catch_load_error))));
}
- return (ZoneWriterPair(ZONE_SUCCESS,
- ZoneWriterPtr(
- result.info->ztable_segment_->
- getZoneWriter(load_action, name, rrclass_))));
+
+ // We can't find the specified zone. If a specific data source was
+ // given, this means the given name of data source doesn't exist, so
+ // we report it so.
+ if (!datasrc_name.empty()) {
+ return (ZoneWriterPair(DATASRC_NOT_FOUND, ZoneWriterPtr()));
+ }
+ return (ZoneWriterPair(ZONE_NOT_FOUND, ZoneWriterPtr()));
}
// NOTE: This function is not tested, it would be complicated. However, the
@@ -457,10 +408,56 @@ ConfigurableClientList::getDataSourceClient(const string& type,
const ConstElementPtr&
configuration)
{
+ if (type == "MasterFiles") {
+ return (DataSourcePair(0, DataSourceClientContainerPtr()));
+ }
+
DataSourceClientContainerPtr
container(new DataSourceClientContainer(type, configuration));
return (DataSourcePair(&container->getInstance(), container));
}
+vector<DataSourceStatus>
+ConfigurableClientList::getStatus() const {
+ vector<DataSourceStatus> result;
+ BOOST_FOREACH(const DataSourceInfo& info, data_sources_) {
+ if (info.ztable_segment_) {
+ result.push_back(DataSourceStatus(
+ info.name_,
+ (info.ztable_segment_->isUsable() ?
+ SEGMENT_INUSE : SEGMENT_WAITING),
+ info.ztable_segment_->getImplType()));
+ } else {
+ result.push_back(DataSourceStatus(info.name_));
+ }
+ }
+ return (result);
+}
+
+ConstZoneTableAccessorPtr
+ConfigurableClientList::getZoneTableAccessor(const std::string& datasrc_name,
+ bool use_cache) const
+{
+ if (!use_cache) {
+ isc_throw(isc::NotImplemented,
+ "getZoneTableAccessor only implemented for cache");
+ }
+
+ // Find the matching data source
+ BOOST_FOREACH(const DataSourceInfo& info, data_sources_) {
+ if (!datasrc_name.empty() && datasrc_name != info.name_) {
+ continue;
+ }
+
+ const internal::CacheConfig* config(info.getCacheConfig());
+ // If caching is disabled for the named data source, this will
+ // return an accessor to an effectivley empty table.
+ return (ConstZoneTableAccessorPtr
+ (new internal::ZoneTableAccessorCache(*config)));
+ }
+
+ return (ConstZoneTableAccessorPtr());
+}
+
}
}
diff --git a/src/lib/datasrc/client_list.h b/src/lib/datasrc/client_list.h
index d1a35b5..77c2fd5 100644
--- a/src/lib/datasrc/client_list.h
+++ b/src/lib/datasrc/client_list.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -21,7 +21,8 @@
#include <dns/rrclass.h>
#include <cc/data.h>
#include <exceptions/exceptions.h>
-#include "memory/zone_table_segment.h"
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/zone_table_accessor.h>
#include <vector>
#include <boost/shared_ptr.hpp>
@@ -46,6 +47,93 @@ class InMemoryClient;
class ZoneWriter;
}
+namespace internal {
+class CacheConfig;
+}
+
+/// \brief Segment status of the cache
+///
+/// Describes the status in which the memory segment for the in-memory cache of
+// /given data source is.
+enum MemorySegmentState {
+ /// \brief No segment used for this data source.
+ ///
+ /// This is usually a result of the cache being disabled.
+ SEGMENT_UNUSED,
+
+ /// \brief It is a mapped segment and we wait for information how to map
+ /// it.
+ SEGMENT_WAITING,
+
+ /// \brief The segment is ready to be used.
+ SEGMENT_INUSE
+};
+
+/// \brief Status of one data source.
+///
+/// This indicates the status a data soure is in. It is used with segment
+/// and cache management, to discover the data sources that need external
+/// mapping or local loading.
+///
+/// In future, it may be extended for other purposes, such as performing an
+/// operation on named data source.
+class DataSourceStatus {
+public:
+ /// \brief Constructor
+ ///
+ /// Sets initial values. If you want to use \c SEGMENT_UNUSED as the
+ /// state, please use the other constructor.
+ DataSourceStatus(const std::string& name, MemorySegmentState state,
+ const std::string& type) :
+ name_(name),
+ type_(type),
+ state_(state)
+ {
+ assert (state != SEGMENT_UNUSED);
+ assert (!type.empty());
+ }
+
+ /// \brief Constructor
+ ///
+ /// Sets initial values. The state is set as \c SEGMENT_UNUSED and
+ /// the type is effectively unspecified.
+ DataSourceStatus(const std::string& name) :
+ name_(name),
+ type_(""),
+ state_(SEGMENT_UNUSED)
+ {}
+
+ /// \brief Get the segment state
+ MemorySegmentState getSegmentState() const {
+ return (state_);
+ }
+
+ /// \brief Get the segment type
+ ///
+ /// \note Specific values of the type are only meaningful for the
+ /// corresponding memory segment implementation and modules that
+ /// directly manage the segments. Other normal applications should
+ /// treat them as opaque identifiers.
+ ///
+ /// \throw isc::InvalidOperation if called and state is SEGMENT_UNUSED.
+ const std::string& getSegmentType() const {
+ if (getSegmentState() == SEGMENT_UNUSED) {
+ isc_throw(isc::InvalidOperation,
+ "No segment used, no type therefore.");
+ }
+ return (type_);
+ }
+
+ /// \brief Get the name.
+ const std::string& getName() const {
+ return (name_);
+ }
+private:
+ std::string name_;
+ std::string type_;
+ MemorySegmentState state_;
+};
+
/// \brief The list of data source clients.
///
/// The purpose of this class is to hold several data source clients and search
@@ -100,7 +188,7 @@ public:
/// \brief Negative answer constructor.
///
- /// This conscructs a result for negative answer. Both pointers are
+ /// This constructs a result for negative answer. Both pointers are
/// NULL, and exact_match_ is false.
FindResult() :
dsrc_client_(NULL),
@@ -157,20 +245,29 @@ public:
/// of the searched name is needed. Therefore, the call would look like:
///
/// \code FindResult result(list->find(queried_name));
- /// FindResult result(list->find(queried_name));
- /// if (result.datasrc_) {
- /// createTheAnswer(result.finder_);
+ /// if (result.dsrc_client_) {
+ /// if (result.finder_) {
+ /// createTheAnswer(result.finder_);
+ /// } else { // broken zone, return SERVFAIL
+ /// createErrorMessage(Rcode.SERVFAIL());
+ /// }
/// } else {
/// createNotAuthAnswer();
/// } \endcode
///
+ /// Note that it is possible that \c finder_ is NULL while \c datasrc_
+ /// is not. This happens if the zone is configured to be served from
+ /// the data source but it is broken in some sense and doesn't hold any
+ /// zone data, e.g., when the zone file has an error or the secondary
+ /// zone hasn't been transferred yet. The caller needs to expect the case
+ /// and handle it accordingly.
+ ///
/// The other scenario is manipulating zone data (XfrOut, XfrIn, DDNS,
/// ...). In this case, the finder itself is not so important. However,
/// we need an exact match (if we want to manipulate zone data, we must
/// know exactly, which zone we are about to manipulate). Then the call
///
/// \code FindResult result(list->find(zone_name, true, false));
- /// FindResult result(list->find(zone_name, true, false));
/// if (result.datasrc_) {
/// ZoneUpdaterPtr updater(result.datasrc_->getUpdater(zone_name);
/// ...
@@ -198,6 +295,19 @@ public:
virtual FindResult find(const dns::Name& zone,
bool want_exact_match = false,
bool want_finder = true) const = 0;
+
+ /// \brief Creates a ZoneTableAccessor object for the specified data
+ /// source.
+ ///
+ /// \param datasrc_name If not empty, the name of the data source.
+ /// \param use_cache If true, create a zone table for in-memory cache.
+ /// \throw NotImplemented if this method is not implemented.
+ /// \return A pointer to the accessor, or NULL if the requested data
+ /// source is not found.
+ virtual ConstZoneTableAccessorPtr
+ getZoneTableAccessor(const std::string& datasrc_name,
+ bool use_cache) const = 0;
+
};
/// \brief Shared pointer to the list.
@@ -205,8 +315,8 @@ typedef boost::shared_ptr<ClientList> ClientListPtr;
/// \brief Shared const pointer to the list.
typedef boost::shared_ptr<const ClientList> ConstClientListPtr;
-/// \Concrete implementation of the ClientList, which is constructed based on
-/// configuration.
+/// \brief Concrete implementation of the ClientList, which is constructed
+/// based on configuration.
///
/// This is the implementation which is expected to be used in the servers.
/// However, it is expected most of the code will use it as the ClientList,
@@ -265,58 +375,78 @@ public:
return (configuration_);
}
- /// \brief Result of the reload() method.
- enum ReloadResult {
- CACHE_DISABLED, ///< The cache is not enabled in this list.
- ZONE_NOT_CACHED, ///< Zone is served directly, not from cache.
- ZONE_NOT_FOUND, ///< Zone does not exist or not cached.
- ZONE_SUCCESS ///< The zone was successfully reloaded or
- /// the writer provided.
- };
-
- /// \brief Reloads a cached zone.
+ /// \brief Resets the zone table segment for a datasource with a new
+ /// memory segment.
///
- /// This method finds a zone which is loaded into a cache and reloads it.
- /// This may be used to renew the cache when the underlying data source
- /// changes.
+ /// See documentation of \c ZoneTableSegment interface
+ /// implementations (such as \c ZoneTableSegmentMapped) for the
+ /// syntax of \c config_params.
///
- /// \param zone The origin of the zone to reload.
- /// \return A status if the command worked.
- /// \throw DataSourceError or anything else that the data source
- /// containing the zone might throw is propagated.
- /// \throw DataSourceError if something unexpected happens, like when
- /// the original data source no longer contains the cached zone.
- ReloadResult reload(const dns::Name& zone);
+ /// \param datasrc_name The name of the data source whose segment to reset
+ /// \param mode The open mode for the new memory segment
+ /// \param config_params The configuration for the new memory segment.
+ /// \return If the data source was found and reset.
+ bool resetMemorySegment
+ (const std::string& datasrc_name,
+ memory::ZoneTableSegment::MemorySegmentOpenMode mode,
+ isc::data::ConstElementPtr config_params);
-private:
/// \brief Convenience type shortcut
typedef boost::shared_ptr<memory::ZoneWriter> ZoneWriterPtr;
-public:
+
+ /// \brief Codes indicating in-memory cache status for a given zone name.
+ ///
+ /// This is used as a result of the getCachedZoneWriter() method.
+ enum CacheStatus {
+ CACHE_DISABLED, ///< The cache is not enabled in this list.
+ ZONE_NOT_CACHED, ///< Zone is not to be cached (including the case
+ /// where caching is disabled for the specific
+ /// data source).
+ ZONE_NOT_FOUND, ///< Zone does not exist in this list.
+ CACHE_NOT_WRITABLE, ///< The cache is not writable (and zones can't
+ /// be loaded)
+ DATASRC_NOT_FOUND, ///< Specific data source for load is specified
+ /// but it's not in the list
+ ZONE_SUCCESS ///< Zone to be cached is successfully found and
+ /// is ready to be loaded
+ };
/// \brief Return value of getCachedZoneWriter()
///
/// A pair containing status and the zone writer, for the
/// getCachedZoneWriter() method.
- typedef std::pair<ReloadResult, ZoneWriterPtr> ZoneWriterPair;
+ typedef std::pair<CacheStatus, ZoneWriterPtr> ZoneWriterPair;
- /// \brief Return a zone writer that can be used to reload a zone.
+ /// \brief Return a zone writer that can be used to (re)load a zone.
+ ///
+ /// By default this method identifies the first data source in the list
+ /// that should serve the zone of the given name, and returns a ZoneWriter
+ /// object that can be used to load the content of the zone, in a specific
+ /// way for that data source.
///
- /// This looks up a cached copy of zone and returns the ZoneWriter
- /// that can be used to reload the content of the zone. This can
- /// be used instead of reload() -- reload() works synchronously, which
- /// is not what is needed every time.
+ /// If the optional \c datasrc_name parameter is provided with a non empty
+ /// string, this method only tries to load the specified zone into or with
+ /// the data source which has the given name, regardless where in the list
+ /// that data source is placed. Even if the given name of zone doesn't
+ /// exist in the data source, other data sources are not searched and
+ /// this method simply returns ZONE_NOT_FOUND in the first element
+ /// of the pair.
///
- /// \param zone The origin of the zone to reload.
- /// \return The result has two parts. The first one is a status describing
+ /// \param zone The origin of the zone to load.
+ /// \param catch_load_errors Whether to make the zone writer catch
+ /// load errors (see \c ZoneWriter constructor documentation).
+ /// \param datasrc_name If not empty, the name of the data source
+ /// to be used for loading the zone (see above).
+ /// \return The result has two parts. The first one is a status indicating
/// if it worked or not (and in case it didn't, also why). If the
/// status is ZONE_SUCCESS, the second part contains a shared pointer
/// to the writer. If the status is anything else, the second part is
/// NULL.
/// \throw DataSourceError or anything else that the data source
/// containing the zone might throw is propagated.
- /// \throw DataSourceError if something unexpected happens, like when
- /// the original data source no longer contains the cached zone.
- ZoneWriterPair getCachedZoneWriter(const dns::Name& zone);
+ ZoneWriterPair getCachedZoneWriter(const dns::Name& zone,
+ bool catch_load_error,
+ const std::string& datasrc_name = "");
/// \brief Implementation of the ClientList::find.
virtual FindResult find(const dns::Name& zone,
@@ -324,21 +454,12 @@ public:
bool want_finder = true) const;
/// \brief This holds one data source client and corresponding information.
- ///
- /// \todo The content yet to be defined.
struct DataSourceInfo {
- // Plays a role of default constructor too (for vector)
- DataSourceInfo(const dns::RRClass& rrclass,
- const boost::shared_ptr
- <isc::datasrc::memory::ZoneTableSegment>&
- ztable_segment,
- bool has_cache = false);
DataSourceInfo(DataSourceClient* data_src_client,
const DataSourceClientContainerPtr& container,
- bool has_cache, const dns::RRClass& rrclass,
- const boost::shared_ptr
- <isc::datasrc::memory::ZoneTableSegment>&
- ztable_segment);
+ boost::shared_ptr<internal::CacheConfig> cache_conf,
+ const dns::RRClass& rrclass,
+ const std::string& name);
DataSourceClient* data_src_client_;
DataSourceClientContainerPtr container_;
@@ -350,6 +471,15 @@ public:
const DataSourceClient* getCacheClient() const;
boost::shared_ptr<memory::InMemoryClient> cache_;
boost::shared_ptr<memory::ZoneTableSegment> ztable_segment_;
+ std::string name_;
+
+ // cache_conf_ can be accessed only from this read-only getter,
+ // to protect its integrity as much as possible.
+ const internal::CacheConfig* getCacheConfig() const {
+ return (cache_conf_.get());
+ }
+ private:
+ boost::shared_ptr<internal::CacheConfig> cache_conf_;
};
/// \brief The collection of data sources.
@@ -369,6 +499,13 @@ public:
/// Also, derived classes could want to create the data source clients
/// in a different way, though inheriting this class is not recommended.
///
+ /// Some types of data sources can be internal to the \c ClientList
+ /// implementation and do not require a corresponding dynamic module
+ /// loaded via \c DataSourceClientContainer. In such a case, this method
+ /// simply returns a pair of null pointers. It will help the caller reduce
+ /// type dependent processing. Currently, "MasterFiles" is considered to
+ /// be this type of data sources.
+ ///
/// The parameters are the same as of the constructor.
/// \return Pair containing both the data source client and the container.
/// The container might be NULL in the derived class, it is
@@ -379,7 +516,16 @@ public:
virtual DataSourcePair getDataSourceClient(const std::string& type,
const data::ConstElementPtr&
configuration);
-public:
+
+ /// \brief Get status information of all internal data sources.
+ ///
+ /// Get a DataSourceStatus for current state of each data source client
+ /// in this list.
+ ///
+ /// This may throw standard exceptions, such as std::bad_alloc. Otherwise,
+ /// it is exception free.
+ std::vector<DataSourceStatus> getStatus() const;
+
/// \brief Access to the data source clients.
///
/// It can be used to examine the loaded list of data sources clients
@@ -387,6 +533,22 @@ public:
/// it might be, so it is just made public (there's no real reason to
/// hide it).
const DataSources& getDataSources() const { return (data_sources_); }
+
+ /// \brief Creates a ZoneTableAccessor object for the specified data
+ /// source.
+ ///
+ /// \param datasrc_name If not empty, the name of the data source
+ /// \param use_cache If true, create a zone table for in-memory cache.
+ /// \note At present, the only concrete implementation of
+ /// ZoneTableAccessor is ZoneTableAccessorCache, so \c use_cache must be
+ /// true.
+ /// \throw NotImplemented if \c use_cache is false.
+ /// \return A pointer to the accessor, or NULL if the requested data
+ /// source is not found.
+ ConstZoneTableAccessorPtr
+ getZoneTableAccessor(const std::string& datasrc_name,
+ bool use_cache) const;
+
private:
struct MutableResult;
/// \brief Internal implementation of find.
diff --git a/src/lib/datasrc/data_source.h b/src/lib/datasrc/data_source.h
deleted file mode 100644
index bf5a7d7..0000000
--- a/src/lib/datasrc/data_source.h
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef DATA_SOURCE_H
-#define DATA_SOURCE_H
-
-#include <stdint.h>
-
-#include <vector>
-
-#include <boost/shared_ptr.hpp>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/name.h>
-#include <dns/rrclass.h>
-#include <cc/data.h>
-
-namespace isc {
-
-namespace dns {
-class Name;
-class RRType;
-class RRset;
-class RRsetList;
-}
-
-namespace datasrc {
-
-/// This exception represents Backend-independent errors relating to
-/// data source operations.
-class DataSourceError : public Exception {
-public:
- DataSourceError(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-/// \brief No such serial number when obtaining difference iterator
-///
-/// Thrown if either the zone/start serial number or zone/end serial number
-/// combination does not exist in the differences table. (Note that this
-/// includes the case where the differences table contains no records related
-/// to that zone.)
-class NoSuchSerial : public DataSourceError {
-public:
- NoSuchSerial(const char* file, size_t line, const char* what) :
- DataSourceError(file, line, what) {}
-};
-
-}
-}
-
-#endif
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index f6d8252..9c85054 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -17,7 +17,7 @@
#include <vector>
#include <datasrc/database.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/zone_iterator.h>
#include <datasrc/rrset_collection_base.h>
@@ -30,7 +30,7 @@
#include <dns/rdataclass.h>
#include <dns/nsec3hash.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/logger.h>
#include <boost/foreach.hpp>
@@ -1233,7 +1233,7 @@ public:
const pair<bool, int> zone(accessor_->getZone(zone_name.toText()));
if (!zone.first) {
// No such zone, can't continue
- isc_throw(DataSourceError, "Zone " + zone_name.toText() +
+ isc_throw(NoSuchZone, "Zone " + zone_name.toText() +
" can not be iterated, because it doesn't exist "
"in this data source");
}
@@ -1644,17 +1644,23 @@ DatabaseUpdater::addRRset(const AbstractRRset& rrset) {
{ cvtr.getName(), cvtr.getType(), cvtr.getTTL(), rdata_txt };
accessor_->addRecordDiff(zone_id_, serial_.getValue(),
Accessor::DIFF_ADD, journal);
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ADDDIFF).
+ arg(cvtr.getName()).arg(cvtr.getType()).arg(rdata_txt);
}
if (nsec3_type) {
const string nsec3_columns[Accessor::ADD_NSEC3_COLUMN_COUNT] =
{ cvtr.getNSEC3Name(), cvtr.getTTL(), cvtr.getType(),
rdata_txt };
accessor_->addNSEC3RecordToZone(nsec3_columns);
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ADDNSEC3).
+ arg(cvtr.getNSEC3Name()).arg(rdata_txt);
} else {
const string columns[Accessor::ADD_COLUMN_COUNT] =
{ cvtr.getName(), cvtr.getRevName(), cvtr.getTTL(),
cvtr.getType(), sigtype, rdata_txt };
accessor_->addRecordToZone(columns);
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_ADDRR).
+ arg(cvtr.getName()).arg(cvtr.getType()).arg(rdata_txt);
}
}
}
@@ -1698,14 +1704,22 @@ DatabaseUpdater::deleteRRset(const AbstractRRset& rrset) {
{ cvtr.getName(), cvtr.getType(), cvtr.getTTL(), rdata_txt };
accessor_->addRecordDiff(zone_id_, serial_.getValue(),
Accessor::DIFF_DELETE, journal);
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_DELETEDIFF).
+ arg(cvtr.getName()).arg(cvtr.getType()).arg(rdata_txt);
}
- const string params[Accessor::DEL_PARAM_COUNT] =
- { nsec3_type ? cvtr.getNSEC3Name() : cvtr.getName(),
- cvtr.getType(), rdata_txt };
if (nsec3_type) {
+ const string params[Accessor::DEL_NSEC3_PARAM_COUNT] =
+ { cvtr.getNSEC3Name(), cvtr.getType(), rdata_txt };
accessor_->deleteNSEC3RecordInZone(params);
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_DELETENSEC3).
+ arg(cvtr.getNSEC3Name()).arg(rdata_txt);
} else {
+ const string params[Accessor::DEL_PARAM_COUNT] =
+ { cvtr.getName(), cvtr.getType(), rdata_txt,
+ cvtr.getRevName() };
accessor_->deleteRecordInZone(params);
+ LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_DATABASE_DELETERR).
+ arg(cvtr.getName()).arg(cvtr.getType()).arg(rdata_txt);
}
}
}
diff --git a/src/lib/datasrc/database.h b/src/lib/datasrc/database.h
index 3302adf..6e675e2 100644
--- a/src/lib/datasrc/database.h
+++ b/src/lib/datasrc/database.h
@@ -24,7 +24,7 @@
#include <dns/rrset.h>
#include <dns/rrtype.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/client.h>
#include <datasrc/zone.h>
#include <datasrc/logger.h>
@@ -116,18 +116,42 @@ public:
ADD_NSEC3_COLUMN_COUNT = 4 ///< Number of columns
};
- /// \brief Definitions of the fields to be passed to deleteRecordInZone()
- /// and deleteNSEC3RecordInZone()
+ /// \brief Definitions of the fields to be passed to deleteRecordInZone().
///
/// Each derived implementation of deleteRecordInZone() should expect
/// the "params" array to be filled with the values as described in this
/// enumeration, in this order.
+ ///
+ /// DEL_RNAME is included in case the reversed form is more convenient
+ /// for the underlying implementation to identify the record to be
+ /// deleted (reversed names are generally easier to sort, which may help
+ /// perform the search faster). It's up to the underlying implementation
+ /// which one (or both) it uses for the search. DEL_NAME and DEL_RNAME
+ /// are mutually convertible with the understanding of DNS names, and
+ /// in that sense redundant. But both are provided so the underlying
+ /// implementation doesn't have to deal with DNS level concepts.
enum DeleteRecordParams {
- DEL_NAME = 0, ///< The owner name of the record (a domain name)
- ///< or the hash label for deleteNSEC3RecordInZone()
+ DEL_NAME = 0, ///< The owner name of the record (a domain name).
DEL_TYPE = 1, ///< The RRType of the record (A/NS/TXT etc.)
DEL_RDATA = 2, ///< Full text representation of the record's RDATA
- DEL_PARAM_COUNT = 3 ///< Number of parameters
+ DEL_RNAME = 3, ///< As DEL_NAME, but with the labels of domain name
+ ///< in reverse order (eg. org.example.).
+ DEL_PARAM_COUNT = 4 ///< Number of parameters
+ };
+
+ /// \brief Definitions of the fields to be passed to
+ /// deleteNSEC3RecordInZone().
+ ///
+ /// Each derived implementation of deleteNSEC3RecordInZone() should expect
+ /// the "params" array to be filled with the values as described in this
+ /// enumeration, in this order.
+ enum DeleteNSEC3RecordParams {
+ DEL_NSEC3_HASH = 0, ///< The hash (1st) label of the owren name,
+ ///< excluding the dot character.
+ DEL_NSEC3_TYPE = 1, ///< The type of RR. Either RRSIG or NSEC3.
+ DEL_NSEC3_RDATA = 2, ///< Full text representation of the record's
+ ///< RDATA. Must match the one in the database.
+ DEL_NSEC3_PARAM_COUNT = 3 ///< Number of parameters.
};
/// \brief Operation mode when adding a record diff.
@@ -161,7 +185,7 @@ public:
///
/// This method looks up a zone for the given name in the database. It
/// should match only exact zone name (eg. name is equal to the zone's
- /// apex), as the DatabaseClient will loop trough the labels itself and
+ /// apex), as the DatabaseClient will loop through the labels itself and
/// find the most suitable zone.
///
/// It is not specified if and what implementation of this method may
@@ -228,7 +252,7 @@ public:
public:
/// \brief Destructor
///
- /// Virtual destructor, so any descendand class is destroyed correctly.
+ /// Virtual destructor, so any descendant class is destroyed correctly.
virtual ~IteratorContext() {}
/// \brief Function to provide next resource record
@@ -314,7 +338,7 @@ public:
/// \note In case there are multiple NSEC3 chains and they collide
/// (unlikely, but it can happen), this can return multiple NSEC3
/// records.
- /// \exception any Since any implementaion can be used, the caller should
+ /// \exception any Since any implementation can be used, the caller should
/// expect any exception to be thrown.
/// \exception isc::NotImplemented in case the database does not support
/// NSEC3
@@ -576,11 +600,8 @@ public:
/// \c addRecordToZone() and \c addNSEC3RecordToZone(), and the same
/// notes apply to this method.
///
- /// This method uses the same set of parameters to specify the record
- /// to be deleted as \c deleteRecordInZone(), but the \c DEL_NAME column
- /// is expected to only store the hash label of the owner name.
- /// This is the same as \c ADD_NSEC3_HASH column for
- /// \c addNSEC3RecordToZone().
+ /// This method uses the \c DeleteNSEC3RecordParams enum to specify the
+ /// values.
///
/// \exception DataSourceError Invalid call without starting a transaction,
/// or other internal database error.
@@ -590,7 +611,7 @@ public:
/// \param params An array of strings that defines a record to be deleted
/// from the NSEC3 namespace of the zone.
virtual void deleteNSEC3RecordInZone(
- const std::string (¶ms)[DEL_PARAM_COUNT]) = 0;
+ const std::string (¶ms)[DEL_NSEC3_PARAM_COUNT]) = 0;
/// \brief Start a general transaction.
///
@@ -868,7 +889,7 @@ public:
/// database.
///
/// Application should not come directly in contact with this class
- /// (it should handle it trough generic ZoneFinder pointer), therefore
+ /// (it should handle it through generic ZoneFinder pointer), therefore
/// it could be completely hidden in the .cc file. But it is provided
/// to allow testing and for rare cases when a database needs slightly
/// different handling, so it can be subclassed.
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index 2235df8..f9a76ed 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -70,6 +70,15 @@ 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.
+% DATASRC_CACHE_ZONE_NOTFOUND Zone %1/%2 not found on data source '%3' to cache
+During data source configuration, a zone is to be loaded (cached) in
+to memory from the underlying data source, but the zone is not found
+in the data source. This particular zone was not loaded, but data
+source configuration continues, possibly loading other zones into
+memory. This is basically some kind of configuration or operation
+error: either the name is incorrect in the configuration, or the zone
+has been removed from the data source without updating the configuration.
+
% DATASRC_CHECK_ERROR post-load check of zone %1/%2 failed: %3
The zone was loaded into the data source successfully, but the content fails
basic sanity checks. See the message if you want to know what exactly is wrong
@@ -83,11 +92,37 @@ with the content. The problem does not stop the new version from being used
but it should still be checked and fixed. See the message to know what exactly
is wrong with the data.
+% DATASRC_DATABASE_ADDDIFF updated diff table for add: %1 %2 %3
+Debug message. A difference record for adding a record to the zone is being
+appended to the difference table. The name, type and rdata of the record is
+logged.
+
+% DATASRC_DATABASE_ADDNSEC3 added NSEC3 RR: %1 %2
+Debug message. A new NSEC3 record is added to the table. The hash and the rdata
+is logged.
+
+% DATASRC_DATABASE_ADDRR added RR: %1 %2 %3
+Debug message. A new resource record is added to the table. The name, type and
+rdata is logged.
+
% DATASRC_DATABASE_COVER_NSEC_UNSUPPORTED %1 doesn't support DNSSEC when asked for NSEC data covering %2
The datasource tried to provide an NSEC proof that the named domain does not
exist, but the database backend doesn't support DNSSEC. No proof is included
in the answer as a result.
+% DATASRC_DATABASE_DELETEDIFF updated diff table for delete: %1 %2 %3
+Debug message. A difference record for removing a record from the zone is being
+appended to the difference table. The name, type and rdata of the record is
+logged.
+
+% DATASRC_DATABASE_DELETENSEC3 deleted NSEC3 RR: %1 %2
+Debug message. An NSEC3 record is removed from the table. The name, type and
+rdata is logged.
+
+% DATASRC_DATABASE_DELETERR deleted RR: %1 %2 %3
+Debug message. A resource record is removed from the table. The name, type and
+rdata is logged.
+
% DATASRC_DATABASE_FINDNSEC3 Looking for NSEC3 for %1 in %2 mode
Debug information. A search in an database data source for NSEC3 that
matches or covers the given name is being started.
@@ -321,23 +356,33 @@ not contain RRs the requested type. AN NXRRSET indication is returned.
A debug message indicating that a query for the given name and RR type is being
processed.
-% DATASRC_LIST_NOT_CACHED zone %1/%2 not cached, cache disabled globally. Will not be available.
-The process disabled caching of RR data completely. However, the given zone
-is provided as a master file and it can be served from memory cache only.
-Therefore, the zone will not be available for this process. If this is
-a problem, you should move the zone to some database backend (sqlite3, for
-example) and use it from there.
-
-% DATASRC_LOAD_FROM_FILE_ERROR Error loading zone %1: %2
-An error was found in the zone data when it was being loaded from a
-file. The zone was not loaded. The specific error is shown in the
+% DATASRC_LIST_CACHE_PENDING in-memory cache for data source '%1' is not yet writable, pending load
+While (re)configuring data source clients, zone data of the shown data
+source cannot be loaded to in-memory cache at that point because the
+cache is not yet ready for writing. This can happen for shared-memory
+type of cache, in which case the cache will be reset later, either
+by a higher level application or by a command from other module.
+
+% DATASRC_LIST_NOT_CACHED zones in data source %1 for class %2 not cached, cache disabled globally. Will not be available.
+The process disabled caching of RR data completely. However, this data source
+is provided from a master file and it can be served from memory cache only.
+Therefore, the entire data source will not be available for this process. If
+this is a problem, you should configure the zones of that data source to some
+database backend (sqlite3, for example) and use it from there.
+
+% DATASRC_LIBRARY_ERROR failure loading %1 datasource library for class %2: %3
+There was a problem loading the dynamic library for a data source. This
+backend is hence not available, and any data sources that use this
+backend will not be available.
+
+% DATASRC_LOAD_ZONE_ERROR Error loading zone %1/%2 on data source '%3': %4
+During data source configuration, an error was found in the zone data
+when it was being loaded in to memory on the shown data source. This
+particular zone was not loaded, but data source configuration
+continues, possibly loading other zones into memory. Subsequent lookups
+will treat this zone as broken. The specific error is shown in the
message, and should be addressed.
-% DATASRC_LOAD_FROM_ITERATOR_ERROR Error loading zone %1: %2
-An error was found in the zone data when it was being loaded from
-another data source. The zone was not loaded. The specific error is
-shown in the message, and should be addressed.
-
% DATASRC_MASTER_LOAD_ERROR %1:%2: Zone '%3/%4' contains error: %5
There's an error in the given master file. The zone won't be loaded for
this reason. Parsing might follow, so you might get further errors and
@@ -550,131 +595,6 @@ given domain or record. The code is 1 for error and 2 for not implemented.
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.
-% DATASRC_SQLITE_CLOSE closing SQLite database
-Debug information. The SQLite data source is closing the database file.
-
-% DATASRC_SQLITE_COMPATIBLE_VERSION database schema V%1.%2 not up to date (expecting V%3.%4) but is compatible
-The version of the SQLite3 database schema used to hold the zone data
-is not the latest one - the current version of BIND 10 was written
-with a later schema version in mind. However, the database is
-compatible with the current version of BIND 10, and BIND 10 will run
-without any problems.
-
-Consult the release notes for your version of BIND 10. Depending on
-the changes made to the database schema, it is possible that improved
-performance could result if the database were upgraded.
-
-% DATASRC_SQLITE_CONNCLOSE Closing sqlite database
-The database file is no longer needed and is being closed.
-
-% DATASRC_SQLITE_CONNOPEN Opening sqlite database file '%1'
-The database file is being opened so it can start providing data.
-
-% DATASRC_SQLITE_CREATE SQLite data source created
-Debug information. An instance of SQLite data source is being created.
-
-% DATASRC_SQLITE_DESTROY SQLite data source destroyed
-Debug information. An instance of SQLite data source is being destroyed.
-
-% DATASRC_SQLITE_DROPCONN SQLite3Database is being deinitialized
-The object around a database connection is being destroyed.
-
-% DATASRC_SQLITE_ENCLOSURE looking for zone containing '%1'
-Debug information. The SQLite data source is trying to identify which zone
-should hold this domain.
-
-% DATASRC_SQLITE_ENCLOSURE_NOT_FOUND no zone contains '%1'
-Debug information. The last SQLITE_ENCLOSURE query was unsuccessful; there's
-no such zone in our data.
-
-% DATASRC_SQLITE_FIND looking for RRset '%1/%2'
-Debug information. The SQLite data source is looking up a resource record
-set.
-
-% DATASRC_SQLITE_FINDADDRS looking for A/AAAA addresses for '%1'
-Debug information. The data source is looking up the addresses for given
-domain name.
-
-% DATASRC_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.
-
-% DATASRC_SQLITE_FINDEXACT looking for exact RRset '%1/%2'
-Debug information. The SQLite data source is looking up an exact resource
-record.
-
-% DATASRC_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.
-
-% DATASRC_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.
-
-% DATASRC_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.
-
-% DATASRC_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.
-
-% DATASRC_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.
-
-% DATASRC_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.
-
-% DATASRC_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.
-
-% DATASRC_SQLITE_INCOMPATIBLE_VERSION database schema V%1.%2 incompatible with version (V%3.%4) expected
-The version of the SQLite3 database schema used to hold the zone data
-is incompatible with the version expected by BIND 10. As a result,
-BIND 10 is unable to run using the database file as the data source.
-
-The database should be updated using the means described in the BIND
-10 documentation.
-
-% DATASRC_SQLITE_NEWCONN SQLite3Database is being initialized
-A wrapper object to hold database connection is being initialized.
-
-% DATASRC_SQLITE_OPEN opening SQLite database '%1'
-Debug information. The SQLite data source is loading an SQLite database in
-the provided file.
-
-% DATASRC_SQLITE_PREVIOUS looking for name previous to '%1'
-This is a debug message. The name given was not found, so the program
-is searching for the next name higher up the hierarchy (e.g. if
-www.example.com were queried for and not found, the software searches
-for the "previous" name, example.com).
-
-% DATASRC_SQLITE_PREVIOUS_NO_ZONE no zone containing '%1'
-The name given was not found, so the program is searching for the next
-name higher up the hierarchy (e.g. if www.example.com were queried
-for and not found, the software searches for the "previous" name,
-example.com). However, this name is not contained in any zone in the
-data source. This is an error since it indicates a problem in the earlier
-processing of the query.
-
-% DATASRC_SQLITE_SETUP setting up new SQLite3 database in '%1'
-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. If this is indeed the first run of
-BIND 10, it is to be expected and completely harmless. If you just configured
-a data source to point to an existing file and you see this, you may have
-misspelled the file name.
-
-% DATASRC_SQLITE_SETUP_OLD_API setting up new 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. This is similar to DATASRC_SQLITE_SETUP
-message, but it is logged from the old API. You should never see it, since the
-API is deprecated.
-
% DATASRC_STATIC_CLASS_NOT_CH static data source can handle CH class only
An error message indicating that a query requesting a RR for a class other
that CH was sent to the static data source (which only handles CH queries).
diff --git a/src/lib/datasrc/exceptions.h b/src/lib/datasrc/exceptions.h
index 749b955..f3bc06f 100644
--- a/src/lib/datasrc/exceptions.h
+++ b/src/lib/datasrc/exceptions.h
@@ -20,6 +20,53 @@
namespace isc {
namespace datasrc {
+/// \brief Top level exception related to data source.
+///
+/// This exception is the most generic form of exception for errors or
+/// unexpected events that can happen in the data source module. In general,
+/// if an application needs to catch these conditions explicitly, it should
+/// catch more specific exceptions derived from this class; the severity
+/// of the conditions will vary very much, and such an application would
+/// normally like to behave differently depending on the severity.
+class DataSourceError : public Exception {
+public:
+ DataSourceError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief No such serial number when obtaining difference iterator
+///
+/// Thrown if either the zone/start serial number or zone/end serial number
+/// combination does not exist in the differences table. (Note that this
+/// includes the case where the differences table contains no records related
+/// to that zone.)
+class NoSuchSerial : public DataSourceError {
+public:
+ NoSuchSerial(const char* file, size_t line, const char* what) :
+ DataSourceError(file, line, what) {}
+};
+
+/// \brief A specified zone does not exist in the specified data source.
+///
+/// This exception is thrown from methods that take a zone name and perform
+/// some action regarding that zone on the corresponding data source.
+class NoSuchZone : public DataSourceError {
+public:
+ NoSuchZone(const char* file, size_t line, const char* what) :
+ DataSourceError(file, line, what) {}
+};
+
+/// \brief An error indicating a zone is recognized but its content is not
+/// available.
+///
+/// This generally indicates a condition that there's an error in the zone
+/// content and it's not successfully loaded.
+class EmptyZone : public DataSourceError {
+public:
+ EmptyZone(const char* file, size_t line, const char* what) :
+ DataSourceError(file, line, what) {}
+};
+
/// Base class for a number of exceptions that are thrown while working
/// with zones.
struct ZoneException : public Exception {
diff --git a/src/lib/datasrc/factory.cc b/src/lib/datasrc/factory.cc
index 33338db..26b31dd 100644
--- a/src/lib/datasrc/factory.cc
+++ b/src/lib/datasrc/factory.cc
@@ -14,7 +14,7 @@
#include "factory.h"
-#include "data_source.h"
+#include "exceptions.h"
#include "database.h"
#include "sqlite3_accessor.h"
@@ -83,8 +83,8 @@ LibraryContainer::LibraryContainer(const std::string& name) {
if (ds_lib_ == NULL) {
// This may cause the filename to appear twice in the actual
// error, but the output of dlerror is implementation-dependent
- isc_throw(DataSourceLibraryError, "dlopen failed for " << name <<
- ": " << dlerror());
+ isc_throw(DataSourceLibraryOpenError,
+ "dlopen failed for " << name << ": " << dlerror());
}
}
diff --git a/src/lib/datasrc/factory.h b/src/lib/datasrc/factory.h
index 45e4f9b..b9b6578 100644
--- a/src/lib/datasrc/factory.h
+++ b/src/lib/datasrc/factory.h
@@ -15,7 +15,7 @@
#ifndef DATA_SOURCE_FACTORY_H
#define DATA_SOURCE_FACTORY_H 1
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/client.h>
#include <cc/data.h>
@@ -27,7 +27,7 @@ namespace isc {
namespace datasrc {
-/// \brief Raised if there is an error loading the datasource implementation
+/// \brief Raised if there is an error in the datasource implementation
/// library
class DataSourceLibraryError : public DataSourceError {
public:
@@ -35,13 +35,22 @@ public:
DataSourceError(file, line, what) {}
};
+/// \brief Raised if there is an error opening the the datasource
+/// implementation library
+class DataSourceLibraryOpenError : public DataSourceLibraryError {
+public:
+ DataSourceLibraryOpenError(const char* file, size_t line,
+ const char* what) :
+ DataSourceLibraryError(file, line, what) {}
+};
+
/// \brief Raised if there is an error reading a symbol from the datasource
/// implementation library
-class DataSourceLibrarySymbolError : public DataSourceError {
+class DataSourceLibrarySymbolError : public DataSourceLibraryError {
public:
DataSourceLibrarySymbolError(const char* file, size_t line,
const char* what) :
- DataSourceError(file, line, what) {}
+ DataSourceLibraryError(file, line, what) {}
};
typedef DataSourceClient* ds_creator(isc::data::ConstElementPtr config,
diff --git a/src/lib/datasrc/master_loader_callbacks.h b/src/lib/datasrc/master_loader_callbacks.h
index ae827c9..e1d3a92 100644
--- a/src/lib/datasrc/master_loader_callbacks.h
+++ b/src/lib/datasrc/master_loader_callbacks.h
@@ -47,7 +47,7 @@ createMasterLoaderCallbacks(const isc::dns::Name& name,
/// loaded RRsets into a zone updater.
///
/// The zone updater should be opened in the replace mode no changes should
-/// have been done to it yet (but it is not checked). It is not commited
+/// have been done to it yet (but it is not checked). It is not committed
/// automatically and it is up to the caller to commit the changes (or not).
/// It must not be destroyed for the whole time of loading.
///
diff --git a/src/lib/datasrc/memory/Makefile.am b/src/lib/datasrc/memory/Makefile.am
index c0ee688..434eaf2 100644
--- a/src/lib/datasrc/memory/Makefile.am
+++ b/src/lib/datasrc/memory/Makefile.am
@@ -17,16 +17,21 @@ libdatasrc_memory_la_SOURCES += rdata_serialization.h rdata_serialization.cc
libdatasrc_memory_la_SOURCES += zone_data.h zone_data.cc
libdatasrc_memory_la_SOURCES += rrset_collection.h rrset_collection.cc
libdatasrc_memory_la_SOURCES += segment_object_holder.h
+libdatasrc_memory_la_SOURCES += segment_object_holder.cc
libdatasrc_memory_la_SOURCES += logger.h logger.cc
libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc
libdatasrc_memory_la_SOURCES += zone_finder.h zone_finder.cc
libdatasrc_memory_la_SOURCES += zone_table_segment.h zone_table_segment.cc
libdatasrc_memory_la_SOURCES += zone_table_segment_local.h zone_table_segment_local.cc
+
+if USE_SHARED_MEMORY
+libdatasrc_memory_la_SOURCES += zone_table_segment_mapped.h zone_table_segment_mapped.cc
+endif
+
libdatasrc_memory_la_SOURCES += zone_data_updater.h zone_data_updater.cc
libdatasrc_memory_la_SOURCES += zone_data_loader.h zone_data_loader.cc
libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc
-libdatasrc_memory_la_SOURCES += zone_writer.h
-libdatasrc_memory_la_SOURCES += zone_writer_local.h zone_writer_local.cc
+libdatasrc_memory_la_SOURCES += zone_writer.h zone_writer.cc
libdatasrc_memory_la_SOURCES += load_action.h
libdatasrc_memory_la_SOURCES += util_internal.h
diff --git a/src/lib/datasrc/memory/domaintree.h b/src/lib/datasrc/memory/domaintree.h
index 4816452..5f41371 100644
--- a/src/lib/datasrc/memory/domaintree.h
+++ b/src/lib/datasrc/memory/domaintree.h
@@ -42,7 +42,7 @@ namespace isc {
namespace datasrc {
namespace memory {
-/// Forward declare DomainTree class here is convinent for following
+/// Forward declare DomainTree class here is convenient for following
/// friend class declare inside DomainTreeNode and DomainTreeNodeChain
template <typename T>
class DomainTree;
@@ -356,6 +356,16 @@ private:
}
}
+ /// \brief Returns if the node color is black
+ bool isBlack() const {
+ return (getColor() == BLACK);
+ }
+
+ /// \brief Returns if the node color is red
+ bool isRed() const {
+ return (!isBlack());
+ }
+
/// \brief Sets the color of this node
void setColor(const DomainTreeNodeColor color) {
if (color == RED) {
@@ -435,6 +445,21 @@ public:
/// This method never throws an exception.
const DomainTreeNode<T>* predecessor() const;
+ /// \brief returns the node distance from the root of its subtree
+ size_t getDistance() const {
+ size_t nodes = 1;
+ for (const DomainTreeNode<T>* node = this;
+ node != NULL;
+ node = node->getParent()) {
+ if (node->isSubTreeRoot()) {
+ break;
+ }
+ ++nodes;
+ }
+
+ return (nodes);
+ }
+
private:
/// \brief private shared implementation of successor and predecessor
///
@@ -489,6 +514,41 @@ private:
const DomainTreeNode<T>* getRight() const {
return (right_.get());
}
+
+ /// \brief Access grandparent node as bare pointer.
+ ///
+ /// The grandparent node is the parent's parent.
+ ///
+ /// \return the grandparent node if one exists, NULL otherwise.
+ DomainTreeNode<T>* getGrandParent() {
+ DomainTreeNode<T>* parent = getParent();
+ if (parent != NULL) {
+ return (parent->getParent());
+ } else {
+ // If there's no parent, there's no grandparent.
+ return (NULL);
+ }
+ }
+
+ /// \brief Access uncle node as bare pointer.
+ ///
+ /// An uncle node is defined as the parent node's sibling. It exists
+ /// at the same level as the parent.
+ ///
+ /// \return the uncle node if one exists, NULL otherwise.
+ DomainTreeNode<T>* getUncle() {
+ DomainTreeNode<T>* grandparent = getGrandParent();
+ if (grandparent == NULL) {
+ // If there's no grandparent, there's no uncle.
+ return (NULL);
+ }
+ if (getParent() == grandparent->getLeft()) {
+ return (grandparent->getRight());
+ } else {
+ return (grandparent->getLeft());
+ }
+ }
+
//@}
/// \brief The subdomain tree.
@@ -705,7 +765,13 @@ public:
// XXX: meaningless initial values:
last_comparison_(0, 0,
isc::dns::NameComparisonResult::EQUAL)
- {}
+ {
+ // To silence cppcheck. We don't really use the values before
+ // initialization, but this is cleaner anyway.
+ for (size_t i = 0; i < RBT_MAX_LEVEL; ++i) {
+ nodes_[i] = NULL;
+ }
+ }
/// \brief Copy constructor.
///
@@ -828,6 +894,7 @@ private:
/// the top node
///
/// \exception None
+ // cppcheck-suppress unusedPrivateFunction (false positive, it is used)
void pop() {
assert(!isEmpty());
--level_count_;
@@ -840,6 +907,7 @@ private:
/// otherwise the node should be the root node of DomainTree.
///
/// \exception None
+ // cppcheck-suppress unusedPrivateFunction (false positive, it is used)
void push(const DomainTreeNode<T>* node) {
assert(level_count_ < RBT_MAX_LEVEL);
nodes_[level_count_++] = node;
@@ -1301,15 +1369,24 @@ public:
/// doesn't exist.
///
/// This method normally involves resource allocation. If it fails
- /// the corresponding standard exception will be thrown.
+ /// \c std::bad_alloc will be thrown. Also, depending on details of the
+ /// specific \c MemorySegment, it can propagate the \c MemorySegmentGrown
+ /// exception.
///
/// This method does not provide the strong exception guarantee in its
- /// strict sense; if an exception is thrown in the middle of this
- /// method, the internal structure may change. However, it should
- /// still retain the same property as a mapping container before this
- /// method is called. For example, the result of \c find() should be
- /// the same. This method provides the weak exception guarantee in its
- /// normal sense.
+ /// strict sense; there can be new empty nodes that are superdomains of
+ /// the domain to be inserted as a side effect. However, the tree
+ /// retains internal integrity otherwise, and, in particular, the intended
+ /// insert operation is "resumable": if the \c insert() method is called
+ /// again with the same argument after resolving the cause of the
+ /// exception (possibly multiple times), it should now succeed. Note,
+ /// however, that in case of \c MemorySegmentGrown the address of the
+ /// `DomainTree` object may have been reallocated if it was created with
+ /// the same \c MemorySegment (which will often be the case in practice).
+ /// So the caller may have to re-get the address before calling \c insert
+ /// again. It can be done using the concept of "named addresses" of
+ /// \c MemorySegment, or the direct caller may not have to worry about it
+ /// if this condition is guaranteed at a higher level.
///
/// \param mem_sgmt A \c MemorySegment object for allocating memory of
/// a new node to be inserted. Must be the same segment as that used
@@ -1684,7 +1761,7 @@ DomainTree<T>::previousNode(DomainTreeNodeChain<T>& node_path) const {
}
}
- // Exchange the node at the top of the path, as we move horizontaly
+ // Exchange the node at the top of the path, as we move horizontally
// through the domain tree
node_path.pop();
node_path.push(node);
@@ -1797,11 +1874,13 @@ DomainTree<T>::insert(util::MemorySegment& mem_sgmt,
} else if (order < 0) {
node->setSubTreeRoot(false);
parent->left_ = node;
+ insertRebalance(current_root, node);
} else {
node->setSubTreeRoot(false);
parent->right_ = node;
+ insertRebalance(current_root, node);
}
- insertRebalance(current_root, node);
+
if (new_node != NULL) {
*new_node = node;
}
@@ -1877,61 +1956,143 @@ DomainTree<T>::nodeFission(util::MemorySegment& mem_sgmt,
}
+/// \brief Fix Red-Black tree properties after an ordinary BST
+/// insertion.
+///
+/// After a normal binary search tree insertion, the Red-Black tree
+/// properties may be violated. This method fixes these properties by
+/// doing tree rotations and recoloring nodes in the tree appropriately.
+///
+/// \param subtree_root The root of the current sub-tree where the node
+/// is being inserted.
+/// \param node The node which was inserted by ordinary BST insertion.
template <typename T>
void
DomainTree<T>::insertRebalance
- (typename DomainTreeNode<T>::DomainTreeNodePtr* root,
+ (typename DomainTreeNode<T>::DomainTreeNodePtr* subtree_root,
DomainTreeNode<T>* node)
{
- DomainTreeNode<T>* uncle;
- DomainTreeNode<T>* parent;
- while (node != (*root).get() &&
- ((parent = node->getParent())->getColor()) ==
- DomainTreeNode<T>::RED) {
- // Here, node->parent_ is not NULL and it is also red, so
- // node->parent_->parent_ is also not NULL.
- if (parent == parent->getParent()->getLeft()) {
- uncle = parent->getParent()->getRight();
-
- if (uncle != NULL && uncle->getColor() ==
- DomainTreeNode<T>::RED) {
- parent->setColor(DomainTreeNode<T>::BLACK);
- uncle->setColor(DomainTreeNode<T>::BLACK);
- parent->getParent()->setColor(DomainTreeNode<T>::RED);
- node = parent->getParent();
- } else {
- if (node == parent->getRight()) {
- node = parent;
- leftRotate(root, node);
- parent = node->getParent();
- }
- parent->setColor(DomainTreeNode<T>::BLACK);
- parent->getParent()->setColor(DomainTreeNode<T>::RED);
- rightRotate(root, parent->getParent());
- }
+ // The node enters this method colored RED. We assume in our
+ // red-black implementation that NULL values in left and right
+ // children are BLACK.
+ //
+ // Case 1. If node is at the subtree root, we don't need to change
+ // its position in the tree. We re-color it BLACK further below
+ // (right before we return).
+ while (node != (*subtree_root).get()) {
+ // Case 2. If the node is not subtree root, but its parent is
+ // colored BLACK, then we're done. This is because the new node
+ // introduces a RED node in the path through it (from its
+ // subtree root to its children colored BLACK) but doesn't
+ // change the red-black properties.
+ DomainTreeNode<T>* parent = node->getParent();
+ if (parent->isBlack()) {
+ break;
+ }
+
+ DomainTreeNode<T>* uncle = node->getUncle();
+ DomainTreeNode<T>* grandparent = node->getGrandParent();
+
+ if ((uncle != NULL) && uncle->isRed()) {
+ // Case 3. Here, the node's parent is colored RED and the
+ // uncle node is also RED. In this case, the grandparent
+ // must be BLACK (due to existing red-black state). We set
+ // both the parent and uncle nodes to BLACK then, change the
+ // grandparent to RED, and iterate the while loop with
+ // node = grandparent. This is the only case that causes
+ // insertion to have a max insertion time of log(n).
+ parent->setColor(DomainTreeNode<T>::BLACK);
+ uncle->setColor(DomainTreeNode<T>::BLACK);
+ grandparent->setColor(DomainTreeNode<T>::RED);
+ node = grandparent;
} else {
- uncle = parent->getParent()->getLeft();
-
- if (uncle != NULL && uncle->getColor() ==
- DomainTreeNode<T>::RED) {
- parent->setColor(DomainTreeNode<T>::BLACK);
- uncle->setColor(DomainTreeNode<T>::BLACK);
- parent->getParent()->setColor(DomainTreeNode<T>::RED);
- node = parent->getParent();
+ // Case 4. Here, the node and node's parent are colored RED,
+ // and the uncle node is BLACK. Only in this case, tree
+ // rotations are necessary.
+
+ /* First we check if we need to convert to a canonical form:
+ *
+ * (a) If the node is the right-child of its parent, and the
+ * node's parent is the left-child of the node's
+ * grandparent, rotate left about the parent so that the old
+ * 'node' becomes the new parent, and the old parent becomes
+ * the new 'node'.
+ *
+ * G(B) G(B)
+ * / \ / \
+ * P(R) U(B) => P*(R) U(B)
+ * \ /
+ * N(R) N*(R)
+ *
+ * (P* is old N, N* is old P)
+ *
+ * (b) If the node is the left-child of its parent, and the
+ * node's parent is the right-child of the node's
+ * grandparent, rotate right about the parent so that the
+ * old 'node' becomes the new parent, and the old parent
+ * becomes the new 'node'.
+ *
+ * G(B) G(B)
+ * / \ / \
+ * U(B) P(R) => U(B) P*(R)
+ * / \
+ * N(R) N*(R)
+ *
+ * (P* is old N, N* is old P)
+ */
+ if ((node == parent->getRight()) &&
+ (parent == grandparent->getLeft())) {
+ node = parent;
+ leftRotate(subtree_root, parent);
+ } else if ((node == parent->getLeft()) &&
+ (parent == grandparent->getRight())) {
+ node = parent;
+ rightRotate(subtree_root, parent);
+ }
+
+ // Also adjust the parent variable (node is already adjusted
+ // above). The grandparent variable need not be adjusted.
+ parent = node->getParent();
+
+ /* Here, we're in a canonical form where the uncle node is
+ * BLACK and both the node and its parent are together
+ * either left-children or right-children of their
+ * corresponding parents.
+ *
+ * G(B) or G(B)
+ * / \ / \
+ * P(R) U(B) U(B) P(R)
+ * / \
+ * N(R) N(R)
+ *
+ * We rotate around the grandparent, right or left,
+ * depending on the orientation above, color the old
+ * grandparent RED (it used to be BLACK) and color the
+ * parent BLACK (it used to be RED). This restores the
+ * red-black property that the number of BLACK nodes from
+ * subtree root to the leaves (the NULL children which are
+ * assumed BLACK) are equal, and that every RED node has a
+ * BLACK parent.
+ */
+ parent->setColor(DomainTreeNode<T>::BLACK);
+ grandparent->setColor(DomainTreeNode<T>::RED);
+
+ if (node == parent->getLeft()) {
+ rightRotate(subtree_root, grandparent);
} else {
- if (node == parent->getLeft()) {
- node = parent;
- rightRotate(root, node);
- parent = node->getParent();
- }
- parent->setColor(DomainTreeNode<T>::BLACK);
- parent->getParent()->setColor(DomainTreeNode<T>::RED);
- leftRotate(root, parent->getParent());
+ leftRotate(subtree_root, grandparent);
}
+
+ // In this case, the tree is ready now and we explicitly
+ // break out of the loop here. Even if we continue the loop,
+ // it will exit the loop in case 2 above, but that's not so
+ // obvious.
+ break;
}
}
- (*root)->setColor(DomainTreeNode<T>::BLACK);
+ // Color sub-tree roots black.
+ (*subtree_root)->setColor(DomainTreeNode<T>::BLACK);
}
@@ -2026,7 +2187,7 @@ DomainTree<T>::dumpTreeHelper(std::ostream& os,
indent(os, depth);
os << node->getLabels() << " ("
- << ((node->getColor() == DomainTreeNode<T>::BLACK) ? "black" : "red")
+ << (node->isBlack() ? "black" : "red")
<< ")";
if (node->isEmpty()) {
os << " [invisible]";
@@ -2090,7 +2251,7 @@ DomainTree<T>::dumpDotHelper(std::ostream& os,
}
os << "\"] [";
- if (node->getColor() == DomainTreeNode<T>::RED) {
+ if (node->isRed()) {
os << "color=red";
} else {
os << "color=black";
diff --git a/src/lib/datasrc/memory/memory_client.cc b/src/lib/datasrc/memory/memory_client.cc
index 66e61a2..0b0c827 100644
--- a/src/lib/datasrc/memory/memory_client.cc
+++ b/src/lib/datasrc/memory/memory_client.cc
@@ -18,15 +18,11 @@
#include <datasrc/memory/logger.h>
#include <datasrc/memory/zone_data.h>
#include <datasrc/memory/rdataset.h>
-#include <datasrc/memory/segment_object_holder.h>
#include <datasrc/memory/treenode_rrset.h>
#include <datasrc/memory/zone_finder.h>
-#include <datasrc/memory/zone_data_loader.h>
#include <datasrc/memory/zone_table_segment.h>
-#include <util/memory_segment_local.h>
-
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/factory.h>
#include <datasrc/result.h>
@@ -34,12 +30,8 @@
#include <dns/rdataclass.h>
#include <dns/rrclass.h>
-#include <algorithm>
#include <utility>
-#include <cctype>
-#include <cassert>
-using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::datasrc::memory;
@@ -49,86 +41,14 @@ namespace isc {
namespace datasrc {
namespace memory {
-using detail::SegmentObjectHolder;
using boost::shared_ptr;
-namespace { // unnamed namespace
-
-// A helper internal class used by the memory client, used for deleting
-// filenames stored in an internal tree.
-class FileNameDeleter {
-public:
- FileNameDeleter() {}
-
- void operator()(std::string* filename) const {
- delete filename;
- }
-};
-
-} // end of unnamed namespace
-
InMemoryClient::InMemoryClient(shared_ptr<ZoneTableSegment> ztable_segment,
RRClass rrclass) :
ztable_segment_(ztable_segment),
- rrclass_(rrclass),
- zone_count_(0),
- file_name_tree_(FileNameTree::create(
- ztable_segment_->getMemorySegment(), false))
+ rrclass_(rrclass)
{}
-InMemoryClient::~InMemoryClient() {
- MemorySegment& mem_sgmt = ztable_segment_->getMemorySegment();
- FileNameDeleter deleter;
- FileNameTree::destroy(mem_sgmt, file_name_tree_, deleter);
-}
-
-result::Result
-InMemoryClient::loadInternal(const isc::dns::Name& zone_name,
- const std::string& filename,
- ZoneData* zone_data)
-{
- MemorySegment& mem_sgmt = ztable_segment_->getMemorySegment();
- SegmentObjectHolder<ZoneData, RRClass> holder(
- mem_sgmt, zone_data, rrclass_);
-
- LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_ADD_ZONE).
- arg(zone_name).arg(rrclass_);
-
- // Set the filename in file_name_tree_ now, so that getFileName()
- // can use it (during zone reloading).
- FileNameNode* node(NULL);
- switch (file_name_tree_->insert(mem_sgmt, zone_name, &node)) {
- case FileNameTree::SUCCESS:
- case FileNameTree::ALREADYEXISTS:
- // These are OK
- break;
- default:
- // Can Not Happen
- assert(false);
- }
- // node must point to a valid node now
- assert(node != NULL);
-
- const std::string* tstr = node->setData(new std::string(filename));
- delete tstr;
-
- ZoneTable* zone_table = ztable_segment_->getHeader().getTable();
- const ZoneTable::AddResult result(zone_table->addZone(mem_sgmt, rrclass_,
- zone_name,
- holder.release()));
- if (result.code == result::SUCCESS) {
- // Only increment the zone count if the zone doesn't already
- // exist.
- ++zone_count_;
- }
- // Destroy the old instance of the zone if there was any
- if (result.zone_data != NULL) {
- ZoneData::destroy(mem_sgmt, result.zone_data, rrclass_);
- }
-
- return (result.code);
-}
-
RRClass
InMemoryClient::getClass() const {
return (rrclass_);
@@ -136,7 +56,8 @@ InMemoryClient::getClass() const {
unsigned int
InMemoryClient::getZoneCount() const {
- return (zone_count_);
+ const ZoneTable* zone_table = ztable_segment_->getHeader().getTable();
+ return (zone_table->getZoneCount());
}
isc::datasrc::DataSourceClient::FindResult
@@ -148,11 +69,11 @@ InMemoryClient::findZone(const isc::dns::Name& zone_name) const {
const ZoneTable::FindResult result(zone_table->findZone(zone_name));
ZoneFinderPtr finder;
- if (result.code != result::NOTFOUND) {
+ if (result.code != result::NOTFOUND && result.zone_data) {
finder.reset(new InMemoryZoneFinder(*result.zone_data, getClass()));
}
- return (DataSourceClient::FindResult(result.code, finder));
+ return (DataSourceClient::FindResult(result.code, finder, result.flags));
}
const ZoneData*
@@ -162,39 +83,6 @@ InMemoryClient::findZoneData(const isc::dns::Name& zone_name) {
return (result.zone_data);
}
-result::Result
-InMemoryClient::load(const isc::dns::Name& zone_name,
- const std::string& filename)
-{
- LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_LOAD).arg(zone_name).
- arg(filename);
-
- MemorySegment& mem_sgmt = ztable_segment_->getMemorySegment();
- ZoneData* zone_data = loadZoneData(mem_sgmt, rrclass_, zone_name,
- filename);
- return (loadInternal(zone_name, filename, zone_data));
-}
-
-result::Result
-InMemoryClient::load(const isc::dns::Name& zone_name, ZoneIterator& iterator) {
- MemorySegment& mem_sgmt = ztable_segment_->getMemorySegment();
- ZoneData* zone_data = loadZoneData(mem_sgmt, rrclass_, zone_name,
- iterator);
- return (loadInternal(zone_name, string(), zone_data));
-}
-
-const std::string
-InMemoryClient::getFileName(const isc::dns::Name& zone_name) const {
- const FileNameNode* node(NULL);
- const FileNameTree::Result result = file_name_tree_->find(zone_name,
- &node);
- if (result == FileNameTree::EXACTMATCH) {
- return (*node->getData());
- } else {
- return (std::string());
- }
-}
-
namespace {
class MemoryIterator : public ZoneIterator {
@@ -354,7 +242,12 @@ InMemoryClient::getIterator(const Name& name, bool separate_rrs) const {
const ZoneTable* zone_table = ztable_segment_->getHeader().getTable();
const ZoneTable::FindResult result(zone_table->findZone(name));
if (result.code != result::SUCCESS) {
- isc_throw(DataSourceError, "No such zone: " + name.toText());
+ isc_throw(NoSuchZone, "no such zone for in-memory iterator: "
+ << name.toText());
+ }
+ if (!result.zone_data) {
+ isc_throw(EmptyZone, "empty zone for in-memory iterator: "
+ << name.toText());
}
return (ZoneIteratorPtr(new MemoryIterator(
@@ -369,7 +262,7 @@ InMemoryClient::getUpdater(const isc::dns::Name&, bool, bool) const {
isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
}
-pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
+std::pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
InMemoryClient::getJournalReader(const isc::dns::Name&, uint32_t,
uint32_t) const
{
diff --git a/src/lib/datasrc/memory/memory_client.h b/src/lib/datasrc/memory/memory_client.h
index 10e8a81..45f0b77 100644
--- a/src/lib/datasrc/memory/memory_client.h
+++ b/src/lib/datasrc/memory/memory_client.h
@@ -55,7 +55,7 @@ class ZoneTableSegment;
class InMemoryClient : public DataSourceClient {
public:
///
- /// \name Constructors and Destructor.
+ /// \name Constructor.
///
//@{
@@ -66,9 +66,6 @@ public:
/// It never throws an exception otherwise.
InMemoryClient(boost::shared_ptr<ZoneTableSegment> ztable_segment,
isc::dns::RRClass rrclass);
-
- /// The destructor.
- ~InMemoryClient();
//@}
/// \brief Returns the class of the data source client.
@@ -81,68 +78,6 @@ public:
/// \return The number of zones stored in the client.
virtual unsigned int getZoneCount() const;
- /// \brief Load zone from masterfile.
- ///
- /// This loads data from masterfile specified by filename. It replaces
- /// current content. The masterfile parsing ability is kind of limited,
- /// see isc::dns::masterLoad.
- ///
- /// This throws isc::dns::MasterLoadError or AddError if there are
- /// problems with loading (missing file, malformed data, unexpected
- /// zone, etc. - see isc::dns::masterLoad for details).
- ///
- /// In case of internal problems, NullRRset or AssertError could
- /// be thrown, but they should not be expected. Exceptions caused by
- /// allocation may be thrown as well.
- ///
- /// If anything is thrown, the previous content is preserved (so it can
- /// be used to update the data, but if user makes a typo, the old one
- /// is kept).
- ///
- /// \param filename The master file to load.
- ///
- /// \todo We may need to split it to some kind of build and commit/abort.
- /// This will probably be needed when a better implementation of
- /// configuration reloading is written.
- result::Result load(const isc::dns::Name& zone_name,
- const std::string& filename);
-
- /// \brief Load zone from another data source.
- ///
- /// This is similar to the other version, but zone's RRsets are provided
- /// by an iterator of another data source. On successful load, the
- /// internal filename will be cleared.
- ///
- /// This implementation assumes the iterator produces combined RRsets,
- /// that is, there should exactly one RRset for the same owner name and
- /// RR type. This means the caller is expected to create the iterator
- /// with \c separate_rrs being \c false. This implementation also assumes
- /// RRsets of different names are not mixed; so if the iterator produces
- /// an RRset of a different name than that of the previous RRset, that
- /// previous name must never appear in the subsequent sequence of RRsets.
- /// Note that the iterator API does not ensure this. If the underlying
- /// implementation does not follow it, load() will fail. Note, however,
- /// that this whole interface is tentative. in-memory zone loading will
- /// have to be revisited fundamentally, and at that point this restriction
- /// probably won't matter.
- result::Result load(const isc::dns::Name& zone_name,
- ZoneIterator& iterator);
-
- /// Return the master file name of the zone
- ///
- /// This method returns the name of the zone's master file to be loaded.
- /// The returned string will be an empty unless the data source client has
- /// successfully loaded the \c zone_name zone from a file before.
- ///
- /// This method should normally not throw an exception. But the creation
- /// of the return string may involve a resource allocation, and if it
- /// fails, the corresponding standard exception will be thrown.
- ///
- /// \return The name of the zone file corresponding to the zone, or
- /// an empty string if the client hasn't loaded the \c zone_name
- /// zone from a file before.
- const std::string getFileName(const isc::dns::Name& zone_name) const;
-
/// Returns a \c ZoneFinder result that best matches the given name.
///
/// This derived version of the method never throws an exception.
@@ -180,20 +115,8 @@ public:
uint32_t end_serial) const;
private:
- // Some type aliases
- typedef DomainTree<std::string> FileNameTree;
- typedef DomainTreeNode<std::string> FileNameNode;
-
- // Common process for zone load. Registers filename internally and
- // adds the ZoneData to the ZoneTable.
- result::Result loadInternal(const isc::dns::Name& zone_name,
- const std::string& filename,
- ZoneData* zone_data);
-
boost::shared_ptr<ZoneTableSegment> ztable_segment_;
const isc::dns::RRClass rrclass_;
- unsigned int zone_count_;
- FileNameTree* file_name_tree_;
};
} // namespace memory
diff --git a/src/lib/datasrc/memory/memory_messages.mes b/src/lib/datasrc/memory/memory_messages.mes
index f8d5328..37539e7 100644
--- a/src/lib/datasrc/memory/memory_messages.mes
+++ b/src/lib/datasrc/memory/memory_messages.mes
@@ -16,6 +16,10 @@ $NAMESPACE isc::datasrc::memory
# \brief Messages for the data source memory library
+% DATASRC_MEMORY_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.
+
% DATASRC_MEMORY_BAD_NSEC3_NAME NSEC3 record has a bad owner name '%1'
The software refuses to load NSEC3 records into a wildcard domain or
the owner name has two or more labels below the zone origin.
@@ -23,6 +27,77 @@ It isn't explicitly forbidden, but no sane zone wouldn have such names
for NSEC3. BIND 9 also refuses NSEC3 at wildcard, so this behavior is
compatible with BIND 9.
+% DATASRC_MEMORY_CHECK_ERROR post-load check of zone %1/%2 failed: %3
+The zone was loaded into the data source successfully, but the content fails
+basic sanity checks. See the message if you want to know what exactly is wrong
+with the data. The data can not be used and previous version, if any, will be
+preserved.
+
+% DATASRC_MEMORY_CHECK_WARNING %1/%2: %3
+The zone was loaded into the data source successfully, but there's some problem
+with the content. The problem does not stop the new version from being used
+(though there may be other problems that do, see DATASRC_MEMORY_CHECK_ERROR),
+but it should still be checked and fixed. See the message to know what exactly
+is wrong with the data.
+
+% DATASRC_MEMORY_CNAME CNAME at the domain '%1'
+Debug information. The requested domain is an alias to a different domain,
+returning the CNAME instead.
+
+% DATASRC_MEMORY_DELEG_FOUND delegation found at '%1'
+Debug information. A delegation point was found above the requested record.
+
+% DATASRC_MEMORY_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.
+
+% DATASRC_MEMORY_DNAME_FOUND DNAME found at '%1'
+Debug information. A DNAME was found instead of the requested information.
+
+% DATASRC_MEMORY_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.
+
+% DATASRC_MEMORY_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.
+
+% DATASRC_MEMORY_FINDNSEC3 finding NSEC3 for %1, mode %2
+Debug information. A search in an in-memory data source for NSEC3 that
+matches or covers the given name is being started.
+
+% DATASRC_MEMORY_FINDNSEC3_COVER found a covering NSEC3 for %1: %2
+Debug information. An NSEC3 that covers the given name is found and
+being returned. The found NSEC3 RRset is also displayed.
+
+% DATASRC_MEMORY_FINDNSEC3_MATCH found a matching NSEC3 for %1 at label count %2: %3
+Debug information. An NSEC3 that matches (a possibly superdomain of)
+the given name is found and being returned. When the shown label
+count is smaller than that of the given name, the matching NSEC3 is
+for a superdomain of the given name (see DATASRC_MEMORY_FINDNSEC3_TRYHASH).
+The found NSEC3 RRset is also displayed.
+
+% DATASRC_MEMORY_FINDNSEC3_TRYHASH looking for NSEC3 for %1 at label count %2 (hash %3)
+Debug information. In an attempt of finding an NSEC3 for the give name,
+(a possibly superdomain of) the name is hashed and searched for in the
+NSEC3 name space. When the shown label count is smaller than that of the
+shown name, the search tries the superdomain name that share the shown
+(higher) label count of the shown name (e.g., for
+www.example.com. with shown label count of 3, example.com. is being
+tried).
+
+% DATASRC_MEMORY_FIND_TYPE_AT_ORIGIN origin query for type %1 in in-memory zone %2/%3 successful
+Debug information. A specific type RRset is requested at a zone origin
+of an in-memory zone and it is found.
+
+% DATASRC_MEMORY_MEM_ADD_EMPTY_ZONE adding an empty zone '%1/%2'
+Debug information. An "empty" zone is being added into the in-memory
+data source. This is conceptual data indicating the state where the
+zone exists but its content isn't available. That would be the case,
+for example, a broken zone specified in the configuration.
+
% DATASRC_MEMORY_MEM_ADD_RRSET adding RRset '%1/%2' into zone '%3'
Debug information. An RRset is being added to the in-memory data source.
@@ -57,7 +132,11 @@ RRset is split into multiple locations is not supported yet.
Debug information. A zone object for this zone is being searched for in the
in-memory data source.
-% DATASRC_MEMORY_MEM_LOAD loading zone '%1' from file '%2'
+% DATASRC_MEMORY_MEM_LOAD_FROM_DATASRC loading zone '%1/%2' from other data source
+Debug information. The content of another data source is being loaded
+into the memory.
+
+% DATASRC_MEMORY_MEM_LOAD_FROM_FILE loading zone '%1/%2' from file '%3'
Debug information. The content of master file is being loaded into the memory.
% DATASRC_MEMORY_MEM_NO_NSEC3PARAM NSEC3PARAM is missing for NSEC3-signed zone %1/%2
@@ -89,33 +168,15 @@ 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.
-% DATASRC_MEMORY_CHECK_ERROR post-load check of zone %1/%2 failed: %3
-The zone was loaded into the data source successfully, but the content fails
-basic sanity checks. See the message if you want to know what exactly is wrong
-with the data. The data can not be used and previous version, if any, will be
-preserved.
-
-% DATASRC_MEMORY_CHECK_WARNING %1/%2: %3
-The zone was loaded into the data source successfully, but there's some problem
-with the content. The problem does not stop the new version from being used
-(though there may be other problems that do, see DATASRC_MEMORY_CHECK_ERROR),
-but it should still be checked and fixed. See the message to know what exactly
-is wrong with the data.
-
-% DATASRC_MEMORY_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.
+% DATASRC_MEMORY_NOT_FOUND requested domain '%1' not found
+Debug information. The requested domain does not exist.
% DATASRC_MEMORY_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.
-% DATASRC_MEMORY_DNAME_FOUND DNAME found at '%1'
-Debug information. A DNAME was found instead of the requested information.
-
-% DATASRC_MEMORY_DELEG_FOUND delegation found at '%1'
-Debug information. A delegation point was found above the requested record.
+% DATASRC_MEMORY_SUCCESS query for '%1/%2' successful
+Debug information. The requested record was found.
% DATASRC_MEMORY_SUPER_STOP stopped as '%1' is superdomain of a zone node, meaning it's empty
Debug information. The search stopped because the requested domain was
@@ -128,54 +189,3 @@ doesn't have the requested record type).
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.
-
-% DATASRC_MEMORY_NOT_FOUND requested domain '%1' not found
-Debug information. The requested domain does not exist.
-
-% DATASRC_MEMORY_FIND_TYPE_AT_ORIGIN origin query for type %1 in in-memory zone %2/%3 successful
-Debug information. A specific type RRset is requested at a zone origin
-of an in-memory zone and it is found.
-
-% DATASRC_MEMORY_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.
-
-% DATASRC_MEMORY_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.
-
-% DATASRC_MEMORY_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.
-
-% DATASRC_MEMORY_SUCCESS query for '%1/%2' successful
-Debug information. The requested record was found.
-
-% DATASRC_MEMORY_CNAME CNAME at the domain '%1'
-Debug information. The requested domain is an alias to a different domain,
-returning the CNAME instead.
-
-% DATASRC_MEMORY_FINDNSEC3 finding NSEC3 for %1, mode %2
-Debug information. A search in an in-memory data source for NSEC3 that
-matches or covers the given name is being started.
-
-% DATASRC_MEMORY_FINDNSEC3_COVER found a covering NSEC3 for %1: %2
-Debug information. An NSEC3 that covers the given name is found and
-being returned. The found NSEC3 RRset is also displayed.
-
-% DATASRC_MEMORY_FINDNSEC3_MATCH found a matching NSEC3 for %1 at label count %2: %3
-Debug information. An NSEC3 that matches (a possibly superdomain of)
-the given name is found and being returned. When the shown label
-count is smaller than that of the given name, the matching NSEC3 is
-for a superdomain of the given name (see DATASRC_MEMORY_FINDNSEC3_TRYHASH).
-The found NSEC3 RRset is also displayed.
-
-% DATASRC_MEMORY_FINDNSEC3_TRYHASH looking for NSEC3 for %1 at label count %2 (hash %3)
-Debug information. In an attempt of finding an NSEC3 for the give name,
-(a possibly superdomain of) the name is hashed and searched for in the
-NSEC3 name space. When the shown label count is smaller than that of the
-shown name, the search tries the superdomain name that share the shown
-(higher) label count of the shown name (e.g., for
-www.example.com. with shown label count of 3, example.com. is being
-tried).
diff --git a/src/lib/datasrc/memory/rdata_serialization.h b/src/lib/datasrc/memory/rdata_serialization.h
index e46786c..3314406 100644
--- a/src/lib/datasrc/memory/rdata_serialization.h
+++ b/src/lib/datasrc/memory/rdata_serialization.h
@@ -364,7 +364,7 @@ struct RdataEncodeSpec;
/// from the field sequence, you'll need to build the complete
/// wire-format data, and then construct a dns::Rdata object from it.
///
-/// To use it, contstruct it with the data you got from RDataEncoder,
+/// To use it, construct it with the data you got from RDataEncoder,
/// provide it with callbacks and then iterate through the data.
/// The callbacks are called with the data fields contained in the
/// data.
@@ -533,7 +533,7 @@ public:
}
}
- /// \brief Rewind the iterator to the beginnig of data.
+ /// \brief Rewind the iterator to the beginning of data.
///
/// The following next() and nextSig() will start iterating from the
/// beginning again.
diff --git a/src/lib/datasrc/memory/rdataset.cc b/src/lib/datasrc/memory/rdataset.cc
index 3841c03..7f37f51 100644
--- a/src/lib/datasrc/memory/rdataset.cc
+++ b/src/lib/datasrc/memory/rdataset.cc
@@ -28,7 +28,6 @@
#include <stdint.h>
#include <algorithm>
#include <cstring>
-#include <typeinfo> // for bad_cast
#include <new> // for the placement new
using namespace isc::dns;
@@ -41,13 +40,12 @@ namespace memory {
namespace {
RRType
getCoveredType(const Rdata& rdata) {
- try {
- const generic::RRSIG& rrsig_rdata =
- dynamic_cast<const generic::RRSIG&>(rdata);
- return (rrsig_rdata.typeCovered());
- } catch (const std::bad_cast&) {
+ const generic::RRSIG* rrsig_rdata =
+ dynamic_cast<const generic::RRSIG*>(&rdata);
+ if (!rrsig_rdata) {
isc_throw(BadValue, "Non RRSIG is given where it's expected");
}
+ return (rrsig_rdata->typeCovered());
}
// A helper for lowestTTL: restore RRTTL object from wire-format 32-bit data.
diff --git a/src/lib/datasrc/memory/rdataset.h b/src/lib/datasrc/memory/rdataset.h
index 250af93..caef551 100644
--- a/src/lib/datasrc/memory/rdataset.h
+++ b/src/lib/datasrc/memory/rdataset.h
@@ -121,12 +121,15 @@ public:
/// returns a pointer to it.
///
/// If the optional \c old_rdataset parameter is set to non NULL,
- /// The given \c RdataSet, RRset, RRSIG will be merged in the new
- /// \c Rdataset object: the new object contain the union set of all
+ /// the given \c RdataSet, RRset, RRSIG will be merged in the new
+ /// \c Rdataset object: the new object will contain the union set of all
/// RDATA and RRSIGs contained in these. Obviously \c old_rdataset
- /// was previously generated for the same RRClass and RRType as those
+ /// must be previously generated for the same RRClass and RRType as those
/// for the given RRsets; it's the caller's responsibility to ensure
/// this condition. If it's not met the result will be undefined.
+ /// No reference to \c old_rdataset is maintained in the newly
+ /// returned \c RdataSet, so if the caller does not need \c
+ /// old_rdataset anymore, it may be freed by the caller.
///
/// In both cases, this method ensures the stored RDATA and RRSIG are
/// unique. Any duplicate data (in the sense of the comparison in the
@@ -173,6 +176,15 @@ public:
/// it cannot contain more than 65535 RRSIGs. If the given RRset(s) fail
/// to meet this condition, an \c RdataSetError exception will be thrown.
///
+ /// This method ensures there'll be no memory leak on exception.
+ /// But addresses allocated from \c mem_sgmt could be relocated if
+ /// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
+ /// must be aware of that possibility and update any such addresses
+ /// accordingly. On successful return, this method ensures there's no
+ /// address relocation.
+ ///
+ /// \throw util::MemorySegmentGrown The memory segment has grown, possibly
+ /// relocating data.
/// \throw isc::BadValue Given RRset(s) are invalid (see the description)
/// \throw RdataSetError Number of RDATAs exceed the limits
/// \throw std::bad_alloc Memory allocation fails.
diff --git a/src/lib/datasrc/memory/segment_object_holder.cc b/src/lib/datasrc/memory/segment_object_holder.cc
new file mode 100644
index 0000000..6d47b9d
--- /dev/null
+++ b/src/lib/datasrc/memory/segment_object_holder.cc
@@ -0,0 +1,41 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 "segment_object_holder.h"
+
+#include <boost/lexical_cast.hpp>
+
+#include <cassert>
+#include <stdint.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace detail {
+
+std::string
+getNextHolderName() {
+ static uint64_t index = 0;
+ ++index;
+ // in practice we should be able to assume this, uint64 is large
+ // and should not overflow
+ assert(index != 0);
+ return ("Segment object holder auto name " +
+ boost::lexical_cast<std::string>(index));
+}
+
+}
+}
+}
+}
diff --git a/src/lib/datasrc/memory/segment_object_holder.h b/src/lib/datasrc/memory/segment_object_holder.h
index 384f4ef..a716d4a 100644
--- a/src/lib/datasrc/memory/segment_object_holder.h
+++ b/src/lib/datasrc/memory/segment_object_holder.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -16,39 +16,99 @@
#define DATASRC_MEMORY_SEGMENT_OBJECT_HOLDER_H 1
#include <util/memory_segment.h>
+#include <string>
+#include <cassert>
namespace isc {
namespace datasrc {
namespace memory {
namespace detail {
+// Internal function to get next yet unused name of segment holder.
+// We need the names of holders to be unique per segment at any given
+// momemnt. This just keeps incrementing number after a prefix with
+// each call, it should be enough (we assert it does not wrap around,
+// but 64bits should be enough).
+//
+// Also, it is not thread safe.
+std::string
+getNextHolderName();
+
// A simple holder to create and use some objects in this implementation
// in an exception safe manner. It works like std::auto_ptr but much
// more simplified.
+//
+// Note, however, that it doesn't take the pointer to hold on construction.
+// This is because the constructor itself can throw or cause address
+// reallocation inside the memory segment. If that happens various
+// undesirable effects can happen, such as memory leak or unintentional access
+// to the pre-reallocated address. To make it safer, we use a separate
+// \c set() method, which is exception free and doesn't cause address
+// reallocation. So the typical usage is to first construct the holder
+// object, then the object to be held, immediately followed by a call to \c
+// set(). Subsequent access to the held address should be done via the \c get()
+// method. get() ensures the address is always valid in the memory segment
+// even if address reallocation happens between set() and get().
+//
// template parameter T is the type of object allocated by mem_sgmt.
// template parameter ARG_T is the type that will be passed to destroy()
// (deleter functor, etc). It must be copyable.
template <typename T, typename ARG_T>
class SegmentObjectHolder {
public:
- SegmentObjectHolder(util::MemorySegment& mem_sgmt, T* obj, ARG_T arg) :
- mem_sgmt_(mem_sgmt), obj_(obj), arg_(arg)
- {}
+ SegmentObjectHolder(util::MemorySegment& mem_sgmt, ARG_T arg) :
+ mem_sgmt_(mem_sgmt), arg_(arg),
+ holder_name_(getNextHolderName()), holding_(true)
+ {
+ if (mem_sgmt_.setNamedAddress(holder_name_.c_str(), NULL)) {
+ // OK. We've grown. The caller might need to be informed, so
+ // we throw. But then, we don't get our destructor, so we
+ // release the memory right away.
+ mem_sgmt_.clearNamedAddress(holder_name_.c_str());
+ isc_throw(isc::util::MemorySegmentGrown,
+ "Segment grown when allocating holder");
+ }
+ }
~SegmentObjectHolder() {
- if (obj_ != NULL) {
- T::destroy(mem_sgmt_, obj_, arg_);
+ if (holding_) {
+ // Use release, as it removes the stored address from segment
+ T* obj = release();
+ if (obj) { // May be NULL if set wasn't called
+ T::destroy(mem_sgmt_, obj, arg_);
+ }
+ }
+ }
+ void set(T* obj) {
+ const bool grown = mem_sgmt_.setNamedAddress(holder_name_.c_str(),
+ obj);
+ // We reserve the space in the constructor, should not grow now
+ assert(!grown);
+ }
+ T* get() {
+ if (holding_) {
+ const util::MemorySegment::NamedAddressResult result =
+ mem_sgmt_.getNamedAddress(holder_name_.c_str());
+ assert(result.first);
+ return (static_cast<T*>(result.second));
+ } else {
+ return (NULL);
}
}
- T* get() { return (obj_); }
T* release() {
- T* ret = obj_;
- obj_ = NULL;
- return (ret);
+ if (holding_) {
+ T* obj = get();
+ mem_sgmt_.clearNamedAddress(holder_name_.c_str());
+ holding_ = false;
+ return (obj);
+ } else {
+ return (NULL);
+ }
}
private:
util::MemorySegment& mem_sgmt_;
- T* obj_;
ARG_T arg_;
+ const std::string holder_name_;
+ bool holding_;
};
} // detail
diff --git a/src/lib/datasrc/memory/zone_data.cc b/src/lib/datasrc/memory/zone_data.cc
index d32fc87..cdcb683 100644
--- a/src/lib/datasrc/memory/zone_data.cc
+++ b/src/lib/datasrc/memory/zone_data.cc
@@ -38,6 +38,10 @@ namespace isc {
namespace datasrc {
namespace memory {
+// Definition of a class static constant. It's public and its address
+// could be needed by applications, so we need an explicit definition.
+const ZoneNode::Flags ZoneData::DNSSEC_SIGNED;
+
namespace {
void
rdataSetDeleter(RRClass rrclass, util::MemorySegment* mem_sgmt,
@@ -91,8 +95,8 @@ NSEC3Data::create(util::MemorySegment& mem_sgmt,
// (with an assertion check for that).
typedef boost::function<void(RdataSet*)> RdataSetDeleterType;
detail::SegmentObjectHolder<ZoneTree, RdataSetDeleterType> holder(
- mem_sgmt, ZoneTree::create(mem_sgmt, true),
- boost::bind(nullDeleter, _1));
+ mem_sgmt, boost::bind(nullDeleter, _1));
+ holder.set(ZoneTree::create(mem_sgmt, true));
ZoneTree* tree = holder.get();
const ZoneTree::Result result =
@@ -165,8 +169,8 @@ ZoneData::create(util::MemorySegment& mem_sgmt, const Name& zone_origin) {
// NSEC3Data::create().
typedef boost::function<void(RdataSet*)> RdataSetDeleterType;
detail::SegmentObjectHolder<ZoneTree, RdataSetDeleterType> holder(
- mem_sgmt, ZoneTree::create(mem_sgmt, true),
- boost::bind(nullDeleter, _1));
+ mem_sgmt, boost::bind(nullDeleter, _1));
+ holder.set(ZoneTree::create(mem_sgmt, true));
ZoneTree* tree = holder.get();
ZoneNode* origin_node = NULL;
@@ -179,6 +183,13 @@ ZoneData::create(util::MemorySegment& mem_sgmt, const Name& zone_origin) {
return (zone_data);
}
+ZoneData*
+ZoneData::create(util::MemorySegment& mem_sgmt) {
+ ZoneData* zone_data = create(mem_sgmt, Name::ROOT_NAME());
+ zone_data->origin_node_->setFlag(EMPTY_ZONE);
+ return (zone_data);
+}
+
void
ZoneData::destroy(util::MemorySegment& mem_sgmt, ZoneData* zone_data,
RRClass zone_class)
diff --git a/src/lib/datasrc/memory/zone_data.h b/src/lib/datasrc/memory/zone_data.h
index c6b3dcc..b4c65f7 100644
--- a/src/lib/datasrc/memory/zone_data.h
+++ b/src/lib/datasrc/memory/zone_data.h
@@ -86,6 +86,15 @@ public:
/// The NSEC3 parameters are extracted and stored within the created
/// \c NSEC3Data object.
///
+ /// This method ensures there'll be no memory leak on exception.
+ /// But addresses allocated from \c mem_sgmt could be relocated if
+ /// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
+ /// must be aware of that possibility and update any such addresses
+ /// accordingly. On successful return, this method ensures there's no
+ /// address relocation.
+ ///
+ /// \throw util::MemorySegmentGrown The memory segment has grown, possibly
+ /// relocating data.
/// \throw std::bad_alloc Memory allocation fails.
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
@@ -102,6 +111,15 @@ public:
/// The NSEC3 hash parameters are extracted and stored within the created
/// \c NSEC3Data object.
///
+ /// This method ensures there'll be no memory leak on exception.
+ /// But addresses allocated from \c mem_sgmt could be relocated if
+ /// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
+ /// must be aware of that possibility and update any such addresses
+ /// accordingly. On successful return, this method ensures there's no
+ /// address relocation.
+ ///
+ /// \throw util::MemorySegmentGrown The memory segment has grown, possibly
+ /// relocating data.
/// \throw std::bad_alloc Memory allocation fails.
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
@@ -360,21 +378,40 @@ private:
/// It never throws an exception.
ZoneData(ZoneTree* zone_tree, ZoneNode* origin_node);
- // Zone node flags.
+ // Zone node flags. When adding a new flag, it's generally advisable to
+ // keep existing values so the binary image of the data is as much
+ // backward compatible as possible. And it can be helpful in practice
+ // for file-mapped data.
private:
// Set in the origin node (which always exists at the same address)
// to indicate whether the zone is signed or not. Internal use,
// so defined as private.
static const ZoneNode::Flags DNSSEC_SIGNED = ZoneNode::FLAG_USER1;
+
public:
/// \brief Node flag indicating it is at a "wildcard level"
///
/// This means one of the node's immediate children is a wildcard.
static const ZoneNode::Flags WILDCARD_NODE = ZoneNode::FLAG_USER2;
+private:
+ // Also set in the origin node, indicating this is a special "empty zone",
+ // that could be created only by the corresponding create() method to be
+ // used for some kind of sentinel data.
+ static const ZoneNode::Flags EMPTY_ZONE = ZoneNode::FLAG_USER3;
+
public:
/// \brief Allocate and construct \c ZoneData.
///
+ /// This method ensures there'll be no memory leak on exception.
+ /// But addresses allocated from \c mem_sgmt could be relocated if
+ /// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
+ /// must be aware of that possibility and update any such addresses
+ /// accordingly. On successful return, this method ensures there's no
+ /// address relocation.
+ ///
+ /// \throw util::MemorySegmentGrown The memory segment has grown, possibly
+ /// relocating data.
/// \throw std::bad_alloc Memory allocation fails.
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
@@ -383,6 +420,23 @@ public:
static ZoneData* create(util::MemorySegment& mem_sgmt,
const dns::Name& zone_origin);
+ /// \brief Allocate and construct a special "empty" \c ZoneData.
+ ///
+ /// A ZoneData object created this way holds all internal integrity
+ /// that those created by the other \c create() method have, but is not
+ /// publicly associated with any actual zone data. It's intended to be
+ /// used as a kind of sentinel data to representing the concept such as
+ /// "broken zone".
+ ///
+ /// Methods calls on empty \c ZoneData object except \c destroy() and
+ /// \c isEmpty() are meaningless, while they shouldn't cause disruption.
+ /// It's caller's responsibility to use empty zone data objects in the
+ /// intended way.
+ ///
+ /// \param mem_sgmt A \c MemorySegment from which memory for the new
+ /// \c ZoneData is allocated.
+ static ZoneData* create(util::MemorySegment& mem_sgmt);
+
/// \brief Destruct and deallocate \c ZoneData.
///
/// It releases all resource allocated in the internal storage NSEC3 for
@@ -455,6 +509,13 @@ public:
/// \throw none
bool isNSEC3Signed() const { return (nsec3_data_); }
+ /// \brief Return whether or not the zone data is "empty".
+ ///
+ /// See the description of \c create() for the concept of empty zone data.
+ ///
+ /// \throw None
+ bool isEmpty() const { return (origin_node_->getFlag(EMPTY_ZONE)); }
+
/// \brief Return NSEC3Data of the zone.
///
/// This method returns non-NULL valid pointer to \c NSEC3Data object
diff --git a/src/lib/datasrc/memory/zone_data_loader.cc b/src/lib/datasrc/memory/zone_data_loader.cc
index e3c9c3f..e796dd4 100644
--- a/src/lib/datasrc/memory/zone_data_loader.cc
+++ b/src/lib/datasrc/memory/zone_data_loader.cc
@@ -50,14 +50,15 @@ typedef boost::function<void(isc::dns::ConstRRsetPtr)> LoadCallback;
// A helper internal class for \c loadZoneData(). make it non-copyable
// to avoid accidental copy.
//
-// The current internal implementation expects that both a normal
-// (non RRSIG) RRset and (when signed) its RRSIG are added at once.
-// Also in the current implementation, the input sequence of RRsets
-// are grouped with their owner name (so once a new owner name is encountered,
-// no subsequent RRset has the previous owner name), but the ordering
-// in the same group is not fixed. So we hold all RRsets of the same
-// owner name in node_rrsets_ and node_rrsigsets_, and add the matching
-// pairs of RRsets to the zone when we see a new owner name.
+// The current internal implementation no longer expects that both a
+// normal (non RRSIG) RRset and (when signed) its RRSIG are added at
+// once, but we do that here anyway to avoid merging RdataSets every
+// single time which can be inefficient.
+//
+// We hold all RRsets of the same owner name in node_rrsets_ and
+// node_rrsigsets_, and add the matching pairs of RRsets to the zone
+// when we see a new owner name. We do this to limit the size of
+// NodeRRsets below. However, RRsets can occur in any order.
//
// The caller is responsible for adding the RRsets of the last group
// in the input sequence by explicitly calling flushNodeRRsets() at the
@@ -86,6 +87,7 @@ private:
private:
NodeRRsets node_rrsets_;
NodeRRsets node_rrsigsets_;
+ std::vector<isc::dns::ConstRRsetPtr> non_consecutive_rrsets_;
ZoneDataUpdater updater_;
};
@@ -93,22 +95,20 @@ void
ZoneDataLoader::addFromLoad(const ConstRRsetPtr& rrset) {
// If we see a new name, flush the temporary holders, adding the
// pairs of RRsets and RRSIGs of the previous name to the zone.
- if ((!node_rrsets_.empty() || !node_rrsigsets_.empty()) &&
+ if ((!node_rrsets_.empty() || !node_rrsigsets_.empty() ||
+ !non_consecutive_rrsets_.empty()) &&
(getCurrentName() != rrset->getName())) {
flushNodeRRsets();
}
- // Store this RRset until it can be added to the zone. The current
- // implementation requires RRs of the same RRset should be added at
- // once, so we check the "duplicate" here.
+ // Store this RRset until it can be added to the zone. If an rrtype
+ // that's already been seen is found, queue it in a different vector
+ // to be merged later.
const bool is_rrsig = rrset->getType() == RRType::RRSIG();
NodeRRsets& node_rrsets = is_rrsig ? node_rrsigsets_ : node_rrsets_;
const RRType& rrtype = is_rrsig ? getCoveredType(rrset) : rrset->getType();
if (!node_rrsets.insert(NodeRRsetsVal(rrtype, rrset)).second) {
- isc_throw(ZoneDataUpdater::AddError,
- "Duplicate add of the same type of"
- << (is_rrsig ? " RRSIG" : "") << " RRset: "
- << rrset->getName() << "/" << rrtype);
+ non_consecutive_rrsets_.insert(non_consecutive_rrsets_.begin(), rrset);
}
if (rrset->getRRsig()) {
@@ -131,14 +131,24 @@ ZoneDataLoader::flushNodeRRsets() {
}
// Normally rrsigsets map should be empty at this point, but it's still
- // possible that an RRSIG that don't has covered RRset is added; they
+ // possible that an RRSIG that doesn't have covered RRset is added; they
// still remain in the map. We add them to the zone separately.
BOOST_FOREACH(NodeRRsetsVal val, node_rrsigsets_) {
updater_.add(ConstRRsetPtr(), val.second);
}
+ // Add any non-consecutive rrsets too.
+ BOOST_FOREACH(ConstRRsetPtr rrset, non_consecutive_rrsets_) {
+ if (rrset->getType() == RRType::RRSIG()) {
+ updater_.add(ConstRRsetPtr(), rrset);
+ } else {
+ updater_.add(rrset, ConstRRsetPtr());
+ }
+ }
+
node_rrsets_.clear();
node_rrsigsets_.clear();
+ non_consecutive_rrsets_.clear();
}
const Name&
@@ -172,35 +182,47 @@ loadZoneDataInternal(util::MemorySegment& mem_sgmt,
const Name& zone_name,
boost::function<void(LoadCallback)> rrset_installer)
{
- SegmentObjectHolder<ZoneData, RRClass> holder(
- mem_sgmt, ZoneData::create(mem_sgmt, zone_name), rrclass);
-
- ZoneDataLoader loader(mem_sgmt, rrclass, zone_name, *holder.get());
- rrset_installer(boost::bind(&ZoneDataLoader::addFromLoad, &loader, _1));
- // Add any last RRsets that were left
- loader.flushNodeRRsets();
-
- const ZoneNode* origin_node = holder.get()->getOriginNode();
- const RdataSet* rdataset = origin_node->getData();
- // If the zone is NSEC3-signed, check if it has NSEC3PARAM
- if (holder.get()->isNSEC3Signed()) {
- if (RdataSet::find(rdataset, RRType::NSEC3PARAM()) == NULL) {
- LOG_WARN(logger, DATASRC_MEMORY_MEM_NO_NSEC3PARAM).
- arg(zone_name).arg(rrclass);
+ while (true) { // Try as long as it takes to load and grow the segment
+ bool created = false;
+ try {
+ SegmentObjectHolder<ZoneData, RRClass> holder(mem_sgmt, rrclass);
+ holder.set(ZoneData::create(mem_sgmt, zone_name));
+
+ // Nothing from this point on should throw MemorySegmentGrown.
+ // It is handled inside here.
+ created = true;
+
+ ZoneDataLoader loader(mem_sgmt, rrclass, zone_name, *holder.get());
+ rrset_installer(boost::bind(&ZoneDataLoader::addFromLoad, &loader,
+ _1));
+ // Add any last RRsets that were left
+ loader.flushNodeRRsets();
+
+ const ZoneNode* origin_node = holder.get()->getOriginNode();
+ const RdataSet* rdataset = origin_node->getData();
+ // If the zone is NSEC3-signed, check if it has NSEC3PARAM
+ if (holder.get()->isNSEC3Signed()) {
+ if (RdataSet::find(rdataset, RRType::NSEC3PARAM()) == NULL) {
+ LOG_WARN(logger, DATASRC_MEMORY_MEM_NO_NSEC3PARAM).
+ arg(zone_name).arg(rrclass);
+ }
+ }
+
+ RRsetCollection collection(*(holder.get()), rrclass);
+ const dns::ZoneCheckerCallbacks
+ callbacks(boost::bind(&logError, &zone_name, &rrclass, _1),
+ boost::bind(&logWarning, &zone_name, &rrclass, _1));
+ if (!dns::checkZone(zone_name, rrclass, collection, callbacks)) {
+ isc_throw(ZoneValidationError,
+ "Errors found when validating zone: "
+ << zone_name << "/" << rrclass);
+ }
+
+ return (holder.release());
+ } catch (const util::MemorySegmentGrown&) {
+ assert(!created);
}
}
-
- RRsetCollection collection(*(holder.get()), rrclass);
- const dns::ZoneCheckerCallbacks
- callbacks(boost::bind(&logError, &zone_name, &rrclass, _1),
- boost::bind(&logWarning, &zone_name, &rrclass, _1));
- if (!dns::checkZone(zone_name, rrclass, collection, callbacks)) {
- isc_throw(ZoneValidationError,
- "Errors found when validating zone: "
- << zone_name << "/" << rrclass);
- }
-
- return (holder.release());
}
// A wrapper for dns::MasterLoader used by loadZoneData() below. Essentially
@@ -243,7 +265,10 @@ loadZoneData(util::MemorySegment& mem_sgmt,
const isc::dns::Name& zone_name,
const std::string& zone_file)
{
- return (loadZoneDataInternal(mem_sgmt, rrclass, zone_name,
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_LOAD_FROM_FILE).
+ arg(zone_name).arg(rrclass).arg(zone_file);
+
+ return (loadZoneDataInternal(mem_sgmt, rrclass, zone_name,
boost::bind(masterLoaderWrapper,
zone_file.c_str(),
zone_name, rrclass,
@@ -256,6 +281,9 @@ loadZoneData(util::MemorySegment& mem_sgmt,
const isc::dns::Name& zone_name,
ZoneIterator& iterator)
{
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_LOAD_FROM_DATASRC).
+ arg(zone_name).arg(rrclass);
+
return (loadZoneDataInternal(mem_sgmt, rrclass, zone_name,
boost::bind(generateRRsetFromIterator,
&iterator, _1)));
diff --git a/src/lib/datasrc/memory/zone_data_loader.h b/src/lib/datasrc/memory/zone_data_loader.h
index 32ed58b..56e1ada 100644
--- a/src/lib/datasrc/memory/zone_data_loader.h
+++ b/src/lib/datasrc/memory/zone_data_loader.h
@@ -57,7 +57,7 @@ ZoneData* loadZoneData(util::MemorySegment& mem_sgmt,
/// \c iterator.
///
/// Throws \c ZoneDataUpdater::AddError if invalid or inconsistent data
-/// is present in the \c zone_file. Throws \c isc::Unexpected if empty
+/// is present in the \c iterator. Throws \c isc::Unexpected if empty
/// RRsets are passed by the zone iterator. Throws \c EmptyZone if an
/// empty zone would be created due to the \c loadZoneData().
///
diff --git a/src/lib/datasrc/memory/zone_data_updater.cc b/src/lib/datasrc/memory/zone_data_updater.cc
index 5bde6d4..a8a88e6 100644
--- a/src/lib/datasrc/memory/zone_data_updater.cc
+++ b/src/lib/datasrc/memory/zone_data_updater.cc
@@ -49,14 +49,14 @@ ZoneDataUpdater::addWildcards(const Name& name) {
// Ensure a separate level exists for the "wildcarding"
// name, and mark the node as "wild".
ZoneNode* node;
- zone_data_.insertName(mem_sgmt_, wname.split(1), &node);
+ zone_data_->insertName(mem_sgmt_, wname.split(1), &node);
node->setFlag(ZoneData::WILDCARD_NODE);
// Ensure a separate level exists for the wildcard name.
// Note: for 'name' itself we do this later anyway, but the
// overhead should be marginal because wildcard names should
// be rare.
- zone_data_.insertName(mem_sgmt_, wname, &node);
+ zone_data_->insertName(mem_sgmt_, wname, &node);
}
}
}
@@ -210,7 +210,7 @@ ZoneDataUpdater::validate(const isc::dns::ConstRRsetPtr rrset) const {
const NSEC3Hash*
ZoneDataUpdater::getNSEC3Hash() {
if (hash_ == NULL) {
- NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
+ NSEC3Data* nsec3_data = zone_data_->getNSEC3Data();
// This should never be NULL in this codepath.
assert(nsec3_data != NULL);
@@ -231,11 +231,11 @@ ZoneDataUpdater::setupNSEC3(const ConstRRsetPtr rrset) {
dynamic_cast<const T&>(
rrset->getRdataIterator()->getCurrent());
- NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
+ NSEC3Data* nsec3_data = zone_data_->getNSEC3Data();
if (nsec3_data == NULL) {
nsec3_data = NSEC3Data::create(mem_sgmt_, zone_name_, nsec3_rdata);
- zone_data_.setNSEC3Data(nsec3_data);
- zone_data_.setSigned(true);
+ zone_data_->setNSEC3Data(nsec3_data);
+ zone_data_->setSigned(true);
} else {
const NSEC3Hash* hash = getNSEC3Hash();
if (!hash->match(nsec3_rdata)) {
@@ -247,14 +247,14 @@ ZoneDataUpdater::setupNSEC3(const ConstRRsetPtr rrset) {
}
void
-ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr rrset,
- const ConstRRsetPtr rrsig)
+ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr& rrset,
+ const ConstRRsetPtr& rrsig)
{
if (rrset) {
setupNSEC3<generic::NSEC3>(rrset);
}
- NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
+ NSEC3Data* nsec3_data = zone_data_->getNSEC3Data();
if (nsec3_data == NULL) {
// This is some tricky case: an RRSIG for NSEC3 is given without the
// covered NSEC3, and we don't even know any NSEC3 related data.
@@ -270,8 +270,12 @@ ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr rrset,
ZoneNode* node;
nsec3_data->insertName(mem_sgmt_, name, &node);
- RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, rrset, rrsig);
- RdataSet* old_rdataset = node->setData(rdataset);
+ // Create a new RdataSet, merging any existing NSEC3 data for this
+ // name.
+ RdataSet* old_rdataset = node->getData();
+ RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, rrset, rrsig,
+ old_rdataset);
+ old_rdataset = node->setData(rdataset);
if (old_rdataset != NULL) {
RdataSet::destroy(mem_sgmt_, old_rdataset, rrclass_);
}
@@ -279,14 +283,14 @@ ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr rrset,
void
ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
- const ConstRRsetPtr rrset,
- const ConstRRsetPtr rrsig)
+ const ConstRRsetPtr& rrset,
+ const ConstRRsetPtr& rrsig)
{
if (rrtype == RRType::NSEC3()) {
addNSEC3(name, rrset, rrsig);
} else {
ZoneNode* node;
- zone_data_.insertName(mem_sgmt_, name, &node);
+ zone_data_->insertName(mem_sgmt_, name, &node);
RdataSet* rdataset_head = node->getData();
@@ -298,21 +302,39 @@ ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
contextCheck(*rrset, rdataset_head);
}
- if (RdataSet::find(rdataset_head, rrtype, true) != NULL) {
- isc_throw(AddError,
- "RRset of the type already exists: "
- << name << " (type: " << rrtype << ")");
- }
-
+ // Create a new RdataSet, merging any existing data for this
+ // type.
+ RdataSet* old_rdataset = RdataSet::find(rdataset_head, rrtype, true);
RdataSet* rdataset_new = RdataSet::create(mem_sgmt_, encoder_,
- rrset, rrsig);
- rdataset_new->next = rdataset_head;
- node->setData(rdataset_new);
+ rrset, rrsig, old_rdataset);
+ if (old_rdataset == NULL) {
+ // There is no existing RdataSet. Prepend the new RdataSet
+ // to the list.
+ rdataset_new->next = rdataset_head;
+ node->setData(rdataset_new);
+ } else {
+ // Replace the old RdataSet in the list with the newly
+ // created one, and destroy the old one.
+ for (RdataSet* cur = rdataset_head, *prev = NULL;
+ cur != NULL;
+ prev = cur, cur = cur->getNext()) {
+ if (cur == old_rdataset) {
+ rdataset_new->next = cur->getNext();
+ if (prev == NULL) {
+ node->setData(rdataset_new);
+ } else {
+ prev->next = rdataset_new;
+ }
+ break;
+ }
+ }
+ RdataSet::destroy(mem_sgmt_, old_rdataset, rrclass_);
+ }
// Ok, we just put it in.
// Convenient (and more efficient) shortcut to check RRsets at origin
- const bool is_origin = (node == zone_data_.getOriginNode());
+ const bool is_origin = (node == zone_data_->getOriginNode());
// If this RRset creates a zone cut at this node, mark the node
// indicating the need for callback in find(). Note that we do this
@@ -334,7 +356,7 @@ ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
// (conceptually "signed" is a broader notion but our
// current zone finder implementation regards "signed" as
// "NSEC signed")
- zone_data_.setSigned(true);
+ zone_data_->setSigned(true);
}
// If we are adding a new SOA at the origin, update zone's min TTL.
@@ -343,7 +365,7 @@ ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
// this should be only once in normal cases) update the TTL.
if (rrset && rrtype == RRType::SOA() && is_origin) {
// Our own validation ensures the RRset is not empty.
- zone_data_.setMinTTL(
+ zone_data_->setMinTTL(
dynamic_cast<const generic::SOA&>(
rrset->getRdataIterator()->getCurrent()).getMinimum());
}
@@ -351,6 +373,24 @@ ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
}
void
+ZoneDataUpdater::addInternal(const isc::dns::Name& name,
+ const isc::dns::RRType& rrtype,
+ const isc::dns::ConstRRsetPtr& rrset,
+ const isc::dns::ConstRRsetPtr& rrsig)
+{
+ // Add wildcards possibly contained in the owner name to the domain
+ // tree. This can only happen for the normal (non-NSEC3) tree.
+ // Note: this can throw an exception, breaking strong exception
+ // guarantee. (see also the note for the call to contextCheck()
+ // above).
+ if (rrtype != RRType::NSEC3()) {
+ addWildcards(name);
+ }
+
+ addRdataSet(name, rrtype, rrset, rrsig);
+}
+
+void
ZoneDataUpdater::add(const ConstRRsetPtr& rrset,
const ConstRRsetPtr& sig_rrset)
{
@@ -375,16 +415,22 @@ ZoneDataUpdater::add(const ConstRRsetPtr& rrset,
arg(rrset ? rrtype.toText() : "RRSIG(" + rrtype.toText() + ")").
arg(zone_name_);
- // Add wildcards possibly contained in the owner name to the domain
- // tree. This can only happen for the normal (non-NSEC3) tree.
- // Note: this can throw an exception, breaking strong exception
- // guarantee. (see also the note for the call to contextCheck()
- // above).
- if (rrtype != RRType::NSEC3()) {
- addWildcards(name);
- }
-
- addRdataSet(name, rrtype, rrset, sig_rrset);
+ // Store the address, it may change during growth and the address inside
+ // would get updated.
+ bool added = false;
+ do {
+ try {
+ addInternal(name, rrtype, rrset, sig_rrset);
+ added = true;
+ } catch (const isc::util::MemorySegmentGrown&) {
+ // The segment has grown. So, we update the base pointer (because
+ // the data may have been remapped somewhere else in the process).
+ zone_data_ =
+ static_cast<ZoneData*>(
+ mem_sgmt_.getNamedAddress("updater_zone_data").second);
+ }
+ // Retry if it didn't add due to the growth
+ } while (!added);
}
} // namespace memory
diff --git a/src/lib/datasrc/memory/zone_data_updater.h b/src/lib/datasrc/memory/zone_data_updater.h
index 9d669a0..e8826bd 100644
--- a/src/lib/datasrc/memory/zone_data_updater.h
+++ b/src/lib/datasrc/memory/zone_data_updater.h
@@ -57,14 +57,15 @@ public:
/// The constructor.
///
- /// \throw none
- ///
/// \param mem_sgmt The memory segment used for the zone data.
/// \param rrclass The RRclass of the zone data.
/// \param zone_name The Name of the zone under which records will be
/// added.
- // \param zone_data The ZoneData object which is populated with
- // record data.
+ /// \param zone_data The ZoneData object which is populated with
+ /// record data.
+ /// \throw InvalidOperation if there's already a zone data updater
+ /// on the given memory segment. Currently, at most one zone data
+ /// updater may exist on the same memory segment.
ZoneDataUpdater(util::MemorySegment& mem_sgmt,
isc::dns::RRClass rrclass,
const isc::dns::Name& zone_name,
@@ -72,12 +73,25 @@ public:
mem_sgmt_(mem_sgmt),
rrclass_(rrclass),
zone_name_(zone_name),
- zone_data_(zone_data),
- hash_(NULL)
- {}
+ hash_(NULL),
+ zone_data_(&zone_data)
+ {
+ if (mem_sgmt_.getNamedAddress("updater_zone_data").first) {
+ isc_throw(isc::InvalidOperation, "A ZoneDataUpdater already exists"
+ " on this memory segment. Destroy it first.");
+ }
+ if (mem_sgmt_.setNamedAddress("updater_zone_data", zone_data_)) {
+ // It might have relocated during the set
+ zone_data_ =
+ static_cast<ZoneData*>(mem_sgmt_.getNamedAddress(
+ "updater_zone_data").second);
+ }
+ assert(zone_data_);
+ }
/// The destructor.
~ZoneDataUpdater() {
+ mem_sgmt_.clearNamedAddress("updater_zone_data");
delete hash_;
}
@@ -159,6 +173,11 @@ private:
// contained in 'name' (e.g., '*.foo.example' in 'bar.*.foo.example').
void addWildcards(const isc::dns::Name& name);
+ void addInternal(const isc::dns::Name& name,
+ const isc::dns::RRType& rrtype,
+ const isc::dns::ConstRRsetPtr& rrset,
+ const isc::dns::ConstRRsetPtr& rrsig);
+
// Does some checks in context of the data that are already in the
// zone. Currently checks for forbidden combinations of RRsets in
// the same domain (CNAME+anything, DNAME+NS). If such condition is
@@ -175,19 +194,19 @@ private:
template <typename T>
void setupNSEC3(const isc::dns::ConstRRsetPtr rrset);
void addNSEC3(const isc::dns::Name& name,
- const isc::dns::ConstRRsetPtr rrset,
- const isc::dns::ConstRRsetPtr rrsig);
+ const isc::dns::ConstRRsetPtr& rrset,
+ const isc::dns::ConstRRsetPtr& rrsig);
void addRdataSet(const isc::dns::Name& name,
const isc::dns::RRType& rrtype,
- const isc::dns::ConstRRsetPtr rrset,
- const isc::dns::ConstRRsetPtr rrsig);
+ const isc::dns::ConstRRsetPtr& rrset,
+ const isc::dns::ConstRRsetPtr& rrsig);
util::MemorySegment& mem_sgmt_;
const isc::dns::RRClass rrclass_;
const isc::dns::Name& zone_name_;
- ZoneData& zone_data_;
RdataEncoder encoder_;
const isc::dns::NSEC3Hash* hash_;
+ ZoneData* zone_data_;
};
} // namespace memory
diff --git a/src/lib/datasrc/memory/zone_finder.cc b/src/lib/datasrc/memory/zone_finder.cc
index 5acc5be..3f61b89 100644
--- a/src/lib/datasrc/memory/zone_finder.cc
+++ b/src/lib/datasrc/memory/zone_finder.cc
@@ -18,7 +18,7 @@
#include <datasrc/memory/rdata_serialization.h>
#include <datasrc/zone_finder.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <dns/labelsequence.h>
#include <dns/name.h>
#include <dns/rrset.h>
@@ -144,7 +144,7 @@ struct FindState {
// These will be set to a domain node of the highest delegation point,
// if any. In fact, we could use a single variable instead of both.
- // But then we would need to distinquish these two cases by something
+ // But then we would need to distinguish these two cases by something
// else and it seemed little more confusing when this was written.
const ZoneNode* zonecut_node_;
const ZoneNode* dname_node_;
diff --git a/src/lib/datasrc/memory/zone_table.cc b/src/lib/datasrc/memory/zone_table.cc
index c0237f5..454a0aa 100644
--- a/src/lib/datasrc/memory/zone_table.cc
+++ b/src/lib/datasrc/memory/zone_table.cc
@@ -12,14 +12,17 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <util/memory_segment.h>
-
-#include <dns/name.h>
-
-#include <datasrc/memory/zone_data.h>
#include <datasrc/memory/zone_table.h>
+#include <datasrc/memory/zone_data.h>
#include <datasrc/memory/domaintree.h>
#include <datasrc/memory/segment_object_holder.h>
+#include <datasrc/memory/logger.h>
+
+#include <exceptions/exceptions.h>
+
+#include <util/memory_segment.h>
+
+#include <dns/name.h>
#include <boost/function.hpp>
#include <boost/bind.hpp>
@@ -39,7 +42,10 @@ void
deleteZoneData(util::MemorySegment* mem_sgmt, ZoneData* zone_data,
RRClass rrclass)
{
- if (zone_data != NULL) {
+ // We shouldn't delete empty zone data here; the only empty zone
+ // that can be passed here is the placeholder for broken zones maintained
+ // in the zone table. It will stay there until the table is destroyed.
+ if (zone_data && !zone_data->isEmpty()) {
ZoneData::destroy(*mem_sgmt, zone_data, rrclass);
}
}
@@ -48,33 +54,63 @@ typedef boost::function<void(ZoneData*)> ZoneDataDeleterType;
ZoneTable*
ZoneTable::create(util::MemorySegment& mem_sgmt, const RRClass& zone_class) {
- SegmentObjectHolder<ZoneTableTree, ZoneDataDeleterType> holder(
- mem_sgmt, ZoneTableTree::create(mem_sgmt),
- boost::bind(deleteZoneData, &mem_sgmt, _1, zone_class));
+ // Create a placeholder "null" zone data
+ SegmentObjectHolder<ZoneData, RRClass> zdholder(mem_sgmt, zone_class);
+ zdholder.set(ZoneData::create(mem_sgmt));
+
+ // create and setup the tree for the table.
+ SegmentObjectHolder<ZoneTableTree, ZoneDataDeleterType> tree_holder(
+ mem_sgmt, boost::bind(deleteZoneData, &mem_sgmt, _1, zone_class));
+ tree_holder.set(ZoneTableTree::create(mem_sgmt));
void* p = mem_sgmt.allocate(sizeof(ZoneTable));
- ZoneTable* zone_table = new(p) ZoneTable(zone_class, holder.get());
- holder.release();
+
+ // Build zone table with the created objects. Its constructor doesn't
+ // throw, so we can release them from the holder at this point.
+ ZoneTable* zone_table = new(p) ZoneTable(zone_class, tree_holder.release(),
+ zdholder.release());
return (zone_table);
}
void
-ZoneTable::destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable)
+ZoneTable::destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable, int)
{
ZoneTableTree::destroy(mem_sgmt, ztable->zones_.get(),
boost::bind(deleteZoneData, &mem_sgmt, _1,
ztable->rrclass_));
+ ZoneData::destroy(mem_sgmt, ztable->null_zone_data_.get(),
+ ztable->rrclass_);
mem_sgmt.deallocate(ztable, sizeof(ZoneTable));
}
ZoneTable::AddResult
-ZoneTable::addZone(util::MemorySegment& mem_sgmt, RRClass zone_class,
+ZoneTable::addZone(util::MemorySegment& mem_sgmt,
const Name& zone_name, ZoneData* content)
{
- if (content == NULL) {
- isc_throw(isc::BadValue, "Zone content must not be NULL");
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_ADD_ZONE).
+ arg(zone_name).arg(rrclass_);
+
+ if (!content || content->isEmpty()) {
+ isc_throw(InvalidParameter,
+ (content ? "empty data" : "NULL") <<
+ " is passed to Zone::addZone");
}
- SegmentObjectHolder<ZoneData, RRClass> holder(mem_sgmt, content,
- zone_class);
+
+ return (addZoneInternal(mem_sgmt, zone_name, content));
+}
+
+ZoneTable::AddResult
+ZoneTable::addEmptyZone(util::MemorySegment& mem_sgmt, const Name& zone_name) {
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_ADD_EMPTY_ZONE).
+ arg(zone_name).arg(rrclass_);
+
+ return (addZoneInternal(mem_sgmt, zone_name, null_zone_data_.get()));
+}
+
+ZoneTable::AddResult
+ZoneTable::addZoneInternal(util::MemorySegment& mem_sgmt,
+ const dns::Name& zone_name,
+ ZoneData* content)
+{
// Get the node where we put the zone
ZoneTableNode* node(NULL);
switch (zones_->insert(mem_sgmt, zone_name, &node)) {
@@ -89,11 +125,11 @@ ZoneTable::addZone(util::MemorySegment& mem_sgmt, RRClass zone_class,
// Can Not Happen
assert(node != NULL);
- // We can release now, setData never throws
- ZoneData* old = node->setData(holder.release());
+ ZoneData* old = node->setData(content);
if (old != NULL) {
- return (AddResult(result::EXIST, old));
+ return (AddResult(result::EXIST, old->isEmpty() ? NULL : old));
} else {
+ ++zone_count_;
return (AddResult(result::SUCCESS, NULL));
}
}
@@ -121,10 +157,17 @@ ZoneTable::findZone(const Name& name) const {
return (FindResult(result::NOTFOUND, NULL));
}
- // Can Not Happen (remember, NOTFOUND is handled)
+ // Can Not Happen (remember, NOTFOUND is handled). node should also have
+ // data because the tree is constructed in the way empty nodes would
+ // be "invisible" for find().
assert(node != NULL);
- return (FindResult(my_result, node->getData()));
+ const ZoneData* zone_data = node->getData();
+ assert(zone_data);
+ const result::ResultFlags flags =
+ zone_data->isEmpty() ? result::ZONE_EMPTY : result::FLAGS_DEFAULT;
+ return (FindResult(my_result, zone_data->isEmpty() ? NULL : zone_data,
+ flags));
}
} // end of namespace memory
diff --git a/src/lib/datasrc/memory/zone_table.h b/src/lib/datasrc/memory/zone_table.h
index 1b369b9..6bad516 100644
--- a/src/lib/datasrc/memory/zone_table.h
+++ b/src/lib/datasrc/memory/zone_table.h
@@ -84,12 +84,16 @@ public:
};
/// \brief Result data of findZone() method.
+ ///
+ /// See \c findZone() about the semantics of the members.
struct FindResult {
FindResult(result::Result param_code,
- const ZoneData* param_zone_data) :
- code(param_code), zone_data(param_zone_data)
+ const ZoneData* param_zone_data,
+ result::ResultFlags param_flags = result::FLAGS_DEFAULT) :
+ code(param_code), flags(param_flags), zone_data(param_zone_data)
{}
const result::Result code;
+ const result::ResultFlags flags;
const ZoneData* const zone_data;
};
@@ -99,12 +103,13 @@ private:
/// An object of this class is always expected to be created by the
/// allocator (\c create()), so the constructor is hidden as private.
///
- /// This constructor internally involves resource allocation, and if
- /// it fails, a corresponding standard exception will be thrown.
- /// It never throws an exception otherwise.
- ZoneTable(const dns::RRClass& rrclass, ZoneTableTree* zones) :
+ /// This constructor never throws.
+ ZoneTable(const dns::RRClass& rrclass, ZoneTableTree* zones,
+ ZoneData* null_zone_data) :
rrclass_(rrclass),
- zones_(zones)
+ zone_count_(0),
+ zones_(zones),
+ null_zone_data_(null_zone_data)
{}
public:
@@ -114,6 +119,15 @@ public:
/// from the given memory segment, constructs the object, and returns
/// a pointer to it.
///
+ /// This method ensures there'll be no memory leak on exception.
+ /// But addresses allocated from \c mem_sgmt could be relocated if
+ /// \c util::MemorySegmentGrown is thrown; the caller or its upper layer
+ /// must be aware of that possibility and update any such addresses
+ /// accordingly. On successful return, this method ensures there's no
+ /// address relocation.
+ ///
+ /// \throw util::MemorySegmentGrown The memory segment has grown, possibly
+ /// relocating data.
/// \throw std::bad_alloc Memory allocation fails.
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
@@ -137,35 +151,83 @@ public:
/// \param ztable A non NULL pointer to a valid \c ZoneTable object
/// that was originally created by the \c create() method (the behavior
/// is undefined if this condition isn't met).
- static void destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable);
+ /// \param unused Ununsed parameter, provided so it's compatible to
+ /// SegmentObjectHolder.
+ static void destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable,
+ int unused = 0);
+
+ /// \brief Return the number of zones contained in the zone table.
+ ///
+ /// \throw None.
+ size_t getZoneCount() const { return (zone_count_); }
- /// Add a new zone to the \c ZoneTable.
+ /// \brief Add a new zone to the \c ZoneTable.
///
/// This method adds a given zone data to the internal table.
///
+ /// On successful completion (i.e., the method returns without an
+ /// exception), the ownership of \c content will be transferred to
+ /// the \c ZoneTable: the caller should not use the \c content hereafter;
+ /// the \c ZoneTable will be responsible to destroy it when the table
+ /// itself is destroyed.
+ ///
+ /// If this method throws, the caller is responsible to take care of
+ /// the passed \c content, whether to destroy it or use for different
+ /// purposes. Note that addresses allocated from \c mem_sgmt could be
+ /// relocated if \c util::MemorySegmentGrown is thrown; the caller or its
+ /// upper layer must be aware of that possibility and update any such
+ /// addresses accordingly. This applies to \c content, as it's expected
+ /// to be created using \c mem_sgmt.
+ ///
+ /// On successful return, this method ensures there's no address
+ /// relocation.
+ ///
+ /// \throw InvalidParameter content is NULL or empty.
+ /// \throw util::MemorySegmentGrown The memory segment has grown, possibly
+ /// relocating data.
/// \throw std::bad_alloc Internal resource allocation fails.
///
/// \param mem_sgmt The \c MemorySegment to allocate zone data to be
/// created. It must be the same segment that was used to create
/// the zone table at the time of create().
/// \param zone_name The name of the zone to be added.
- /// \param zone_class The RR class of the zone. It must be the RR class
- /// that is supposed to be associated to the zone table.
/// \param content This one should hold the zone content (the ZoneData).
- /// The ownership is passed onto the zone table. Must not be null.
- /// Must correspond to the name and class and must be allocated from
- /// mem_sgmt.
+ /// The ownership is passed onto the zone table. Must not be null or
+ /// empty. Must correspond to the name and class and must be allocated
+ /// from mem_sgmt.
/// \return \c result::SUCCESS If the zone is successfully
/// added to the zone table.
/// \return \c result::EXIST The zone table already contained
/// zone of the same origin. The old data is replaced and returned
- /// inside the result.
+ /// inside the result unless it's empty; if the zone was previously
+ /// added by \c addEmptyZone(), the data returned is NULL.
AddResult addZone(util::MemorySegment& mem_sgmt,
- dns::RRClass zone_class,
const dns::Name& zone_name,
ZoneData* content);
- /// Find a zone that best matches the given name in the \c ZoneTable.
+ /// \brief Add an empty zone to the \c ZoneTable.
+ ///
+ /// This method is similar to \c addZone(), but adds a conceptual "empty"
+ /// zone of the given zone name to the table. The added empty zone
+ /// affects subsequent calls to \c addZone() (and \c addEmptyZone() itself)
+ /// and \c findZone() as described for these methods.
+ ///
+ /// The intended meaning of an empty zone in the table is that the zone
+ /// is somehow broken, such as configured to be loaded but loading failed.
+ /// But this class is not aware of such interpretation; it's up to the
+ /// user of the class how to use the concept of empty zones.
+ ///
+ /// It returns an \c AddResult object as described for \c addZone().
+ ///
+ /// The same notes on exception safety as that for \c addZone() applies.
+ ///
+ /// \param mem_sgmt Same as addZone().
+ /// \param zone_name Same as addZone().
+ AddResult addEmptyZone(util::MemorySegment& mem_sgmt,
+ const dns::Name& zone_name);
+
+ /// \brief Find a zone that best matches the given name in the
+ /// \c ZoneTable.
///
/// It searches the internal storage for a zone that gives the
/// longest match against \c name, and returns the result in the
@@ -176,8 +238,11 @@ public:
/// - \c result::PARTIALMATCH: A zone whose origin is a
/// super domain of \c name is found (but there is no exact match)
/// - \c result::NOTFOUND: For all other cases.
- /// - \c zone_data: corresponding zone data of the found zone; NULL if
- /// no matching zone is found.
+ /// - \c flags If the zone is empty (added by \c addEmptyZone()),
+ /// result::ZONE_EMPTY is set.
+ /// - \c zone_data: corresponding zone data of the found zone if found and
+ /// non empty; NULL if no matching zone is found or the found zone is
+ /// empty.
///
/// \throw none
///
@@ -187,7 +252,20 @@ public:
private:
const dns::RRClass rrclass_;
+ size_t zone_count_;
boost::interprocess::offset_ptr<ZoneTableTree> zones_;
+
+ // this is a shared placeholder for broken zones
+ boost::interprocess::offset_ptr<ZoneData> null_zone_data_;
+
+ // Common routine for addZone and addEmptyZone. This method can throw
+ // util::MemorySegmentGrown, in which case addresses from mem_sgmt
+ // can be relocated. The caller is responsible for destroying content
+ // on exception, if it needs to be destroyed. On successful return it
+ // ensures there's been no address relocation.
+ AddResult addZoneInternal(util::MemorySegment& mem_sgmt,
+ const dns::Name& zone_name,
+ ZoneData* content);
};
}
}
diff --git a/src/lib/datasrc/memory/zone_table_segment.cc b/src/lib/datasrc/memory/zone_table_segment.cc
index 50587c4..2e1a1dc 100644
--- a/src/lib/datasrc/memory/zone_table_segment.cc
+++ b/src/lib/datasrc/memory/zone_table_segment.cc
@@ -12,8 +12,16 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include "config.h"
+
#include <datasrc/memory/zone_table_segment.h>
#include <datasrc/memory/zone_table_segment_local.h>
+#ifdef USE_SHARED_MEMORY
+#include <datasrc/memory/zone_table_segment_mapped.h>
+#endif
+#include <datasrc/memory/zone_writer.h>
+
+#include <string>
using namespace isc::dns;
@@ -22,13 +30,19 @@ namespace datasrc {
namespace memory {
ZoneTableSegment*
-ZoneTableSegment::create(const isc::data::Element&, const RRClass& rrclass) {
- /// FIXME: For now, we always return ZoneTableSegmentLocal. This
- /// should be updated eventually to parse the passed Element
- /// argument and construct a corresponding ZoneTableSegment
- /// implementation.
-
- return (new ZoneTableSegmentLocal(rrclass));
+ZoneTableSegment::create(const RRClass& rrclass, const std::string& type) {
+ // This will be a few sequences of if-else and hardcoded. Not really
+ // sophisticated, but we don't expect to have too many types at the moment.
+ // Until that it becomes a real issue we won't be too smart.
+ if (type == "local") {
+ return (new ZoneTableSegmentLocal(rrclass));
+#ifdef USE_SHARED_MEMORY
+ } else if (type == "mapped") {
+ return (new ZoneTableSegmentMapped(rrclass));
+#endif
+ }
+ isc_throw(UnknownSegmentType, "Zone table segment type not supported: "
+ << type);
}
void
diff --git a/src/lib/datasrc/memory/zone_table_segment.h b/src/lib/datasrc/memory/zone_table_segment.h
index 88e69f6..0f1942e 100644
--- a/src/lib/datasrc/memory/zone_table_segment.h
+++ b/src/lib/datasrc/memory/zone_table_segment.h
@@ -15,15 +15,20 @@
#ifndef ZONE_TABLE_SEGMENT_H
#define ZONE_TABLE_SEGMENT_H
+#include <exceptions/exceptions.h>
+
#include <dns/rrclass.h>
+
#include <datasrc/memory/zone_table.h>
-#include "load_action.h"
+#include <datasrc/memory/load_action.h>
+
#include <cc/data.h>
#include <util/memory_segment.h>
#include <boost/interprocess/offset_ptr.hpp>
-#include <stdlib.h>
+#include <cstdlib>
+#include <string>
namespace isc {
// Some forward declarations
@@ -35,12 +40,45 @@ namespace datasrc {
namespace memory {
class ZoneWriter;
+/// \brief Exception thrown when unknown or unsupported type of
+/// ZoneTableSegment is asked to be created.
+class UnknownSegmentType : public Exception {
+public:
+ UnknownSegmentType(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ {}
+};
+
+/// \brief Exception thrown when a \c reset() on a \c ZoneTableSegment
+/// fails (due to various reasons). When this exception is thrown, a
+/// strong exception safety guarantee is provided, and the
+/// \c ZoneTableSegment is usable as before.
+class ResetFailed : public isc::Exception {
+public:
+ ResetFailed(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+/// \brief Exception thrown when a \c reset() on a \c ZoneTableSegment
+/// fails (due to various reasons), and it was not able to preserve the
+/// state of the \c ZoneTableSegment. When this exception is thrown,
+/// only basic exception safety guarantee is provided and the
+/// \c ZoneTableSegment must be expected as cleared.
+class ResetFailedAndSegmentCleared : public isc::Exception {
+public:
+ ResetFailedAndSegmentCleared(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
/// \brief Memory-management independent entry point that contains a
/// pointer to a zone table in memory.
///
-/// An instance of this type lives inside a ZoneTableSegment
-/// implementation. It contains an offset pointer to the zone table (a
-/// map from domain names to zone locators) in memory.
+/// An instance of this type lives inside a \c ZoneTableSegment
+/// implementation. It contains an offset pointer to the \c ZoneTable (a
+/// map from domain names to zone locators) in the \c ZoneTableSegment.
struct ZoneTableHeader {
public:
ZoneTableHeader(ZoneTable* zone_table) :
@@ -60,14 +98,19 @@ private:
boost::interprocess::offset_ptr<ZoneTable> table_;
};
-/// \brief Manages a ZoneTableHeader, an entry point into a table of
+/// \brief Manages a \c ZoneTableHeader, an entry point into a table of
/// zones
///
/// This class specifies an interface for derived implementations which
-/// return a pointer to an object of type ZoneTableHeader, an entry
+/// return a pointer to an object of type \c ZoneTableHeader, an entry
/// point into a table of zones regardless of the underlying memory
-/// management implementation. Derived classes would implement the
-/// interface for specific memory-implementation behavior.
+/// management implementation. Derived classes implement the interface
+/// for the specific memory-implementation behavior.
+///
+/// Note: At some point in the future, methods such as \c reset(),
+/// \c clear(), \c getHeader(), \c isWritable(), \c isUsable() may
+/// become non-virtual methods. Such a change should not affect any code
+/// that uses this class, but please be aware of such plans.
class ZoneTableSegment {
protected:
/// \brief Protected constructor
@@ -75,71 +118,225 @@ protected:
/// An instance implementing this interface is expected to be
/// created by the factory method (\c create()), so this constructor
/// is protected.
- ZoneTableSegment(isc::dns::RRClass)
+ ZoneTableSegment(const isc::dns::RRClass&)
{}
public:
/// \brief Destructor
virtual ~ZoneTableSegment() {}
- /// \brief Return the ZoneTableHeader for the zone table segment.
+ /// \brief Return a string name for the \c ZoneTableSegment
+ /// implementation.
+ ///
+ /// Implementations of this method should ensure that the returned
+ /// string is identical to the corresponding string passed to
+ /// \c ZoneTableSegment::create() for that implementation.
+ ///
+ /// \throw None This method's implementations must be
+ /// exception-free.
+ virtual const std::string& getImplType() const = 0;
+
+ /// \brief Return the \c ZoneTableHeader for the zone table segment.
+ ///
+ /// As long as \c isUsable() returns true, this method must always
+ /// succeed without throwing an exception. If \c isUsable() returns
+ /// false, a derived class implementation can throw
+ /// \c isc::InvalidOperation depending on its implementation
+ /// details. Applications are generally expected to call this
+ /// method only when \c isUsable() returns true (either by making
+ /// sure explicitly or by some other indirect means).
+ ///
+ /// \throw isc::InvalidOperation may be thrown by some
+ /// implementations if this method is called without calling
+ /// \c reset() successfully first.
virtual ZoneTableHeader& getHeader() = 0;
- /// \brief const version of \c getHeader().
+ /// \brief \c const version of \c getHeader().
+ ///
+ /// See the non- \c const version for documentation.
virtual const ZoneTableHeader& getHeader() const = 0;
/// \brief Return the MemorySegment for the zone table segment.
+ ///
+ /// \throw isc::InvalidOperation may be thrown by some
+ /// implementations if this method is called without calling
+ /// \c reset() successfully first.
virtual isc::util::MemorySegment& getMemorySegment() = 0;
- /// \brief Create an instance depending on the memory segment model
+ /// \brief Return true if the segment is writable.
+ ///
+ /// The user of the zone table segment will load or update zones
+ /// into the segment only for writable ones. The precise definition
+ /// of "writability" differs in different derived classes (see
+ /// derived class documentation). In general, however, the user
+ /// should only rely on this interface rather than assume a specific
+ /// definition for a specific type of segment.
+ ///
+ /// \throw None This method's implementations must be
+ /// exception-free.
+ virtual bool isWritable() const = 0;
+
+ /// \brief Create an instance depending on the requested memory
+ /// segment implementation type.
///
- /// This is a factory method to create a derived ZoneTableSegment
+ /// This is a factory method to create a derived \c ZoneTableSegment
/// object based on the \c config passed. The method returns a
/// dynamically-allocated object. The caller is responsible for
/// destroying it with \c ZoneTableSegment::destroy().
///
- /// FIXME: For now, we always return ZoneTableSegmentLocal
- /// regardless of the passed \c config.
+ /// \throw UnknownSegmentType The memory segment type specified in
+ /// \c config is not known or not supported in this implementation.
///
- /// \param config The configuration based on which a derived object
- /// is returned.
- /// \return Returns a ZoneTableSegment object
- static ZoneTableSegment* create(const isc::data::Element& config,
- const isc::dns::RRClass& rrclass);
+ /// \param rrclass The RR class of the zones to be maintained in the table.
+ /// \param type The memory segment type to be used.
+ /// \return Returns a \c ZoneTableSegment object of the specified type.
+ static ZoneTableSegment* create(const isc::dns::RRClass& rrclass,
+ const std::string& type);
- /// \brief Temporary/Testing version of create.
+ /// \brief Destroy a \c ZoneTableSegment
///
- /// This exists as a temporary solution during the migration phase
- /// towards using the ZoneTableSegment. It doesn't take a config,
- /// but a memory segment instead. If you can, you should use the
- /// other version, this one will be gone soon.
+ /// This method destroys the passed \c ZoneTableSegment. It must be
+ /// passed a segment previously created by
+ /// \c ZoneTableSegment::create().
///
- /// \param segment The memory segment to use.
- /// \return Returns a new ZoneTableSegment object.
- /// \todo Remove this method.
- static ZoneTableSegment* create(isc::util::MemorySegment& segment);
+ /// \param segment The segment to destroy.
+ static void destroy(ZoneTableSegment* segment);
- /// \brief Destroy a ZoneTableSegment
+ /// \brief The mode using which to create a MemorySegment.
///
- /// This method destroys the passed ZoneTableSegment. It must be
- /// passed a segment previously created by \c ZoneTableSegment::create().
+ /// Here, a \c MemorySegment (see its class documentation) is an
+ /// interface to a storage area, and provides operations to allocate
+ /// and deallocate from that storage area, and also to look up
+ /// addresses in that area. The storage area can be a buffer in
+ /// memory, a file on disk, or some kind of shared memory depending
+ /// on the \c MemorySegment implementation being used. In every
+ /// case in the documentation below, when we mention \c
+ /// MemorySegment, we mean both the \c MemorySegment object which
+ /// interfaces to the storage area and the contents of the
+ /// associated storage area.
///
- /// \param segment The segment to destroy.
- static void destroy(ZoneTableSegment* segment);
+ /// - CREATE: If the \c MemorySegment's storage area doesn't exist,
+ /// create it. If it exists, overwrite it with a new
+ /// storage area (which does not remember old data). In
+ /// both cases, create a \c MemorySegment for it in
+ /// read+write mode.
+ ///
+ /// - READ_WRITE: If the \c MemorySegment's storage area doesn't
+ /// exist, create it. If it exists, use the existing
+ /// storage area as-is (keeping the existing data
+ /// intact). In both cases, create a \c MemorySegment
+ /// for it in read+write mode.
+ ///
+ /// - READ_ONLY: If the \c MemorySegment's storage area doesn't
+ /// exist, throw an exception. If it exists, create a
+ /// \c MemorySegment for it in read-only mode.
+ enum MemorySegmentOpenMode {
+ CREATE,
+ READ_WRITE,
+ READ_ONLY
+ };
- /// \brief Create a zone write corresponding to this segment
+ /// \brief Close the current \c MemorySegment (if open) and open the
+ /// requested one.
+ ///
+ /// When we talk about "opening" a \c MemorySegment, it means to
+ /// construct a usable \c MemorySegment object that interfaces to
+ /// the actual memory storage area. "Closing" is the opposite
+ /// operation of opening.
+ ///
+ /// In case opening the new \c MemorySegment fails for some reason,
+ /// one of the following documented (further below) exceptions may
+ /// be thrown. In case failures occur, implementations of this
+ /// method must strictly provide the associated behavior as follows
+ /// and in the exception documentation below. Code that uses
+ /// \c ZoneTableSegment would depend on such assurances.
+ ///
+ /// First, in case a \c ZoneTableSegment was reset successfully
+ /// before and is currently usable (\c isUsable() returns true), and
+ /// an invalid configuration is passed in \c params to \c reset(),
+ /// the isc::InvalidParameter exception must be thrown. In this
+ /// case, a strong exception safety guarantee must be provided, and
+ /// the \c ZoneTableSegment must be usable as before.
+ ///
+ /// In case a \c ZoneTableSegment was reset successfully before and
+ /// is currently usable (\c isUsable() returns true), and the attempt
+ /// to reset to a different \c MemorySegment storage area fails,
+ /// the \c ResetFailed exception must be thrown. In this
+ /// case, a strong exception safety guarantee must be provided, and
+ /// the \c ZoneTableSegment must be usable as before.
+ ///
+ /// In case a \c ZoneTableSegment was reset successfully before and
+ /// is currently usable (\c isUsable() returns true), and the attempt
+ /// to reset to the same \c MemorySegment storage area fails, the
+ /// \c ResetFailedAndSegmentCleared exception must be thrown. In
+ /// this case, only basic exception safety guarantee is provided and
+ /// the \c ZoneTableSegment must be expected as cleared.
+ ///
+ /// In case a \c ZoneTableSegment was not reset successfully before
+ /// and is currently not usable (\c isUsable() returns false), and
+ /// the attempt to reset fails, the \c ResetFailed exception must be
+ /// thrown. In this unique case, a strong exception safety guarantee
+ /// is provided by default, as the \c ZoneTableSegment was clear
+ /// previously, and remains cleared.
+ ///
+ /// In all other cases, \c ZoneTableSegment contents can be expected
+ /// as reset.
///
- /// This creates a new write that can be used to update zones
- /// inside this zone table segment.
+ /// See \c MemorySegmentOpenMode for a definition of "storage area"
+ /// and the various modes in which a \c MemorySegment can be opened.
///
- /// \param loadAction Callback to provide the actual data.
- /// \param origin The origin of the zone to reload.
- /// \param rrclass The class of the zone to reload.
- /// \return New instance of a zone writer. The ownership is passed
- /// onto the caller and the caller needs to \c delete it when
- /// it's done with the writer.
- virtual ZoneWriter* getZoneWriter(const LoadAction& load_action,
- const dns::Name& origin,
- const dns::RRClass& rrclass) = 0;
+ /// \c params should contain an implementation-defined
+ /// configuration. See the specific \c ZoneTableSegment
+ /// implementation class for details of what to pass in this
+ /// argument.
+ ///
+ /// \throw isc::InvalidParameter if the configuration in \c params
+ /// has incorrect syntax, but there is a strong exception safety
+ /// guarantee and the \c ZoneTableSegment is usable or unusable as
+ /// before.
+ ///
+ /// \throw ResetFailed if there was a problem in opening the new
+ /// memory store, but there is a strong exception safety guarantee
+ /// and the \c ZoneTableSegment is usable or unusable as before.
+ ///
+ /// \throw ResetFailedAndSegmentCleared if there was a problem in
+ /// opening the new memory store, but there is only a basic
+ /// exception safety guarantee and the \c ZoneTableSegment is not
+ /// usable without a further successful \c reset().
+ ///
+ /// \throw isc::NotImplemented Some implementations may choose to
+ /// not implement this method. In this case, there must be a strong
+ /// exception safety guarantee and the \c ZoneTableSegment is usable
+ /// or unusable as before.
+ ///
+ /// \param mode The open mode (see the MemorySegmentOpenMode
+ /// documentation).
+ /// \param params An element containing implementation-specific
+ /// config (see the description).
+ virtual void reset(MemorySegmentOpenMode mode,
+ isc::data::ConstElementPtr params) = 0;
+
+ /// \brief Close the currently configured \c MemorySegment (if
+ /// open).
+ ///
+ /// See the \c reset() method's documentation for a definition of
+ /// "open" and "close".
+ ///
+ /// Implementations of this method should close any currently
+ /// configured \c MemorySegment and clear the `ZoneTableSegment` to
+ /// a freshly constructed state.
+ ///
+ /// \throw isc::NotImplemented Some implementations may choose to
+ /// not implement this method. In this case, there must be a strong
+ /// exception safety guarantee and the \c ZoneTableSegment is usable
+ /// or unusable as before.
+ virtual void clear() = 0;
+
+ /// \brief Return true if the \c ZoneTableSegment has been
+ /// successfully \c reset().
+ ///
+ /// Note that after calling \c clear(), this method will return
+ /// false until the segment is reset successfully again.
+ virtual bool isUsable() const = 0;
};
} // namespace memory
@@ -147,3 +344,7 @@ public:
} // namespace isc
#endif // ZONE_TABLE_SEGMENT_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/memory/zone_table_segment_local.cc b/src/lib/datasrc/memory/zone_table_segment_local.cc
index fdaf678..e0ee369 100644
--- a/src/lib/datasrc/memory/zone_table_segment_local.cc
+++ b/src/lib/datasrc/memory/zone_table_segment_local.cc
@@ -13,7 +13,6 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <datasrc/memory/zone_table_segment_local.h>
-#include "zone_writer_local.h"
using namespace isc::dns;
using namespace isc::util;
@@ -24,6 +23,7 @@ namespace memory {
ZoneTableSegmentLocal::ZoneTableSegmentLocal(const RRClass& rrclass) :
ZoneTableSegment(rrclass),
+ impl_type_("local"),
header_(ZoneTable::create(mem_sgmt_, rrclass))
{
}
@@ -38,6 +38,28 @@ ZoneTableSegmentLocal::~ZoneTableSegmentLocal() {
assert(mem_sgmt_.allMemoryDeallocated());
}
+const std::string&
+ZoneTableSegmentLocal::getImplType() const {
+ return (impl_type_);
+}
+
+void
+ZoneTableSegmentLocal::reset(MemorySegmentOpenMode,
+ isc::data::ConstElementPtr)
+{
+ isc_throw(isc::NotImplemented,
+ "ZoneTableSegmentLocal::reset() is not implemented and "
+ "should not be used.");
+}
+
+void
+ZoneTableSegmentLocal::clear()
+{
+ isc_throw(isc::NotImplemented,
+ "ZoneTableSegmentLocal::clear() is not implemented and "
+ "should not be used.");
+}
+
// After more methods' definitions are added here, it would be a good
// idea to move getHeader() and getMemorySegment() definitions to the
// header file.
@@ -56,14 +78,6 @@ ZoneTableSegmentLocal::getMemorySegment() {
return (mem_sgmt_);
}
-ZoneWriter*
-ZoneTableSegmentLocal::getZoneWriter(const LoadAction& load_action,
- const dns::Name& name,
- const dns::RRClass& rrclass)
-{
- return (new ZoneWriterLocal(this, load_action, name, rrclass));
-}
-
} // namespace memory
} // namespace datasrc
} // namespace isc
diff --git a/src/lib/datasrc/memory/zone_table_segment_local.h b/src/lib/datasrc/memory/zone_table_segment_local.h
index e08ca39..c2312fa 100644
--- a/src/lib/datasrc/memory/zone_table_segment_local.h
+++ b/src/lib/datasrc/memory/zone_table_segment_local.h
@@ -18,19 +18,22 @@
#include <datasrc/memory/zone_table_segment.h>
#include <util/memory_segment_local.h>
+#include <string>
+
namespace isc {
namespace datasrc {
namespace memory {
-/// \brief Local implementation of ZoneTableSegment class
+/// \brief Local implementation of \c ZoneTableSegment class
///
/// This class specifies a concrete implementation for a
-/// MemorySegmentLocal based ZoneTableSegment. Please see the
-/// ZoneTableSegment class documentation for usage.
+/// \c MemorySegmentLocal -based \c ZoneTableSegment. Please see the
+/// \c ZoneTableSegment class documentation for usage.
class ZoneTableSegmentLocal : public ZoneTableSegment {
- // This is so that ZoneTableSegmentLocal can be instantiated from
- // ZoneTableSegment::create().
+ // This is so that \c ZoneTableSegmentLocal can be instantiated from
+ // \c ZoneTableSegment::create().
friend class ZoneTableSegment;
+
protected:
/// \brief Protected constructor
///
@@ -38,26 +41,60 @@ protected:
/// (\c ZoneTableSegment::create()), so this constructor is
/// protected.
ZoneTableSegmentLocal(const isc::dns::RRClass& rrclass);
+
public:
/// \brief Destructor
virtual ~ZoneTableSegmentLocal();
- /// \brief Return the ZoneTableHeader for the local zone table
- /// segment implementation.
+ /// \brief Returns "local" as the implementation type.
+ virtual const std::string& getImplType() const;
+
+ /// \brief Return the \c ZoneTableHeader for this local zone table
+ /// segment.
virtual ZoneTableHeader& getHeader();
- /// \brief const version of \c getHeader().
+ /// \brief \c const version of \c getHeader().
virtual const ZoneTableHeader& getHeader() const;
- /// \brief Return the MemorySegment for the local zone table segment
- /// implementation (a MemorySegmentLocal instance).
+ /// \brief Return the \c MemorySegment for the local zone table
+ /// segment implementation (a \c MemorySegmentLocal instance).
virtual isc::util::MemorySegment& getMemorySegment();
- /// \brief Concrete implementation of ZoneTableSegment::getZoneWriter
- virtual ZoneWriter* getZoneWriter(const LoadAction& load_action,
- const dns::Name& origin,
- const dns::RRClass& rrclass);
+ /// \brief Return true if the segment is writable.
+ ///
+ /// Local segments are always writable. This implementation always
+ /// returns true.
+ virtual bool isWritable() const {
+ return (true);
+ }
+
+ /// \brief This method is not implemented.
+ ///
+ /// Resetting a local \c ZoneTableSegment is not supported at this
+ /// time.
+ ///
+ /// \throw isc::NotImplemented
+ virtual void reset(MemorySegmentOpenMode mode,
+ isc::data::ConstElementPtr params);
+
+ /// \brief This method is not implemented.
+ ///
+ /// Clearing a local \c ZoneTableSegment is not supported at this
+ /// time.
+ ///
+ /// \throw isc::NotImplemented
+ virtual void clear();
+
+ /// \brief Return true if the segment is usable.
+ ///
+ /// Local segments are always usable. This implementation always
+ /// returns true.
+ virtual bool isUsable() const {
+ return (true);
+ }
+
private:
+ std::string impl_type_;
isc::util::MemorySegmentLocal mem_sgmt_;
ZoneTableHeader header_;
};
diff --git a/src/lib/datasrc/memory/zone_table_segment_mapped.cc b/src/lib/datasrc/memory/zone_table_segment_mapped.cc
new file mode 100644
index 0000000..330107f
--- /dev/null
+++ b/src/lib/datasrc/memory/zone_table_segment_mapped.cc
@@ -0,0 +1,392 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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/memory/zone_table_segment_mapped.h>
+#include <datasrc/memory/zone_table.h>
+#include <datasrc/memory/segment_object_holder.h>
+
+#include <memory>
+
+using namespace isc::data;
+using namespace isc::dns;
+using namespace isc::util;
+using isc::datasrc::memory::detail::SegmentObjectHolder;
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+namespace { // unnamed namespace
+
+// The name with which the zone table checksum is associated in the segment.
+const char* const ZONE_TABLE_CHECKSUM_NAME = "zone_table_checksum";
+
+// The name with which the zone table header is associated in the segment.
+const char* const ZONE_TABLE_HEADER_NAME = "zone_table_header";
+
+} // end of unnamed namespace
+
+ZoneTableSegmentMapped::ZoneTableSegmentMapped(const RRClass& rrclass) :
+ ZoneTableSegment(rrclass),
+ impl_type_("mapped"),
+ rrclass_(rrclass)
+{
+}
+
+ZoneTableSegmentMapped::~ZoneTableSegmentMapped() {
+ sync();
+}
+
+const std::string&
+ZoneTableSegmentMapped::getImplType() const {
+ return (impl_type_);
+}
+
+bool
+ZoneTableSegmentMapped::processChecksum(MemorySegmentMapped& segment,
+ bool create, bool has_allocations,
+ std::string& error_msg)
+{
+ const MemorySegment::NamedAddressResult result =
+ segment.getNamedAddress(ZONE_TABLE_CHECKSUM_NAME);
+ if (result.first) {
+ if (create) {
+ // There must be no previously saved checksum.
+ error_msg = "There is already a saved checksum in the segment "
+ "opened in create mode";
+ return (false);
+ } else {
+ // The segment was already shrunk when it was last
+ // closed. Check that its checksum is consistent.
+ assert(result.second);
+ size_t* checksum = static_cast<size_t*>(result.second);
+ const size_t saved_checksum = *checksum;
+ // First, clear the checksum so that getCheckSum() returns a
+ // consistent value.
+ *checksum = 0;
+ const size_t new_checksum = segment.getCheckSum();
+ if (saved_checksum != new_checksum) {
+ error_msg = "Saved checksum doesn't match segment data";
+ return (false);
+ }
+ }
+ } else {
+ if ((!create) && has_allocations) {
+ // If we are resetting in READ_WRITE mode, and some memory
+ // was already allocated but there is no checksum name, that
+ // indicates that the segment is corrupted.
+ error_msg = "Existing segment is missing a checksum name";
+ return (false);
+ }
+
+ // Allocate space for a checksum (which is saved during close).
+ void* checksum = NULL;
+ while (!checksum) {
+ try {
+ checksum = segment.allocate(sizeof(size_t));
+ } catch (const MemorySegmentGrown&) {
+ // Do nothing and try again.
+ }
+ }
+ *static_cast<size_t*>(checksum) = 0;
+ segment.setNamedAddress(ZONE_TABLE_CHECKSUM_NAME, checksum);
+ }
+
+ return (true);
+}
+
+bool
+ZoneTableSegmentMapped::processHeader(MemorySegmentMapped& segment,
+ bool create, bool has_allocations,
+ std::string& error_msg)
+{
+ const MemorySegment::NamedAddressResult result =
+ segment.getNamedAddress(ZONE_TABLE_HEADER_NAME);
+ if (result.first) {
+ if (create) {
+ // There must be no previously saved header.
+ error_msg = "There is already a saved ZoneTableHeader in the "
+ "segment opened in create mode";
+ return (false);
+ } else {
+ assert(result.second);
+ }
+ } else {
+ if ((!create) && has_allocations) {
+ // If we are resetting in READ_WRITE mode, and some memory
+ // was already allocated but there is no header name, that
+ // indicates that the segment is corrupted.
+ error_msg = "Existing segment is missing a ZoneTableHeader name";
+ return (false);
+ }
+
+ while (true) {
+ try {
+ SegmentObjectHolder<ZoneTable, int> zt_holder(segment, 0);
+ zt_holder.set(ZoneTable::create(segment, rrclass_));
+ void* ptr = segment.allocate(sizeof(ZoneTableHeader));
+ ZoneTableHeader* new_header = new(ptr)
+ ZoneTableHeader(zt_holder.release());
+ segment.setNamedAddress(ZONE_TABLE_HEADER_NAME, new_header);
+ break;
+ } catch (const MemorySegmentGrown&) {}
+ }
+ }
+
+ return (true);
+}
+
+MemorySegmentMapped*
+ZoneTableSegmentMapped::openReadWrite(const std::string& filename,
+ bool create)
+{
+ const MemorySegmentMapped::OpenMode mode = create ?
+ MemorySegmentMapped::CREATE_ONLY :
+ MemorySegmentMapped::OPEN_OR_CREATE;
+ // In case there is a problem, we throw. We want the segment to be
+ // automatically destroyed then.
+ std::auto_ptr<MemorySegmentMapped> segment
+ (new MemorySegmentMapped(filename, mode));
+
+ // This flag is used inside processCheckSum() and processHeader(),
+ // and must be initialized before we make any further allocations.
+ const bool has_allocations = !segment->allMemoryDeallocated();
+
+ std::string error_msg;
+ if ((!processChecksum(*segment, create, has_allocations, error_msg)) ||
+ (!processHeader(*segment, create, has_allocations, error_msg))) {
+ if (mem_sgmt_) {
+ isc_throw(ResetFailed,
+ "Error in resetting zone table segment to use "
+ << filename << ": " << error_msg);
+ } else {
+ isc_throw(ResetFailedAndSegmentCleared,
+ "Error in resetting zone table segment to use "
+ << filename << ": " << error_msg);
+ }
+ }
+
+ return (segment.release());
+}
+
+MemorySegmentMapped*
+ZoneTableSegmentMapped::openReadOnly(const std::string& filename) {
+ // In case the checksum or table header is missing, we throw. We
+ // want the segment to be automatically destroyed then.
+ std::auto_ptr<MemorySegmentMapped> segment
+ (new MemorySegmentMapped(filename));
+ // There must be a previously saved checksum.
+ MemorySegment::NamedAddressResult result =
+ segment->getNamedAddress(ZONE_TABLE_CHECKSUM_NAME);
+ if (!result.first) {
+ const std::string error_msg =
+ "There is no previously saved checksum in a "
+ "mapped segment opened in read-only mode";
+ if (mem_sgmt_) {
+ isc_throw(ResetFailed,
+ "Error in resetting zone table segment to use "
+ << filename << ": " << error_msg);
+ } else {
+ isc_throw(ResetFailedAndSegmentCleared,
+ "Error in resetting zone table segment to use "
+ << filename << ": " << error_msg);
+ }
+ }
+
+ // We can't verify the checksum here as we can't set the checksum to
+ // 0 for checksum calculation in a read-only segment. So we continue
+ // without verifying the checksum.
+
+ // There must be a previously saved ZoneTableHeader.
+ result = segment->getNamedAddress(ZONE_TABLE_HEADER_NAME);
+ if (result.first) {
+ assert(result.second);
+ } else {
+ const std::string error_msg =
+ "There is no previously saved ZoneTableHeader in a "
+ "mapped segment opened in read-only mode.";
+ if (mem_sgmt_) {
+ isc_throw(ResetFailed,
+ "Error in resetting zone table segment to use "
+ << filename << ": " << error_msg);
+ } else {
+ isc_throw(ResetFailedAndSegmentCleared,
+ "Error in resetting zone table segment to use "
+ << filename << ": " << error_msg);
+ }
+ }
+
+ return (segment.release());
+}
+
+void
+ZoneTableSegmentMapped::reset(MemorySegmentOpenMode mode,
+ isc::data::ConstElementPtr params)
+{
+ if (!params || params->getType() != Element::map) {
+ isc_throw(isc::InvalidParameter,
+ "Configuration does not contain a map");
+ }
+
+ if (!params->contains("mapped-file")) {
+ isc_throw(isc::InvalidParameter,
+ "Configuration does not contain a \"mapped-file\" key");
+ }
+
+ ConstElementPtr mapped_file = params->get("mapped-file");
+ if ((!mapped_file) || (mapped_file->getType() != Element::string)) {
+ isc_throw(isc::InvalidParameter,
+ "Value of \"mapped-file\" is not a string");
+ }
+
+ const std::string filename = mapped_file->stringValue();
+
+ if (mem_sgmt_ && (filename == current_filename_)) {
+ // This reset() is an attempt to re-open the currently open
+ // mapped file. We cannot do this in many mode combinations
+ // unless we close the existing mapped file. So just close it.
+ clear();
+ } else {
+ sync();
+ }
+
+ // In case current_filename_ below fails, we want the segment to be
+ // automatically destroyed.
+ std::auto_ptr<MemorySegmentMapped> segment;
+
+ switch (mode) {
+ case CREATE:
+ segment.reset(openReadWrite(filename, true));
+ break;
+
+ case READ_WRITE:
+ segment.reset(openReadWrite(filename, false));
+ break;
+
+ case READ_ONLY:
+ segment.reset(openReadOnly(filename));
+ break;
+
+ default:
+ isc_throw(isc::InvalidParameter,
+ "Invalid MemorySegmentOpenMode passed to reset()");
+ }
+
+ current_filename_ = filename;
+ current_mode_ = mode;
+ mem_sgmt_.reset(segment.release());
+
+ if (!isWritable()) {
+ // Given what we setup above, the following must not throw at
+ // this point. If it does, all bets are off.
+ cached_ro_header_ = getHeaderHelper<ZoneTableHeader>(true);
+ }
+}
+
+void
+ZoneTableSegmentMapped::sync() {
+ // Synchronize checksum, etc.
+ if (mem_sgmt_ && isWritable()) {
+ // If there is a previously opened segment, and it was opened in
+ // read-write mode, update its checksum.
+ mem_sgmt_->shrinkToFit();
+ const MemorySegment::NamedAddressResult result =
+ mem_sgmt_->getNamedAddress(ZONE_TABLE_CHECKSUM_NAME);
+ assert(result.first);
+ assert(result.second);
+ size_t* checksum = static_cast<size_t*>(result.second);
+ // First, clear the checksum so that getCheckSum() returns a
+ // consistent value.
+ *checksum = 0;
+ const size_t new_checksum = mem_sgmt_->getCheckSum();
+ // Now, update it into place.
+ *checksum = new_checksum;
+ }
+}
+
+void
+ZoneTableSegmentMapped::clear() {
+ if (mem_sgmt_) {
+ sync();
+ mem_sgmt_.reset();
+ }
+}
+
+template<typename T>
+T*
+ZoneTableSegmentMapped::getHeaderHelper(bool initial) const {
+ if (!isUsable()) {
+ isc_throw(isc::InvalidOperation,
+ "getHeader() called without calling reset() first");
+ }
+
+ if (!isWritable() && !initial) {
+ // The header address would not have changed since reset() for
+ // READ_ONLY segments.
+ return (cached_ro_header_);
+ }
+
+ const MemorySegment::NamedAddressResult result =
+ mem_sgmt_->getNamedAddress(ZONE_TABLE_HEADER_NAME);
+ if (!result.first) {
+ isc_throw(isc::Unexpected,
+ "Unable to look up the address of the table header in "
+ "getHeader()");
+ }
+
+ T* header = static_cast<ZoneTableHeader*>(result.second);
+ assert(header);
+ return (header);
+}
+
+ZoneTableHeader&
+ZoneTableSegmentMapped::getHeader() {
+ return (*getHeaderHelper<ZoneTableHeader>(false));
+}
+
+const ZoneTableHeader&
+ZoneTableSegmentMapped::getHeader() const {
+ return (*getHeaderHelper<const ZoneTableHeader>(false));
+}
+
+MemorySegment&
+ZoneTableSegmentMapped::getMemorySegment() {
+ if (!isUsable()) {
+ isc_throw(isc::InvalidOperation,
+ "getMemorySegment() called without calling reset() first");
+ }
+ return (*mem_sgmt_);
+}
+
+bool
+ZoneTableSegmentMapped::isUsable() const {
+ // If mem_sgmt_ is not empty, then it is usable.
+ return (mem_sgmt_);
+}
+
+bool
+ZoneTableSegmentMapped::isWritable() const {
+ if (!isUsable()) {
+ // If reset() was never performed for this segment, or if the
+ // most recent reset() had failed, or if the segment had been
+ // cleared, then the segment is not writable.
+ return (false);
+ }
+
+ return ((current_mode_ == CREATE) || (current_mode_ == READ_WRITE));
+}
+
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
diff --git a/src/lib/datasrc/memory/zone_table_segment_mapped.h b/src/lib/datasrc/memory/zone_table_segment_mapped.h
new file mode 100644
index 0000000..1776314
--- /dev/null
+++ b/src/lib/datasrc/memory/zone_table_segment_mapped.h
@@ -0,0 +1,144 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef ZONE_TABLE_SEGMENT_MAPPED_H
+#define ZONE_TABLE_SEGMENT_MAPPED_H
+
+#include <datasrc/memory/zone_table_segment.h>
+#include <util/memory_segment_mapped.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+/// \brief Mapped-file based implementation of \c ZoneTableSegment class
+///
+/// This class specifies a concrete implementation for a memory-mapped
+/// \c ZoneTableSegment. Please see the \c ZoneTableSegment class
+/// documentation for usage.
+class ZoneTableSegmentMapped : public ZoneTableSegment {
+ // This is so that \c ZoneTableSegmentMapped can be instantiated
+ // from \c ZoneTableSegment::create().
+ friend class ZoneTableSegment;
+
+protected:
+ /// \brief Protected constructor
+ ///
+ /// Instances are expected to be created by the factory method
+ /// (\c ZoneTableSegment::create()), so this constructor is
+ /// protected.
+ ZoneTableSegmentMapped(const isc::dns::RRClass& rrclass);
+
+public:
+ /// \brief Destructor
+ virtual ~ZoneTableSegmentMapped();
+
+ /// \brief Returns "mapped" as the implementation type.
+ virtual const std::string& getImplType() const;
+
+ /// \brief Return the \c ZoneTableHeader for this mapped zone table
+ /// segment.
+ ///
+ /// \throws isc::InvalidOperation if this method is called without a
+ /// successful \c reset() call first.
+ virtual ZoneTableHeader& getHeader();
+
+ /// \brief const version of \c getHeader().
+ virtual const ZoneTableHeader& getHeader() const;
+
+ /// \brief Return the \c MemorySegment for the memory-mapped zone
+ /// table segment implementation (a \c MemorySegmentMapped
+ /// instance).
+ ///
+ /// \throws isc::InvalidOperation if this method is called without a
+ /// successful \c reset() call first.
+ virtual isc::util::MemorySegment& getMemorySegment();
+
+ /// \brief Returns if the segment is writable.
+ ///
+ /// Segments successfully opened in CREATE or READ_WRITE modes are
+ /// writable. Segments opened in READ_ONLY mode are not writable.
+ /// If the \c ZoneTableSegment was cleared for some reason, it is
+ /// not writable until it is reset successfully.
+ virtual bool isWritable() const;
+
+ /// \brief Close the current \c MemorySegment (if open) and open the
+ /// requested one.
+ ///
+ /// See \c MemorySegmentOpenMode for a definition of "storage area"
+ /// and the various modes in which a \c MemorySegment can be opened.
+ ///
+ /// \c params should be a map containing a "mapped-file" key that
+ /// points to a string value containing the filename of a mapped
+ /// file. E.g.,
+ ///
+ /// {"mapped-file": "/var/bind10/mapped-files/zone-sqlite3.mapped.0"}
+ ///
+ /// Please see the \c ZoneTableSegment API documentation for the
+ /// behavior in case of exceptions.
+ ///
+ /// \throws isc::Unexpected when it's unable to lookup a named
+ /// address that it expected to be present. This is extremely
+ /// unlikely, and it points to corruption.
+ ///
+ /// \param mode The open mode (see the \c MemorySegmentOpenMode
+ /// documentation in \c ZoneTableSegment class).
+ /// \param params An element containing config for the mapped file
+ /// (see the description).
+ virtual void reset(MemorySegmentOpenMode mode,
+ isc::data::ConstElementPtr params);
+
+ /// \brief Close the currently configured \c MemorySegment (if
+ /// open). See the base class for a definition of "open" and
+ /// "close".
+ virtual void clear();
+
+ /// \brief Return true if the segment is usable.
+ ///
+ /// See the base class for the description.
+ virtual bool isUsable() const;
+
+private:
+ void sync();
+
+ bool processChecksum(isc::util::MemorySegmentMapped& segment, bool create,
+ bool has_allocations, std::string& error_msg);
+ bool processHeader(isc::util::MemorySegmentMapped& segment, bool create,
+ bool has_allocations, std::string& error_msg);
+
+ isc::util::MemorySegmentMapped* openReadWrite(const std::string& filename,
+ bool create);
+ isc::util::MemorySegmentMapped* openReadOnly(const std::string& filename);
+
+ template<typename T> T* getHeaderHelper(bool initial) const;
+
+private:
+ std::string impl_type_;
+ isc::dns::RRClass rrclass_;
+ MemorySegmentOpenMode current_mode_;
+ std::string current_filename_;
+ // Internally holds a MemorySegmentMapped. This is NULL on
+ // construction, and is set by the \c reset() method.
+ boost::scoped_ptr<isc::util::MemorySegmentMapped> mem_sgmt_;
+ ZoneTableHeader* cached_ro_header_;
+};
+
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // ZONE_TABLE_SEGMENT_MAPPED_H
diff --git a/src/lib/datasrc/memory/zone_writer.cc b/src/lib/datasrc/memory/zone_writer.cc
new file mode 100644
index 0000000..ea70bb9
--- /dev/null
+++ b/src/lib/datasrc/memory/zone_writer.cc
@@ -0,0 +1,175 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/memory/zone_writer.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/segment_object_holder.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <dns/rrclass.h>
+
+#include <datasrc/exceptions.h>
+
+#include <memory>
+
+using std::auto_ptr;
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+ZoneTableSegment&
+checkZoneTableSegment(ZoneTableSegment& segment) {
+ if (!segment.isWritable()) {
+ isc_throw(isc::InvalidOperation,
+ "Attempt to construct ZoneWriter for a read-only segment");
+ }
+ return (segment);
+}
+
+struct ZoneWriter::Impl {
+ Impl(ZoneTableSegment& segment, const LoadAction& load_action,
+ const dns::Name& origin, const dns::RRClass& rrclass,
+ bool throw_on_load_error) :
+ // We validate segment first so we can use it to initialize
+ // data_holder_ safely.
+ segment_(checkZoneTableSegment(segment)),
+ load_action_(load_action),
+ origin_(origin),
+ rrclass_(rrclass),
+ state_(ZW_UNUSED),
+ catch_load_error_(throw_on_load_error)
+ {
+ while (true) {
+ try {
+ data_holder_.reset(
+ new ZoneDataHolder(segment.getMemorySegment(), rrclass_));
+ break;
+ } catch (const isc::util::MemorySegmentGrown&) {}
+ }
+ }
+
+ ZoneTableSegment& segment_;
+ const LoadAction load_action_;
+ const dns::Name origin_;
+ const dns::RRClass rrclass_;
+ enum State {
+ ZW_UNUSED,
+ ZW_LOADED,
+ ZW_INSTALLED,
+ ZW_CLEANED
+ };
+ State state_;
+ const bool catch_load_error_;
+ typedef detail::SegmentObjectHolder<ZoneData, dns::RRClass> ZoneDataHolder;
+ boost::scoped_ptr<ZoneDataHolder> data_holder_;
+};
+
+ZoneWriter::ZoneWriter(ZoneTableSegment& segment,
+ const LoadAction& load_action,
+ const dns::Name& origin,
+ const dns::RRClass& rrclass,
+ bool throw_on_load_error) :
+ impl_(new Impl(segment, load_action, origin, rrclass, throw_on_load_error))
+{
+}
+
+ZoneWriter::~ZoneWriter() {
+ // Clean up everything there might be left if someone forgot, just
+ // in case.
+ cleanup();
+ delete impl_;
+}
+
+void
+ZoneWriter::load(std::string* error_msg) {
+ if (impl_->state_ != Impl::ZW_UNUSED) {
+ isc_throw(isc::InvalidOperation, "Trying to load twice");
+ }
+
+ try {
+ ZoneData* zone_data =
+ impl_->load_action_(impl_->segment_.getMemorySegment());
+
+ if (!zone_data) {
+ // Bug inside impl_->load_action_.
+ isc_throw(isc::InvalidOperation,
+ "No data returned from load action");
+ }
+
+ impl_->data_holder_->set(zone_data);
+
+ } catch (const ZoneLoaderException& ex) {
+ if (!impl_->catch_load_error_) {
+ throw;
+ }
+ if (error_msg) {
+ *error_msg = ex.what();
+ }
+ }
+
+ impl_->state_ = Impl::ZW_LOADED;
+}
+
+void
+ZoneWriter::install() {
+ if (impl_->state_ != Impl::ZW_LOADED) {
+ isc_throw(isc::InvalidOperation, "No data to install");
+ }
+
+ // Check the internal integrity assumption: we should have non NULL
+ // zone data or we've allowed load error to create an empty zone.
+ assert(impl_->data_holder_.get() || impl_->catch_load_error_);
+
+ while (impl_->state_ != Impl::ZW_INSTALLED) {
+ try {
+ ZoneTableHeader& header = impl_->segment_.getHeader();
+ ZoneTable* table(header.getTable());
+ if (!table) {
+ isc_throw(isc::Unexpected, "No zone table present");
+ }
+ // We still need to hold the zone data until we return from
+ // addZone in case it throws, but we then need to immediately
+ // release it as the ownership is transferred to the zone table.
+ // we release this by (re)set it to the old data; that way we can
+ // use the holder for the final cleanup.
+ const ZoneTable::AddResult result(
+ impl_->data_holder_->get() ?
+ table->addZone(impl_->segment_.getMemorySegment(),
+ impl_->origin_, impl_->data_holder_->get()) :
+ table->addEmptyZone(impl_->segment_.getMemorySegment(),
+ impl_->origin_));
+ impl_->data_holder_->set(result.zone_data);
+ impl_->state_ = Impl::ZW_INSTALLED;
+ } catch (const isc::util::MemorySegmentGrown&) {}
+ }
+}
+
+void
+ZoneWriter::cleanup() {
+ // We eat the data (if any) now.
+
+ ZoneData* zone_data = impl_->data_holder_->release();
+ if (zone_data) {
+ ZoneData::destroy(impl_->segment_.getMemorySegment(), zone_data,
+ impl_->rrclass_);
+ impl_->state_ = Impl::ZW_CLEANED;
+ }
+}
+
+}
+}
+}
diff --git a/src/lib/datasrc/memory/zone_writer.h b/src/lib/datasrc/memory/zone_writer.h
index 0e8f285..bdd350c 100644
--- a/src/lib/datasrc/memory/zone_writer.h
+++ b/src/lib/datasrc/memory/zone_writer.h
@@ -15,30 +15,58 @@
#ifndef MEM_ZONE_WRITER_H
#define MEM_ZONE_WRITER_H
-#include "load_action.h"
+#include <datasrc/memory/load_action.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <dns/dns_fwd.h>
namespace isc {
namespace datasrc {
namespace memory {
+class ZoneTableSegment;
/// \brief Does an update to a zone.
///
-/// This abstract base class represents the work of a reload of a zone.
-/// The work is divided into three stages -- load(), install() and cleanup().
-/// They should be called in this order for the effect to take place.
+/// This represents the work of a (re)load of a zone. The work is divided
+/// into three stages -- load(), install() and cleanup(). They should
+/// be called in this order for the effect to take place.
///
/// We divide them so the update of zone data can be done asynchronously,
/// in a different thread. The install() operation is the only one that needs
/// to be done in a critical section.
///
-/// Each derived class implementation must provide the strong exception
-/// guarantee for each public method. That is, when any of the methods
-/// throws, the entire state should stay the same as before the call
-/// (how to achieve that may be implementation dependant).
-class ZoneWriter {
+/// This class provides strong exception guarantee for each public
+/// method. That is, when any of the methods throws, the entire state
+/// stays the same as before the call.
+class ZoneWriter : boost::noncopyable {
public:
- /// \brief Virtual destructor.
- virtual ~ZoneWriter() {};
+ /// \brief Constructor
+ ///
+ /// If \c catch_load_error is set to true, the \c load() method will
+ /// internally catch load related errors reported as a DataSourceError
+ /// exception, and subsequent \c install() method will add a special
+ /// empty zone to the zone table segment. If it's set to false, \c load()
+ /// will simply propagate the exception. This parameter would normally
+ /// be set to false as it's not desirable to install a broken zone;
+ /// however, it would be better to be set to true at the initial loading
+ /// so the zone table recognizes the existence of the zone (and being
+ /// aware that it's broken).
+ ///
+ /// \throw isc::InvalidOperation if \c segment is read-only.
+ ///
+ /// \param segment The zone table segment to store the zone into.
+ /// \param load_action The callback used to load data.
+ /// \param name The name of the zone.
+ /// \param rrclass The class of the zone.
+ /// \param catch_load_error true if loading errors are to be caught
+ /// internally; false otherwise.
+ ZoneWriter(ZoneTableSegment& segment,
+ const LoadAction& load_action, const dns::Name& name,
+ const dns::RRClass& rrclass, bool catch_load_error);
+
+ /// \brief Destructor.
+ ~ZoneWriter();
/// \brief Get the zone data into memory.
///
@@ -49,6 +77,12 @@ public:
/// This is the first method you should call on the object. Never call it
/// multiple times.
///
+ /// If the optional parameter \c error_msg is given and non NULL, and
+ /// if the writer object was constructed with \c catch_load_error being
+ /// true, then error_msg will be filled with text indicating the reason
+ /// for the error in case a load error happens. In other cases any
+ /// passed non NULL error_msg will be intact.
+ ///
/// \note As this contains reading of files or other data sources, or with
/// some other source of the data to load, it may throw quite anything.
/// If it throws, do not call any other methods on the object and
@@ -56,24 +90,33 @@ public:
/// \note After successful load(), you have to call cleanup() some time
/// later.
/// \throw isc::InvalidOperation if called second time.
- virtual void load() = 0;
+ /// \throw DataSourceError load related error (not thrown if constructed
+ /// with catch_load_error being \c true).
+ ///
+ /// \param error_msg If non NULL, used as a placeholder to store load error
+ /// messages.
+ void load(std::string* error_msg = NULL);
/// \brief Put the changes to effect.
///
/// This replaces the old version of zone with the one previously prepared
/// by load(). It takes ownership of the old zone data, if any.
///
- /// You may call it only after successful load() and at most once.
+ /// You may call it only after successful load() and at most once. It
+ /// includes the case the writer is constructed with catch_load_error
+ /// being true and load() encountered and caught a DataSourceError
+ /// exception. In this case this method installs a special empty zone
+ /// to the table.
///
/// The operation is expected to be fast and is meant to be used inside
/// a critical section.
///
- /// This may throw in rare cases, depending on the concrete implementation.
- /// If it throws, you still need to call cleanup().
+ /// This may throw in rare cases. If it throws, you still need to
+ /// call cleanup().
///
/// \throw isc::InvalidOperation if called without previous load() or for
/// the second time or cleanup() was called already.
- virtual void install() = 0;
+ void install();
/// \brief Clean up resources.
///
@@ -81,12 +124,22 @@ public:
/// one loaded by load() in case install() was not called or was not
/// successful, or the one replaced in install().
///
- /// Generally, this should never throw.
- virtual void cleanup() = 0;
+ /// \throw none
+ void cleanup();
+
+private:
+ // We hide details as this class will be used by various applications
+ // and we use some internal data structures in the implementation.
+ struct Impl;
+ Impl* impl_;
};
}
}
}
-#endif
+#endif // MEM_ZONE_WRITER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/memory/zone_writer_local.cc b/src/lib/datasrc/memory/zone_writer_local.cc
deleted file mode 100644
index 0cd9587..0000000
--- a/src/lib/datasrc/memory/zone_writer_local.cc
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include "zone_writer_local.h"
-#include "zone_data.h"
-#include "zone_table_segment_local.h"
-
-#include <memory>
-
-using std::auto_ptr;
-
-namespace isc {
-namespace datasrc {
-namespace memory {
-
-ZoneWriterLocal::ZoneWriterLocal(ZoneTableSegmentLocal* segment,
- const LoadAction& load_action,
- const dns::Name& origin,
- const dns::RRClass& rrclass) :
- segment_(segment),
- load_action_(load_action),
- origin_(origin),
- rrclass_(rrclass),
- zone_data_(NULL),
- state_(ZW_UNUSED)
-{}
-
-ZoneWriterLocal::~ZoneWriterLocal() {
- // Clean up everything there might be left if someone forgot, just
- // in case.
- cleanup();
-}
-
-void
-ZoneWriterLocal::load() {
- if (state_ != ZW_UNUSED) {
- isc_throw(isc::InvalidOperation, "Trying to load twice");
- }
-
- zone_data_ = load_action_(segment_->getMemorySegment());
-
- if (zone_data_ == NULL) {
- // Bug inside load_action_.
- isc_throw(isc::InvalidOperation, "No data returned from load action");
- }
-
- state_ = ZW_LOADED;
-}
-
-void
-ZoneWriterLocal::install() {
- if (state_ != ZW_LOADED) {
- isc_throw(isc::InvalidOperation, "No data to install");
- }
-
-
- ZoneTable* table(segment_->getHeader().getTable());
- if (table == NULL) {
- isc_throw(isc::Unexpected, "No zone table present");
- }
- const ZoneTable::AddResult result(table->addZone(
- segment_->getMemorySegment(),
- rrclass_, origin_, zone_data_));
-
- state_ = ZW_INSTALLED;
- zone_data_ = result.zone_data;
-}
-
-void
-ZoneWriterLocal::cleanup() {
- // We eat the data (if any) now.
-
- if (zone_data_ != NULL) {
- ZoneData::destroy(segment_->getMemorySegment(), zone_data_, rrclass_);
- zone_data_ = NULL;
- state_ = ZW_CLEANED;
- }
-}
-
-}
-}
-}
diff --git a/src/lib/datasrc/memory/zone_writer_local.h b/src/lib/datasrc/memory/zone_writer_local.h
deleted file mode 100644
index 7231a57..0000000
--- a/src/lib/datasrc/memory/zone_writer_local.h
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef MEM_ZONE_WRITER_LOCAL_H
-#define MEM_ZONE_WRITER_LOCAL_H
-
-#include "zone_writer.h"
-
-#include <dns/rrclass.h>
-#include <dns/name.h>
-
-namespace isc {
-namespace datasrc {
-namespace memory {
-
-class ZoneData;
-class ZoneTableSegmentLocal;
-
-/// \brief Writer implementation which loads data locally.
-///
-/// This implementation prepares a clean zone data and lets one callback
-/// to fill it and another to install it somewhere. The class does mostly
-/// nothing (and delegates the work to the callbacks), just stores little bit
-/// of state between the calls.
-class ZoneWriterLocal : public ZoneWriter {
-public:
- /// \brief Constructor
- ///
- /// \param segment The zone table segment to store the zone into.
- /// \param load_action The callback used to load data.
- /// \param install_action The callback used to install the loaded zone.
- /// \param rrclass The class of the zone.
- ZoneWriterLocal(ZoneTableSegmentLocal* segment,
- const LoadAction& load_action, const dns::Name& name,
- const dns::RRClass& rrclass);
-
- /// \brief Destructor
- ~ZoneWriterLocal();
-
- /// \brief Loads the data.
- ///
- /// This calls the load_action (passed to constructor) and stores the
- /// data for future use.
- ///
- /// \throw isc::InvalidOperation if it is called the second time in
- /// lifetime of the object.
- /// \throw Whatever the load_action throws, it is propagated up.
- virtual void load();
-
- /// \brief Installs the zone.
- ///
- /// It modifies the zone table accessible through the segment (passed to
- /// constructor).
- ///
- /// \throw isc::InvalidOperation if it is called the second time in
- /// lifetime of the object or if load() was not called previously or if
- /// cleanup() was already called.
- virtual void install();
-
- /// \brief Clean up memory.
- ///
- /// Cleans up the memory used by load()ed zone if not yet installed, or
- /// the old zone replaced by install().
- virtual void cleanup();
-private:
- ZoneTableSegmentLocal* segment_;
- LoadAction load_action_;
- dns::Name origin_;
- dns::RRClass rrclass_;
- ZoneData* zone_data_;
- enum State {
- ZW_UNUSED,
- ZW_LOADED,
- ZW_INSTALLED,
- ZW_CLEANED
- };
- State state_;
-};
-
-}
-}
-}
-
-#endif
diff --git a/src/lib/datasrc/result.h b/src/lib/datasrc/result.h
index 5a28d08..7a042cb 100644
--- a/src/lib/datasrc/result.h
+++ b/src/lib/datasrc/result.h
@@ -18,13 +18,10 @@
namespace isc {
namespace datasrc {
namespace result {
-/// Result codes of various public methods of in memory data source
+/// \brief Result codes of various public methods of DataSourceClient.
///
/// The detailed semantics may differ in different methods.
/// See the description of specific methods for more details.
-///
-/// Note: this is intended to be used from other data sources eventually,
-/// but for now it's specific to in memory data source and its backend.
enum Result {
SUCCESS, ///< The operation is successful.
EXIST, ///< The search key is already stored.
@@ -32,8 +29,26 @@ enum Result {
PARTIALMATCH ///< Only a partial match is found.
};
+/// \brief Flags for supplemental information along with the \c Result
+///
+/// Initially there's only one flag defined, but several flags will be added
+/// later. One likely case is to indicate a flag that is listed in in-memory
+/// but its content is served in the underlying data source. This will help
+/// when only a subset of zones are cached in-memory so the lookup code can
+/// efficiently detect whether it doesn't exist or is not just cached.
+/// When more flags are added, the logical-or operation should be allowed
+/// (by defining \c operator|) on these flags.
+enum ResultFlags {
+ FLAGS_DEFAULT = 0, // no flags
+ ZONE_EMPTY = 1 ///< The zone found is empty, normally meaning it's broken
+};
+
}
}
}
-#endif
+#endif // DATASRC_RESULT_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index a858028..93f468c 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -23,8 +23,9 @@
#include <dns/name.h>
#include <datasrc/sqlite3_accessor.h>
+#include <datasrc/sqlite3_datasrc_messages.h>
#include <datasrc/logger.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/factory.h>
#include <datasrc/database.h>
#include <util/filename.h>
@@ -103,7 +104,9 @@ const char* const text_statements[NUM_STATEMENTS] = {
"INSERT INTO records " // ADD_RECORD
"(zone_id, name, rname, ttl, rdtype, sigtype, rdata) "
"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
- "DELETE FROM records WHERE zone_id=?1 AND name=?2 " // DEL_RECORD
+ // DEL_RECORD:
+ // Delete based on the reverse name, as that one has an index.
+ "DELETE FROM records WHERE zone_id=?1 AND rname=?2 " // DEL_RECORD
"AND rdtype=?3 AND rdata=?4",
// ITERATE_RECORDS:
@@ -1068,7 +1071,7 @@ private:
/// \param serial Zone serial number for which an index is being sought.
/// \param diff Code to delete record additions or deletions
///
- /// \return int ID of the row in the difss table corresponding to the
+ /// \return int ID of the row in the diffs table corresponding to the
/// statement.
///
/// \exception TooLittleData Internal error, no result returned when one
@@ -1294,19 +1297,27 @@ SQLite3Accessor::deleteRecordInZone(const string (¶ms)[DEL_PARAM_COUNT]) {
isc_throw(DataSourceError, "deleting record in SQLite3 "
"data source without transaction");
}
- doUpdate<const string (&)[DEL_PARAM_COUNT]>(
- *dbparameters_, DEL_RECORD, params, "delete record from zone");
+ // We don't pass all the parameters to the query, one name (reserve one
+ // in this case) is sufficient. Pass only the needed ones.
+ const size_t SQLITE3_DEL_PARAM_COUNT = DEL_PARAM_COUNT - 1;
+ const string sqlite3_params[SQLITE3_DEL_PARAM_COUNT] = {
+ params[DEL_RNAME],
+ params[DEL_TYPE],
+ params[DEL_RDATA]
+ };
+ doUpdate<const string (&)[SQLITE3_DEL_PARAM_COUNT]>(
+ *dbparameters_, DEL_RECORD, sqlite3_params, "delete record from zone");
}
void
SQLite3Accessor::deleteNSEC3RecordInZone(
- const string (¶ms)[DEL_PARAM_COUNT])
+ const string (¶ms)[DEL_NSEC3_PARAM_COUNT])
{
if (!dbparameters_->updating_zone) {
isc_throw(DataSourceError, "deleting NSEC3-related record in SQLite3 "
"data source without transaction");
}
- doUpdate<const string (&)[DEL_PARAM_COUNT]>(
+ doUpdate<const string (&)[DEL_NSEC3_PARAM_COUNT]>(
*dbparameters_, DEL_NSEC3_RECORD, params,
"delete NSEC3 record from zone");
}
diff --git a/src/lib/datasrc/sqlite3_accessor.h b/src/lib/datasrc/sqlite3_accessor.h
index d014193..f8c4138 100644
--- a/src/lib/datasrc/sqlite3_accessor.h
+++ b/src/lib/datasrc/sqlite3_accessor.h
@@ -17,7 +17,7 @@
#define DATASRC_SQLITE3_ACCESSOR_H
#include <datasrc/database.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <exceptions/exceptions.h>
@@ -230,7 +230,7 @@ public:
const std::string (¶ms)[DEL_PARAM_COUNT]);
virtual void deleteNSEC3RecordInZone(
- const std::string (¶ms)[DEL_PARAM_COUNT]);
+ const std::string (¶ms)[DEL_NSEC3_PARAM_COUNT]);
/// This derived version of the method prepares an SQLite3 statement
/// for adding the diff first time it's called, and if it fails throws
@@ -250,7 +250,7 @@ public:
virtual std::string findPreviousName(int zone_id, const std::string& rname)
const;
- /// \brief Conrete implemantion of the pure virtual method of
+ /// \brief Concrete implementation of the pure virtual method of
/// DatabaseAccessor
virtual std::string findPreviousNSEC3Hash(int zone_id,
const std::string& hash) const;
diff --git a/src/lib/datasrc/sqlite3_accessor_link.cc b/src/lib/datasrc/sqlite3_accessor_link.cc
index 56e0c2f..6f38de4 100644
--- a/src/lib/datasrc/sqlite3_accessor_link.cc
+++ b/src/lib/datasrc/sqlite3_accessor_link.cc
@@ -19,6 +19,8 @@
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/database.h>
+#include <log/message_initializer.h>
+
#include <string>
using namespace std;
@@ -77,6 +79,9 @@ checkConfig(ConstElementPtr config, ElementPtr errors) {
DataSourceClient *
createInstance(isc::data::ConstElementPtr config, std::string& error) {
+ // Initialize the logging dictionary
+ isc::log::MessageInitializer::loadDictionary(true);
+
ElementPtr errors(Element::createList());
if (!checkConfig(config, errors)) {
error = "Configuration error: " + errors->str();
diff --git a/src/lib/datasrc/sqlite3_datasrc_messages.mes b/src/lib/datasrc/sqlite3_datasrc_messages.mes
new file mode 100644
index 0000000..a462d43
--- /dev/null
+++ b/src/lib/datasrc/sqlite3_datasrc_messages.mes
@@ -0,0 +1,142 @@
+# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::datasrc
+
+# \brief Messages for the SQLITE3 data source backend
+
+% DATASRC_SQLITE_CLOSE closing SQLite database
+Debug information. The SQLite data source is closing the database file.
+
+% DATASRC_SQLITE_COMPATIBLE_VERSION database schema V%1.%2 not up to date (expecting V%3.%4) but is compatible
+The version of the SQLite3 database schema used to hold the zone data
+is not the latest one - the current version of BIND 10 was written
+with a later schema version in mind. However, the database is
+compatible with the current version of BIND 10, and BIND 10 will run
+without any problems.
+
+Consult the release notes for your version of BIND 10. Depending on
+the changes made to the database schema, it is possible that improved
+performance could result if the database were upgraded.
+
+% DATASRC_SQLITE_CONNCLOSE Closing sqlite database
+The database file is no longer needed and is being closed.
+
+% DATASRC_SQLITE_CONNOPEN Opening sqlite database file '%1'
+The database file is being opened so it can start providing data.
+
+% DATASRC_SQLITE_CREATE SQLite data source created
+Debug information. An instance of SQLite data source is being created.
+
+% DATASRC_SQLITE_DESTROY SQLite data source destroyed
+Debug information. An instance of SQLite data source is being destroyed.
+
+% DATASRC_SQLITE_DROPCONN SQLite3Database is being deinitialized
+The object around a database connection is being destroyed.
+
+% DATASRC_SQLITE_ENCLOSURE looking for zone containing '%1'
+Debug information. The SQLite data source is trying to identify which zone
+should hold this domain.
+
+% DATASRC_SQLITE_ENCLOSURE_NOT_FOUND no zone contains '%1'
+Debug information. The last SQLITE_ENCLOSURE query was unsuccessful; there's
+no such zone in our data.
+
+% DATASRC_SQLITE_FIND looking for RRset '%1/%2'
+Debug information. The SQLite data source is looking up a resource record
+set.
+
+% DATASRC_SQLITE_FINDADDRS looking for A/AAAA addresses for '%1'
+Debug information. The data source is looking up the addresses for given
+domain name.
+
+% DATASRC_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.
+
+% DATASRC_SQLITE_FINDEXACT looking for exact RRset '%1/%2'
+Debug information. The SQLite data source is looking up an exact resource
+record.
+
+% DATASRC_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.
+
+% DATASRC_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.
+
+% DATASRC_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.
+
+% DATASRC_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.
+
+% DATASRC_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.
+
+% DATASRC_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.
+
+% DATASRC_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.
+
+% DATASRC_SQLITE_INCOMPATIBLE_VERSION database schema V%1.%2 incompatible with version (V%3.%4) expected
+The version of the SQLite3 database schema used to hold the zone data
+is incompatible with the version expected by BIND 10. As a result,
+BIND 10 is unable to run using the database file as the data source.
+
+The database should be updated using the means described in the BIND
+10 documentation.
+
+% DATASRC_SQLITE_NEWCONN SQLite3Database is being initialized
+A wrapper object to hold database connection is being initialized.
+
+% DATASRC_SQLITE_OPEN opening SQLite database '%1'
+Debug information. The SQLite data source is loading an SQLite database in
+the provided file.
+
+% DATASRC_SQLITE_PREVIOUS looking for name previous to '%1'
+This is a debug message. The name given was not found, so the program
+is searching for the next name higher up the hierarchy (e.g. if
+www.example.com were queried for and not found, the software searches
+for the "previous" name, example.com).
+
+% DATASRC_SQLITE_PREVIOUS_NO_ZONE no zone containing '%1'
+The name given was not found, so the program is searching for the next
+name higher up the hierarchy (e.g. if www.example.com were queried
+for and not found, the software searches for the "previous" name,
+example.com). However, this name is not contained in any zone in the
+data source. This is an error since it indicates a problem in the earlier
+processing of the query.
+
+% DATASRC_SQLITE_SETUP setting up new SQLite3 database in '%1'
+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. If this is indeed the first run of
+BIND 10, it is to be expected and completely harmless. If you just configured
+a data source to point to an existing file and you see this, you may have
+misspelled the file name.
+
+% DATASRC_SQLITE_SETUP_OLD_API setting up new 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. This is similar to DATASRC_SQLITE_SETUP
+message, but it is logged from the old API. You should never see it, since the
+API is deprecated.
diff --git a/src/lib/datasrc/static.zone.pre b/src/lib/datasrc/static.zone.pre
index 13c0c9d..8046410 100644
--- a/src/lib/datasrc/static.zone.pre
+++ b/src/lib/datasrc/static.zone.pre
@@ -1,10 +1,10 @@
-;; This is the content of the BIND./CH zone. It contains the version and
-;; authors (called VERSION.BIND. and AUTHORS.BIND.). You can add more or
-;; modify the zone. Then you can reload the zone by issuing the command
+;; This file contains records for the BIND./CH zone. It contains version
+;; (VERSION.BIND.) and authors (AUTHORS.BIND.) information. You can add
+;; more records or modify this zone file like any other zone file. If
+;; you modify this file, you can reload the zone by issuing the
+;; following command in the bindctl program:
;;
-;; loadzone CH BIND
-;;
-;; in the bindctl.
+;; Auth loadzone CH BIND
;; This is here mostly for technical reasons.
BIND. 0 CH SOA bind. authors.bind. 0 28800 7200 604800 86400
diff --git a/src/lib/datasrc/static_datasrc.h b/src/lib/datasrc/static_datasrc.h
deleted file mode 100644
index d5d8875..0000000
--- a/src/lib/datasrc/static_datasrc.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER 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_STATIC_H
-#define DATASRC_STATIC_H
-
-#include <datasrc/database.h>
-#include <cc/data.h>
-
-#include <string>
-
-namespace isc {
-namespace datasrc {
-
-/// \brief Creates an instance of the static datasource client
-///
-/// Currently the configuration passed here must be a StringElement,
-/// containing the path to a zone file for the BIND./CH zone.
-///
-/// \param config The configuration for the datasource instance (see above)
-/// \param error This string will be set to an error message if an error occurs
-/// during initialization
-/// \return An instance of the static datasource client, or NULL if there was
-/// an error
-extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr config,
- std::string& error);
-
-/// \brief Destroy the instance created by createInstance()
-extern "C" void destroyInstance(DataSourceClient* instance);
-
-}
-}
-
-#endif // DATASRC_STATIC_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/datasrc/static_datasrc_link.cc b/src/lib/datasrc/static_datasrc_link.cc
deleted file mode 100644
index 1b767a4..0000000
--- a/src/lib/datasrc/static_datasrc_link.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include "client.h"
-#include "static_datasrc.h"
-#include <datasrc/memory/memory_client.h>
-#include <datasrc/memory/zone_table_segment.h>
-
-#include <cc/data.h>
-#include <dns/rrclass.h>
-
-#include <memory>
-#include <exception>
-
-using namespace isc::data;
-using namespace isc::dns;
-using namespace boost;
-using namespace std;
-
-namespace isc {
-namespace datasrc {
-
-DataSourceClient*
-createInstance(ConstElementPtr config, string& error) {
- try {
- // FIXME: Fix the config that should be passed to
- // ZoneTableSegment::create() when it actually uses the config
- // to do something.
- shared_ptr<memory::ZoneTableSegment> ztable_segment(
- memory::ZoneTableSegment::create(isc::data::NullElement(),
- RRClass::CH()));
- // Create the data source
- auto_ptr<memory::InMemoryClient> client
- (new memory::InMemoryClient(ztable_segment, RRClass::CH()));
-
- // Fill it with data
- const string path(config->stringValue());
- client->load(Name("BIND"), path);
-
- return (client.release());
- }
- catch (const std::exception& e) {
- error = e.what();
- }
- catch (...) {
- error = "Unknown exception";
- }
- return (NULL);
-}
-
-void
-destroyInstance(DataSourceClient* instance) {
- delete instance;
-}
-
-}
-}
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 7960963..f9380d8 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -48,6 +48,7 @@ common_ldadd += $(GTEST_LDADD) $(SQLITE_LIBS)
run_unittests_SOURCES = $(common_sources)
run_unittests_SOURCES += test_client.h test_client.cc
+run_unittests_SOURCES += mock_client.h mock_client.cc
run_unittests_SOURCES += logger_unittest.cc
run_unittests_SOURCES += client_unittest.cc
run_unittests_SOURCES += database_unittest.h database_unittest.cc
@@ -58,10 +59,14 @@ run_unittests_SOURCES += faked_nsec3.h faked_nsec3.cc
run_unittests_SOURCES += client_list_unittest.cc
run_unittests_SOURCES += master_loader_callbacks_test.cc
run_unittests_SOURCES += zone_loader_unittest.cc
+run_unittests_SOURCES += cache_config_unittest.cc
+run_unittests_SOURCES += zone_table_accessor_unittest.cc
# We need the actual module implementation in the tests (they are not part
# of libdatasrc)
run_unittests_SOURCES += $(top_srcdir)/src/lib/datasrc/sqlite3_accessor.cc
+# Also, as of #2746, sqlite3-specific log messages are in a separate file
+nodist_run_unittests_SOURCES = $(abs_top_builddir)/src/lib/datasrc/sqlite3_datasrc_messages.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
@@ -71,7 +76,7 @@ run_unittests_LDADD = $(common_ldadd)
noinst_PROGRAMS+= $(TESTS)
# For the factory unit tests, we need to specify that we want
-# the loadable backend libraries from the build tree, and not from
+# the loadable backend libraries from the build tree, and not from
# the installation directory. Therefore we build it into a separate
# binary, and call that from check-local with B10_FROM_BUILD set.
# Also, we only want to do this when static building is not used,
diff --git a/src/lib/datasrc/tests/cache_config_unittest.cc b/src/lib/datasrc/tests/cache_config_unittest.cc
new file mode 100644
index 0000000..34dd5d1
--- /dev/null
+++ b/src/lib/datasrc/tests/cache_config_unittest.cc
@@ -0,0 +1,312 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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/cache_config.h>
+#include <datasrc/exceptions.h>
+#include <datasrc/memory/load_action.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/tests/mock_client.h>
+
+#include <cc/data.h>
+#include <util/memory_segment_local.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <gtest/gtest.h>
+
+#include <iterator> // for std::distance
+
+using namespace isc::datasrc;
+using namespace isc::data;
+using namespace isc::dns;
+using isc::datasrc::unittest::MockDataSourceClient;
+using isc::datasrc::internal::CacheConfig;
+using isc::datasrc::internal::CacheConfigError;
+using isc::datasrc::memory::LoadAction;
+using isc::datasrc::memory::ZoneData;
+
+namespace {
+
+const char* zones[] = {
+ "example.org.",
+ "example.com.",
+ "null.org", // test for bad iterator case
+ NULL
+};
+
+class CacheConfigTest : public ::testing::Test {
+protected:
+ CacheConfigTest() :
+ mock_client_(zones),
+ master_config_(Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"params\": "
+ " {\".\": \"" TEST_DATA_DIR "/root.zone\"}"
+ "}")),
+ mock_config_(Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-zones\": [\".\"]}"))
+ {}
+
+ virtual void TearDown() {
+ EXPECT_TRUE(msgmt_.allMemoryDeallocated());
+ }
+
+ MockDataSourceClient mock_client_;
+ const ConstElementPtr master_config_; // valid config for MasterFiles
+ const ConstElementPtr mock_config_; // valid config for MasterFiles
+ isc::util::MemorySegmentLocal msgmt_;
+};
+
+size_t
+countZones(const CacheConfig& cache_config) {
+ return (std::distance(cache_config.begin(), cache_config.end()));
+}
+
+TEST_F(CacheConfigTest, constructMasterFiles) {
+ // A simple case: configuring a MasterFiles table with a single zone
+ const CacheConfig cache_conf("MasterFiles", 0, *master_config_, true);
+ EXPECT_EQ(1, countZones(cache_conf));
+
+ // With multiple zones. Note that the constructor doesn't check if the
+ // file exists, so they can be anything.
+ const ConstElementPtr config_elem_multi(
+ Element::fromJSON("{\"cache-enable\": true,"
+ " \"params\": "
+ "{\"example.com\": \"file1\","
+ " \"example.org\": \"file2\","
+ " \"example.info\": \"file3\"}"
+ "}"));
+ const CacheConfig cache_conf2("MasterFiles", 0, *config_elem_multi, true);
+ EXPECT_EQ(3, countZones(cache_conf2));
+
+ // A bit unusual, but acceptable case: empty parameters, so no zones.
+ const CacheConfig cache_conf3("MasterFiles", 0,
+ *Element::fromJSON("{\"cache-enable\": true,"
+ " \"params\": {}}"),
+ true);
+ EXPECT_EQ(0, countZones(cache_conf3));
+}
+
+TEST_F(CacheConfigTest, badConstructMasterFiles) {
+ // no "params"
+ EXPECT_THROW(CacheConfig("MasterFiles", 0,
+ *Element::fromJSON("{\"cache-enable\": true}"),
+ true),
+ isc::data::TypeError);
+
+ // no "cache-enable"
+ EXPECT_THROW(CacheConfig("MasterFiles", 0,
+ *Element::fromJSON("{\"params\": {}}"), true),
+ CacheConfigError);
+ // cache disabled for MasterFiles
+ EXPECT_THROW(CacheConfig("MasterFiles", 0,
+ *Element::fromJSON("{\"cache-enable\": false,"
+ " \"params\": {}}"), true),
+ CacheConfigError);
+ // cache enabled but not "allowed"
+ EXPECT_THROW(CacheConfig("MasterFiles", 0,
+ *Element::fromJSON("{\"cache-enable\": false,"
+ " \"params\": {}}"), false),
+ CacheConfigError);
+ // type error for cache-enable
+ EXPECT_THROW(CacheConfig("MasterFiles", 0,
+ *Element::fromJSON("{\"cache-enable\": 1,"
+ " \"params\": {}}"), true),
+ isc::data::TypeError);
+
+ // "params" is not a map
+ EXPECT_THROW(CacheConfig("MasterFiles", 0,
+ *Element::fromJSON("{\"cache-enable\": true,"
+ " \"params\": []}"), true),
+ isc::data::TypeError);
+
+ // bogus zone name
+ const ConstElementPtr bad_config(Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"params\": "
+ "{\"bad..name\": \"file1\"}}"));
+ EXPECT_THROW(CacheConfig("MasterFiles", 0, *bad_config, true),
+ isc::dns::EmptyLabel);
+
+ // file name is not a string
+ const ConstElementPtr bad_config2(Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"params\": {\".\": 1}}"));
+ EXPECT_THROW(CacheConfig("MasterFiles", 0, *bad_config2, true),
+ isc::data::TypeError);
+
+ // Specify data source client (must be null for MasterFiles)
+ EXPECT_THROW(CacheConfig("MasterFiles", &mock_client_,
+ *Element::fromJSON("{\"cache-enable\": true,"
+ " \"params\": {}}"), true),
+ isc::InvalidParameter);
+}
+
+TEST_F(CacheConfigTest, getLoadActionWithMasterFiles) {
+ uint8_t labels_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
+
+ const CacheConfig cache_conf("MasterFiles", 0, *master_config_, true);
+
+ // Check getLoadAction. Since it returns a mere functor, we can only
+ // check the behavior by actually calling it. For the purpose of this
+ // test, it should suffice if we confirm the call succeeds and shows
+ // some reasonably valid behavior (we'll check the origin name for that).
+ LoadAction action = cache_conf.getLoadAction(RRClass::IN(),
+ Name::ROOT_NAME());
+ ZoneData* zone_data = action(msgmt_);
+ ASSERT_TRUE(zone_data);
+ EXPECT_EQ(".", zone_data->getOriginNode()->
+ getAbsoluteLabels(labels_buf).toText());
+ ZoneData::destroy(msgmt_, zone_data, RRClass::IN());
+
+ // If the specified zone name is not configured to be cached,
+ // getLoadAction returns empty (false) functor.
+ EXPECT_FALSE(cache_conf.getLoadAction(RRClass::IN(), Name("example.com")));
+}
+
+TEST_F(CacheConfigTest, constructWithMock) {
+ // Performing equivalent set of tests as constructMasterFiles
+
+ // Configure with a single zone.
+ const CacheConfig cache_conf("mock", &mock_client_, *mock_config_, true);
+ EXPECT_EQ(1, countZones(cache_conf));
+ EXPECT_TRUE(cache_conf.isEnabled());
+
+ // Configure with multiple zones.
+ const ConstElementPtr config_elem_multi(
+ Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-zones\": "
+ "[\"example.com\", \"example.org\",\"example.info\"]"
+ "}"));
+ const CacheConfig cache_conf2("mock", &mock_client_, *config_elem_multi,
+ true);
+ EXPECT_EQ(3, countZones(cache_conf2));
+
+ // Empty
+ const CacheConfig cache_conf3(
+ "mock", &mock_client_,
+ *Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-zones\": []}"), true);
+ EXPECT_EQ(0, countZones(cache_conf3));
+
+ // disabled. value of cache-zones are ignored.
+ const ConstElementPtr config_elem_disabled(
+ Element::fromJSON("{\"cache-enable\": false,"
+ " \"cache-zones\": [\"example.com\"]}"));
+ EXPECT_FALSE(CacheConfig("mock", &mock_client_, *config_elem_disabled,
+ true).isEnabled());
+ // enabled but not "allowed". same effect.
+ EXPECT_FALSE(CacheConfig("mock", &mock_client_,
+ *Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-zones\": []}"),
+ false).isEnabled());
+}
+
+TEST_F(CacheConfigTest, badConstructWithMock) {
+ // no "cache-zones" (may become valid in future, but for now "notimp")
+ EXPECT_THROW(CacheConfig("mock", &mock_client_,
+ *Element::fromJSON("{\"cache-enable\": true}"),
+ true),
+ isc::NotImplemented);
+
+ // "cache-zones" is not a list
+ EXPECT_THROW(CacheConfig("mock", &mock_client_,
+ *Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-zones\": {}}"),
+ true),
+ isc::data::TypeError);
+
+ // "cache-zone" entry is not a string
+ EXPECT_THROW(CacheConfig("mock", &mock_client_,
+ *Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-zones\": [1]}"),
+ true),
+ isc::data::TypeError);
+
+ // bogus zone name
+ const ConstElementPtr bad_config(Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"cache-zones\": [\"bad..\"]}"));
+ EXPECT_THROW(CacheConfig("mock", &mock_client_, *bad_config, true),
+ isc::dns::EmptyLabel);
+
+ // duplicate zone name (note that comparison is case insensitive)
+ const ConstElementPtr dup_config(Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"cache-zones\": "
+ " [\"example\", \"EXAMPLE\"]}"));
+ EXPECT_THROW(CacheConfig("mock", &mock_client_, *dup_config, true),
+ CacheConfigError);
+
+ // datasrc is null
+ EXPECT_THROW(CacheConfig("mock", 0, *mock_config_, true),
+ isc::InvalidParameter);
+}
+
+TEST_F(CacheConfigTest, getLoadActionWithMock) {
+ uint8_t labels_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
+
+ // Similar to MasterFiles counterpart, but using underlying source
+ // data source.
+
+ // Note: there's a mismatch between this configuration and the actual
+ // mock data source content: example.net doesn't exist in the data source.
+ const ConstElementPtr config(Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"cache-zones\": [\"example.org\","
+ " \"example.net\", \"null.org\"]}"));
+ const CacheConfig cache_conf("mock", &mock_client_, *config, true);
+ LoadAction action = cache_conf.getLoadAction(RRClass::IN(),
+ Name("example.org"));
+ ZoneData* zone_data = action(msgmt_);
+ ASSERT_TRUE(zone_data);
+ EXPECT_EQ("example.org.", zone_data->getOriginNode()->
+ getAbsoluteLabels(labels_buf).toText());
+ ZoneData::destroy(msgmt_, zone_data, RRClass::IN());
+
+ // Zone not configured for the cache
+ EXPECT_FALSE(cache_conf.getLoadAction(RRClass::IN(), Name("example.com")));
+
+ // Zone configured for the cache but doesn't exist in the underling data
+ // source.
+ EXPECT_THROW(cache_conf.getLoadAction(RRClass::IN(), Name("example.net")),
+ NoSuchZone);
+
+ // buggy data source client: it returns a null pointer from getIterator.
+ EXPECT_THROW(cache_conf.getLoadAction(RRClass::IN(), Name("null.org")),
+ isc::Unexpected);
+}
+
+TEST_F(CacheConfigTest, getSegmentType) {
+ // Default type
+ EXPECT_EQ("local",
+ CacheConfig("MasterFiles", 0,
+ *master_config_, true).getSegmentType());
+
+ // If we explicitly configure it, that value should be used.
+ ConstElementPtr config(Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-type\": \"mapped\","
+ " \"params\": {}}" ));
+ EXPECT_EQ("mapped",
+ CacheConfig("MasterFiles", 0, *config, true).getSegmentType());
+
+ // Wrong types: should be rejected at construction time
+ ConstElementPtr badconfig(Element::fromJSON("{\"cache-enable\": true,"
+ " \"cache-type\": 1,"
+ " \"params\": {}}"));
+ EXPECT_THROW(CacheConfig("MasterFiles", 0, *badconfig, true),
+ isc::data::TypeError);
+}
+
+}
diff --git a/src/lib/datasrc/tests/client_list_unittest.cc b/src/lib/datasrc/tests/client_list_unittest.cc
index 0838fb6..256e2ed 100644
--- a/src/lib/datasrc/tests/client_list_unittest.cc
+++ b/src/lib/datasrc/tests/client_list_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,27 +12,36 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
+
#include <datasrc/client_list.h>
#include <datasrc/client.h>
+#include <datasrc/factory.h>
+#include <datasrc/cache_config.h>
#include <datasrc/zone_iterator.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/memory/memory_client.h>
#include <datasrc/memory/zone_table_segment.h>
#include <datasrc/memory/zone_finder.h>
#include <datasrc/memory/zone_writer.h>
+#include <datasrc/tests/mock_client.h>
+
#include <dns/rrclass.h>
#include <dns/rrttl.h>
#include <dns/rdataclass.h>
#include <gtest/gtest.h>
+#include <boost/format.hpp>
#include <boost/shared_ptr.hpp>
+#include <boost/interprocess/file_mapping.hpp>
#include <set>
#include <fstream>
using namespace isc::datasrc;
+using isc::datasrc::unittest::MockDataSourceClient;
using isc::datasrc::memory::InMemoryClient;
using isc::datasrc::memory::ZoneTableSegment;
using isc::datasrc::memory::InMemoryZoneFinder;
@@ -45,162 +54,6 @@ using namespace std;
namespace {
-// A test data source. It pretends it has some zones.
-class MockDataSourceClient : public DataSourceClient {
-public:
- class Finder : public ZoneFinder {
- public:
- Finder(const Name& origin) :
- origin_(origin)
- {}
- Name getOrigin() const { return (origin_); }
- // The rest is not to be called, so just have them
- RRClass getClass() const {
- isc_throw(isc::NotImplemented, "Not implemented");
- }
- shared_ptr<Context> find(const Name&, const RRType&,
- const FindOptions)
- {
- isc_throw(isc::NotImplemented, "Not implemented");
- }
- shared_ptr<Context> findAll(const Name&,
- vector<ConstRRsetPtr>&,
- const FindOptions)
- {
- isc_throw(isc::NotImplemented, "Not implemented");
- }
- FindNSEC3Result findNSEC3(const Name&, bool) {
- isc_throw(isc::NotImplemented, "Not implemented");
- }
- private:
- Name origin_;
- };
- class Iterator : public ZoneIterator {
- public:
- Iterator(const Name& origin, bool include_a) :
- origin_(origin),
- soa_(new RRset(origin_, RRClass::IN(), RRType::SOA(),
- RRTTL(3600)))
- {
- // The RData here is bogus, but it is not used to anything. There
- // just needs to be some.
- soa_->addRdata(rdata::generic::SOA(Name::ROOT_NAME(),
- Name::ROOT_NAME(),
- 0, 0, 0, 0, 0));
- rrsets_.push_back(soa_);
-
- RRsetPtr rrset(new RRset(origin_, RRClass::IN(), RRType::NS(),
- RRTTL(3600)));
- rrset->addRdata(rdata::generic::NS(Name::ROOT_NAME()));
- rrsets_.push_back(rrset);
-
- if (include_a) {
- // Dummy A rrset. This is used for checking zone data
- // after reload.
- rrset.reset(new RRset(Name("tstzonedata").concatenate(origin_),
- RRClass::IN(), RRType::A(),
- RRTTL(3600)));
- rrset->addRdata(rdata::in::A("192.0.2.1"));
- rrsets_.push_back(rrset);
- }
-
- rrsets_.push_back(ConstRRsetPtr());
-
- it_ = rrsets_.begin();
- }
- virtual isc::dns::ConstRRsetPtr getNextRRset() {
- ConstRRsetPtr result = *it_;
- ++it_;
- return (result);
- }
- virtual isc::dns::ConstRRsetPtr getSOA() const {
- return (soa_);
- }
- private:
- const Name origin_;
- const RRsetPtr soa_;
- std::vector<ConstRRsetPtr> rrsets_;
- std::vector<ConstRRsetPtr>::const_iterator it_;
- };
- // Constructor from a list of zones.
- MockDataSourceClient(const char* zone_names[]) :
- have_a_(true), use_baditerator_(true)
- {
- for (const char** zone(zone_names); *zone; ++zone) {
- zones.insert(Name(*zone));
- }
- }
- // Constructor from configuration. The list of zones will be empty, but
- // it will keep the configuration inside for further inspection.
- MockDataSourceClient(const string& type,
- const ConstElementPtr& configuration) :
- type_(type),
- configuration_(configuration),
- have_a_(true), use_baditerator_(true)
- {
- EXPECT_NE("MasterFiles", type) << "MasterFiles is a special case "
- "and it never should be created as a data source client";
- if (configuration_->getType() == Element::list) {
- for (size_t i(0); i < configuration_->size(); ++i) {
- zones.insert(Name(configuration_->get(i)->stringValue()));
- }
- }
- }
- virtual FindResult findZone(const Name& name) const {
- if (zones.empty()) {
- return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
- }
- set<Name>::const_iterator it(zones.upper_bound(name));
- if (it == zones.begin()) {
- return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
- }
- --it;
- NameComparisonResult compar(it->compare(name));
- const ZoneFinderPtr finder(new Finder(*it));
- switch (compar.getRelation()) {
- case NameComparisonResult::EQUAL:
- return (FindResult(result::SUCCESS, finder));
- case NameComparisonResult::SUPERDOMAIN:
- return (FindResult(result::PARTIALMATCH, finder));
- default:
- return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
- }
- }
- // These methods are not used. They just need to be there to have
- // complete vtable.
- virtual ZoneUpdaterPtr getUpdater(const Name&, bool, bool) const {
- isc_throw(isc::NotImplemented, "Not implemented");
- }
- virtual pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
- getJournalReader(const Name&, uint32_t, uint32_t) const
- {
- isc_throw(isc::NotImplemented, "Not implemented");
- }
- virtual ZoneIteratorPtr getIterator(const Name& name, bool) const {
- if (use_baditerator_ && name == Name("noiter.org")) {
- isc_throw(isc::NotImplemented, "Asked not to be implemented");
- } else if (use_baditerator_ && name == Name("null.org")) {
- return (ZoneIteratorPtr());
- } else {
- FindResult result(findZone(name));
- if (result.code == isc::datasrc::result::SUCCESS) {
- return (ZoneIteratorPtr(new Iterator(name, have_a_)));
- } else {
- isc_throw(DataSourceError, "No such zone");
- }
- }
- }
- void disableA() { have_a_ = false; }
- void disableBadIterator() { use_baditerator_ = false; }
- const string type_;
- const ConstElementPtr configuration_;
-private:
- set<Name> zones;
- bool have_a_; // control the iterator behavior whether to include A record
- bool use_baditerator_; // whether to use bogus zone iterators for tests
-};
-
-
// The test version is the same as the normal version. We, however, add
// some methods to dig directly in the internals, for the tests.
class TestedList : public ConfigurableClientList {
@@ -219,6 +72,13 @@ public:
if (type == "error") {
isc_throw(DataSourceError, "The error data source type");
}
+ if (type == "library_error") {
+ isc_throw(DataSourceLibraryError,
+ "The library error data source type");
+ }
+ if (type == "MasterFiles") {
+ return (DataSourcePair(0, DataSourceClientContainerPtr()));
+ }
shared_ptr<MockDataSourceClient>
ds(new MockDataSourceClient(type, configuration));
// Make sure it is deleted when the test list is deleted.
@@ -252,12 +112,25 @@ const char* ds_zones[][3] = {
const size_t ds_count = (sizeof(ds_zones) / sizeof(*ds_zones));
-class ListTest : public ::testing::Test {
+class SegmentType {
+public:
+ virtual ~SegmentType() {}
+ virtual ConstElementPtr getCacheConfig(bool enabled, const Name& zone)
+ const = 0;
+ virtual void reset(ConfigurableClientList& list,
+ const std::string& datasrc_name,
+ ZoneTableSegment::MemorySegmentOpenMode mode,
+ ConstElementPtr config_params) = 0;
+ virtual std::string getType() = 0;
+};
+
+class ListTest : public ::testing::TestWithParam<SegmentType*> {
public:
ListTest() :
rrclass_(RRClass::IN()),
// The empty list corresponds to a list with no elements inside
list_(new TestedList(rrclass_)),
+ negative_result_(),
config_elem_(Element::fromJSON("["
"{"
" \"type\": \"test_type\","
@@ -268,9 +141,7 @@ public:
" \"type\": \"test_type\","
" \"params\": [\"example.org\", \"example.com\", "
" \"noiter.org\", \"null.org\"]"
- "}]")),
- config_(Element::fromJSON("{}")),
- ztable_segment_(ZoneTableSegment::create(*config_, rrclass_))
+ "}]"))
{
for (size_t i(0); i < ds_count; ++ i) {
shared_ptr<MockDataSourceClient>
@@ -278,34 +149,84 @@ public:
ds_.push_back(ds);
ds_info_.push_back(ConfigurableClientList::DataSourceInfo(
ds.get(), DataSourceClientContainerPtr(),
- false, rrclass_, ztable_segment_));
+ boost::shared_ptr<internal::CacheConfig>(),
+ rrclass_, ""));
+ }
+ }
+
+ ~ListTest() {
+ ds_info_.clear();
+ for (size_t i(0); i < ds_count; ++ i) {
+ ds_[i].reset();
+ }
+ ds_.clear();
+
+ for (size_t i(0); i < ds_count; ++ i) {
+ boost::interprocess::file_mapping::remove(
+ getMappedFilename(i).c_str());
}
}
+ static std::string getMappedFilename(size_t index) {
+ return (boost::str(boost::format(TEST_DATA_BUILDDIR "/test%d.mapped")
+ % index));
+ }
+
// Install a "fake" cached zone using a temporary underlying data source
- // client.
- void prepareCache(size_t index, const Name& zone) {
- // Prepare the temporary data source client
- const char* zones[2];
- const std::string zonename_txt = zone.toText();
- zones[0] = zonename_txt.c_str();
- zones[1] = NULL;
- MockDataSourceClient mock_client(zones);
+ // client. If 'enabled' is set to false, emulate a disabled cache, in
+ // which case there will be no data in memory.
+ void prepareCache(size_t index, const Name& zone, bool enabled = true) {
+ ConfigurableClientList::DataSourceInfo& dsrc_info =
+ list_->getDataSources()[index];
+ MockDataSourceClient* mock_client =
+ static_cast<MockDataSourceClient*>(dsrc_info.data_src_client_);
+
// Disable some default features of the mock to distinguish the
// temporary case from normal case.
- mock_client.disableA();
- mock_client.disableBadIterator();
+ mock_client->disableA();
+ mock_client->disableBadIterator();
+
+ // Build new cache config to load the specified zone, and replace
+ // the data source info with the new config.
+ ConstElementPtr cache_conf_elem =
+ GetParam()->getCacheConfig(enabled, zone);
+ boost::shared_ptr<internal::CacheConfig> cache_conf(
+ new internal::CacheConfig("mock", mock_client, *cache_conf_elem,
+ true));
+ dsrc_info = ConfigurableClientList::DataSourceInfo(
+ dsrc_info.data_src_client_,
+ dsrc_info.container_,
+ cache_conf, rrclass_, dsrc_info.name_);
+
+ // Load the data into the zone table.
+ if (enabled) {
+ const ConstElementPtr config_ztable_segment(
+ Element::fromJSON("{\"mapped-file\": \"" +
+ getMappedFilename(index) +
+ "\"}"));
+
+ GetParam()->reset(*list_, dsrc_info.name_,
+ memory::ZoneTableSegment::CREATE,
+ config_ztable_segment);
+
+ const ConfigurableClientList::ZoneWriterPair result =
+ list_->getCachedZoneWriter(zone, false, dsrc_info.name_);
+
+ ASSERT_EQ(ConfigurableClientList::ZONE_SUCCESS, result.first);
+ result.second->load();
+ result.second->install();
+ // not absolutely necessary, but just in case
+ result.second->cleanup();
- // Create cache from the temporary data source, and push it to the
- // client list.
- const shared_ptr<InMemoryClient> cache(
- new InMemoryClient(ztable_segment_, rrclass_));
- cache->load(zone, *mock_client.getIterator(zone, false));
+ GetParam()->reset(*list_, dsrc_info.name_,
+ memory::ZoneTableSegment::READ_WRITE,
+ config_ztable_segment);
+ }
- ConfigurableClientList::DataSourceInfo& dsrc_info =
- list_->getDataSources()[index];
- dsrc_info.cache_ = cache;
- dsrc_info.ztable_segment_ = ztable_segment_;
+ // On completion of load revert to the previous state of underlying
+ // data source.
+ mock_client->enableA();
+ mock_client->enableBadIterator();
}
// Check the positive result is as we expect it.
void positiveResult(const ClientList::FindResult& result,
@@ -333,6 +254,18 @@ public:
EXPECT_EQ(dsrc.get(), result.dsrc_client_);
}
}
+
+ // check the result with empty (broken) zones. Right now this can only
+ // happen for in-memory caches.
+ void emptyResult(const ClientList::FindResult& result, bool exact,
+ const char* trace_txt)
+ {
+ SCOPED_TRACE(trace_txt);
+ ASSERT_FALSE(result.finder_);
+ EXPECT_EQ(exact, result.exact_match_);
+ EXPECT_TRUE(dynamic_cast<InMemoryClient*>(result.dsrc_client_));
+ }
+
// Configure the list with multiple data sources, according to
// some configuration. It uses the index as parameter, to be able to
// loop through the configurations.
@@ -377,17 +310,89 @@ public:
EXPECT_EQ(cache, list_->getDataSources()[index].cache_ !=
shared_ptr<InMemoryClient>());
}
+ ConfigurableClientList::CacheStatus doReload(
+ const Name& origin, const string& datasrc_name = "");
+ void accessorIterate(const ConstZoneTableAccessorPtr& accessor,
+ int numZones, const string& zoneName);
+
const RRClass rrclass_;
shared_ptr<TestedList> list_;
const ClientList::FindResult negative_result_;
vector<shared_ptr<MockDataSourceClient> > ds_;
vector<ConfigurableClientList::DataSourceInfo> ds_info_;
- const ConstElementPtr config_elem_, config_elem_zones_, config_;
- shared_ptr<ZoneTableSegment> ztable_segment_;
+ const ConstElementPtr config_elem_, config_elem_zones_;
+};
+
+class LocalSegmentType : public SegmentType {
+public:
+ virtual ConstElementPtr getCacheConfig(bool enabled, const Name& zone)
+ const
+ {
+ return (Element::fromJSON("{\"type\": \"mock\","
+ " \"cache-enable\": " +
+ string(enabled ? "true," : "false,") +
+ " \"cache-zones\": "
+ " [\"" + zone.toText() + "\"]}"));
+ }
+ virtual void reset(ConfigurableClientList&, const std::string&,
+ ZoneTableSegment::MemorySegmentOpenMode,
+ ConstElementPtr) {
+ // We must not call reset on local ZoneTableSegments.
+ }
+ virtual std::string getType() {
+ return ("local");
+ }
};
+LocalSegmentType local_segment_type;
+
+INSTANTIATE_TEST_CASE_P(ListTestLocal, ListTest,
+ ::testing::Values(static_cast<SegmentType*>(
+ &local_segment_type)));
+
+#ifdef USE_SHARED_MEMORY
+
+class MappedSegmentType : public SegmentType {
+public:
+ virtual ConstElementPtr getCacheConfig(bool enabled, const Name& zone)
+ const
+ {
+ return (Element::fromJSON("{\"type\": \"mock\","
+ " \"cache-enable\": " +
+ string(enabled ? "true," : "false,") +
+ " \"cache-type\": \"mapped\"," +
+ " \"cache-zones\": "
+ " [\"" + zone.toText() + "\"]}"));
+ }
+ virtual void reset(ConfigurableClientList& list,
+ const std::string& datasrc_name,
+ ZoneTableSegment::MemorySegmentOpenMode mode,
+ ConstElementPtr config_params) {
+ EXPECT_TRUE(list.resetMemorySegment(datasrc_name, mode,
+ config_params));
+ }
+ virtual std::string getType() {
+ return ("mapped");
+ }
+};
+
+MappedSegmentType mapped_segment_type;
+
+INSTANTIATE_TEST_CASE_P(ListTestMapped, ListTest,
+ ::testing::Values(static_cast<SegmentType*>(
+ &mapped_segment_type)));
+
+#endif
+
+// Calling reset on empty list finds no data and returns false.
+TEST_P(ListTest, emptyReset) {
+ EXPECT_FALSE(list_->resetMemorySegment("Something",
+ memory::ZoneTableSegment::CREATE,
+ Element::create()));
+}
+
// Test the test itself
-TEST_F(ListTest, selfTest) {
+TEST_P(ListTest, selfTest) {
EXPECT_EQ(result::SUCCESS, ds_[0]->findZone(Name("example.org")).code);
EXPECT_EQ(result::PARTIALMATCH,
ds_[0]->findZone(Name("sub.example.org")).code);
@@ -401,14 +406,14 @@ TEST_F(ListTest, selfTest) {
}
// Test the list we create with empty configuration is, in fact, empty
-TEST_F(ListTest, emptyList) {
+TEST_P(ListTest, emptyList) {
EXPECT_TRUE(list_->getDataSources().empty());
}
// Check the values returned by a find on an empty list. It should be
// a negative answer (nothing found) no matter if we want an exact or inexact
// match.
-TEST_F(ListTest, emptySearch) {
+TEST_P(ListTest, emptySearch) {
// No matter what we try, we don't get an answer.
// Note: we don't have operator<< for the result class, so we cannot use
@@ -425,7 +430,7 @@ TEST_F(ListTest, emptySearch) {
// Put a single data source inside the list and check it can find an
// exact match if there's one.
-TEST_F(ListTest, singleDSExactMatch) {
+TEST_P(ListTest, singleDSExactMatch) {
list_->getDataSources().push_back(ds_info_[0]);
// This zone is not there
EXPECT_TRUE(negative_result_ == list_->find(Name("org."), true));
@@ -439,7 +444,7 @@ TEST_F(ListTest, singleDSExactMatch) {
}
// When asking for a partial match, we get all that the exact one, but more.
-TEST_F(ListTest, singleDSBestMatch) {
+TEST_P(ListTest, singleDSBestMatch) {
list_->getDataSources().push_back(ds_info_[0]);
// This zone is not there
EXPECT_TRUE(negative_result_ == list_->find(Name("org.")));
@@ -459,7 +464,7 @@ const char* const test_names[] = {
"With a duplicity"
};
-TEST_F(ListTest, multiExactMatch) {
+TEST_P(ListTest, multiExactMatch) {
// Run through all the multi-configurations
for (size_t i(0); i < sizeof(test_names) / sizeof(*test_names); ++i) {
SCOPED_TRACE(test_names[i]);
@@ -478,7 +483,7 @@ TEST_F(ListTest, multiExactMatch) {
}
}
-TEST_F(ListTest, multiBestMatch) {
+TEST_P(ListTest, multiBestMatch) {
// Run through all the multi-configurations
for (size_t i(0); i < 4; ++ i) {
SCOPED_TRACE(test_names[i]);
@@ -499,7 +504,7 @@ TEST_F(ListTest, multiBestMatch) {
}
// Check the configuration is empty when the list is empty
-TEST_F(ListTest, configureEmpty) {
+TEST_P(ListTest, configureEmpty) {
const ConstElementPtr elem(new ListElement);
list_->configure(elem, true);
EXPECT_TRUE(list_->getDataSources().empty());
@@ -508,16 +513,16 @@ TEST_F(ListTest, configureEmpty) {
}
// Check we can get multiple data sources and they are in the right order.
-TEST_F(ListTest, configureMulti) {
+TEST_P(ListTest, configureMulti) {
const ConstElementPtr elem(Element::fromJSON("["
"{"
" \"type\": \"type1\","
- " \"cache\": \"off\","
+ " \"cache-enable\": false,"
" \"params\": {}"
"},"
"{"
" \"type\": \"type2\","
- " \"cache\": \"off\","
+ " \"cache-enable\": false,"
" \"params\": {}"
"}]"
));
@@ -530,7 +535,7 @@ TEST_F(ListTest, configureMulti) {
}
// Check we can pass whatever we want to the params
-TEST_F(ListTest, configureParams) {
+TEST_P(ListTest, configureParams) {
const char* params[] = {
"true",
"false",
@@ -546,7 +551,7 @@ TEST_F(ListTest, configureParams) {
ConstElementPtr elem(Element::fromJSON(string("["
"{"
" \"type\": \"t\","
- " \"cache\": \"off\","
+ " \"cache-enable\": false,"
" \"params\": ") + *param +
"}]"));
list_->configure(elem, true);
@@ -555,7 +560,34 @@ TEST_F(ListTest, configureParams) {
}
}
-TEST_F(ListTest, wrongConfig) {
+TEST_P(ListTest, status) {
+ EXPECT_TRUE(list_->getStatus().empty());
+ const ConstElementPtr elem(Element::fromJSON("["
+ "{"
+ " \"type\": \"type1\","
+ " \"cache-enable\": false,"
+ " \"params\": {}"
+ "},"
+ "{"
+ " \"type\": \"type2\","
+ " \"cache-enable\": true,"
+ " \"cache-zones\": [],"
+ " \"name\": \"Test name\","
+ " \"params\": {}"
+ "}]"
+ ));
+ list_->configure(elem, true);
+ const vector<DataSourceStatus> statuses(list_->getStatus());
+ ASSERT_EQ(2, statuses.size());
+ EXPECT_EQ("type1", statuses[0].getName());
+ EXPECT_EQ(SEGMENT_UNUSED, statuses[0].getSegmentState());
+ EXPECT_THROW(statuses[0].getSegmentType(), isc::InvalidOperation);
+ EXPECT_EQ("Test name", statuses[1].getName());
+ EXPECT_EQ(SEGMENT_INUSE, statuses[1].getSegmentState());
+ EXPECT_EQ("local", statuses[1].getSegmentType());
+}
+
+TEST_P(ListTest, wrongConfig) {
const char* configs[] = {
// A lot of stuff missing from there
"[{\"type\": \"test_type\", \"params\": 13}, {}]",
@@ -653,7 +685,7 @@ TEST_F(ListTest, wrongConfig) {
}
// The param thing defaults to null. Cache is not used yet.
-TEST_F(ListTest, defaults) {
+TEST_P(ListTest, defaults) {
const ConstElementPtr elem(Element::fromJSON("["
"{"
" \"type\": \"type1\""
@@ -664,7 +696,7 @@ TEST_F(ListTest, defaults) {
}
// Check we can call the configure multiple times, to change the configuration
-TEST_F(ListTest, reconfigure) {
+TEST_P(ListTest, reconfigure) {
const ConstElementPtr empty(new ListElement);
list_->configure(config_elem_, true);
checkDS(0, "test_type", "{}", false);
@@ -675,7 +707,7 @@ TEST_F(ListTest, reconfigure) {
}
// Make sure the data source error exception from the factory is propagated
-TEST_F(ListTest, dataSrcError) {
+TEST_P(ListTest, dataSrcError) {
const ConstElementPtr elem(Element::fromJSON("["
"{"
" \"type\": \"error\""
@@ -686,8 +718,37 @@ TEST_F(ListTest, dataSrcError) {
checkDS(0, "test_type", "{}", false);
}
+// In case of library errors, the rest of the data sources should be
+// unaffected.
+TEST_P(ListTest, dataSrcLibraryError) {
+ EXPECT_EQ(0, list_->getDataSources().size());
+ const ConstElementPtr elem(Element::fromJSON("["
+ "{"
+ " \"type\": \"type1\","
+ " \"cache-enable\": false,"
+ " \"params\": {}"
+ "},"
+ "{"
+ " \"type\": \"library_error\","
+ " \"cache-enable\": false,"
+ " \"params\": {}"
+ "},"
+ "{"
+ " \"type\": \"type2\","
+ " \"cache-enable\": false,"
+ " \"params\": {}"
+ "}]"
+ ));
+ list_->configure(elem, true);
+ EXPECT_EQ(2, list_->getDataSources().size());
+ checkDS(0, "type1", "{}", false);
+ checkDS(1, "type2", "{}", false);
+ // Check the exact configuration is preserved
+ EXPECT_EQ(elem, list_->getConfiguration());
+}
+
// Check we can get the cache
-TEST_F(ListTest, configureCacheEmpty) {
+TEST_P(ListTest, configureCacheEmpty) {
const ConstElementPtr elem(Element::fromJSON("["
"{"
" \"type\": \"type1\","
@@ -709,7 +770,7 @@ TEST_F(ListTest, configureCacheEmpty) {
}
// But no cache if we disallow it globally
-TEST_F(ListTest, configureCacheDisabled) {
+TEST_P(ListTest, configureCacheDisabled) {
const ConstElementPtr elem(Element::fromJSON("["
"{"
" \"type\": \"type1\","
@@ -731,7 +792,7 @@ TEST_F(ListTest, configureCacheDisabled) {
}
// Put some zones into the cache
-TEST_F(ListTest, cacheZones) {
+TEST_P(ListTest, cacheZones) {
const ConstElementPtr elem(Element::fromJSON("["
"{"
" \"type\": \"type1\","
@@ -765,20 +826,23 @@ TEST_F(ListTest, cacheZones) {
// Check the caching handles misbehaviour from the data source and
// misconfiguration gracefully
-TEST_F(ListTest, badCache) {
+TEST_P(ListTest, badCache) {
list_->configure(config_elem_, true);
checkDS(0, "test_type", "{}", false);
- // First, the zone is not in the data source
+ // First, the zone is not in the data source. configure() should still
+ // succeed, and the existence zone should be cached.
const ConstElementPtr elem1(Element::fromJSON("["
"{"
- " \"type\": \"type1\","
+ " \"type\": \"test_type\","
" \"cache-enable\": true,"
- " \"cache-zones\": [\"example.org\"],"
- " \"params\": []"
+ " \"cache-zones\": [\"example.org\", \"example.com\"],"
+ " \"params\": [\"example.org\"]"
"}]"));
- EXPECT_THROW(list_->configure(elem1, true),
- ConfigurableClientList::ConfigurationError);
- checkDS(0, "test_type", "{}", false);
+ list_->configure(elem1, true); // shouldn't cause disruption
+ checkDS(0, "test_type", "[\"example.org\"]", true);
+ const shared_ptr<InMemoryClient> cache(list_->getDataSources()[0].cache_);
+ EXPECT_EQ(1, cache->getZoneCount());
+ EXPECT_EQ(result::SUCCESS, cache->findZone(Name("example.org")).code);
// Now, the zone doesn't give an iterator
const ConstElementPtr elem2(Element::fromJSON("["
"{"
@@ -788,7 +852,7 @@ TEST_F(ListTest, badCache) {
" \"params\": [\"noiter.org\"]"
"}]"));
EXPECT_THROW(list_->configure(elem2, true), isc::NotImplemented);
- checkDS(0, "test_type", "{}", false);
+ checkDS(0, "test_type", "[\"example.org\"]", true);
// Now, the zone returns NULL iterator
const ConstElementPtr elem3(Element::fromJSON("["
"{"
@@ -798,7 +862,7 @@ TEST_F(ListTest, badCache) {
" \"params\": [\"null.org\"]"
"}]"));
EXPECT_THROW(list_->configure(elem3, true), isc::Unexpected);
- checkDS(0, "test_type", "{}", false);
+ checkDS(0, "test_type", "[\"example.org\"]", true);
// The autodetection of zones is not enabled
const ConstElementPtr elem4(Element::fromJSON("["
"{"
@@ -807,10 +871,38 @@ TEST_F(ListTest, badCache) {
" \"params\": [\"example.org\"]"
"}]"));
EXPECT_THROW(list_->configure(elem4, true), isc::NotImplemented);
- checkDS(0, "test_type", "{}", false);
+ checkDS(0, "test_type", "[\"example.org\"]", true);
}
-TEST_F(ListTest, masterFiles) {
+// This test relies on the property of mapped type of cache.
+TEST_P(ListTest,
+#ifdef USE_SHARED_MEMORY
+ cacheInNonWritableSegment
+#else
+ DISABLED_cacheInNonWritableSegment
+#endif
+ )
+{
+ // Initializing data source with non writable zone table memory segment
+ // is possible. Loading is just postponed
+ const ConstElementPtr elem(Element::fromJSON("["
+ "{"
+ " \"type\": \"test_type\","
+ " \"cache-enable\": true,"
+ " \"cache-type\": \"mapped\","
+ " \"cache-zones\": [\"example.org\"],"
+ " \"params\": [\"example.org\"]"
+ "}]"));
+ list_->configure(elem, true); // no disruption
+ checkDS(0, "test_type", "[\"example.org\"]", true);
+ const shared_ptr<InMemoryClient> cache(list_->getDataSources()[0].cache_);
+
+ // Likewise, reload attempt will fail.
+ EXPECT_EQ(ConfigurableClientList::CACHE_NOT_WRITABLE,
+ doReload(Name("example.org")));
+}
+
+TEST_P(ListTest, masterFiles) {
const ConstElementPtr elem(Element::fromJSON("["
"{"
" \"type\": \"MasterFiles\","
@@ -826,7 +918,7 @@ TEST_F(ListTest, masterFiles) {
list_->getDataSources()[0].data_src_client_);
// And it can search
- positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "com",
+ positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "root",
true);
// If cache is not enabled, nothing is loaded
@@ -834,9 +926,58 @@ TEST_F(ListTest, masterFiles) {
EXPECT_EQ(0, list_->getDataSources().size());
}
-TEST_F(ListTest, BadMasterFile) {
+// Test the names are set correctly and collission is detected.
+TEST_P(ListTest, names) {
+ // Explicit name
+ const ConstElementPtr elem1(Element::fromJSON("["
+ "{"
+ " \"type\": \"MasterFiles\","
+ " \"cache-enable\": true,"
+ " \"params\": {"
+ " \".\": \"" TEST_DATA_DIR "/root.zone\""
+ " },"
+ " \"name\": \"Whatever\""
+ "}]"));
+ list_->configure(elem1, true);
+ EXPECT_EQ("Whatever", list_->getDataSources()[0].name_);
+
+ // Default name
+ const ConstElementPtr elem2(Element::fromJSON("["
+ "{"
+ " \"type\": \"MasterFiles\","
+ " \"cache-enable\": true,"
+ " \"params\": {"
+ " \".\": \"" TEST_DATA_DIR "/root.zone\""
+ " }"
+ "}]"));
+ list_->configure(elem2, true);
+ EXPECT_EQ("MasterFiles", list_->getDataSources()[0].name_);
+
+ // Collission
+ const ConstElementPtr elem3(Element::fromJSON("["
+ "{"
+ " \"type\": \"MasterFiles\","
+ " \"cache-enable\": true,"
+ " \"params\": {"
+ " \".\": \"" TEST_DATA_DIR "/root.zone\""
+ " }"
+ "},"
+ "{"
+ " \"type\": \"MasterFiles\","
+ " \"cache-enable\": true,"
+ " \"params\": {"
+ " \".\": \"" TEST_DATA_DIR "/root.zone\""
+ " },"
+ " \"name\": \"MasterFiles\""
+ "}]"));
+ EXPECT_THROW(list_->configure(elem3, true),
+ ConfigurableClientList::ConfigurationError);
+}
+
+TEST_P(ListTest, BadMasterFile) {
// Configuration should succeed, and the good zones in the list
- // below should be loaded. No bad zones should be loaded.
+ // below should be loaded. Bad zones won't be "loaded" in its usual sense,
+ // but are still recognized with conceptual "empty" data.
const ConstElementPtr elem(Element::fromJSON("["
"{"
" \"type\": \"MasterFiles\","
@@ -872,40 +1013,25 @@ TEST_F(ListTest, BadMasterFile) {
positiveResult(list_->find(Name("example.com."), true), ds_[0],
Name("example.com."), true, "example.com", true);
- EXPECT_TRUE(negative_result_ == list_->find(Name("example.org."), true));
- EXPECT_TRUE(negative_result_ == list_->find(Name("foo.bar"), true));
- EXPECT_TRUE(negative_result_ == list_->find(Name("example.net."), true));
- EXPECT_TRUE(negative_result_ == list_->find(Name("example.edu."), true));
- EXPECT_TRUE(negative_result_ == list_->find(Name("example.info."), true));
+ // Bad cases: should result in "empty zone", whether the match is exact
+ // or partial.
+ emptyResult(list_->find(Name("foo.bar"), true), true, "foo.bar");
+ emptyResult(list_->find(Name("example.net."), true), true, "example.net");
+ emptyResult(list_->find(Name("example.edu."), true), true, "example.edu");
+ emptyResult(list_->find(Name("example.info."), true), true,
+ "example.info");
+ emptyResult(list_->find(Name("www.example.edu."), false), false,
+ "example.edu, partial");
positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "root",
true);
+ // This one simply doesn't exist.
+ EXPECT_TRUE(list_->find(Name("example.org."), true) == negative_result_);
}
-// This allows us to test two versions of the reloading code
-// (One by calling reload(), one by obtaining a ZoneWriter and
-// playing with that). Once we deprecate reload(), we should revert this
-// change and not use typed tests any more.
-template<class UpdateType>
-class ReloadTest : public ListTest {
-public:
- ConfigurableClientList::ReloadResult doReload(const Name& origin);
-};
-
-// Version with calling reload()
-class ReloadUpdateType {};
-template<>
-ConfigurableClientList::ReloadResult
-ReloadTest<ReloadUpdateType>::doReload(const Name& origin) {
- return (list_->reload(origin));
-};
-
-// Version with the ZoneWriter
-class WriterUpdateType {};
-template<>
-ConfigurableClientList::ReloadResult
-ReloadTest<WriterUpdateType>::doReload(const Name& origin) {
+ConfigurableClientList::CacheStatus
+ListTest::doReload(const Name& origin, const string& datasrc_name) {
ConfigurableClientList::ZoneWriterPair
- result(list_->getCachedZoneWriter(origin));
+ result(list_->getCachedZoneWriter(origin, false, datasrc_name));
if (result.first == ConfigurableClientList::ZONE_SUCCESS) {
// Can't use ASSERT_NE here, it would want to return(), which
// it can't in non-void function.
@@ -918,142 +1044,241 @@ ReloadTest<WriterUpdateType>::doReload(const Name& origin) {
"but the writer is NULL";
}
} else {
- EXPECT_EQ(static_cast<memory::ZoneWriter*>(NULL),
- result.second.get());
+ EXPECT_EQ(static_cast<memory::ZoneWriter*>(NULL), result.second.get());
}
return (result.first);
}
-// Typedefs for the GTEST guts to make it work
-typedef ::testing::Types<ReloadUpdateType, WriterUpdateType> UpdateTypes;
-TYPED_TEST_CASE(ReloadTest, UpdateTypes);
+// Check that ZoneWriter doesn't throw when asked not to
+TEST_P(ListTest, checkZoneWriterCatchesExceptions) {
+ const ConstElementPtr config_elem_zones_(Element::fromJSON("["
+ "{"
+ " \"type\": \"MasterFiles\","
+ " \"params\": {"
+ " \"example.edu\": \"" TEST_DATA_DIR "example.edu-broken\""
+ " },"
+ " \"cache-enable\": true"
+ "}]"));
+
+ list_->configure(config_elem_zones_, true);
+ ConfigurableClientList::ZoneWriterPair
+ result(list_->getCachedZoneWriter(Name("example.edu"), true));
+ ASSERT_EQ(ConfigurableClientList::ZONE_SUCCESS, result.first);
+ ASSERT_TRUE(result.second);
+
+ std::string error_msg;
+ // Because of the way we called getCachedZoneWriter() with
+ // catch_load_error=true, the following should not throw and must
+ // return an error message in error_msg.
+ EXPECT_NO_THROW(result.second->load(&error_msg));
+ EXPECT_FALSE(error_msg.empty());
+ result.second->cleanup();
+}
+
+// Check that ZoneWriter throws when asked to
+TEST_P(ListTest, checkZoneWriterThrows) {
+ const ConstElementPtr config_elem_zones_(Element::fromJSON("["
+ "{"
+ " \"type\": \"MasterFiles\","
+ " \"params\": {"
+ " \"example.edu\": \"" TEST_DATA_DIR "example.edu-broken\""
+ " },"
+ " \"cache-enable\": true"
+ "}]"));
+
+ list_->configure(config_elem_zones_, true);
+ ConfigurableClientList::ZoneWriterPair
+ result(list_->getCachedZoneWriter(Name("example.edu"), false));
+ ASSERT_EQ(ConfigurableClientList::ZONE_SUCCESS, result.first);
+ ASSERT_TRUE(result.second);
+
+ std::string error_msg;
+ // Because of the way we called getCachedZoneWriter() with
+ // catch_load_error=false, the following should throw and must not
+ // modify error_msg.
+ EXPECT_THROW(result.second->load(&error_msg),
+ isc::datasrc::ZoneLoaderException);
+ EXPECT_TRUE(error_msg.empty());
+ result.second->cleanup();
+}
// Test we can reload a zone
-TYPED_TEST(ReloadTest, reloadSuccess) {
- this->list_->configure(this->config_elem_zones_, true);
+TEST_P(ListTest, reloadSuccess) {
+ list_->configure(config_elem_zones_, true);
+
+ const vector<DataSourceStatus> statii_before(list_->getStatus());
+ ASSERT_EQ(1, statii_before.size());
+ EXPECT_EQ("test_type", statii_before[0].getName());
+ EXPECT_EQ(SEGMENT_UNUSED, statii_before[0].getSegmentState());
+ EXPECT_THROW(statii_before[0].getSegmentType(), isc::InvalidOperation);
+
const Name name("example.org");
- this->prepareCache(0, name);
+ prepareCache(0, name);
// The cache currently contains a tweaked version of zone, which
// doesn't have "tstzonedata" A record. So the lookup should result
// in NXDOMAIN.
EXPECT_EQ(ZoneFinder::NXDOMAIN,
- this->list_->find(name).finder_->
+ list_->find(name).finder_->
find(Name("tstzonedata").concatenate(name),
RRType::A())->code);
// Now reload the full zone. It should be there now.
- EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS, this->doReload(name));
+ EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS, doReload(name));
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(name).finder_->
+ list_->find(name).finder_->
find(Name("tstzonedata").concatenate(name),
RRType::A())->code);
+
+ const vector<DataSourceStatus> statii_after(list_->getStatus());
+ ASSERT_EQ(1, statii_after.size());
+ EXPECT_EQ("test_type", statii_after[0].getName());
+ EXPECT_EQ(SEGMENT_INUSE, statii_after[0].getSegmentState());
+ EXPECT_EQ(GetParam()->getType(), statii_after[0].getSegmentType());
}
// The cache is not enabled. The load should be rejected.
-TYPED_TEST(ReloadTest, reloadNotEnabled) {
- this->list_->configure(this->config_elem_zones_, false);
+//
+// FIXME: This test is broken by #2853 and needs to be fixed or
+// removed. Please see #2991 for details.
+TEST_P(ListTest, DISABLED_reloadNotAllowed) {
+ list_->configure(config_elem_zones_, false);
const Name name("example.org");
// We put the cache in even when not enabled. This won't confuse the thing.
- this->prepareCache(0, name);
+ prepareCache(0, name);
// See the reloadSuccess test. This should result in NXDOMAIN.
EXPECT_EQ(ZoneFinder::NXDOMAIN,
- this->list_->find(name).finder_->
+ list_->find(name).finder_->
find(Name("tstzonedata").concatenate(name),
RRType::A())->code);
// Now reload. It should reject it.
- EXPECT_EQ(ConfigurableClientList::CACHE_DISABLED, this->doReload(name));
+ EXPECT_EQ(ConfigurableClientList::CACHE_DISABLED, doReload(name));
// Nothing changed here
EXPECT_EQ(ZoneFinder::NXDOMAIN,
- this->list_->find(name).finder_->
+ list_->find(name).finder_->
find(Name("tstzonedata").concatenate(name),
RRType::A())->code);
}
+// Similar to the previous case, but the cache is disabled in config.
+TEST_P(ListTest, reloadNotEnabled) {
+ list_->configure(config_elem_zones_, true);
+ const Name name("example.org");
+ // We put the cache, actually disabling it.
+ prepareCache(0, name, false);
+ // In this case we cannot really look up due to the limitation of
+ // the mock implementation. We only check reload fails.
+ EXPECT_EQ(ConfigurableClientList::ZONE_NOT_CACHED, doReload(name));
+}
+
// Test several cases when the zone does not exist
-TYPED_TEST(ReloadTest, reloadNoSuchZone) {
- this->list_->configure(this->config_elem_zones_, true);
+TEST_P(ListTest, reloadNoSuchZone) {
+ list_->configure(config_elem_zones_, true);
const Name name("example.org");
// We put the cache in even when not enabled. This won't confuse the
// reload method, as that one looks at the real state of things, not
// at the configuration.
- this->prepareCache(0, Name("example.com"));
+ prepareCache(0, Name("example.com"));
// Not in the data sources
EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND,
- this->doReload(Name("exmaple.cz")));
- // Not cached
- EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND, this->doReload(name));
+ doReload(Name("exmaple.cz")));
+ // If it's not configured to be cached, it won't be reloaded.
+ EXPECT_EQ(ConfigurableClientList::ZONE_NOT_CACHED, doReload(name));
// Partial match
EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND,
- this->doReload(Name("sub.example.com")));
+ doReload(Name("sub.example.com")));
// Nothing changed here - these zones don't exist
EXPECT_EQ(static_cast<isc::datasrc::DataSourceClient*>(NULL),
- this->list_->find(name).dsrc_client_);
+ list_->find(name).dsrc_client_);
EXPECT_EQ(static_cast<isc::datasrc::DataSourceClient*>(NULL),
- this->list_->find(Name("example.cz")).dsrc_client_);
+ list_->find(Name("example.cz")).dsrc_client_);
EXPECT_EQ(static_cast<isc::datasrc::DataSourceClient*>(NULL),
- this->list_->find(Name("sub.example.com"), true).dsrc_client_);
+ list_->find(Name("sub.example.com"), true).dsrc_client_);
// Not reloaded, so A record shouldn't be visible yet.
EXPECT_EQ(ZoneFinder::NXDOMAIN,
- this->list_->find(Name("example.com")).finder_->
+ list_->find(Name("example.com")).finder_->
find(Name("tstzonedata.example.com"),
RRType::A())->code);
}
-// Check we gracefuly throw an exception when a zone disappeared in
-// the underlying data source when we want to reload it
-TYPED_TEST(ReloadTest, reloadZoneGone) {
- this->list_->configure(this->config_elem_, true);
+// Check we gracefully reject reloading (i.e. no exception) when a zone
+// disappeared in the underlying data source when we want to reload it
+TEST_P(ListTest, reloadZoneGone) {
+ list_->configure(config_elem_zones_, true);
const Name name("example.org");
- // We put in a cache for non-existant zone. This emulates being loaded
+ // We put in a cache for non-existent zone. This emulates being loaded
// and then the zone disappearing. We prefill the cache, so we can check
// it.
- this->prepareCache(0, name);
+ prepareCache(0, name);
// The (cached) zone contains zone's SOA
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(name).finder_->find(name,
- RRType::SOA())->code);
- // The zone is not there, so abort the reload.
- EXPECT_THROW(this->doReload(name), DataSourceError);
+ list_->find(name).finder_->find(name, RRType::SOA())->code);
+ // Remove the zone from the data source.
+ static_cast<MockDataSourceClient*>(
+ list_->getDataSources()[0].data_src_client_)->eraseZone(name);
+
+ // The zone is not there, so reload doesn't take place.
+ EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND, doReload(name));
// The (cached) zone is not hurt.
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(name).finder_->find(name,
- RRType::SOA())->code);
+ list_->find(name).finder_->find(name, RRType::SOA())->code);
+}
+
+TEST_P(ListTest, reloadNewZone) {
+ // Test the case where a zone to be cached originally doesn't exist
+ // in the underlying data source and is added later. reload() will
+ // succeed once it's available in the data source.
+ const ConstElementPtr elem(Element::fromJSON("["
+ "{"
+ " \"type\": \"test_type\","
+ " \"cache-enable\": true,"
+ " \"cache-zones\": [\"example.org\", \"example.com\"],"
+ " \"params\": [\"example.org\"]"
+ "}]"));
+ list_->configure(elem, true);
+ checkDS(0, "test_type", "[\"example.org\"]", true); // no example.com
+
+ // We can't reload it either
+ EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND,
+ doReload(Name("example.com")));
+
+ // If we add the zone, we can now reload it
+ EXPECT_TRUE(static_cast<MockDataSourceClient*>(
+ list_->getDataSources()[0].data_src_client_)->
+ insertZone(Name("example.com")));
+ EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS,
+ doReload(Name("example.com")));
}
// The underlying data source throws. Check we don't modify the state.
-TYPED_TEST(ReloadTest, reloadZoneThrow) {
- this->list_->configure(this->config_elem_zones_, true);
+TEST_P(ListTest, reloadZoneThrow) {
+ list_->configure(config_elem_zones_, true);
const Name name("noiter.org");
- this->prepareCache(0, name);
+ prepareCache(0, name);
// The zone contains stuff now
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(name).finder_->find(name,
- RRType::SOA())->code);
+ list_->find(name).finder_->find(name, RRType::SOA())->code);
// The iterator throws, so abort the reload.
- EXPECT_THROW(this->doReload(name), isc::NotImplemented);
+ EXPECT_THROW(doReload(name), isc::NotImplemented);
// The zone is not hurt.
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(name).finder_->find(name,
- RRType::SOA())->code);
+ list_->find(name).finder_->find(name, RRType::SOA())->code);
}
-TYPED_TEST(ReloadTest, reloadNullIterator) {
- this->list_->configure(this->config_elem_zones_, true);
+TEST_P(ListTest, reloadNullIterator) {
+ list_->configure(config_elem_zones_, true);
const Name name("null.org");
- this->prepareCache(0, name);
+ prepareCache(0, name);
// The zone contains stuff now
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(name).finder_->find(name,
- RRType::SOA())->code);
+ list_->find(name).finder_->find(name, RRType::SOA())->code);
// The iterator throws, so abort the reload.
- EXPECT_THROW(this->doReload(name), isc::Unexpected);
+ EXPECT_THROW(doReload(name), isc::Unexpected);
// The zone is not hurt.
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(name).finder_->find(name,
- RRType::SOA())->code);
+ list_->find(name).finder_->find(name, RRType::SOA())->code);
}
// Test we can reload the master files too (special-cased)
-TYPED_TEST(ReloadTest, reloadMasterFile) {
+TEST_P(ListTest, reloadMasterFile) {
const char* const install_cmd = INSTALL_PROG " -c " TEST_DATA_DIR
"/root.zone " TEST_DATA_BUILDDIR "/root.zone.copied";
if (system(install_cmd) != 0) {
@@ -1071,21 +1296,167 @@ TYPED_TEST(ReloadTest, reloadMasterFile) {
" \".\": \"" TEST_DATA_BUILDDIR "/root.zone.copied\""
" }"
"}]"));
- this->list_->configure(elem, true);
+ list_->configure(elem, true);
// Add a record that is not in the zone
EXPECT_EQ(ZoneFinder::NXDOMAIN,
- this->list_->find(Name(".")).finder_->find(Name("nosuchdomain"),
- RRType::TXT())->code);
+ list_->find(Name(".")).finder_->find(Name("nosuchdomain"),
+ RRType::TXT())->code);
ofstream f;
f.open(TEST_DATA_BUILDDIR "/root.zone.copied", ios::out | ios::app);
f << "nosuchdomain.\t\t3600\tIN\tTXT\ttest" << std::endl;
f.close();
// Do the reload.
- EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS, this->doReload(Name(".")));
+ EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS, doReload(Name(".")));
// It is here now.
EXPECT_EQ(ZoneFinder::SUCCESS,
- this->list_->find(Name(".")).finder_->find(Name("nosuchdomain"),
- RRType::TXT())->code);
+ list_->find(Name(".")).finder_->find(Name("nosuchdomain"),
+ RRType::TXT())->code);
+}
+
+TEST_P(ListTest, reloadByDataSourceName) {
+ // We use three data sources (and their clients). 2nd and 3rd have
+ // the same name of the zones.
+ const ConstElementPtr config_elem = Element::fromJSON(
+ "[{\"type\": \"test_type1\", \"params\": [\"example.org\"]},"
+ " {\"type\": \"test_type2\", \"params\": [\"example.com\"]},"
+ " {\"type\": \"test_type3\", \"params\": [\"example.com\"]}]");
+ list_->configure(config_elem, true);
+ // Prepare in-memory cache for the 1st and 2nd data sources.
+ prepareCache(0, Name("example.org"));
+ prepareCache(1, Name("example.com"));
+
+ // Normal case: both zone name and data source name matches.
+ // See the reloadSuccess test about the NXDOMAIN/SUCCESS checks.
+ EXPECT_EQ(ZoneFinder::NXDOMAIN,
+ list_->find(Name("tstzonedata.example.com")).finder_->
+ find(Name("tstzonedata.example.com"), RRType::A())->code);
+ EXPECT_EQ(ConfigurableClientList::ZONE_SUCCESS,
+ doReload(Name("example.com"), "test_type2"));
+ EXPECT_EQ(ZoneFinder::SUCCESS,
+ list_->find(Name("tstzonedata.example.com")).finder_->
+ find(Name("tstzonedata.example.com"), RRType::A())->code);
+
+ // The specified zone exists in the first entry of the list, but a
+ // different data source name is specified (in which the specified zone
+ // doesn't exist), so reloading should fail, and the cache status of the
+ // first data source shouldn't change.
+ EXPECT_EQ(ZoneFinder::NXDOMAIN,
+ list_->find(Name("tstzonedata.example.org")).finder_->
+ find(Name("tstzonedata.example.org"), RRType::A())->code);
+ EXPECT_EQ(ConfigurableClientList::ZONE_NOT_FOUND,
+ doReload(Name("example.org"), "test_type2"));
+ EXPECT_EQ(ZoneFinder::NXDOMAIN,
+ list_->find(Name("tstzonedata.example.org")).finder_->
+ find(Name("tstzonedata.example.org"), RRType::A())->code);
+
+ // Likewise, if a specific data source is given, normal name matching
+ // isn't suppressed and the 3rd data source will be used. There cache
+ // is disabled, so reload should fail due to "not cached".
+ EXPECT_EQ(ConfigurableClientList::ZONE_NOT_CACHED,
+ doReload(Name("example.com"), "test_type3"));
+
+ // specified name of data source doesn't exist.
+ EXPECT_EQ(ConfigurableClientList::DATASRC_NOT_FOUND,
+ doReload(Name("example.org"), "test_type4"));
+}
+
+// This takes the accessor provided by getZoneTableAccessor(), iterates
+// through the table, and verifies that the expected number of zones are
+// present, as well as the named zone.
+void
+ListTest::accessorIterate(const ConstZoneTableAccessorPtr& accessor,
+ int numZones, const string& zoneName="")
+{
+ // Confirm basic iterator behavior.
+ ASSERT_TRUE(accessor);
+ ZoneTableAccessor::IteratorPtr it = accessor->getIterator();
+ ASSERT_TRUE(it);
+ // Iterator does not guarantee ordering, so we look for the target
+ // name anywhere in the table.
+ bool found = false;
+ int i;
+ for (i = 0; !it->isLast(); ++i, it->next()) {
+ if (Name(zoneName) == it->getCurrent().origin) {
+ found = true;
+ }
+ }
+ EXPECT_EQ(i, numZones);
+ if (numZones > 0) {
+ EXPECT_TRUE(found);
+ }
+}
+
+TEST_F(ListTest, zoneTableAccessor) {
+ // empty configuration
+ const ConstElementPtr elem(new ListElement);
+ list_->configure(elem, true);
+ // null pointer treated as false
+ EXPECT_FALSE(list_->getZoneTableAccessor("", true));
+
+ // empty list; expect it to return an empty list
+ list_->configure(config_elem_, true);
+ ConstZoneTableAccessorPtr z(list_->getZoneTableAccessor("", true));
+ accessorIterate(z, 0);
+
+ const ConstElementPtr elem2(Element::fromJSON("["
+ "{"
+ " \"type\": \"type1\","
+ " \"cache-enable\": true,"
+ " \"cache-zones\": [\"example.com\"],"
+ " \"params\": [\"example.com\"]"
+ "},"
+ "{"
+ " \"type\": \"type2\","
+ " \"cache-enable\": false,"
+ " \"params\": [\"example.org\"]"
+ "},"
+ "{"
+ " \"type\": \"type3\","
+ " \"cache-enable\": true,"
+ " \"cache-zones\": [\"example.net\", \"example.info\"],"
+ " \"params\": [\"example.net\", \"example.info\"]"
+ "}]"));
+
+ // allow_cache = false
+ // ask for a non-existent zone table, expect null
+ list_->configure(elem2, false);
+ EXPECT_FALSE(list_->getZoneTableAccessor("bogus", true));
+ // ask for any zone table, expect an empty list
+ z = list_->getZoneTableAccessor("", true);
+ accessorIterate(z, 0);
+
+ // allow_cache = true, use_cache = false
+ list_->configure(elem2, true);
+ EXPECT_THROW(list_->getZoneTableAccessor("", false), isc::NotImplemented);
+ EXPECT_THROW(list_->getZoneTableAccessor("type1", false),
+ isc::NotImplemented);
+
+ // datasrc not found, returns NULL pointer
+ EXPECT_FALSE(list_->getZoneTableAccessor("bogus", true));
+
+ // return first datasrc
+ z = list_->getZoneTableAccessor("", true);
+ accessorIterate(z, 1, "example.com");
+
+ // datasrc has cache disabled, returns accessor to empty list
+ z = list_->getZoneTableAccessor("type2", true);
+ accessorIterate(z, 0);
+
+ // search by name
+ z = list_->getZoneTableAccessor("type3", true);
+ accessorIterate(z, 2, "example.net");
+}
+
+// Check the status holds data
+TEST(DataSourceStatus, status) {
+ const DataSourceStatus status("Test", SEGMENT_INUSE, "local");
+ EXPECT_EQ("Test", status.getName());
+ EXPECT_EQ(SEGMENT_INUSE, status.getSegmentState());
+ EXPECT_EQ("local", status.getSegmentType());
+ const DataSourceStatus status_unused("Unused");
+ EXPECT_EQ("Unused", status_unused.getName());
+ EXPECT_EQ(SEGMENT_UNUSED, status_unused.getSegmentState());
+ EXPECT_THROW(status_unused.getSegmentType(), isc::InvalidOperation);
}
}
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index 83ff004..e6a27b4 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -25,7 +25,7 @@
#include <datasrc/database.h>
#include <datasrc/zone.h>
#include <datasrc/zone_finder.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/zone_iterator.h>
#include <testutils/dnsmessage_test.h>
@@ -167,7 +167,8 @@ public:
virtual void addNSEC3RecordToZone(const string (&)[ADD_NSEC3_COLUMN_COUNT])
{}
virtual void deleteRecordInZone(const string (&)[DEL_PARAM_COUNT]) {}
- virtual void deleteNSEC3RecordInZone(const string (&)[DEL_PARAM_COUNT]) {}
+ virtual void deleteNSEC3RecordInZone(const string
+ (&)[DEL_NSEC3_PARAM_COUNT]) {}
virtual void addRecordDiff(int, uint32_t, DiffOperation,
const std::string (&)[DIFF_PARAM_COUNT]) {}
@@ -634,9 +635,8 @@ private:
};
// Common subroutine for deleteRecordinZone and deleteNSEC3RecordInZone.
- void deleteRecord(Domains& domains,
- const string (¶ms)[DEL_PARAM_COUNT])
- {
+ template<size_t param_count>
+ void deleteRecord(Domains& domains, const string (¶ms)[param_count]) {
vector<vector<string> >& records =
domains[params[DatabaseAccessor::DEL_NAME]];
records.erase(remove_if(records.begin(), records.end(),
@@ -655,7 +655,7 @@ public:
}
virtual void deleteNSEC3RecordInZone(
- const string (¶ms)[DEL_PARAM_COUNT])
+ const string (¶ms)[DEL_NSEC3_PARAM_COUNT])
{
deleteRecord(*update_nsec3_namespace_, params);
}
@@ -1137,6 +1137,9 @@ const char* TEST_NSEC3_RECORDS[][5] = {
};
DatabaseClientTest::DatabaseClientTest() :
+ // We need to initialize to something, and not being mock is safer
+ // until we know for sure.
+ is_mock_(false),
zname_("example.org"), qname_("www.example.org"),
qclass_(dns::RRClass::IN()),
qtype_(dns::RRType::A()),
@@ -1417,7 +1420,7 @@ TEST(GenericDatabaseClientTest, noAccessorException) {
// If the zone doesn't exist, exception is thrown
TEST_P(DatabaseClientTest, noZoneIterator) {
- EXPECT_THROW(client_->getIterator(Name("example.com")), DataSourceError);
+ EXPECT_THROW(client_->getIterator(Name("example.com")), NoSuchZone);
}
// If the zone doesn't exist and iteration is not implemented, it still throws
@@ -1427,7 +1430,7 @@ TEST(GenericDatabaseClientTest, noZoneNotImplementedIterator) {
boost::shared_ptr<DatabaseAccessor>(
new NopAccessor())).getIterator(
Name("example.com")),
- DataSourceError);
+ NoSuchZone);
}
TEST(GenericDatabaseClientTest, notImplementedIterator) {
@@ -2566,7 +2569,7 @@ TEST_P(DatabaseClientTest, wildcard) {
doFindTest(*finder, isc::dns::Name("a.*.wild.example.org"),
qtype_, qtype_, rrttl_, ZoneFinder::NXDOMAIN,
expected_rdatas_, expected_sig_rdatas_);
- // These should be canceled, since it is below a domain which exitsts
+ // These should be canceled, since it is below a domain which exists
doFindTest(*finder, isc::dns::Name("nothing.here.wild.example.org"),
qtype_, qtype_, rrttl_, ZoneFinder::NXDOMAIN,
expected_rdatas_, expected_sig_rdatas_);
diff --git a/src/lib/datasrc/tests/factory_unittest.cc b/src/lib/datasrc/tests/factory_unittest.cc
index cc03ce3..2708e0e 100644
--- a/src/lib/datasrc/tests/factory_unittest.cc
+++ b/src/lib/datasrc/tests/factory_unittest.cc
@@ -16,7 +16,7 @@
#include <datasrc/datasrc_config.h>
#include <datasrc/factory.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/sqlite3_accessor.h>
#include <dns/rrclass.h>
@@ -28,8 +28,6 @@ using namespace isc::datasrc;
using namespace isc::data;
std::string SQLITE_DBFILE_EXAMPLE_ORG = TEST_DATA_DIR "/example.org.sqlite3";
-const std::string STATIC_DS_FILE = TEST_DATA_DIR "/static.zone";
-const std::string STATIC_BAD_DS_FILE = TEST_DATA_DIR "/static-bad.zone";
const std::string ROOT_ZONE_FILE = TEST_DATA_DIR "/root.zone";
namespace {
@@ -41,6 +39,8 @@ void
pathtestHelper(const std::string& file, const std::string& expected_error) {
std::string error;
try {
+ // cppcheck-suppress unusedScopedObject We just check if it throws
+ // to create, not use it. That's OK.
DataSourceClientContainer(file, ElementPtr());
} catch (const DataSourceLibraryError& dsle) {
error = dsle.what();
@@ -166,55 +166,5 @@ TEST(FactoryTest, badType) {
DataSourceError);
}
-// Check the static data source can be loaded.
-TEST(FactoryTest, staticDS) {
- // The only configuration is the file to load.
- const ConstElementPtr config(new StringElement(STATIC_DS_FILE));
- // Get the data source
- DataSourceClientContainer dsc("static", config);
- // And try getting something out to see if it really works.
- DataSourceClient::FindResult
- result(dsc.getInstance().findZone(isc::dns::Name("BIND")));
- ASSERT_EQ(result::SUCCESS, result.code);
- EXPECT_EQ(isc::dns::Name("BIND"), result.zone_finder->getOrigin());
- EXPECT_EQ(isc::dns::RRClass::CH(), result.zone_finder->getClass());
- const isc::dns::ConstRRsetPtr
- version(result.zone_finder->find(isc::dns::Name("VERSION.BIND"),
- isc::dns::RRType::TXT())->rrset);
- ASSERT_NE(isc::dns::ConstRRsetPtr(), version);
- EXPECT_EQ(isc::dns::Name("VERSION.BIND"), version->getName());
- EXPECT_EQ(isc::dns::RRClass::CH(), version->getClass());
- EXPECT_EQ(isc::dns::RRType::TXT(), version->getType());
-}
-
-// Check that file not containing BIND./CH is rejected
-TEST(FactoryTest, staticDSBadFile) {
- // The only configuration is the file to load.
- const ConstElementPtr config(new StringElement(STATIC_BAD_DS_FILE));
- // See it does not want the file
- EXPECT_THROW(DataSourceClientContainer("static", config), DataSourceError);
-}
-
-// Check that some bad configs are rejected
-TEST(FactoryTest, staticDSBadConfig) {
- const char* configs[] = {
- // The file does not exist
- "\"/does/not/exist\"",
- // Bad types
- "null",
- "42",
- "{}",
- "[]",
- "true",
- NULL
- };
- for (const char** config(configs); *config; ++config) {
- SCOPED_TRACE(*config);
- EXPECT_THROW(DataSourceClientContainer("static",
- Element::fromJSON(*config)),
- DataSourceError);
- }
-}
-
} // end anonymous namespace
diff --git a/src/lib/datasrc/tests/memory/Makefile.am b/src/lib/datasrc/tests/memory/Makefile.am
index f77d212..fd60af5 100644
--- a/src/lib/datasrc/tests/memory/Makefile.am
+++ b/src/lib/datasrc/tests/memory/Makefile.am
@@ -4,9 +4,18 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/lib/dns
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_builddir)\"
AM_CXXFLAGS = $(B10_CXXFLAGS)
+if USE_SHARED_MEMORY
+# See src/lib/util/Makefile.am. We need this because some tests directly
+# use the Boost managed_mapped_file. It's not ideal to deal with it as
+# a module-wide flag, but considering it's for tests and only for limited
+# environments, it would still be better to introduce more complicated setup.
+AM_CXXFLAGS += $(BOOST_MAPPED_FILE_CXXFLAG)
+endif
+
if USE_STATIC_LINK
AM_LDFLAGS = -static
endif
@@ -21,6 +30,7 @@ if HAVE_GTEST
TESTS += run_unittests
run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += zone_loader_util.h zone_loader_util.cc
run_unittests_SOURCES += rdata_serialization_unittest.cc
run_unittests_SOURCES += rdataset_unittest.cc
run_unittests_SOURCES += domaintree_unittest.cc
@@ -29,14 +39,19 @@ run_unittests_SOURCES += zone_table_unittest.cc
run_unittests_SOURCES += zone_data_unittest.cc
run_unittests_SOURCES += zone_finder_unittest.cc
run_unittests_SOURCES += ../../tests/faked_nsec3.h ../../tests/faked_nsec3.cc
-run_unittests_SOURCES += memory_segment_test.h
+run_unittests_SOURCES += memory_segment_mock.h
run_unittests_SOURCES += segment_object_holder_unittest.cc
run_unittests_SOURCES += memory_client_unittest.cc
run_unittests_SOURCES += rrset_collection_unittest.cc
run_unittests_SOURCES += zone_data_loader_unittest.cc
run_unittests_SOURCES += zone_data_updater_unittest.cc
-run_unittests_SOURCES += zone_table_segment_test.h
+run_unittests_SOURCES += zone_table_segment_mock.h
run_unittests_SOURCES += zone_table_segment_unittest.cc
+
+if USE_SHARED_MEMORY
+run_unittests_SOURCES += zone_table_segment_mapped_unittest.cc
+endif
+
run_unittests_SOURCES += zone_writer_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
diff --git a/src/lib/datasrc/tests/memory/domaintree_unittest.cc b/src/lib/datasrc/tests/memory/domaintree_unittest.cc
index 45e256a..3e6bceb 100644
--- a/src/lib/datasrc/tests/memory/domaintree_unittest.cc
+++ b/src/lib/datasrc/tests/memory/domaintree_unittest.cc
@@ -17,6 +17,7 @@
#include <exceptions/exceptions.h>
#include <util/memory_segment_local.h>
+#include <util/random/random_number_generator.h>
#include <dns/name.h>
#include <dns/rrclass.h>
@@ -28,6 +29,8 @@
#include <dns/tests/unittest_util.h>
+#include <boost/format.hpp>
+
using namespace std;
using namespace isc;
using namespace isc::dns;
@@ -94,6 +97,10 @@ protected:
const char* const domain_names[] = {
"c", "b", "a", "x.d.e.f", "z.d.e.f", "g.h", "i.g.h", "o.w.y.d.e.f",
"j.z.d.e.f", "p.w.y.d.e.f", "q.w.y.d.e.f", "k.g.h"};
+ const int node_distances[] = {
+ 3, 1, 2, 2, 2, 3, 1, 2, 1, 1, 2, 2
+ };
+
int name_count = sizeof(domain_names) / sizeof(domain_names[0]);
for (int i = 0; i < name_count; ++i) {
dtree.insert(mem_sgmt_, Name(domain_names[i]), &dtnode);
@@ -103,7 +110,8 @@ protected:
dtree_expose_empty_node.insert(mem_sgmt_, Name(domain_names[i]),
&dtnode);
- EXPECT_EQ(static_cast<int*>(NULL), dtnode->setData(new int(i + 1)));
+ EXPECT_EQ(static_cast<int*>(NULL), dtnode->setData(
+ new int(node_distances[i])));
}
}
@@ -126,6 +134,110 @@ TEST_F(DomainTreeTest, nodeCount) {
EXPECT_EQ(0, dtree.getNodeCount());
}
+TEST_F(DomainTreeTest, getDistance) {
+ TestDomainTreeNodeChain node_path;
+ const TestDomainTreeNode* node = NULL;
+ EXPECT_EQ(TestDomainTree::EXACTMATCH,
+ dtree_expose_empty_node.find(Name("a"),
+ &node,
+ node_path));
+ while (node != NULL) {
+ const int* distance = node->getData();
+ if (distance != NULL) {
+ EXPECT_EQ(*distance, node->getDistance());
+ }
+ node = dtree_expose_empty_node.nextNode(node_path);
+ }
+}
+
+void
+checkDistances(const TestDomainTree& tree, int distance) {
+ TestDomainTreeNodeChain node_path;
+ const TestDomainTreeNode* node = NULL;
+
+ // Try to find a node left of the left-most node, and start from its
+ // next node (which is the left-most node in its subtree).
+ EXPECT_EQ(TestDomainTree::NOTFOUND,
+ tree.find<void*>(Name("0"), &node, node_path, NULL, NULL));
+ while ((node = tree.nextNode(node_path)) != NULL) {
+ // The distance from each node to its sub-tree root must be less
+ // than 2 * log(n).
+ EXPECT_GE(2 * distance, node->getDistance());
+ }
+}
+
+TEST_F(DomainTreeTest, checkDistanceRandom) {
+ // This test checks an important performance-related property of the
+ // DomainTree (a red-black tree), which is important for us: the
+ // longest path from a sub-tree's root to a node is no more than
+ // 2log(n). This tests that the tree is balanced.
+
+ // Names are inserted in random order.
+
+ TreeHolder mytree_holder(mem_sgmt_, TestDomainTree::create(mem_sgmt_));
+ TestDomainTree& mytree = *mytree_holder.get();
+ isc::util::random::UniformRandomIntegerGenerator gen('a', 'z');
+ const int log_num_nodes = 20;
+
+ // Make a large million+ node top-level domain tree, i.e., the
+ // following code inserts names such as:
+ //
+ // savoucnsrkrqzpkqypbygwoiliawpbmz.
+ // wkadamcbbpjtundbxcmuayuycposvngx.
+ // wzbpznemtooxdpjecdxynsfztvnuyfao.
+ // yueojmhyffslpvfmgyfwioxegfhepnqq.
+ //
+ for (int i = 0; i < (1 << log_num_nodes); i++) {
+ string namestr;
+ while (true) {
+ for (int j = 0; j < 32; j++) {
+ namestr += gen();
+ }
+ namestr += '.';
+
+ if (mytree.insert(mem_sgmt_, Name(namestr), &dtnode) ==
+ TestDomainTree::SUCCESS) {
+ break;
+ }
+
+ namestr.clear();
+ }
+
+ EXPECT_EQ(static_cast<int*>(NULL), dtnode->setData(new int(i + 1)));
+ }
+
+ checkDistances(mytree, log_num_nodes);
+}
+
+TEST_F(DomainTreeTest, checkDistanceSorted) {
+ // This test checks an important performance-related property of the
+ // DomainTree (a red-black tree), which is important for us: the
+ // longest path from a sub-tree's root to a node is no more than
+ // 2log(n). This tests that the tree is balanced.
+
+ // Names are inserted in sorted order.
+
+ TreeHolder mytree_holder(mem_sgmt_, TestDomainTree::create(mem_sgmt_));
+ TestDomainTree& mytree = *mytree_holder.get();
+ const int log_num_nodes = 20;
+
+ // Make a large million+ node top-level domain tree, i.e., the
+ // following code inserts names such as:
+ //
+ // name00000000.
+ // name00000001.
+ // name00000002.
+ // name00000003.
+ //
+ for (int i = 0; i < (1 << log_num_nodes); i++) {
+ const string namestr(boost::str(boost::format("name%08x.") % i));
+ mytree.insert(mem_sgmt_, Name(namestr), &dtnode);
+ EXPECT_EQ(static_cast<int*>(NULL), dtnode->setData(new int(i + 1)));
+ }
+
+ checkDistances(mytree, log_num_nodes);
+}
+
TEST_F(DomainTreeTest, setGetData) {
// set new data to an existing node. It should have some data.
int* newdata = new int(11);
diff --git a/src/lib/datasrc/tests/memory/memory_client_unittest.cc b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
index 74e7917..73862e3 100644
--- a/src/lib/datasrc/tests/memory/memory_client_unittest.cc
+++ b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <datasrc/tests/memory/zone_loader_util.h>
+
#include <exceptions/exceptions.h>
#include <util/memory_segment_local.h>
@@ -26,7 +28,7 @@
#include <dns/masterload.h>
#include <datasrc/result.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/memory/zone_data.h>
#include <datasrc/memory/zone_table.h>
#include <datasrc/memory/zone_data_updater.h>
@@ -35,13 +37,14 @@
#include <testutils/dnsmessage_test.h>
-#include "memory_segment_test.h"
-#include "zone_table_segment_test.h"
+#include <datasrc/tests/memory/memory_segment_mock.h>
+#include <datasrc/tests/memory/zone_table_segment_mock.h>
#include <gtest/gtest.h>
#include <boost/lexical_cast.hpp>
#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
#include <new> // for bad_alloc
@@ -53,6 +56,7 @@ using namespace isc::datasrc::memory;
using namespace isc::testutils;
using boost::shared_ptr;
using std::vector;
+using isc::datasrc::memory::test::loadZoneIntoTable;
namespace {
@@ -74,6 +78,7 @@ const char* rrset_data[] = {
const char* rrset_data_separated[] = {
"example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. "
"68 3600 300 3600000 3600",
+ "example.org. 3600 IN NS ns1.example.org.",
"a.example.org. 3600 IN A 192.168.0.1", // these two belong to the same
"a.example.org. 3600 IN A 192.168.0.2", // RRset, but are separated.
NULL
@@ -83,6 +88,7 @@ const char* rrset_data_separated[] = {
const char* rrset_data_sigseparated[] = {
"example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. "
"68 3600 300 3600000 3600",
+ "example.org. 3600 IN NS ns1.example.org.",
"a.example.org. 3600 IN A 192.168.0.1",
"a.example.org. 3600 IN RRSIG A 5 3 3600 20150420235959 20051021000000 "
"40430 example.org. FAKEFAKE",
@@ -163,30 +169,26 @@ public:
class MemoryClientTest : public ::testing::Test {
protected:
MemoryClientTest() : zclass_(RRClass::IN()),
- ztable_segment_(new test::ZoneTableSegmentTest(
+ ztable_segment_(new test::ZoneTableSegmentMock(
zclass_, mem_sgmt_)),
client_(new InMemoryClient(ztable_segment_, zclass_))
{}
- ~MemoryClientTest() {
- delete client_;
- }
void TearDown() {
- delete client_;
- client_ = NULL;
+ client_.reset();
ztable_segment_.reset();
EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here.
}
const RRClass zclass_;
- test::MemorySegmentTest mem_sgmt_;
+ test::MemorySegmentMock mem_sgmt_;
shared_ptr<ZoneTableSegment> ztable_segment_;
- InMemoryClient* client_;
+ boost::scoped_ptr<InMemoryClient> client_;
};
TEST_F(MemoryClientTest, loadRRsetDoesntMatchOrigin) {
// Attempting to load example.org to example.com zone should result
// in an exception.
- EXPECT_THROW(client_->load(Name("example.com"),
- TEST_DATA_DIR "/example.org-empty.zone"),
+ EXPECT_THROW(loadZoneData(mem_sgmt_, zclass_, Name("example.com"),
+ TEST_DATA_DIR "/example.org-empty.zone"),
ZoneLoaderException);
}
@@ -194,8 +196,8 @@ TEST_F(MemoryClientTest, loadErrorsInParsingZoneMustNotLeak1) {
// Attempting to load broken example.org zone should result in an
// exception. This should not leak ZoneData and other such
// allocations.
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-broken1.zone"),
+ EXPECT_THROW(loadZoneData(mem_sgmt_, zclass_, Name("example.org"),
+ TEST_DATA_DIR "/example.org-broken1.zone"),
ZoneLoaderException);
// Teardown checks for memory segment leaks
}
@@ -204,50 +206,45 @@ TEST_F(MemoryClientTest, loadErrorsInParsingZoneMustNotLeak2) {
// Attempting to load broken example.org zone should result in an
// exception. This should not leak ZoneData and other such
// allocations.
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-broken2.zone"),
+ EXPECT_THROW(loadZoneData(mem_sgmt_, zclass_, Name("example.org"),
+ TEST_DATA_DIR "/example.org-broken2.zone"),
ZoneLoaderException);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadNonExistentZoneFile) {
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR "/somerandomfilename"),
+ EXPECT_THROW(loadZoneData(mem_sgmt_, zclass_, Name("example.org"),
+ TEST_DATA_DIR "/somerandomfilename"),
ZoneLoaderException);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadEmptyZoneFileThrows) {
// When an empty zone file is loaded, the origin doesn't even have
- // an SOA RR. This condition should be avoided, and hence load()
- // should throw when an empty zone is loaded.
-
- EXPECT_EQ(0, client_->getZoneCount());
-
- EXPECT_THROW(client_->load(Name("."),
- TEST_DATA_DIR "/empty.zone"),
+ // an SOA RR. This condition should be avoided, and hence it results in
+ // an exception.
+ EXPECT_THROW(loadZoneData(mem_sgmt_, zclass_, Name("."),
+ TEST_DATA_DIR "/empty.zone"),
ZoneValidationError);
-
- EXPECT_EQ(0, client_->getZoneCount());
-
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, load) {
// This is a simple load check for a "full" and correct zone that
// should not result in any exceptions.
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org.zone");
- const ZoneData* zone_data =
- client_->findZoneData(Name("example.org"));
+ ZoneData* zone_data = loadZoneData(mem_sgmt_, zclass_,
+ Name("example.org"),
+ TEST_DATA_DIR
+ "/example.org.zone");
ASSERT_NE(static_cast<const ZoneData*>(NULL), zone_data);
EXPECT_FALSE(zone_data->isSigned());
EXPECT_FALSE(zone_data->isNSEC3Signed());
+ ZoneData::destroy(mem_sgmt_, zone_data, zclass_);
}
TEST_F(MemoryClientTest, loadFromIterator) {
- client_->load(Name("example.org"),
- *MockIterator::makeIterator(rrset_data));
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ *MockIterator::makeIterator(rrset_data));
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
@@ -261,13 +258,13 @@ TEST_F(MemoryClientTest, loadFromIterator) {
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::NS(), rrset->getType());
- // RRType::MX() RRset
+ // RRType::A() RRset
rrset = iterator->getNextRRset();
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::MX(), rrset->getType());
EXPECT_EQ(1, rrset->getRRsigDataCount()); // this RRset is signed
- // RRType::A() RRset
+ // RRType::MX() RRset
rrset = iterator->getNextRRset();
EXPECT_TRUE(rrset);
EXPECT_EQ(RRType::A(), rrset->getType());
@@ -279,23 +276,23 @@ TEST_F(MemoryClientTest, loadFromIterator) {
// Iterating past the end should result in an exception
EXPECT_THROW(iterator->getNextRRset(), isc::Unexpected);
- // Loading the zone with an iterator separating RRs of the same RRset
- // will fail because the resulting sequence doesn't meet assumptions of
- // the (current) in-memory implementation.
- EXPECT_THROW(client_->load(Name("example.org"),
- *MockIterator::makeIterator(
- rrset_data_separated)),
- ZoneDataUpdater::AddError);
+ // NOTE: The rest of the tests is not actually about InMemoryClient
+
+ // Loading the zone with an iterator separating RRs of the same
+ // RRset should not fail. It is acceptable to load RRs of the same
+ // type again.
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ *MockIterator::makeIterator(rrset_data_separated));
// Similar to the previous case, but with separated RRSIGs.
- EXPECT_THROW(client_->load(Name("example.org"),
- *MockIterator::makeIterator(
- rrset_data_sigseparated)),
- ZoneDataUpdater::AddError);
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ *MockIterator::makeIterator(rrset_data_sigseparated));
// Emulating bogus iterator implementation that passes empty RRSIGs.
- EXPECT_THROW(client_->load(Name("example.org"),
- *MockIterator::makeIterator(rrset_data, true)),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ *MockIterator::makeIterator(rrset_data,
+ true)),
isc::Unexpected);
}
@@ -308,24 +305,24 @@ TEST_F(MemoryClientTest, loadMemoryAllocationFailures) {
mem_sgmt_.setThrowCount(i);
EXPECT_THROW({
shared_ptr<ZoneTableSegment> ztable_segment(
- new test::ZoneTableSegmentTest(
+ new test::ZoneTableSegmentMock(
zclass_, mem_sgmt_));
// Include the InMemoryClient construction too here. Now,
// even allocations done from InMemoryClient constructor
- // fail (due to MemorySegmentTest throwing) and we check for
+ // fail (due to MemorySegmentMock throwing) and we check for
// leaks when this happens.
InMemoryClient client2(ztable_segment, zclass_);
- client2.load(Name("example.org"),
- TEST_DATA_DIR "/example.org.zone");
+ loadZoneIntoTable(*ztable_segment, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org.zone");
}, std::bad_alloc);
}
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadNSEC3Signed) {
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-nsec3-signed.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-nsec3-signed.zone");
const ZoneData* zone_data =
client_->findZoneData(Name("example.org"));
ASSERT_NE(static_cast<const ZoneData*>(NULL), zone_data);
@@ -336,8 +333,8 @@ TEST_F(MemoryClientTest, loadNSEC3Signed) {
TEST_F(MemoryClientTest, loadNSEC3EmptySalt) {
// Load NSEC3 with empty ("-") salt. This should not throw or crash
// or anything.
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-nsec3-empty-salt.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-nsec3-empty-salt.zone");
const ZoneData* zone_data =
client_->findZoneData(Name("example.org"));
ASSERT_NE(static_cast<const ZoneData*>(NULL), zone_data);
@@ -346,8 +343,8 @@ TEST_F(MemoryClientTest, loadNSEC3EmptySalt) {
}
TEST_F(MemoryClientTest, loadNSEC3SignedNoParam) {
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-nsec3-signed-no-param.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-nsec3-signed-no-param.zone");
const ZoneData* zone_data =
client_->findZoneData(Name("example.org"));
ASSERT_NE(static_cast<const ZoneData*>(NULL), zone_data);
@@ -360,14 +357,14 @@ TEST_F(MemoryClientTest, loadReloadZone) {
// doesn't increase.
EXPECT_EQ(0, client_->getZoneCount());
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-empty.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-empty.zone");
EXPECT_EQ(1, client_->getZoneCount());
// Reload zone with same data
- client_->load(Name("example.org"),
- client_->getFileName(Name("example.org")));
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-empty.zone");
EXPECT_EQ(1, client_->getZoneCount());
const ZoneData* zone_data =
@@ -396,8 +393,8 @@ TEST_F(MemoryClientTest, loadReloadZone) {
// Reload zone with different data
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-rrsigs.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-rrsigs.zone");
EXPECT_EQ(1, client_->getZoneCount());
zone_data = client_->findZoneData(Name("example.org"));
@@ -439,118 +436,155 @@ TEST_F(MemoryClientTest, loadReloadZone) {
}
TEST_F(MemoryClientTest, loadDuplicateType) {
- // This should not result in any exceptions:
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-duplicate-type.zone");
-
- // This should throw:
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-duplicate-type-bad.zone"),
- ZoneDataUpdater::AddError);
+ // This should not result in any exceptions (multiple records of the
+ // same name, type are present, one after another in sequence).
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-duplicate-type.zone");
+
+ // This should not result in any exceptions (multiple records of the
+ // same name, type are present, but not one after another in
+ // sequence).
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-duplicate-type-bad.zone");
+
+ const ZoneData* zone_data =
+ client_->findZoneData(Name("example.org"));
+ EXPECT_NE(static_cast<const ZoneData*>(NULL), zone_data);
+
+ /* Check ns1.example.org */
+ const ZoneTree& tree = zone_data->getZoneTree();
+ const ZoneNode* node;
+ ZoneTree::Result zresult(tree.find(Name("ns1.example.org"), &node));
+ EXPECT_EQ(ZoneTree::EXACTMATCH, zresult);
+
+ const RdataSet* set = node->getData();
+ EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
+ EXPECT_EQ(RRType::AAAA(), set->type);
+
+ set = set->getNext();
+ EXPECT_NE(static_cast<const RdataSet*>(NULL), set);
+ EXPECT_EQ(RRType::A(), set->type);
+ // 192.168.0.1 and 192.168.0.2
+ EXPECT_EQ(2, set->getRdataCount());
+
+ set = set->getNext();
+ EXPECT_EQ(static_cast<const RdataSet*>(NULL), set);
+
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadMultipleCNAMEThrows) {
// Multiple CNAME RRs should throw.
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-multiple-cname.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-multiple-cname.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadMultipleDNAMEThrows) {
// Multiple DNAME RRs should throw.
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-multiple-dname.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-multiple-dname.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadMultipleNSEC3Throws) {
// Multiple NSEC3 RRs should throw.
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-multiple-nsec3.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-multiple-nsec3.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadMultipleNSEC3PARAMThrows) {
// Multiple NSEC3PARAM RRs should throw.
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-multiple-nsec3param.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-multiple-nsec3param.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadOutOfZoneThrows) {
// Out of zone names should throw.
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-out-of-zone.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-out-of-zone.zone"),
ZoneLoaderException);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadWildcardNSThrows) {
// Wildcard NS names should throw
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-wildcard-ns.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-wildcard-ns.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadWildcardDNAMEThrows) {
// Wildcard DNAME names should throw
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-wildcard-dname.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-wildcard-dname.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadWildcardNSEC3Throws) {
// Wildcard NSEC3 names should throw
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-wildcard-nsec3.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-wildcard-nsec3.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadNSEC3WithFewerLabelsThrows) {
// NSEC3 names with labels != (origin_labels + 1) should throw
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-nsec3-fewer-labels.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-nsec3-fewer-labels.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadNSEC3WithMoreLabelsThrows) {
// NSEC3 names with labels != (origin_labels + 1) should throw
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-nsec3-more-labels.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-nsec3-more-labels.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadCNAMEAndNotNSECThrows) {
// CNAME and not NSEC should throw
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-cname-and-not-nsec-1.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-cname-and-not-nsec-1.zone"),
ZoneDataUpdater::AddError);
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-cname-and-not-nsec-2.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-cname-and-not-nsec-2.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
@@ -558,41 +592,41 @@ TEST_F(MemoryClientTest, loadCNAMEAndNotNSECThrows) {
TEST_F(MemoryClientTest, loadDNAMEAndNSApex1) {
// DNAME + NS (apex) is OK
- client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-dname-ns-apex-1.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-dname-ns-apex-1.zone");
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadDNAMEAndNSApex2) {
// DNAME + NS (apex) is OK (reverse order)
- client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-dname-ns-apex-2.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-dname-ns-apex-2.zone");
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadDNAMEAndNSNonApex1) {
// DNAME + NS (non-apex) must throw
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-dname-ns-nonapex-1.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-dname-ns-nonapex-1.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadDNAMEAndNSNonApex2) {
// DNAME + NS (non-apex) must throw (reverse order)
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-dname-ns-nonapex-2.zone"),
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ TEST_DATA_DIR
+ "/example.org-dname-ns-nonapex-2.zone"),
ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, loadRRSIGs) {
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-rrsigs.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-rrsigs.zone");
EXPECT_EQ(1, client_->getZoneCount());
}
@@ -616,37 +650,31 @@ TEST_F(MemoryClientTest, loadRRSIGsRdataMixedCoveredTypes) {
rrsets_vec.push_back(rrset);
- EXPECT_THROW(
- client_->load(Name("example.org"),
- *MockVectorIterator::makeIterator(rrsets_vec)),
- ZoneDataUpdater::AddError);
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ *MockVectorIterator::makeIterator(
+ rrsets_vec)),
+ ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
TEST_F(MemoryClientTest, getZoneCount) {
EXPECT_EQ(0, client_->getZoneCount());
- client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-empty.zone");
+ // We've updated the zone table already in the client, so the count
+ // should also be incremented indirectly.
EXPECT_EQ(1, client_->getZoneCount());
}
-TEST_F(MemoryClientTest, getFileNameForNonExistentZone) {
- // Zone "example.org." doesn't exist
- EXPECT_TRUE(client_->getFileName(Name("example.org.")).empty());
-}
-
-TEST_F(MemoryClientTest, getFileName) {
- client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
- EXPECT_EQ(TEST_DATA_DIR "/example.org-empty.zone",
- client_->getFileName(Name("example.org")));
-}
-
TEST_F(MemoryClientTest, getIteratorForNonExistentZone) {
// Zone "." doesn't exist
- EXPECT_THROW(client_->getIterator(Name(".")), DataSourceError);
+ EXPECT_THROW(client_->getIterator(Name(".")), NoSuchZone);
}
TEST_F(MemoryClientTest, getIterator) {
- client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-empty.zone");
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
// First we have the SOA
@@ -666,9 +694,18 @@ TEST_F(MemoryClientTest, getIterator) {
EXPECT_THROW(iterator->getNextRRset(), isc::Unexpected);
}
+TEST_F(MemoryClientTest, getIteratorForEmptyZone) {
+ // trying to load a broken zone (zone file not existent). It's internally
+ // stored an empty zone.
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/no-such-file.zone", true);
+ // Then getIterator will result in an exception.
+ EXPECT_THROW(client_->getIterator(Name("example.org")), EmptyZone);
+}
+
TEST_F(MemoryClientTest, getIteratorSeparateRRs) {
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-multiple.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-multiple.zone");
// separate_rrs = false
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
@@ -720,8 +757,8 @@ TEST_F(MemoryClientTest, getIteratorSeparateRRs) {
// Test we get RRSIGs and NSEC3s too for iterating with separate RRs
TEST_F(MemoryClientTest, getIteratorSeparateSigned) {
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-nsec3-signed.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-nsec3-signed.zone");
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org"), true));
bool seen_rrsig = false, seen_nsec3 = false;
for (ConstRRsetPtr rrset = iterator->getNextRRset();
@@ -738,7 +775,8 @@ TEST_F(MemoryClientTest, getIteratorSeparateSigned) {
}
TEST_F(MemoryClientTest, getIteratorGetSOAThrowsNotImplemented) {
- client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-empty.zone");
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
// This method is not implemented.
@@ -754,16 +792,46 @@ TEST_F(MemoryClientTest, addEmptyRRsetThrows) {
rrsets_vec.push_back(RRsetPtr(new RRset(Name("example.org"), zclass_,
RRType::A(), RRTTL(3600))));
- EXPECT_THROW(
- client_->load(Name("example.org"),
- *MockVectorIterator::makeIterator(rrsets_vec)),
- ZoneDataUpdater::AddError);
+ EXPECT_THROW(loadZoneIntoTable(*ztable_segment_, Name("example.org"),
+ zclass_,
+ *MockVectorIterator::makeIterator(
+ rrsets_vec)),
+ ZoneDataUpdater::AddError);
// Teardown checks for memory segment leaks
}
+TEST_F(MemoryClientTest, findEmptyZone) {
+ // trying to load a broken zone (zone file not existent). It's internally
+ // stored an empty zone.
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/no-such-file.zone", true);
+
+ using namespace isc::datasrc::result;
+
+ // findZone() returns the match, with NULL zone finder and the result
+ // flag indicating it's empty.
+ const DataSourceClient::FindResult result =
+ client_->findZone(Name("example.org"));
+ EXPECT_EQ(SUCCESS, result.code);
+ EXPECT_EQ(ZONE_EMPTY, result.flags);
+ EXPECT_FALSE(result.zone_finder);
+
+ // Same for the case of subdomain match
+ const DataSourceClient::FindResult result_sub =
+ client_->findZone(Name("www.example.org"));
+ EXPECT_EQ(PARTIALMATCH, result_sub.code);
+ EXPECT_EQ(ZONE_EMPTY, result_sub.flags);
+ EXPECT_FALSE(result_sub.zone_finder);
+
+ // findZoneData() will simply NULL (this is for testing only anyway,
+ // so any result would be okay as long as it doesn't cause disruption).
+ EXPECT_EQ(static_cast<const ZoneData*>(NULL),
+ client_->findZoneData(Name("example.org")));
+}
+
TEST_F(MemoryClientTest, findZoneData) {
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-rrsigs.zone");
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ TEST_DATA_DIR "/example.org-rrsigs.zone");
const ZoneData* zone_data = client_->findZoneData(Name("example.com"));
EXPECT_EQ(static_cast<const ZoneData*>(NULL), zone_data);
diff --git a/src/lib/datasrc/tests/memory/memory_segment_mock.h b/src/lib/datasrc/tests/memory/memory_segment_mock.h
new file mode 100644
index 0000000..92c291d
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/memory_segment_mock.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_MEMORY_SEGMENT_TEST_H
+#define DATASRC_MEMORY_SEGMENT_TEST_H 1
+
+#include <util/memory_segment_local.h>
+
+#include <cstddef> // for size_t
+#include <new> // for bad_alloc
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace test {
+
+// A special memory segment that can be used for tests. It normally behaves
+// like a "local" memory segment. If "throw count" is set to non 0 via
+// setThrowCount(), it continues the normal behavior until the specified
+// number of calls to allocate(), exclusive, and throws an exception at the
+// next call. For example, if count is set to 3, the next two calls to
+// allocate() will succeed, and the 3rd call will fail with an exception.
+// This segment object can be used after the exception is thrown, and the
+// count is internally reset to 0.
+class MemorySegmentMock : public isc::util::MemorySegmentLocal {
+public:
+ MemorySegmentMock() : throw_count_(0) {}
+ virtual void* allocate(std::size_t size) {
+ if (throw_count_ > 0) {
+ if (--throw_count_ == 0) {
+ throw std::bad_alloc();
+ }
+ }
+ return (isc::util::MemorySegmentLocal::allocate(size));
+ }
+ void setThrowCount(std::size_t count) { throw_count_ = count; }
+
+private:
+ std::size_t throw_count_;
+};
+
+} // namespace test
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_SEGMENT_TEST_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/tests/memory/memory_segment_test.h b/src/lib/datasrc/tests/memory/memory_segment_test.h
deleted file mode 100644
index 3195a9b..0000000
--- a/src/lib/datasrc/tests/memory/memory_segment_test.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef DATASRC_MEMORY_SEGMENT_TEST_H
-#define DATASRC_MEMORY_SEGMENT_TEST_H 1
-
-#include <util/memory_segment_local.h>
-
-#include <cstddef> // for size_t
-#include <new> // for bad_alloc
-
-namespace isc {
-namespace datasrc {
-namespace memory {
-namespace test {
-
-// A special memory segment that can be used for tests. It normally behaves
-// like a "local" memory segment. If "throw count" is set to non 0 via
-// setThrowCount(), it continues the normal behavior until the specified
-// number of calls to allocate(), exclusive, and throws an exception at the
-// next call. For example, if count is set to 3, the next two calls to
-// allocate() will succeed, and the 3rd call will fail with an exception.
-// This segment object can be used after the exception is thrown, and the
-// count is internally reset to 0.
-class MemorySegmentTest : public isc::util::MemorySegmentLocal {
-public:
- MemorySegmentTest() : throw_count_(0) {}
- virtual void* allocate(std::size_t size) {
- if (throw_count_ > 0) {
- if (--throw_count_ == 0) {
- throw std::bad_alloc();
- }
- }
- return (isc::util::MemorySegmentLocal::allocate(size));
- }
- void setThrowCount(std::size_t count) { throw_count_ = count; }
-
-private:
- std::size_t throw_count_;
-};
-
-} // namespace test
-} // namespace memory
-} // namespace datasrc
-} // namespace isc
-
-#endif // DATASRC_MEMORY_SEGMENT_TEST_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc b/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
index fb01dd6..4e4397e 100644
--- a/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
+++ b/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
@@ -825,7 +825,7 @@ TEST_F(RdataSerializationTest, badAddRdata) {
EXPECT_THROW(encoder_.addRdata(*aaaa_rdata_), std::bad_cast);
const ConstRdataPtr rp_rdata =
- createRdata(RRType::RP(), RRClass::IN(), "a.example. b.example");
+ createRdata(RRType::RP(), RRClass::IN(), "a.example. b.example.");
encoder_.start(RRClass::IN(), RRType::NS());
EXPECT_THROW(encoder_.addRdata(*rp_rdata), std::bad_cast);
diff --git a/src/lib/datasrc/tests/memory/rdataset_unittest.cc b/src/lib/datasrc/tests/memory/rdataset_unittest.cc
index 1a964ef..31e5be2 100644
--- a/src/lib/datasrc/tests/memory/rdataset_unittest.cc
+++ b/src/lib/datasrc/tests/memory/rdataset_unittest.cc
@@ -191,20 +191,16 @@ TEST_F(RdataSetTest, mergeCreate) {
SCOPED_TRACE("creating merge case " + lexical_cast<string>(i) +
", " + lexical_cast<string>(j));
// Create old rdataset
- SegmentObjectHolder<RdataSet, RRClass> holder1(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_,
+ SegmentObjectHolder<RdataSet, RRClass> holder1(mem_sgmt_, rrclass);
+ holder1.set(RdataSet::create(mem_sgmt_, encoder_,
(i & 1) != 0 ? a_rrsets[0] : null_rrset,
- (i & 2) != 0 ? rrsig_rrsets[0] : null_rrset),
- rrclass);
+ (i & 2) != 0 ? rrsig_rrsets[0] : null_rrset));
// Create merged rdataset, based on the old one and RRsets
- SegmentObjectHolder<RdataSet, RRClass> holder2(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_,
+ SegmentObjectHolder<RdataSet, RRClass> holder2(mem_sgmt_, rrclass);
+ holder2.set(RdataSet::create(mem_sgmt_, encoder_,
(j & 1) != 0 ? a_rrsets[1] : null_rrset,
(j & 2) != 0 ? rrsig_rrsets[1] : null_rrset,
- holder1.get()),
- rrclass);
+ holder1.get()));
// Set up the expected data for the case.
vector<string> expected_rdata;
@@ -241,16 +237,14 @@ TEST_F(RdataSetTest, duplicate) {
// After suppressing duplicates, it should be the same as the default
// RdataSet. Check that.
- SegmentObjectHolder<RdataSet, RRClass> holder1(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, dup_rrset, dup_rrsig), rrclass);
+ SegmentObjectHolder<RdataSet, RRClass> holder1(mem_sgmt_, rrclass);
+ holder1.set(RdataSet::create(mem_sgmt_, encoder_, dup_rrset, dup_rrsig));
checkRdataSet(*holder1.get(), def_rdata_txt_, def_rrsig_txt_);
// Confirm the same thing for the merge mode.
- SegmentObjectHolder<RdataSet, RRClass> holder2(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, a_rrset_, rrsig_rrset_,
- holder1.get()), rrclass);
+ SegmentObjectHolder<RdataSet, RRClass> holder2(mem_sgmt_, rrclass);
+ holder2.set(RdataSet::create(mem_sgmt_, encoder_, a_rrset_, rrsig_rrset_,
+ holder1.get()));
checkRdataSet(*holder2.get(), def_rdata_txt_, def_rrsig_txt_);
}
@@ -275,24 +269,21 @@ TEST_F(RdataSetTest, getNext) {
TEST_F(RdataSetTest, find) {
// Create some RdataSets and make a chain of them.
- SegmentObjectHolder<RdataSet, RRClass> holder1(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, a_rrset_, ConstRRsetPtr()),
- RRClass::IN());
+ SegmentObjectHolder<RdataSet, RRClass> holder1(mem_sgmt_, RRClass::IN());
+ holder1.set(RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
+ ConstRRsetPtr()));
ConstRRsetPtr aaaa_rrset =
textToRRset("www.example.com. 1076895760 IN AAAA 2001:db8::1");
- SegmentObjectHolder<RdataSet, RRClass> holder2(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, aaaa_rrset, ConstRRsetPtr()),
- RRClass::IN());
+ SegmentObjectHolder<RdataSet, RRClass> holder2(mem_sgmt_, RRClass::IN());
+ holder2.set(RdataSet::create(mem_sgmt_, encoder_, aaaa_rrset,
+ ConstRRsetPtr()));
ConstRRsetPtr sigonly_rrset =
textToRRset("www.example.com. 1076895760 IN RRSIG "
"TXT 5 2 3600 20120814220826 20120715220826 "
"1234 example.com. FAKE");
- SegmentObjectHolder<RdataSet, RRClass> holder3(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), sigonly_rrset),
- RRClass::IN());
+ SegmentObjectHolder<RdataSet, RRClass> holder3(mem_sgmt_, RRClass::IN());
+ holder3.set(RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(),
+ sigonly_rrset));
RdataSet* rdataset_a = holder1.get();
RdataSet* rdataset_aaaa = holder2.get();
@@ -376,10 +367,8 @@ TEST_F(RdataSetTest, createManyRRs) {
TEST_F(RdataSetTest, mergeCreateManyRRs) {
ConstRRsetPtr rrset = textToRRset("example.com. 3600 IN TXT some-text");
- SegmentObjectHolder<RdataSet, RRClass> holder(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, rrset, ConstRRsetPtr()),
- RRClass::IN());
+ SegmentObjectHolder<RdataSet, RRClass> holder(mem_sgmt_, RRClass::IN());
+ holder.set(RdataSet::create(mem_sgmt_, encoder_, rrset, ConstRRsetPtr()));
checkCreateManyRRs(boost::bind(&RdataSet::create, _1, _2, _3, _4,
holder.get()), rrset->getRdataCount());
@@ -474,10 +463,8 @@ TEST_F(RdataSetTest, mergeCreateManyRRSIGs) {
ConstRRsetPtr rrsig = textToRRset(
"example.com. 3600 IN RRSIG A 5 2 3600 20120814220826 20120715220826 "
"1234 example.com. FAKEFAKE");
- SegmentObjectHolder<RdataSet, RRClass> holder(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), rrsig),
- rrclass);
+ SegmentObjectHolder<RdataSet, RRClass> holder(mem_sgmt_, rrclass);
+ holder.set(RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), rrsig));
checkCreateManyRRSIGs(boost::bind(&RdataSet::create, _1, _2, _3, _4,
holder.get()), rrsig->getRdataCount());
@@ -543,12 +530,10 @@ TEST_F(RdataSetTest, badCreate) {
TEST_F(RdataSetTest, badMergeCreate) {
// The 'old RdataSet' for merge. Its content doesn't matter much; the test
// should trigger exception before examining it except for the last checks.
- SegmentObjectHolder<RdataSet, RRClass> holder(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_,
+ SegmentObjectHolder<RdataSet, RRClass> holder(mem_sgmt_, RRClass::IN());
+ holder.set(RdataSet::create(mem_sgmt_, encoder_,
textToRRset("www.example.com. 0 IN AAAA 2001:db8::1"),
- ConstRRsetPtr()),
- RRClass::IN());
+ ConstRRsetPtr()));
checkBadCreate(boost::bind(&RdataSet::create, _1, _2, _3, _4,
holder.get()));
@@ -585,9 +570,8 @@ TEST_F(RdataSetTest, varyingTTL) {
RdataSet::destroy(mem_sgmt_, rdataset, rrclass);
// RRSIG's TTL is smaller
- SegmentObjectHolder<RdataSet, RRClass> holder1(
- mem_sgmt_,
- RdataSet::create(mem_sgmt_, encoder_, aaaa_large, sig_small), rrclass);
+ SegmentObjectHolder<RdataSet, RRClass> holder1(mem_sgmt_, rrclass);
+ holder1.set(RdataSet::create(mem_sgmt_, encoder_, aaaa_large, sig_small));
EXPECT_EQ(RRTTL(10), restoreTTL(holder1.get()->getTTLData()));
// Merging another RRset (w/o sig) that has larger TTL
diff --git a/src/lib/datasrc/tests/memory/rrset_collection_unittest.cc b/src/lib/datasrc/tests/memory/rrset_collection_unittest.cc
index 04e285b..f3d3934 100644
--- a/src/lib/datasrc/tests/memory/rrset_collection_unittest.cc
+++ b/src/lib/datasrc/tests/memory/rrset_collection_unittest.cc
@@ -15,15 +15,17 @@
#include <datasrc/memory/rrset_collection.h>
-#include "memory_segment_test.h"
-
#include <datasrc/memory/zone_data_loader.h>
#include <datasrc/memory/segment_object_holder.h>
#include <dns/rrttl.h>
#include <dns/rdataclass.h>
+#include <datasrc/tests/memory/memory_segment_mock.h>
+
#include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
+
using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace std;
@@ -43,22 +45,24 @@ public:
rrclass("IN"),
origin("example.org"),
zone_file(TEST_DATA_DIR "/rrset-collection.zone"),
- zone_data_holder(mem_sgmt,
- loadZoneData(mem_sgmt, rrclass, origin, zone_file),
- rrclass),
- collection(*zone_data_holder.get(), rrclass)
- {}
+ zone_data_holder(mem_sgmt, rrclass)
+ {
+ zone_data_holder.set(loadZoneData(mem_sgmt, rrclass, origin,
+ zone_file));
+ collection.reset(new RRsetCollection(*zone_data_holder.get(),
+ rrclass));
+ }
const RRClass rrclass;
const Name origin;
std::string zone_file;
- test::MemorySegmentTest mem_sgmt;
+ test::MemorySegmentMock mem_sgmt;
SegmentObjectHolder<ZoneData, RRClass> zone_data_holder;
- RRsetCollection collection;
+ boost::scoped_ptr<RRsetCollection> collection;
};
TEST_F(RRsetCollectionTest, find) {
- const RRsetCollection& ccln = collection;
+ const RRsetCollection& ccln = *collection;
ConstRRsetPtr rrset = ccln.find(Name("www.example.org"), rrclass,
RRType::A());
EXPECT_TRUE(rrset);
diff --git a/src/lib/datasrc/tests/memory/segment_object_holder_unittest.cc b/src/lib/datasrc/tests/memory/segment_object_holder_unittest.cc
index d27e364..73aa18b 100644
--- a/src/lib/datasrc/tests/memory/segment_object_holder_unittest.cc
+++ b/src/lib/datasrc/tests/memory/segment_object_holder_unittest.cc
@@ -12,10 +12,17 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
+
#include <util/memory_segment_local.h>
+#include <util/memory_segment_mapped.h>
#include <datasrc/memory/segment_object_holder.h>
+#ifdef USE_SHARED_MEMORY
+#include <boost/interprocess/managed_mapped_file.hpp>
+#endif
+
#include <gtest/gtest.h>
using namespace isc::util;
@@ -24,12 +31,14 @@ using namespace isc::datasrc::memory::detail;
namespace {
const int TEST_ARG_VAL = 42; // arbitrary chosen magic number
+const char* const mapped_file = TEST_DATA_BUILDDIR "/test.mapped";
class TestObject {
public:
static void destroy(MemorySegment& sgmt, TestObject* obj, int arg) {
sgmt.deallocate(obj, sizeof(*obj));
EXPECT_EQ(TEST_ARG_VAL, arg);
+ EXPECT_TRUE(obj);
}
};
@@ -42,7 +51,8 @@ useHolder(MemorySegment& sgmt, TestObject* obj, bool release) {
// deallocate().
typedef SegmentObjectHolder<TestObject, int> HolderType;
- HolderType holder(sgmt, obj, TEST_ARG_VAL);
+ HolderType holder(sgmt, TEST_ARG_VAL);
+ holder.set(obj);
EXPECT_EQ(obj, holder.get());
if (release) {
EXPECT_EQ(obj, holder.release());
@@ -64,4 +74,70 @@ TEST(SegmentObjectHolderTest, foo) {
useHolder(sgmt, obj, false);
EXPECT_TRUE(sgmt.allMemoryDeallocated());
}
+
+// Test nothing bad happens if the holder is not set before it is destroyed
+TEST(SegmentObjectHolderTest, destroyNotSet) {
+ MemorySegmentLocal sgmt;
+ {
+ typedef SegmentObjectHolder<TestObject, int> HolderType;
+ HolderType holder(sgmt, TEST_ARG_VAL);
+ }
+ EXPECT_TRUE(sgmt.allMemoryDeallocated());
+}
+
+#ifdef USE_SHARED_MEMORY
+// Keep allocating bigger and bigger chunks of data until the allocation
+// fails with growing the segment.
+void
+allocateUntilGrows(MemorySegment& segment, size_t& current_size) {
+ // Create an object that will not be explicitly deallocated.
+ // It must be deallocated by the segment holder and even in case
+ // the position moved.
+ void *object_memory = segment.allocate(sizeof(TestObject));
+ TestObject* object = new(object_memory) TestObject;
+ SegmentObjectHolder<TestObject, int> holder(segment, TEST_ARG_VAL);
+ holder.set(object);
+ while (true) {
+ void* data = segment.allocate(current_size);
+ segment.deallocate(data, current_size);
+ current_size *= 2;
+ }
+}
+
+// Check that the segment thing releases stuff even in case it throws
+// SegmentGrown exception and the thing moves address
+TEST(SegmentObjectHolderTest, grow) {
+ MemorySegmentMapped segment(mapped_file,
+ isc::util::MemorySegmentMapped::CREATE_ONLY);
+ // Allocate a bit of memory, to get a unique address
+ void* mark = segment.allocate(1);
+ segment.setNamedAddress("mark", mark);
+
+ // We'd like to cause 'mark' will be mapped at a different address on
+ // MemorySegmentGrown; there doesn't seem to be a reliable and safe way
+ // to cause this situation, but opening another mapped region seems to
+ // often work in practice. We use Boost managed_mapped_file directly
+ // to ignore the imposed file lock with MemorySegmentMapped.
+ using boost::interprocess::managed_mapped_file;
+ using boost::interprocess::open_only;
+ managed_mapped_file mapped_sgmt(open_only, mapped_file);
+
+ // Try allocating bigger and bigger chunks of data until the segment
+ // actually relocates
+ size_t alloc_size = 1024;
+ EXPECT_THROW(allocateUntilGrows(segment, alloc_size), MemorySegmentGrown);
+ // Confirm it's now mapped at a different address.
+ EXPECT_NE(mark, segment.getNamedAddress("mark").second)
+ << "portability assumption for the test doesn't hold; "
+ "disable the test by setting env variable GTEST_FILTER to "
+ "'-SegmentObjectHolderTest.grow'";
+ mark = segment.getNamedAddress("mark").second;
+ segment.clearNamedAddress("mark");
+ segment.deallocate(mark, 1);
+ EXPECT_TRUE(segment.allMemoryDeallocated());
+ // Remove the file
+ EXPECT_EQ(0, unlink(mapped_file));
+}
+#endif
+
}
diff --git a/src/lib/datasrc/tests/memory/testdata/Makefile.am b/src/lib/datasrc/tests/memory/testdata/Makefile.am
index b076837..da27482 100644
--- a/src/lib/datasrc/tests/memory/testdata/Makefile.am
+++ b/src/lib/datasrc/tests/memory/testdata/Makefile.am
@@ -29,6 +29,7 @@ EXTRA_DIST += example.org-rrsigs.zone
EXTRA_DIST += example.org-wildcard-dname.zone
EXTRA_DIST += example.org-wildcard-ns.zone
EXTRA_DIST += example.org-wildcard-nsec3.zone
+EXTRA_DIST += template.zone
EXTRA_DIST += rrset-collection.zone
EXTRA_DIST += 2503-test.zone
diff --git a/src/lib/datasrc/tests/memory/testdata/example.org-duplicate-type-bad.zone b/src/lib/datasrc/tests/memory/testdata/example.org-duplicate-type-bad.zone
index 06c7dff..1198934 100644
--- a/src/lib/datasrc/tests/memory/testdata/example.org-duplicate-type-bad.zone
+++ b/src/lib/datasrc/tests/memory/testdata/example.org-duplicate-type-bad.zone
@@ -1,4 +1,7 @@
-example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 77 3600 300 3600000 3600
+example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 78 3600 300 3600000 3600
+example.org. 3600 IN NS ns1.example.org.
+example.org. 3600 IN NS ns2.example.org.
ns1.example.org. 3600 IN A 192.168.0.1
ns1.example.org. 3600 IN AAAA ::1
+someother.example.org. 3600 IN AAAA ::1
ns1.example.org. 3600 IN A 192.168.0.2
diff --git a/src/lib/datasrc/tests/memory/testdata/template.zone b/src/lib/datasrc/tests/memory/testdata/template.zone
new file mode 100644
index 0000000..d8ae6d8
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/testdata/template.zone
@@ -0,0 +1,4 @@
+; a zone file that can be used with any origin.
+
+@ 3600 IN SOA . . 1 0 0 0 0
+@ 3600 IN NS ns1
diff --git a/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
index abc6f13..0e5677c 100644
--- a/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
@@ -12,22 +12,35 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
+
#include <datasrc/memory/zone_data_loader.h>
#include <datasrc/memory/rdataset.h>
#include <datasrc/memory/zone_data.h>
#include <datasrc/memory/zone_data_updater.h>
+#include <datasrc/memory/segment_object_holder.h>
+#include <datasrc/zone_iterator.h>
#include <util/buffer.h>
#include <dns/name.h>
#include <dns/rrclass.h>
+#include <dns/rdataclass.h>
+#ifdef USE_SHARED_MEMORY
+#include <util/memory_segment_mapped.h>
+#endif
+#include <util/memory_segment_local.h>
-#include "memory_segment_test.h"
+#include <datasrc/tests/memory/memory_segment_mock.h>
#include <gtest/gtest.h>
using namespace isc::dns;
using namespace isc::datasrc::memory;
+#ifdef USE_SHARED_MEMORY
+using isc::util::MemorySegmentMapped;
+#endif
+using isc::datasrc::memory::detail::SegmentObjectHolder;
namespace {
@@ -41,7 +54,7 @@ protected:
EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here.
}
const RRClass zclass_;
- test::MemorySegmentTest mem_sgmt_;
+ test::MemorySegmentMock mem_sgmt_;
ZoneData* zone_data_;
};
@@ -73,4 +86,35 @@ TEST_F(ZoneDataLoaderTest, zoneMinTTL) {
EXPECT_EQ(RRTTL(1200), RRTTL(b));
}
+// Load bunch of small zones, hoping some of the relocation will happen
+// during the memory creation, not only Rdata creation.
+// Note: this doesn't even compile unless USE_SHARED_MEMORY is defined.
+#ifdef USE_SHARED_MEMORY
+TEST(ZoneDataLoaterTest, relocate) {
+ const char* const mapped_file = TEST_DATA_BUILDDIR "/test.mapped";
+ MemorySegmentMapped segment(mapped_file,
+ isc::util::MemorySegmentMapped::CREATE_ONLY,
+ 4096);
+ const size_t zone_count = 10000;
+ typedef SegmentObjectHolder<ZoneData, RRClass> Holder;
+ typedef boost::shared_ptr<Holder> HolderPtr;
+ std::vector<HolderPtr> zones;
+ for (size_t i = 0; i < zone_count; ++i) {
+ // Load some zone
+ ZoneData* data = loadZoneData(segment, RRClass::IN(),
+ Name("example.org"),
+ TEST_DATA_DIR
+ "/example.org-nsec3-signed.zone");
+ // Store it, so it is cleaned up later
+ zones.push_back(HolderPtr(new Holder(segment, RRClass::IN())));
+ zones.back()->set(data);
+
+ }
+ // Deallocate all the zones now.
+ zones.clear();
+ EXPECT_TRUE(segment.allMemoryDeallocated());
+ EXPECT_EQ(0, unlink(mapped_file));
+}
+#endif
+
}
diff --git a/src/lib/datasrc/tests/memory/zone_data_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_unittest.cc
index ffbd0f6..54a0fc4 100644
--- a/src/lib/datasrc/tests/memory/zone_data_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_data_unittest.cc
@@ -16,8 +16,6 @@
#include <datasrc/memory/rdata_serialization.h>
#include <datasrc/memory/rdataset.h>
-#include "memory_segment_test.h"
-
#include <dns/rdataclass.h>
#include <exceptions/exceptions.h>
@@ -30,6 +28,7 @@
#include <dns/rrttl.h>
#include <testutils/dnsmessage_test.h>
+#include <datasrc/tests/memory/memory_segment_mock.h>
#include <gtest/gtest.h>
@@ -73,7 +72,7 @@ protected:
EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated());
}
- MemorySegmentTest mem_sgmt_;
+ MemorySegmentMock mem_sgmt_;
NSEC3Data* nsec3_data_;
const generic::NSEC3PARAM param_rdata_, param_rdata_nosalt_,
param_rdata_largesalt_;
@@ -88,7 +87,7 @@ protected:
// Shared by both test cases using NSEC3 and NSEC3PARAM Rdata
template <typename RdataType>
void
-checkNSEC3Data(MemorySegmentTest& mem_sgmt,
+checkNSEC3Data(MemorySegmentMock& mem_sgmt,
const Name& zone_name,
const RdataType& expect_rdata)
{
@@ -278,4 +277,14 @@ TEST_F(ZoneDataTest, minTTL) {
zone_data_->setMinTTL(1200);
EXPECT_EQ(RRTTL(1200), createRRTTL(zone_data_->getMinTTLData()));
}
+
+TEST_F(ZoneDataTest, emptyData) {
+ // normally create zone data are never "empty"
+ EXPECT_FALSE(zone_data_->isEmpty());
+
+ // zone data instance created by the special create() is made "empty".
+ ZoneData* empty_data = ZoneData::create(mem_sgmt_);
+ EXPECT_TRUE(empty_data->isEmpty());
+ ZoneData::destroy(mem_sgmt_, empty_data, RRClass::IN());
+}
}
diff --git a/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
index 93ca0c9..d4918fa 100644
--- a/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
+
#include <datasrc/memory/zone_data_updater.h>
#include <datasrc/memory/rdataset.h>
#include <datasrc/memory/zone_data.h>
@@ -25,11 +27,14 @@
#include <dns/rrset.h>
#include <dns/rrttl.h>
-#include "memory_segment_test.h"
+#include <util/memory_segment_local.h>
+#include <util/memory_segment_mapped.h>
+#include <datasrc/tests/memory/memory_segment_mock.h>
#include <gtest/gtest.h>
#include <boost/scoped_ptr.hpp>
+#include <boost/lexical_cast.hpp>
#include <cassert>
@@ -39,71 +44,158 @@ using namespace isc::datasrc::memory;
namespace {
-class ZoneDataUpdaterTest : public ::testing::Test {
+const char* const mapped_file = TEST_DATA_BUILDDIR "/test.mapped";
+
+// An abstract factory class for the segments. We want fresh segment for each
+// test, so we have different factories for them.
+class SegmentCreator {
+public:
+ virtual ~SegmentCreator() {}
+ typedef boost::shared_ptr<isc::util::MemorySegment> SegmentPtr;
+ // Create the segment.
+ virtual SegmentPtr create() const = 0;
+ // Clean-up after the test. Most of them will be just NOP (the default),
+ // but the file-mapped one needs to remove the file.
+ virtual void cleanup() const {}
+};
+
+ZoneNode*
+getNode(isc::util::MemorySegment& mem_sgmt, const Name& name,
+ ZoneData* zone_data)
+{
+ ZoneNode* node = NULL;
+ zone_data->insertName(mem_sgmt, name, &node);
+ EXPECT_NE(static_cast<ZoneNode*>(NULL), node);
+ return (node);
+}
+
+class ZoneDataUpdaterTest : public ::testing::TestWithParam<SegmentCreator*> {
protected:
ZoneDataUpdaterTest() :
zname_("example.org"), zclass_(RRClass::IN()),
- zone_data_(ZoneData::create(mem_sgmt_, zname_)),
- updater_(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_, *zone_data_))
- {}
+ mem_sgmt_(GetParam()->create())
+ {
+ ZoneData* data = ZoneData::create(*mem_sgmt_, zname_);
+ mem_sgmt_->setNamedAddress("Test zone data", data);
+ updater_.reset(new ZoneDataUpdater(*mem_sgmt_, zclass_, zname_,
+ *data));
+ }
~ZoneDataUpdaterTest() {
- if (zone_data_ != NULL) {
- ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
- }
- if (!mem_sgmt_.allMemoryDeallocated()) {
+ assert(updater_);
+ ZoneData::destroy(*mem_sgmt_, getZoneData(), zclass_);
+ // Release the updater, so it frees all memory inside the segment too
+ updater_.reset();
+ mem_sgmt_->clearNamedAddress("Test zone data");
+ if (!mem_sgmt_->allMemoryDeallocated()) {
ADD_FAILURE() << "Memory leak detected";
}
+ GetParam()->cleanup();
}
void clearZoneData() {
- assert(zone_data_ != NULL);
- ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
- zone_data_ = ZoneData::create(mem_sgmt_, zname_);
- updater_.reset(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_,
- *zone_data_));
+ assert(updater_);
+ ZoneData::destroy(*mem_sgmt_, getZoneData(), zclass_);
+ mem_sgmt_->clearNamedAddress("Test zone data");
+ updater_.reset();
+ ZoneData* data = ZoneData::create(*mem_sgmt_, zname_);
+ mem_sgmt_->setNamedAddress("Test zone data", data);
+ updater_.reset(new ZoneDataUpdater(*mem_sgmt_, zclass_, zname_,
+ *data));
+ }
+
+ ZoneData* getZoneData() {
+ return (static_cast<ZoneData*>(
+ mem_sgmt_->getNamedAddress("Test zone data").second));
}
const Name zname_;
const RRClass zclass_;
- test::MemorySegmentTest mem_sgmt_;
- ZoneData* zone_data_;
+ boost::shared_ptr<isc::util::MemorySegment> mem_sgmt_;
boost::scoped_ptr<ZoneDataUpdater> updater_;
};
-TEST_F(ZoneDataUpdaterTest, bothNull) {
+class TestSegmentCreator : public SegmentCreator {
+public:
+ virtual SegmentPtr create() const {
+ return (SegmentPtr(new test::MemorySegmentMock));
+ }
+};
+
+TestSegmentCreator test_segment_creator;
+
+INSTANTIATE_TEST_CASE_P(TestSegment, ZoneDataUpdaterTest,
+ ::testing::Values(static_cast<SegmentCreator*>(
+ &test_segment_creator)));
+
+class MemorySegmentCreator : public SegmentCreator {
+public:
+ virtual SegmentPtr create() const {
+ // We are not really supposed to create the segment directly in real
+ // code, but it should be OK inside tests.
+ return (SegmentPtr(new isc::util::MemorySegmentLocal));
+ }
+};
+
+MemorySegmentCreator memory_segment_creator;
+
+INSTANTIATE_TEST_CASE_P(LocalSegment, ZoneDataUpdaterTest,
+ ::testing::Values(static_cast<SegmentCreator*>(
+ &memory_segment_creator)));
+
+#ifdef USE_SHARED_MEMORY
+class MappedSegmentCreator : public SegmentCreator {
+public:
+ MappedSegmentCreator(size_t initial_size =
+ isc::util::MemorySegmentMapped::INITIAL_SIZE) :
+ initial_size_(initial_size)
+ {}
+ virtual SegmentPtr create() const {
+ return (SegmentPtr(new isc::util::MemorySegmentMapped(
+ mapped_file,
+ isc::util::MemorySegmentMapped::CREATE_ONLY,
+ initial_size_)));
+ }
+ virtual void cleanup() const {
+ EXPECT_EQ(0, unlink(mapped_file));
+ }
+private:
+ const size_t initial_size_;
+};
+
+// There should be no initialization fiasco there. We only set int value inside
+// and don't use it until the create() is called.
+MappedSegmentCreator small_creator(4092), default_creator;
+
+INSTANTIATE_TEST_CASE_P(MappedSegment, ZoneDataUpdaterTest, ::testing::Values(
+ static_cast<SegmentCreator*>(&small_creator),
+ static_cast<SegmentCreator*>(&default_creator)));
+#endif
+
+TEST_P(ZoneDataUpdaterTest, bothNull) {
// At least either covered RRset or RRSIG must be non NULL.
EXPECT_THROW(updater_->add(ConstRRsetPtr(), ConstRRsetPtr()),
ZoneDataUpdater::NullRRset);
}
-ZoneNode*
-getNode(isc::util::MemorySegment& mem_sgmt, const Name& name,
- ZoneData* zone_data)
-{
- ZoneNode* node = NULL;
- zone_data->insertName(mem_sgmt, name, &node);
- EXPECT_NE(static_cast<ZoneNode*>(NULL), node);
- return (node);
-}
-
-TEST_F(ZoneDataUpdaterTest, zoneMinTTL) {
+TEST_P(ZoneDataUpdaterTest, zoneMinTTL) {
// If we add SOA, zone's min TTL will be updated.
updater_->add(textToRRset(
"example.org. 3600 IN SOA . . 0 0 0 0 1200",
zclass_, zname_),
ConstRRsetPtr());
- isc::util::InputBuffer b(zone_data_->getMinTTLData(), sizeof(uint32_t));
+ isc::util::InputBuffer b(getZoneData()->getMinTTLData(), sizeof(uint32_t));
EXPECT_EQ(RRTTL(1200), RRTTL(b));
}
-TEST_F(ZoneDataUpdaterTest, rrsigOnly) {
+TEST_P(ZoneDataUpdaterTest, rrsigOnly) {
// RRSIG that doesn't have covered RRset can be added. The resulting
// rdataset won't have "normal" RDATA but sig RDATA.
updater_->add(ConstRRsetPtr(), textToRRset(
"www.example.org. 3600 IN RRSIG A 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- ZoneNode* node = getNode(mem_sgmt_, Name("www.example.org"), zone_data_);
+ ZoneNode* node = getNode(*mem_sgmt_, Name("www.example.org"),
+ getZoneData());
const RdataSet* rdset = node->getData();
ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
rdset = RdataSet::find(rdset, RRType::A(), true);
@@ -111,19 +203,17 @@ TEST_F(ZoneDataUpdaterTest, rrsigOnly) {
EXPECT_EQ(0, rdset->getRdataCount());
EXPECT_EQ(1, rdset->getSigRdataCount());
- // The RRSIG covering A prohibits an actual A RRset from being added.
- // This should be loosened in future version, but we check the current
- // behavior.
- EXPECT_THROW(updater_->add(
- textToRRset("www.example.org. 3600 IN A 192.0.2.1"),
- ConstRRsetPtr()), ZoneDataUpdater::AddError);
+ // The RRSIG covering A must not prohibit an actual A RRset from
+ // being added later.
+ updater_->add(textToRRset("www.example.org. 3600 IN A 192.0.2.1"),
+ ConstRRsetPtr());
// The special "wildcarding" node mark should be added for the RRSIG-only
// case, too.
updater_->add(ConstRRsetPtr(), textToRRset(
"*.wild.example.org. 3600 IN RRSIG A 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- node = getNode(mem_sgmt_, Name("wild.example.org"), zone_data_);
+ node = getNode(*mem_sgmt_, Name("wild.example.org"), getZoneData());
EXPECT_TRUE(node->getFlag(ZoneData::WILDCARD_NODE));
// Simply adding RRSIG covering (delegating NS) shouldn't enable callback
@@ -131,14 +221,14 @@ TEST_F(ZoneDataUpdaterTest, rrsigOnly) {
updater_->add(ConstRRsetPtr(), textToRRset(
"child.example.org. 3600 IN RRSIG NS 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- node = getNode(mem_sgmt_, Name("child.example.org"), zone_data_);
+ node = getNode(*mem_sgmt_, Name("child.example.org"), getZoneData());
EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK));
// Same for DNAME
updater_->add(ConstRRsetPtr(), textToRRset(
"dname.example.org. 3600 IN RRSIG DNAME 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- node = getNode(mem_sgmt_, Name("dname.example.org"), zone_data_);
+ node = getNode(*mem_sgmt_, Name("dname.example.org"), getZoneData());
EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK));
// Likewise, RRSIG for NSEC3PARAM alone shouldn't make the zone
@@ -146,13 +236,13 @@ TEST_F(ZoneDataUpdaterTest, rrsigOnly) {
updater_->add(ConstRRsetPtr(), textToRRset(
"example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- EXPECT_FALSE(zone_data_->isNSEC3Signed());
+ EXPECT_FALSE(getZoneData()->isNSEC3Signed());
// And same for (RRSIG for) NSEC and "is signed".
updater_->add(ConstRRsetPtr(), textToRRset(
"example.org. 3600 IN RRSIG NSEC 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- EXPECT_FALSE(zone_data_->isSigned());
+ EXPECT_FALSE(getZoneData()->isSigned());
}
// Commonly used checks for rrsigForNSEC3Only
@@ -170,7 +260,7 @@ checkNSEC3Rdata(isc::util::MemorySegment& mem_sgmt, const Name& name,
EXPECT_EQ(1, rdset->getSigRdataCount());
}
-TEST_F(ZoneDataUpdaterTest, rrsigForNSEC3Only) {
+TEST_P(ZoneDataUpdaterTest, rrsigForNSEC3Only) {
// Adding only RRSIG covering NSEC3 is tricky. It should go to the
// separate NSEC3 tree, but the separate space is only created when
// NSEC3 or NSEC3PARAM is added. So, in many cases RRSIG-only is allowed,
@@ -185,12 +275,12 @@ TEST_F(ZoneDataUpdaterTest, rrsigForNSEC3Only) {
textToRRset(
"example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- EXPECT_TRUE(zone_data_->isNSEC3Signed());
+ EXPECT_TRUE(getZoneData()->isNSEC3Signed());
updater_->add(ConstRRsetPtr(),
textToRRset(
"09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_);
+ checkNSEC3Rdata(*mem_sgmt_, Name("09GM.example.org"), getZoneData());
// Clear the current content of zone, then add NSEC3
clearZoneData();
@@ -203,7 +293,7 @@ TEST_F(ZoneDataUpdaterTest, rrsigForNSEC3Only) {
textToRRset(
"09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
"20150420235959 20051021000000 1 example.org. FAKE"));
- checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_);
+ checkNSEC3Rdata(*mem_sgmt_, Name("09GM.example.org"), getZoneData());
// If we add only RRSIG without any NSEC3 related data beforehand,
// it will be rejected; it's a limitation of the current implementation.
@@ -216,4 +306,39 @@ TEST_F(ZoneDataUpdaterTest, rrsigForNSEC3Only) {
isc::NotImplemented);
}
+// Generate many small RRsets. This tests that the underlying memory segment
+// can grow during the execution and that the updater handles that well.
+//
+// Some of the grows will happen inserting the RRSIG, some with the TXT. Or,
+// at least we hope so.
+TEST_P(ZoneDataUpdaterTest, manySmallRRsets) {
+ for (size_t i = 0; i < 32768; ++i) {
+ const std::string name(boost::lexical_cast<std::string>(i) +
+ ".example.org.");
+ updater_->add(textToRRset(name + " 3600 IN TXT " +
+ std::string(30, 'X')),
+ textToRRset(name + " 3600 IN RRSIG TXT 5 3 3600 "
+ "20150420235959 20051021000000 1 "
+ "example.org. FAKE"));
+ ZoneNode* node = getNode(*mem_sgmt_,
+ Name(boost::lexical_cast<std::string>(i) +
+ ".example.org"), getZoneData());
+ const RdataSet* rdset = node->getData();
+ ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+ rdset = RdataSet::find(rdset, RRType::TXT(), true);
+ ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+ EXPECT_EQ(1, rdset->getRdataCount());
+ EXPECT_EQ(1, rdset->getSigRdataCount());
+ }
+}
+
+TEST_P(ZoneDataUpdaterTest, updaterCollision) {
+ ZoneData* zone_data = ZoneData::create(*mem_sgmt_,
+ Name("another.example.com."));
+ EXPECT_THROW(ZoneDataUpdater(*mem_sgmt_, RRClass::IN(),
+ Name("another.example.com."), *zone_data),
+ isc::InvalidOperation);
+ ZoneData::destroy(*mem_sgmt_, zone_data, RRClass::IN());
+}
+
}
diff --git a/src/lib/datasrc/tests/memory/zone_finder_unittest.cc b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
index b1ebaf4..e07ab27 100644
--- a/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
@@ -12,8 +12,9 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include "memory_segment_test.h"
-#include "zone_table_segment_test.h"
+#include <datasrc/tests/memory/memory_segment_mock.h>
+#include <datasrc/tests/memory/zone_table_segment_mock.h>
+#include <datasrc/tests/memory/zone_loader_util.h>
// NOTE: this faked_nsec3 inclusion (and all related code below)
// was ported during #2109 for the convenience of implementing #2218
@@ -21,14 +22,14 @@
// In #2219 the original is expected to be removed, and this file should
// probably be moved here (and any leftover code not handled in #2218 should
// be cleaned up)
-#include "../../tests/faked_nsec3.h"
+#include <datasrc/tests/faked_nsec3.h>
#include <datasrc/memory/zone_finder.h>
#include <datasrc/memory/zone_data_updater.h>
#include <datasrc/memory/rdata_serialization.h>
#include <datasrc/memory/zone_table_segment.h>
#include <datasrc/memory/memory_client.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/client.h>
#include <testutils/dnsmessage_test.h>
@@ -97,7 +98,7 @@ protected:
origin_("example.org"),
zone_data_(ZoneData::create(mem_sgmt_, origin_)),
zone_finder_(*zone_data_, class_),
- updater_(mem_sgmt_, class_, origin_, *zone_data_)
+ updater_(new ZoneDataUpdater(mem_sgmt_, class_, origin_, *zone_data_))
{
// Build test RRsets. Below, we construct an RRset for
// each textual RR(s) of zone_data, and assign it to the corresponding
@@ -195,7 +196,7 @@ protected:
}
void addToZoneData(const ConstRRsetPtr rrset) {
- updater_.add(rrset, rrset->getRRsig());
+ updater_->add(rrset, rrset->getRRsig());
}
/// \brief expensive rrset converter
@@ -220,10 +221,10 @@ protected:
const RRClass class_;
const Name origin_;
// The zone finder to torture by tests
- MemorySegmentTest mem_sgmt_;
+ MemorySegmentMock mem_sgmt_;
memory::ZoneData* zone_data_;
memory::InMemoryZoneFinder zone_finder_;
- ZoneDataUpdater updater_;
+ boost::scoped_ptr<ZoneDataUpdater> updater_;
// Placeholder for storing RRsets to be checked with rrsetsCheck()
vector<ConstRRsetPtr> actual_rrsets_;
@@ -292,7 +293,7 @@ protected:
* \param zone_finder Check different InMemoryZoneFinder object than
* zone_finder_ (if NULL, uses zone_finder_)
* \param check_wild_answer Checks that the answer has the same RRs, type
- * class and TTL as the eqxpected answer and that the name corresponds
+ * class and TTL as the expected answer and that the name corresponds
* to the one searched. It is meant for checking answers for wildcard
* queries.
*/
@@ -1548,19 +1549,19 @@ TEST_F(InMemoryZoneFinderTest, findOrphanRRSIG) {
// Add A for ns.example.org, and RRSIG-only covering TXT for the same name.
// query for the TXT should result in NXRRSET.
addToZoneData(rr_ns_a_);
- updater_.add(ConstRRsetPtr(),
- textToRRset(
- "ns.example.org. 300 IN RRSIG TXT 5 3 300 20120814220826 "
- "20120715220826 1234 example.com. FAKE"));
+ updater_->add(ConstRRsetPtr(),
+ textToRRset(
+ "ns.example.org. 300 IN RRSIG TXT 5 3 300 20120814220826 "
+ "20120715220826 1234 example.com. FAKE"));
findTest(Name("ns.example.org"), RRType::TXT(),
ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
// Add RRSIG-only covering NSEC. This shouldn't be returned when NSEC is
// requested, whether it's for NXRRSET or NXDOMAIN
- updater_.add(ConstRRsetPtr(),
- textToRRset(
- "ns.example.org. 300 IN RRSIG NSEC 5 3 300 "
- "20120814220826 20120715220826 1234 example.com. FAKE"));
+ updater_->add(ConstRRsetPtr(),
+ textToRRset(
+ "ns.example.org. 300 IN RRSIG NSEC 5 3 300 "
+ "20120814220826 20120715220826 1234 example.com. FAKE"));
// The added RRSIG for NSEC could be used for NXRRSET but shouldn't
findTest(Name("ns.example.org"), RRType::TXT(),
ZoneFinder::NXRRSET, true, ConstRRsetPtr(),
@@ -1571,29 +1572,29 @@ TEST_F(InMemoryZoneFinderTest, findOrphanRRSIG) {
expected_flags, NULL, ZoneFinder::FIND_DNSSEC);
// RRSIG-only CNAME shouldn't be accidentally confused with real CNAME.
- updater_.add(ConstRRsetPtr(),
- textToRRset(
- "nocname.example.org. 300 IN RRSIG CNAME 5 3 300 "
- "20120814220826 20120715220826 1234 example.com. FAKE"));
+ updater_->add(ConstRRsetPtr(),
+ textToRRset(
+ "nocname.example.org. 300 IN RRSIG CNAME 5 3 300 "
+ "20120814220826 20120715220826 1234 example.com. FAKE"));
findTest(Name("nocname.example.org"), RRType::A(),
ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
// RRSIG-only for NS wouldn't invoke delegation anyway, but we check this
// case explicitly.
- updater_.add(ConstRRsetPtr(),
- textToRRset(
- "nodelegation.example.org. 300 IN RRSIG NS 5 3 300 "
- "20120814220826 20120715220826 1234 example.com. FAKE"));
+ updater_->add(ConstRRsetPtr(),
+ textToRRset(
+ "nodelegation.example.org. 300 IN RRSIG NS 5 3 300 "
+ "20120814220826 20120715220826 1234 example.com. FAKE"));
findTest(Name("nodelegation.example.org"), RRType::A(),
ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
findTest(Name("www.nodelegation.example.org"), RRType::A(),
ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
// Same for RRSIG-only for DNAME
- updater_.add(ConstRRsetPtr(),
- textToRRset(
- "nodname.example.org. 300 IN RRSIG DNAME 5 3 300 "
- "20120814220826 20120715220826 1234 example.com. FAKE"));
+ updater_->add(ConstRRsetPtr(),
+ textToRRset(
+ "nodname.example.org. 300 IN RRSIG DNAME 5 3 300 "
+ "20120814220826 20120715220826 1234 example.com. FAKE"));
findTest(Name("www.nodname.example.org"), RRType::A(),
ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
// If we have a delegation NS at this node, it will be a bit trickier,
@@ -1610,12 +1611,14 @@ TEST_F(InMemoryZoneFinderTest, findOrphanRRSIG) {
// \brief testcase for #2504 (Problem in inmem NSEC denial of existence
// handling)
TEST_F(InMemoryZoneFinderTest, NSECNonExistentTest) {
+ const Name name("example.com.");
shared_ptr<ZoneTableSegment> ztable_segment(
- new ZoneTableSegmentTest(class_, mem_sgmt_));
+ new ZoneTableSegmentMock(class_, mem_sgmt_));
+ updater_.reset();
+ loadZoneIntoTable(*ztable_segment, name, class_,
+ TEST_DATA_DIR "/2504-test.zone");
InMemoryClient client(ztable_segment, class_);
- Name name("example.com.");
- client.load(name, TEST_DATA_DIR "/2504-test.zone");
DataSourceClient::FindResult result(client.findZone(name));
// Check for a non-existing name
@@ -1756,10 +1759,10 @@ TEST_F(InMemoryZoneFinderNSEC3Test, RRSIGOnly) {
// add an RRSIG-only NSEC3 to the NSEC3 space, and try to find it; it
// should result in an exception.
const string n8_hash = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
- updater_.add(ConstRRsetPtr(),
- textToRRset(
- n8_hash + ".example.org. 300 IN RRSIG NSEC3 5 3 300 "
- "20120814220826 20120715220826 1234 example.com. FAKE"));
+ updater_->add(ConstRRsetPtr(),
+ textToRRset(
+ n8_hash + ".example.org. 300 IN RRSIG NSEC3 5 3 300 "
+ "20120814220826 20120715220826 1234 example.com. FAKE"));
EXPECT_THROW(zone_finder_.findNSEC3(Name("n8.example.org"), false),
DataSourceError);
}
@@ -1771,16 +1774,18 @@ TEST_F(InMemoryZoneFinderNSEC3Test, findNSEC3MissingOrigin) {
DefaultNSEC3HashCreator creator;
setNSEC3HashCreator(&creator);
+ const Name name("example.com.");
shared_ptr<ZoneTableSegment> ztable_segment(
- new ZoneTableSegmentTest(class_, mem_sgmt_));
+ new ZoneTableSegmentMock(class_, mem_sgmt_));
+ updater_.reset();
+ loadZoneIntoTable(*ztable_segment, name, class_,
+ TEST_DATA_DIR "/2503-test.zone");
InMemoryClient client(ztable_segment, class_);
- Name name("example.com.");
- client.load(name, TEST_DATA_DIR "/2503-test.zone");
DataSourceClient::FindResult result(client.findZone(name));
// Check for a non-existing name
- Name search_name("nonexist.example.com.");
+ const Name search_name("nonexist.example.com.");
ZoneFinder::FindNSEC3Result find_result(
result.zone_finder->findNSEC3(search_name, true));
// findNSEC3() must have completed (not throw or assert). Because
diff --git a/src/lib/datasrc/tests/memory/zone_loader_util.cc b/src/lib/datasrc/tests/memory/zone_loader_util.cc
new file mode 100644
index 0000000..155871d
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/zone_loader_util.cc
@@ -0,0 +1,89 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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/tests/memory/zone_loader_util.h>
+
+#include <datasrc/zone_iterator.h>
+#include <datasrc/cache_config.h>
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/zone_data_loader.h>
+#include <datasrc/memory/zone_writer.h>
+
+#include <dns/dns_fwd.h>
+
+#include <cc/data.h>
+
+#include <string>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace test {
+
+void
+loadZoneIntoTable(ZoneTableSegment& zt_sgmt, const dns::Name& zname,
+ const dns::RRClass& zclass, const std::string& zone_file,
+ bool load_error_ok)
+{
+ const isc::datasrc::internal::CacheConfig cache_conf(
+ "MasterFiles", NULL, *data::Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"params\": {\"" + zname.toText() + "\": \"" + zone_file +
+ "\"}}"), true);
+ memory::ZoneWriter writer(zt_sgmt, cache_conf.getLoadAction(zclass, zname),
+ zname, zclass, load_error_ok);
+ writer.load();
+ writer.install();
+ writer.cleanup();
+}
+
+namespace {
+// borrowed from CacheConfig's internal
+class IteratorLoader {
+public:
+ IteratorLoader(const dns::RRClass& rrclass, const dns::Name& name,
+ ZoneIterator& iterator) :
+ rrclass_(rrclass),
+ name_(name),
+ iterator_(iterator)
+ {}
+ memory::ZoneData* operator()(util::MemorySegment& segment) {
+ return (memory::loadZoneData(segment, rrclass_, name_, iterator_));
+ }
+private:
+ const dns::RRClass rrclass_;
+ const dns::Name name_;
+ ZoneIterator& iterator_;
+};
+}
+
+void
+loadZoneIntoTable(ZoneTableSegment& zt_sgmt, const dns::Name& zname,
+ const dns::RRClass& zclass, ZoneIterator& iterator)
+{
+ memory::ZoneWriter writer(zt_sgmt, IteratorLoader(zclass, zname, iterator),
+ zname, zclass, false);
+ writer.load();
+ writer.install();
+ writer.cleanup();
+}
+
+} // namespace test
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/tests/memory/zone_loader_util.h b/src/lib/datasrc/tests/memory/zone_loader_util.h
new file mode 100644
index 0000000..6d1f764
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/zone_loader_util.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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_MEMORY_TEST_ZONE_LOADER_UTIL_H
+#define DATASRC_MEMORY_TEST_ZONE_LOADER_UTIL_H 1
+
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/zone_data_loader.h>
+
+#include <dns/dns_fwd.h>
+
+#include <string>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace test {
+
+/// \brief A shortcut utility to load a specified zone into ZoneTableSegment
+/// from a file.
+///
+/// This function does nothing special, simply provides a shortcut for commonly
+/// used pattern that would be used in tests with a ZoneTableSegment loading
+/// a zone from file into it.
+///
+/// If the optional load_error_ok parameter is set to true, it will create
+/// an internal empty zone in the table when it encounters a loading error.
+/// Otherwise ZoneLoaderException will be thrown in such cases.
+void
+loadZoneIntoTable(ZoneTableSegment& zt_sgmt, const dns::Name& zname,
+ const dns::RRClass& zclass, const std::string& zone_file,
+ bool load_error_ok = false);
+
+/// \brief A shortcut utility to load a specified zone into ZoneTableSegment
+/// from a zone iterator.
+///
+/// This is similar to the other version, but use a zone iterator as the
+/// source of the zone data.
+void
+loadZoneIntoTable(ZoneTableSegment& zt_sgmt, const dns::Name& zname,
+ const dns::RRClass& zclass, ZoneIterator& iterator);
+} // namespace test
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_TEST_ZONE_LOADER_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/tests/memory/zone_table_segment_mapped_unittest.cc b/src/lib/datasrc/tests/memory/zone_table_segment_mapped_unittest.cc
new file mode 100644
index 0000000..b770e38
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/zone_table_segment_mapped_unittest.cc
@@ -0,0 +1,566 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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/memory/zone_writer.h>
+#include <datasrc/memory/zone_table_segment_mapped.h>
+#include <util/random/random_number_generator.h>
+#include <util/unittests/check_valgrind.h>
+
+#include <gtest/gtest.h>
+#include <boost/format.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/interprocess/file_mapping.hpp>
+
+#include <memory>
+#include <cerrno>
+
+#include <sys/stat.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc::memory;
+using namespace isc::data;
+using namespace isc::util;
+using namespace isc::util::random;
+using namespace std;
+using boost::scoped_ptr;
+
+namespace {
+
+const char* const mapped_file = TEST_DATA_BUILDDIR "/test.mapped";
+const char* const mapped_file2 = TEST_DATA_BUILDDIR "/test2.mapped";
+
+class ZoneTableSegmentMappedTest : public ::testing::Test {
+protected:
+ ZoneTableSegmentMappedTest() :
+ ztable_segment_(
+ ZoneTableSegment::create(RRClass::IN(), "mapped")),
+ config_params_(
+ Element::fromJSON(
+ "{\"mapped-file\": \"" + std::string(mapped_file) + "\"}")),
+ config_params2_(
+ Element::fromJSON(
+ "{\"mapped-file\": \"" + std::string(mapped_file2) + "\"}"))
+ {
+ EXPECT_NE(static_cast<void*>(NULL), ztable_segment_.get());
+ // Verify that a ZoneTableSegmentMapped is created.
+ ZoneTableSegmentMapped* mapped_segment =
+ dynamic_cast<ZoneTableSegmentMapped*>(ztable_segment_.get());
+ EXPECT_NE(static_cast<void*>(NULL), mapped_segment);
+
+ createTestData();
+ }
+
+ ~ZoneTableSegmentMappedTest() {
+ ZoneTableSegment::destroy(ztable_segment_.release());
+ boost::interprocess::file_mapping::remove(mapped_file);
+ boost::interprocess::file_mapping::remove(mapped_file2);
+ }
+
+ typedef std::pair<std::string, int> TestDataElement;
+
+ void createTestData() {
+ UniformRandomIntegerGenerator gen(0, INT_MAX);
+ for (int i = 0; i < 256; ++i) {
+ const string name(boost::str(boost::format("name%d") % i));
+ const int value = gen();
+ test_data_.push_back(TestDataElement(name, value));
+ }
+ }
+
+ void setupMappedFiles();
+ void addData(MemorySegment& segment);
+ bool verifyData(const MemorySegment& segment);
+
+ // Ideally, this should be something similar to a
+ // SegmentObjectHolder, not an auto_ptr.
+ std::auto_ptr<ZoneTableSegment> ztable_segment_;
+ const ConstElementPtr config_params_;
+ const ConstElementPtr config_params2_;
+ std::vector<TestDataElement> test_data_;
+};
+
+bool
+fileExists(const char* path) {
+ struct stat sb;
+ const int status = stat(path, &sb);
+ if (status != 0) {
+ EXPECT_EQ(ENOENT, errno);
+ return (false);
+ }
+ return (true);
+}
+
+void
+deleteChecksum(MemorySegment& segment) {
+ segment.clearNamedAddress("zone_table_checksum");
+}
+
+void
+corruptChecksum(MemorySegment& segment) {
+ const MemorySegment::NamedAddressResult result =
+ segment.getNamedAddress("zone_table_checksum");
+ ASSERT_TRUE(result.first);
+
+ size_t checksum = *static_cast<size_t*>(result.second);
+ ++checksum;
+ *static_cast<size_t*>(result.second) = checksum;
+}
+
+void
+deleteHeader(MemorySegment& segment) {
+ segment.clearNamedAddress("zone_table_header");
+}
+
+void
+ZoneTableSegmentMappedTest::addData(MemorySegment& segment) {
+ // For purposes of this test, we assume that the following
+ // allocations do not resize the mapped segment. For this, we have
+ // to keep the size of test data reasonably small in
+ // createTestData().
+
+ // One by one, add all the elements in test_data_.
+ for (int i = 0; i < test_data_.size(); ++i) {
+ void* ptr = segment.allocate(sizeof(int));
+ ASSERT_TRUE(ptr);
+ *static_cast<int*>(ptr) = test_data_[i].second;
+ const bool grew = segment.setNamedAddress(test_data_[i].first.c_str(),
+ ptr);
+ ASSERT_FALSE(grew);
+ }
+}
+
+bool
+ZoneTableSegmentMappedTest::verifyData(const MemorySegment& segment) {
+ // One by one, verify all the elements in test_data_ exist and have
+ // the expected values.
+ for (int i = 0; i < test_data_.size(); ++i) {
+ const MemorySegment::NamedAddressResult result =
+ segment.getNamedAddress(test_data_[i].first.c_str());
+ if (!result.first) {
+ return (false);
+ }
+ if (*static_cast<int*>(result.second) != test_data_[i].second) {
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+void
+ZoneTableSegmentMappedTest::setupMappedFiles() {
+ ztable_segment_->reset(ZoneTableSegment::CREATE, config_params_);
+ addData(ztable_segment_->getMemorySegment());
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ ztable_segment_->reset(ZoneTableSegment::CREATE, config_params2_);
+ addData(ztable_segment_->getMemorySegment());
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Now, clear the segment, closing the underlying mapped file.
+ ztable_segment_->clear();
+}
+
+TEST_F(ZoneTableSegmentMappedTest, getImplType) {
+ EXPECT_EQ("mapped", ztable_segment_->getImplType());
+}
+
+TEST_F(ZoneTableSegmentMappedTest, getHeaderUninitialized) {
+ // This should throw as we haven't called reset() yet.
+ EXPECT_THROW(ztable_segment_->getHeader(), isc::InvalidOperation);
+}
+
+TEST_F(ZoneTableSegmentMappedTest, getMemorySegmentUninitialized) {
+ // This should throw as we haven't called reset() yet.
+ EXPECT_THROW(ztable_segment_->getMemorySegment(), isc::InvalidOperation);
+}
+
+TEST_F(ZoneTableSegmentMappedTest, isUsableUninitialized) {
+ // isUsable() must return false by default, when the segment has not
+ // been reset() yet.
+ EXPECT_FALSE(ztable_segment_->isUsable());
+}
+
+TEST_F(ZoneTableSegmentMappedTest, isWritableUninitialized) {
+ // isWritable() must return false by default, when the segment has
+ // not been reset() yet.
+ EXPECT_FALSE(ztable_segment_->isWritable());
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetBadConfig) {
+ // Open a mapped file in create mode.
+ ztable_segment_->reset(ZoneTableSegment::CREATE, config_params_);
+
+ // Populate it with some data.
+ addData(ztable_segment_->getMemorySegment());
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // All the following resets() with invalid configuration must
+ // provide a strong exception guarantee that the segment is still
+ // usable as before.
+
+ // NULL is passed in config params
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::CREATE,
+ ConstElementPtr());
+ }, isc::InvalidParameter);
+
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Not a map
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::CREATE,
+ Element::fromJSON("42"));
+ }, isc::InvalidParameter);
+
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Empty map
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::CREATE,
+ Element::fromJSON("{}"));
+ }, isc::InvalidParameter);
+
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // No "mapped-file" key
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::CREATE,
+ Element::fromJSON("{\"foo\": \"bar\"}"));
+ }, isc::InvalidParameter);
+
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Value of "mapped-file" key is not a string
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::CREATE,
+ Element::fromJSON("{\"mapped-file\": 42}"));
+ }, isc::InvalidParameter);
+
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, reset) {
+ // By default, the mapped file doesn't exist, so we cannot open it
+ // in READ_ONLY mode (which does not create the file).
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::READ_ONLY, config_params_);
+ }, MemorySegmentOpenError);
+
+ // The following should still throw, unaffected by the failed open.
+ EXPECT_THROW(ztable_segment_->getHeader(), isc::InvalidOperation);
+ EXPECT_THROW(ztable_segment_->getMemorySegment(), isc::InvalidOperation);
+
+ // isUsable() and isWritable() must still return false, because the
+ // segment has not been successfully reset() yet.
+ EXPECT_FALSE(ztable_segment_->isUsable());
+ EXPECT_FALSE(ztable_segment_->isWritable());
+
+ // If a Python binding passes an invalid integer as the mode,
+ // reset() should reject it.
+ EXPECT_THROW({
+ ztable_segment_->reset
+ (static_cast<ZoneTableSegment::MemorySegmentOpenMode>(1234),
+ config_params_);
+ }, isc::InvalidParameter);
+
+ // READ_WRITE mode must create the mapped file if it doesn't exist
+ // (and must not result in an exception).
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+
+ // The following method calls should no longer throw:
+ EXPECT_NO_THROW(ztable_segment_->getHeader());
+ EXPECT_NO_THROW(ztable_segment_->getMemorySegment());
+
+ // Let's try to re-open the mapped file in READ_ONLY mode. It should
+ // not fail now.
+ ztable_segment_->reset(ZoneTableSegment::READ_ONLY, config_params_);
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_FALSE(ztable_segment_->isWritable());
+
+ // Re-creating the mapped file should erase old data and should not
+ // trigger any exceptions inside reset() due to old data (such as
+ // named addresses).
+ ztable_segment_->reset(ZoneTableSegment::CREATE, config_params_);
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+
+ // When we reset() with an invalid paramter and it fails, then the
+ // segment should still be usable.
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::CREATE,
+ Element::fromJSON("{}"));
+ }, isc::InvalidParameter);
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+ // The following should not throw.
+ EXPECT_NO_THROW(ztable_segment_->getHeader());
+ EXPECT_NO_THROW(ztable_segment_->getMemorySegment());
+
+ // READ_WRITE with an existing map file ought to work too. This
+ // would use existing named addresses. This actually re-opens the
+ // currently open map.
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetCreate) {
+ // At this point, the underlying file must not exist.
+ ASSERT_FALSE(fileExists(mapped_file));
+
+ // Open the underlying mapped file in create mode.
+ ztable_segment_->reset(ZoneTableSegment::CREATE, config_params_);
+
+ ASSERT_TRUE(ztable_segment_->isUsable());
+ ASSERT_TRUE(ztable_segment_->isWritable());
+
+ // Add the data.
+ addData(ztable_segment_->getMemorySegment());
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Close the segment.
+ ztable_segment_->clear();
+
+ // At this point, the underlying file must still exist.
+ ASSERT_TRUE(fileExists(mapped_file));
+
+ // Open the underlying mapped file in create mode again.
+ ztable_segment_->reset(ZoneTableSegment::CREATE, config_params_);
+
+ // The old data should be gone.
+ EXPECT_FALSE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetReadWrite) {
+ // At this point, the underlying file must not exist.
+ ASSERT_FALSE(fileExists(mapped_file));
+
+ // Open the underlying mapped file in read+write mode.
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+ ASSERT_TRUE(ztable_segment_->isUsable());
+ ASSERT_TRUE(ztable_segment_->isWritable());
+
+ // Add the data.
+ addData(ztable_segment_->getMemorySegment());
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Close the segment.
+ ztable_segment_->clear();
+
+ // At this point, the underlying file must still exist.
+ ASSERT_TRUE(fileExists(mapped_file));
+
+ // Open the underlying mapped file in read+write mode again.
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+ // The old data should still be available.
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetReadOnly) {
+ // At this point, the underlying file must not exist.
+ ASSERT_FALSE(fileExists(mapped_file));
+
+ // Open the underlying mapped file in read+write mode.
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+ ASSERT_TRUE(ztable_segment_->isUsable());
+ ASSERT_TRUE(ztable_segment_->isWritable());
+
+ // Add the data.
+ addData(ztable_segment_->getMemorySegment());
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Close the segment.
+ ztable_segment_->clear();
+
+ // At this point, the underlying file must still exist.
+ ASSERT_TRUE(fileExists(mapped_file));
+
+ // Open the underlying mapped file in read-only mode again.
+ ztable_segment_->reset(ZoneTableSegment::READ_ONLY, config_params_);
+
+ // The old data should still be available.
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // But trying to allocate new data should result in an exception as
+ // the segment is read-only!
+ EXPECT_THROW(addData(ztable_segment_->getMemorySegment()),
+ MemorySegmentError);
+}
+
+TEST_F(ZoneTableSegmentMappedTest, clearUninitialized) {
+ // Clearing a segment that has not been reset() is a nop, as clear()
+ // returns it to a fresh uninitialized state anyway.
+ EXPECT_NO_THROW(ztable_segment_->clear());
+
+ // The following should still throw, because the segment has not
+ // been successfully reset() yet.
+ EXPECT_THROW(ztable_segment_->getHeader(), isc::InvalidOperation);
+ EXPECT_THROW(ztable_segment_->getMemorySegment(), isc::InvalidOperation);
+
+ // isWritable() must still return false, because the segment has not
+ // been successfully reset() yet.
+ EXPECT_FALSE(ztable_segment_->isUsable());
+ EXPECT_FALSE(ztable_segment_->isWritable());
+}
+
+TEST_F(ZoneTableSegmentMappedTest, clear) {
+ // First, open an underlying mapped file in read+write mode (doesn't
+ // exist yet)
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+ // The following method calls should no longer throw:
+ EXPECT_NO_THROW(ztable_segment_->getHeader());
+ EXPECT_NO_THROW(ztable_segment_->getMemorySegment());
+
+ // Now, clear the segment.
+ ztable_segment_->clear();
+
+ EXPECT_FALSE(ztable_segment_->isUsable());
+ EXPECT_FALSE(ztable_segment_->isWritable());
+ // The following method calls should now throw.
+ EXPECT_THROW(ztable_segment_->getHeader(), isc::InvalidOperation);
+ EXPECT_THROW(ztable_segment_->getMemorySegment(), isc::InvalidOperation);
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetFailedCorruptedChecksum) {
+ setupMappedFiles();
+
+ // Open mapped file 1 in read-write mode
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+ // Corrupt mapped file 2.
+ scoped_ptr<MemorySegmentMapped> segment
+ (new MemorySegmentMapped(mapped_file2,
+ MemorySegmentMapped::OPEN_OR_CREATE));
+ EXPECT_TRUE(verifyData(*segment));
+ corruptChecksum(*segment);
+ segment.reset();
+
+ // Resetting to mapped file 2 in read-write mode should fail
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params2_);
+ }, ResetFailed);
+
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+ // Check for the old data in the segment to make sure it is still
+ // available and correct.
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetFailedMissingChecksum) {
+ setupMappedFiles();
+
+ // Open mapped file 1 in read-write mode
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+ // Corrupt mapped file 2.
+ scoped_ptr<MemorySegmentMapped> segment
+ (new MemorySegmentMapped(mapped_file2,
+ MemorySegmentMapped::OPEN_OR_CREATE));
+ EXPECT_TRUE(verifyData(*segment));
+ deleteChecksum(*segment);
+ segment.reset();
+
+ // Resetting to mapped file 2 in read-only mode should fail
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::READ_ONLY, config_params2_);
+ }, ResetFailed);
+
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+ // Check for the old data in the segment to make sure it is still
+ // available and correct.
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetFailedMissingHeader) {
+ setupMappedFiles();
+
+ // Open mapped file 1 in read-write mode
+ ztable_segment_->reset(ZoneTableSegment::READ_WRITE, config_params_);
+
+ // Corrupt mapped file 2.
+ scoped_ptr<MemorySegmentMapped> segment
+ (new MemorySegmentMapped(mapped_file2,
+ MemorySegmentMapped::OPEN_OR_CREATE));
+ EXPECT_TRUE(verifyData(*segment));
+ deleteHeader(*segment);
+ segment.reset();
+
+ // Resetting to mapped file 2 in read-only mode should fail
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::READ_ONLY, config_params2_);
+ }, ResetFailed);
+
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+ // Check for the old data in the segment to make sure it is still
+ // available and correct.
+ EXPECT_TRUE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+TEST_F(ZoneTableSegmentMappedTest, resetCreateOverCorruptedFile) {
+ setupMappedFiles();
+
+ // Corrupt mapped file 1.
+ scoped_ptr<MemorySegmentMapped> segment
+ (new MemorySegmentMapped(mapped_file,
+ MemorySegmentMapped::OPEN_OR_CREATE));
+ EXPECT_TRUE(verifyData(*segment));
+ corruptChecksum(*segment);
+ segment.reset();
+
+ // Resetting to mapped file 1 in CREATE mode over a corrupted file
+ // should pass.
+ EXPECT_NO_THROW(ztable_segment_->reset(ZoneTableSegment::CREATE,
+ config_params_));
+
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+ // Check for the old data in the segment. It should not be present
+ // (as we opened the segment in CREATE mode).
+ EXPECT_FALSE(verifyData(ztable_segment_->getMemorySegment()));
+
+ // Now try the same with missing checksum.
+ setupMappedFiles();
+
+ // Corrupt mapped file 1.
+ segment.reset(new MemorySegmentMapped(mapped_file,
+ MemorySegmentMapped::OPEN_OR_CREATE));
+ EXPECT_TRUE(verifyData(*segment));
+ deleteChecksum(*segment);
+ segment.reset();
+
+ // Resetting to mapped file 1 in CREATE mode over a file missing
+ // checksum should pass.
+ EXPECT_NO_THROW(ztable_segment_->reset(ZoneTableSegment::CREATE,
+ config_params_));
+
+ EXPECT_TRUE(ztable_segment_->isUsable());
+ EXPECT_TRUE(ztable_segment_->isWritable());
+ // Check for the old data in the segment. It should not be present
+ // (as we opened the segment in CREATE mode).
+ EXPECT_FALSE(verifyData(ztable_segment_->getMemorySegment()));
+}
+
+} // anonymous namespace
diff --git a/src/lib/datasrc/tests/memory/zone_table_segment_mock.h b/src/lib/datasrc/tests/memory/zone_table_segment_mock.h
new file mode 100644
index 0000000..f73ef49
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/zone_table_segment_mock.h
@@ -0,0 +1,94 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_MEMORY_ZONE_TABLE_SEGMENT_TEST_H
+#define DATASRC_MEMORY_ZONE_TABLE_SEGMENT_TEST_H 1
+
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/zone_table.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_writer.h>
+
+#include <string>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace test {
+
+// A special ZoneTableSegment that can be used for tests. It can be
+// passed a MemorySegment that can be used later to test if all memory
+// was de-allocated on it.
+class ZoneTableSegmentMock : public ZoneTableSegment {
+public:
+ ZoneTableSegmentMock(const isc::dns::RRClass& rrclass,
+ isc::util::MemorySegment& mem_sgmt) :
+ ZoneTableSegment(rrclass),
+ impl_type_("mock"),
+ mem_sgmt_(mem_sgmt),
+ header_(ZoneTable::create(mem_sgmt_, rrclass))
+ {}
+
+ virtual ~ZoneTableSegmentMock() {
+ ZoneTable::destroy(mem_sgmt_, header_.getTable());
+ }
+
+ const std::string& getImplType() const {
+ return (impl_type_);
+ }
+
+ virtual void reset(MemorySegmentOpenMode, isc::data::ConstElementPtr) {
+ isc_throw(isc::NotImplemented, "reset() is not implemented");
+ }
+
+ virtual void clear() {
+ isc_throw(isc::NotImplemented, "clear() is not implemented");
+ }
+
+ virtual ZoneTableHeader& getHeader() {
+ return (header_);
+ }
+
+ virtual const ZoneTableHeader& getHeader() const {
+ return (header_);
+ }
+
+ virtual isc::util::MemorySegment& getMemorySegment() {
+ return (mem_sgmt_);
+ }
+
+ virtual bool isUsable() const {
+ return (true);
+ }
+
+ virtual bool isWritable() const {
+ return (true);
+ }
+
+private:
+ std::string impl_type_;
+ isc::util::MemorySegment& mem_sgmt_;
+ ZoneTableHeader header_;
+};
+
+} // namespace test
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_ZONE_TABLE_SEGMENT_TEST_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/tests/memory/zone_table_segment_test.h b/src/lib/datasrc/tests/memory/zone_table_segment_test.h
deleted file mode 100644
index 2078036..0000000
--- a/src/lib/datasrc/tests/memory/zone_table_segment_test.h
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef DATASRC_MEMORY_ZONE_TABLE_SEGMENT_TEST_H
-#define DATASRC_MEMORY_ZONE_TABLE_SEGMENT_TEST_H 1
-
-#include <datasrc/memory/zone_table_segment.h>
-#include <datasrc/memory/zone_table.h>
-#include <datasrc/memory/zone_data.h>
-#include <datasrc/memory/zone_writer_local.h>
-
-namespace isc {
-namespace datasrc {
-namespace memory {
-namespace test {
-
-// A special ZoneTableSegment that can be used for tests. It can be
-// passed a MemorySegment that can be used later to test if all memory
-// was de-allocated on it.
-class ZoneTableSegmentTest : public ZoneTableSegment {
-public:
- ZoneTableSegmentTest(isc::dns::RRClass rrclass,
- isc::util::MemorySegment& mem_sgmt) :
- ZoneTableSegment(rrclass),
- mem_sgmt_(mem_sgmt),
- header_(ZoneTable::create(mem_sgmt_, rrclass))
- {}
-
- virtual ~ZoneTableSegmentTest() {
- ZoneTable::destroy(mem_sgmt_, header_.getTable());
- }
-
- virtual ZoneTableHeader& getHeader() {
- return (header_);
- }
-
- virtual const ZoneTableHeader& getHeader() const {
- return (header_);
- }
-
- virtual isc::util::MemorySegment& getMemorySegment() {
- return (mem_sgmt_);
- }
-
- virtual ZoneWriter* getZoneWriter(const LoadAction& load_action,
- const dns::Name& name,
- const dns::RRClass& rrclass)
- {
- return (new Writer(this, load_action, name, rrclass));
- }
-
-private:
- isc::util::MemorySegment& mem_sgmt_;
- ZoneTableHeader header_;
-
- // A writer for this segment. The implementation is similar
- // to ZoneWriterLocal, but all the error handling is stripped
- // for simplicity. Also, we do everything inside the
- // install(), for the same reason. We just need something
- // inside the tests, not a full-blown implementation
- // for background loading.
- class Writer : public ZoneWriter {
- public:
- Writer(ZoneTableSegmentTest* segment, const LoadAction& load_action,
- const dns::Name& name, const dns::RRClass& rrclass) :
- segment_(segment),
- load_action_(load_action),
- name_(name),
- rrclass_(rrclass)
- {}
-
- void load() {}
-
- void install() {
- ZoneTable* table(segment_->getHeader().getTable());
- const ZoneTable::AddResult
- result(table->addZone(segment_->getMemorySegment(), rrclass_,
- name_,
- load_action_(segment_->
- getMemorySegment())));
- if (result.zone_data != NULL) {
- ZoneData::destroy(segment_->getMemorySegment(),
- result.zone_data, rrclass_);
- }
- }
-
- virtual void cleanup() {}
- private:
- ZoneTableSegmentTest* segment_;
- LoadAction load_action_;
- dns::Name name_;
- dns::RRClass rrclass_;
- };
-};
-
-} // namespace test
-} // namespace memory
-} // namespace datasrc
-} // namespace isc
-
-#endif // DATASRC_MEMORY_ZONE_TABLE_SEGMENT_TEST_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc b/src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc
index ac114e2..1027a26 100644
--- a/src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_table_segment_unittest.cc
@@ -12,9 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <datasrc/memory/zone_writer_local.h>
+#include <datasrc/memory/zone_writer.h>
#include <datasrc/memory/zone_table_segment_local.h>
-#include <util/memory_segment_local.h>
#include <gtest/gtest.h>
#include <boost/scoped_ptr.hpp>
@@ -31,8 +30,7 @@ namespace {
class ZoneTableSegmentTest : public ::testing::Test {
protected:
ZoneTableSegmentTest() :
- ztable_segment_(ZoneTableSegment::create(isc::data::NullElement(),
- RRClass::IN()))
+ ztable_segment_(ZoneTableSegment::create(RRClass::IN(), "local"))
{}
void TearDown() {
@@ -43,10 +41,32 @@ protected:
ZoneTableSegment* ztable_segment_;
};
+TEST_F(ZoneTableSegmentTest, getImplType) {
+ EXPECT_EQ("local", ztable_segment_->getImplType());
+}
TEST_F(ZoneTableSegmentTest, create) {
// By default, a local zone table segment is created.
EXPECT_NE(static_cast<void*>(NULL), ztable_segment_);
+
+ // Unknown types of segment are rejected.
+ EXPECT_THROW(ZoneTableSegment::create(RRClass::IN(), "unknown"),
+ UnknownSegmentType);
+}
+
+TEST_F(ZoneTableSegmentTest, reset) {
+ // reset() should throw that it's not implemented so that any
+ // accidental calls are found out.
+ EXPECT_THROW({
+ ztable_segment_->reset(ZoneTableSegment::CREATE,
+ Element::fromJSON("{}"));
+ }, isc::NotImplemented);
+}
+
+TEST_F(ZoneTableSegmentTest, clear) {
+ // clear() should throw that it's not implemented so that any
+ // accidental calls are found out.
+ EXPECT_THROW(ztable_segment_->clear(), isc::NotImplemented);
}
// Helper function to check const and non-const methods.
@@ -76,22 +96,14 @@ TEST_F(ZoneTableSegmentTest, getMemorySegment) {
mem_sgmt.allMemoryDeallocated(); // use mem_sgmt
}
-ZoneData*
-loadAction(MemorySegment&) {
- // The function won't be called, so this is OK
- return (NULL);
+TEST_F(ZoneTableSegmentTest, isUsable) {
+ // Local segments are always usable.
+ EXPECT_TRUE(ztable_segment_->isUsable());
}
-// Test we can get a writer.
-TEST_F(ZoneTableSegmentTest, getZoneWriter) {
- scoped_ptr<ZoneWriter>
- writer(ztable_segment_->getZoneWriter(loadAction, Name("example.org"),
- RRClass::IN()));
- // We have to get something
- EXPECT_NE(static_cast<void*>(NULL), writer.get());
- // And for now, it should be the local writer
- EXPECT_NE(static_cast<void*>(NULL),
- dynamic_cast<ZoneWriterLocal*>(writer.get()));
+TEST_F(ZoneTableSegmentTest, isWritable) {
+ // Local segments are always writable.
+ EXPECT_TRUE(ztable_segment_->isWritable());
}
} // anonymous namespace
diff --git a/src/lib/datasrc/tests/memory/zone_table_unittest.cc b/src/lib/datasrc/tests/memory/zone_table_unittest.cc
index 3c53a59..7357322 100644
--- a/src/lib/datasrc/tests/memory/zone_table_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_table_unittest.cc
@@ -12,8 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include "memory_segment_test.h"
-
#include <exceptions/exceptions.h>
#include <util/memory_segment_local.h>
@@ -26,6 +24,8 @@
#include <datasrc/memory/zone_table.h>
#include <datasrc/memory/segment_object_holder.h>
+#include <datasrc/tests/memory/memory_segment_mock.h>
+
#include <gtest/gtest.h>
#include <new> // for bad_alloc
@@ -56,7 +56,7 @@ protected:
}
const RRClass zclass_;
const Name zname1, zname2, zname3;
- test::MemorySegmentTest mem_sgmt_;
+ test::MemorySegmentMock mem_sgmt_;
ZoneTable* zone_table;
};
@@ -70,27 +70,38 @@ TEST_F(ZoneTableTest, create) {
}
TEST_F(ZoneTableTest, addZone) {
- // It doesn't accept empty (NULL) zones
- EXPECT_THROW(zone_table->addZone(mem_sgmt_, zclass_, zname1, NULL),
- isc::BadValue);
+ // By default there's no zone contained.
+ EXPECT_EQ(0, zone_table->getZoneCount());
+
+ // It doesn't accept NULL as zone data
+ EXPECT_THROW(zone_table->addZone(mem_sgmt_, zname1, NULL),
+ isc::InvalidParameter);
+ EXPECT_EQ(0, zone_table->getZoneCount()); // count is still 0
+
+ // or an empty zone data
+ SegmentObjectHolder<ZoneData, RRClass> holder_empty(
+ mem_sgmt_, zclass_);
+ holder_empty.set(ZoneData::create(mem_sgmt_));
+ EXPECT_THROW(zone_table->addZone(mem_sgmt_, zname1, holder_empty.get()),
+ isc::InvalidParameter);
SegmentObjectHolder<ZoneData, RRClass> holder1(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname1), zclass_);
+ mem_sgmt_, zclass_);
+ holder1.set(ZoneData::create(mem_sgmt_, zname1));
const ZoneData* data1(holder1.get());
// Normal successful case.
- const ZoneTable::AddResult result1(zone_table->addZone(mem_sgmt_, zclass_,
- zname1,
+ const ZoneTable::AddResult result1(zone_table->addZone(mem_sgmt_, zname1,
holder1.release()));
EXPECT_EQ(result::SUCCESS, result1.code);
EXPECT_EQ(static_cast<const ZoneData*>(NULL), result1.zone_data);
// It got released by it
EXPECT_EQ(static_cast<const ZoneData*>(NULL), holder1.get());
+ EXPECT_EQ(1, zone_table->getZoneCount()); // count is now incremented
- // Duplicate add doesn't replace the existing data.
- SegmentObjectHolder<ZoneData, RRClass> holder2(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname1), zclass_);
- const ZoneTable::AddResult result2(zone_table->addZone(mem_sgmt_, zclass_,
- zname1,
+ // Duplicate add replaces the existing data wit the newly added one.
+ SegmentObjectHolder<ZoneData, RRClass> holder2(mem_sgmt_, zclass_);
+ holder2.set(ZoneData::create(mem_sgmt_, zname1));
+ const ZoneTable::AddResult result2(zone_table->addZone(mem_sgmt_, zname1,
holder2.release()));
EXPECT_EQ(result::EXIST, result2.code);
// The old one gets out
@@ -99,55 +110,98 @@ TEST_F(ZoneTableTest, addZone) {
EXPECT_EQ(static_cast<const ZoneData*>(NULL), holder2.get());
// We need to release the old one manually
ZoneData::destroy(mem_sgmt_, result2.zone_data, zclass_);
+ EXPECT_EQ(1, zone_table->getZoneCount()); // count doesn't change.
SegmentObjectHolder<ZoneData, RRClass> holder3(
- mem_sgmt_, ZoneData::create(mem_sgmt_, Name("EXAMPLE.COM")),
- zclass_);
+ mem_sgmt_, zclass_);
+ holder3.set(ZoneData::create(mem_sgmt_, Name("EXAMPLE.COM")));
// names are compared in a case insensitive manner.
- const ZoneTable::AddResult result3(zone_table->addZone(mem_sgmt_, zclass_,
+ const ZoneTable::AddResult result3(zone_table->addZone(mem_sgmt_,
Name("EXAMPLE.COM"),
holder3.release()));
EXPECT_EQ(result::EXIST, result3.code);
ZoneData::destroy(mem_sgmt_, result3.zone_data, zclass_);
// Add some more different ones. Should just succeed.
SegmentObjectHolder<ZoneData, RRClass> holder4(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname2), zclass_);
+ mem_sgmt_, zclass_);
+ holder4.set(ZoneData::create(mem_sgmt_, zname2));
EXPECT_EQ(result::SUCCESS,
- zone_table->addZone(mem_sgmt_, zclass_, zname2,
- holder4.release()).code);
+ zone_table->addZone(mem_sgmt_, zname2, holder4.release()).code);
+ EXPECT_EQ(2, zone_table->getZoneCount());
SegmentObjectHolder<ZoneData, RRClass> holder5(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname3), zclass_);
+ mem_sgmt_, zclass_);
+ holder5.set(ZoneData::create(mem_sgmt_, zname3));
EXPECT_EQ(result::SUCCESS,
- zone_table->addZone(mem_sgmt_, zclass_, zname3,
- holder5.release()).code);
+ zone_table->addZone(mem_sgmt_, zname3, holder5.release()).code);
+ EXPECT_EQ(3, zone_table->getZoneCount());
// Have the memory segment throw an exception in extending the internal
- // tree. It still shouldn't cause memory leak (which would be detected
- // in TearDown()).
+ // tree. We'll destroy it after that via SegmentObjectHolder.
SegmentObjectHolder<ZoneData, RRClass> holder6(
- mem_sgmt_, ZoneData::create(mem_sgmt_, Name("example.org")), zclass_);
+ mem_sgmt_, zclass_);
+ holder6.set(ZoneData::create(mem_sgmt_, Name("example.org")));
mem_sgmt_.setThrowCount(1);
- EXPECT_THROW(zone_table->addZone(mem_sgmt_, zclass_, Name("example.org"),
- holder6.release()),
+ EXPECT_THROW(zone_table->addZone(mem_sgmt_, Name("example.org"),
+ holder6.get()),
std::bad_alloc);
}
+TEST_F(ZoneTableTest, addEmptyZone) {
+ // By default there's no zone contained.
+ EXPECT_EQ(0, zone_table->getZoneCount());
+
+ // Adding an empty zone. It should succeed.
+ const ZoneTable::AddResult result1 =
+ zone_table->addEmptyZone(mem_sgmt_, zname1);
+ EXPECT_EQ(result::SUCCESS, result1.code);
+ EXPECT_EQ(static_cast<const ZoneData*>(NULL), result1.zone_data);
+ EXPECT_EQ(1, zone_table->getZoneCount());
+
+ // The empty zone can be "found", with the ZONE_EMPTY flag on, and the
+ // returned ZoneData being NULL.
+ const ZoneTable::FindResult fresult1 = zone_table->findZone(zname1);
+ EXPECT_EQ(result::SUCCESS, fresult1.code);
+ EXPECT_EQ(result::ZONE_EMPTY, fresult1.flags);
+ EXPECT_EQ(static_cast<const ZoneData*>(NULL), fresult1.zone_data);
+
+ // Replacing an empty zone with non-empty one. Should be no problem, but
+ // the empty zone data are not returned in the result structure; it's
+ // internal to the ZoneTable implementation.
+ SegmentObjectHolder<ZoneData, RRClass> holder2(mem_sgmt_, zclass_);
+ holder2.set(ZoneData::create(mem_sgmt_, zname1));
+ const ZoneTable::AddResult result2(zone_table->addZone(mem_sgmt_, zname1,
+ holder2.release()));
+ EXPECT_EQ(result::EXIST, result2.code);
+ EXPECT_EQ(static_cast<const ZoneData*>(NULL), result2.zone_data);
+ EXPECT_EQ(1, zone_table->getZoneCount());
+
+ // Replacing a non-empty zone with an empty one is also okay. It's not
+ // different from replacing with another non-empty one.
+ const ZoneTable::AddResult result3 =
+ zone_table->addEmptyZone(mem_sgmt_, zname1);
+ EXPECT_EQ(result::EXIST, result3.code);
+ EXPECT_NE(static_cast<const ZoneData*>(NULL), result3.zone_data);
+ ZoneData::destroy(mem_sgmt_, result3.zone_data, zclass_);
+ EXPECT_EQ(1, zone_table->getZoneCount());
+}
+
TEST_F(ZoneTableTest, findZone) {
SegmentObjectHolder<ZoneData, RRClass> holder1(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname1), zclass_);
+ mem_sgmt_, zclass_);
+ holder1.set(ZoneData::create(mem_sgmt_, zname1));
ZoneData* zone_data = holder1.get();
- EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zclass_, zname1,
+ EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zname1,
holder1.release()).code);
SegmentObjectHolder<ZoneData, RRClass> holder2(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname2), zclass_);
+ mem_sgmt_, zclass_);
+ holder2.set(ZoneData::create(mem_sgmt_, zname2));
EXPECT_EQ(result::SUCCESS,
- zone_table->addZone(mem_sgmt_, zclass_, zname2,
- holder2.release()).code);
+ zone_table->addZone(mem_sgmt_, zname2, holder2.release()).code);
SegmentObjectHolder<ZoneData, RRClass> holder3(
- mem_sgmt_, ZoneData::create(mem_sgmt_, zname3), zclass_);
+ mem_sgmt_, zclass_);
+ holder3.set(ZoneData::create(mem_sgmt_, zname3));
EXPECT_EQ(result::SUCCESS,
- zone_table->addZone(mem_sgmt_, zclass_, zname3,
- holder3.release()).code);
+ zone_table->addZone(mem_sgmt_, zname3, holder3.release()).code);
const ZoneTable::FindResult find_result1 =
zone_table->findZone(Name("example.com"));
@@ -169,9 +223,9 @@ TEST_F(ZoneTableTest, findZone) {
// make sure the partial match is indeed the longest match by adding
// a zone with a shorter origin and query again.
SegmentObjectHolder<ZoneData, RRClass> holder4(
- mem_sgmt_, ZoneData::create(mem_sgmt_, Name("com")), zclass_);
- EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zclass_,
- Name("com"),
+ mem_sgmt_, zclass_);
+ holder4.set(ZoneData::create(mem_sgmt_, Name("com")));
+ EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, Name("com"),
holder4.release()).code);
EXPECT_EQ(zone_data,
zone_table->findZone(Name("www.example.com")).zone_data);
diff --git a/src/lib/datasrc/tests/memory/zone_writer_unittest.cc b/src/lib/datasrc/tests/memory/zone_writer_unittest.cc
index 13bcc3b..e1fe672 100644
--- a/src/lib/datasrc/tests/memory/zone_writer_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_writer_unittest.cc
@@ -12,59 +12,75 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <datasrc/memory/zone_writer_local.h>
+#include <config.h>
+
+#include <datasrc/memory/zone_writer.h>
#include <datasrc/memory/zone_table_segment_local.h>
#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_data_loader.h>
+#include <datasrc/memory/load_action.h>
+#include <datasrc/memory/zone_table.h>
+#include <datasrc/exceptions.h>
+#include <datasrc/result.h>
+
+#include <util/memory_segment_mapped.h>
#include <cc/data.h>
+
#include <dns/rrclass.h>
#include <dns/name.h>
+#include <datasrc/tests/memory/memory_segment_mock.h>
+#include <datasrc/tests/memory/zone_table_segment_mock.h>
+
#include <gtest/gtest.h>
#include <boost/scoped_ptr.hpp>
#include <boost/bind.hpp>
+#include <boost/format.hpp>
+
+#include <string>
+#include <unistd.h>
using boost::scoped_ptr;
using boost::bind;
using isc::dns::RRClass;
using isc::dns::Name;
+using isc::datasrc::ZoneLoaderException;
using namespace isc::datasrc::memory;
+using namespace isc::datasrc::memory::test;
namespace {
class TestException {};
-class ZoneWriterLocalTest : public ::testing::Test {
-public:
- ZoneWriterLocalTest() :
- // FIXME: The NullElement probably isn't the best one, but we don't
- // know how the config will look, so it just fills the argument
- // (which is currently ignored)
- segment_(ZoneTableSegment::create(isc::data::NullElement(),
- RRClass::IN())),
+class ZoneWriterTest : public ::testing::Test {
+protected:
+ ZoneWriterTest() :
+ segment_(new ZoneTableSegmentMock(RRClass::IN(), mem_sgmt_)),
writer_(new
- ZoneWriterLocal(dynamic_cast<ZoneTableSegmentLocal*>(segment_.
- get()),
- bind(&ZoneWriterLocalTest::loadAction, this, _1),
- Name("example.org"), RRClass::IN())),
+ ZoneWriter(*segment_,
+ bind(&ZoneWriterTest::loadAction, this, _1),
+ Name("example.org"), RRClass::IN(), false)),
load_called_(false),
load_throw_(false),
+ load_loader_throw_(false),
load_null_(false),
load_data_(false)
{}
- void TearDown() {
+ virtual void TearDown() {
// Release the writer
writer_.reset();
}
-protected:
- scoped_ptr<ZoneTableSegment> segment_;
- scoped_ptr<ZoneWriterLocal> writer_;
+ MemorySegmentMock mem_sgmt_;
+ scoped_ptr<ZoneTableSegmentMock> segment_;
+ scoped_ptr<ZoneWriter> writer_;
bool load_called_;
bool load_throw_;
+ bool load_loader_throw_;
bool load_null_;
bool load_data_;
-private:
+public:
ZoneData* loadAction(isc::util::MemorySegment& segment) {
// Make sure it is the correct segment passed. We know the
// exact instance, can compare pointers to them.
@@ -74,6 +90,9 @@ private:
if (load_throw_) {
throw TestException();
}
+ if (load_loader_throw_) {
+ isc_throw(ZoneLoaderException, "faked loader exception");
+ }
if (load_null_) {
// Be nasty to the caller and return NULL, which is forbidden
@@ -91,9 +110,39 @@ private:
}
};
+class ReadOnlySegment : public ZoneTableSegmentMock {
+public:
+ ReadOnlySegment(const isc::dns::RRClass& rrclass,
+ isc::util::MemorySegment& mem_sgmt) :
+ ZoneTableSegmentMock(rrclass, mem_sgmt)
+ {}
+
+ // Returns false indicating that the segment is not usable. We
+ // override this too as ZoneTableSegment implementations may use it
+ // internally.
+ virtual bool isUsable() const {
+ return (false);
+ }
+
+ // Returns false indicating it is a read-only segment. It is used in
+ // the ZoneWriter tests.
+ virtual bool isWritable() const {
+ return (false);
+ }
+};
+
+TEST_F(ZoneWriterTest, constructForReadOnlySegment) {
+ MemorySegmentMock mem_sgmt;
+ ReadOnlySegment ztable_segment(RRClass::IN(), mem_sgmt);
+ EXPECT_THROW(ZoneWriter(ztable_segment,
+ bind(&ZoneWriterTest::loadAction, this, _1),
+ Name("example.org"), RRClass::IN(), false),
+ isc::InvalidOperation);
+}
+
// We call it the way we are supposed to, check every callback is called in the
// right moment.
-TEST_F(ZoneWriterLocalTest, correctCall) {
+TEST_F(ZoneWriterTest, correctCall) {
// Nothing called before we call it
EXPECT_FALSE(load_called_);
@@ -110,7 +159,7 @@ TEST_F(ZoneWriterLocalTest, correctCall) {
EXPECT_NO_THROW(writer_->cleanup());
}
-TEST_F(ZoneWriterLocalTest, loadTwice) {
+TEST_F(ZoneWriterTest, loadTwice) {
// Load it the first time
EXPECT_NO_THROW(writer_->load());
EXPECT_TRUE(load_called_);
@@ -131,7 +180,7 @@ TEST_F(ZoneWriterLocalTest, loadTwice) {
// Try loading after call to install and call to cleanup. Both is
// forbidden.
-TEST_F(ZoneWriterLocalTest, loadLater) {
+TEST_F(ZoneWriterTest, loadLater) {
// Load first, so we can install
EXPECT_NO_THROW(writer_->load());
EXPECT_NO_THROW(writer_->install());
@@ -149,7 +198,7 @@ TEST_F(ZoneWriterLocalTest, loadLater) {
}
// Try calling install at various bad times
-TEST_F(ZoneWriterLocalTest, invalidInstall) {
+TEST_F(ZoneWriterTest, invalidInstall) {
// Nothing loaded yet
EXPECT_THROW(writer_->install(), isc::InvalidOperation);
EXPECT_FALSE(load_called_);
@@ -166,7 +215,7 @@ TEST_F(ZoneWriterLocalTest, invalidInstall) {
// We check we can clean without installing first and nothing bad
// happens. We also misuse the testcase to check we can't install
// after cleanup.
-TEST_F(ZoneWriterLocalTest, cleanWithoutInstall) {
+TEST_F(ZoneWriterTest, cleanWithoutInstall) {
EXPECT_NO_THROW(writer_->load());
EXPECT_NO_THROW(writer_->cleanup());
@@ -177,7 +226,7 @@ TEST_F(ZoneWriterLocalTest, cleanWithoutInstall) {
}
// Test the case when load callback throws
-TEST_F(ZoneWriterLocalTest, loadThrows) {
+TEST_F(ZoneWriterTest, loadThrows) {
load_throw_ = true;
EXPECT_THROW(writer_->load(), TestException);
@@ -189,9 +238,60 @@ TEST_F(ZoneWriterLocalTest, loadThrows) {
EXPECT_NO_THROW(writer_->cleanup());
}
+// Emulate the situation where load() throws loader error.
+TEST_F(ZoneWriterTest, loadLoaderException) {
+ std::string error_msg;
+
+ // By default, the exception is propagated.
+ load_loader_throw_ = true;
+ EXPECT_THROW(writer_->load(), ZoneLoaderException);
+ // In this case, passed error_msg won't be updated.
+ writer_.reset(new ZoneWriter(*segment_,
+ bind(&ZoneWriterTest::loadAction, this, _1),
+ Name("example.org"), RRClass::IN(), false));
+ EXPECT_THROW(writer_->load(&error_msg), ZoneLoaderException);
+ EXPECT_EQ("", error_msg);
+
+ // If we specify allowing load error, load() will succeed and install()
+ // adds an empty zone. Note that we implicitly pass NULL to load()
+ // as it's the default parameter, so the following also confirms it doesn't
+ // cause disruption.
+ writer_.reset(new ZoneWriter(*segment_,
+ bind(&ZoneWriterTest::loadAction, this, _1),
+ Name("example.org"), RRClass::IN(), true));
+ writer_->load();
+ writer_->install();
+ writer_->cleanup();
+
+ // Check an empty zone has been really installed.
+ using namespace isc::datasrc::result;
+ const ZoneTable* ztable = segment_->getHeader().getTable();
+ ASSERT_TRUE(ztable);
+ const ZoneTable::FindResult result = ztable->findZone(Name("example.org"));
+ EXPECT_EQ(SUCCESS, result.code);
+ EXPECT_EQ(ZONE_EMPTY, result.flags);
+
+ // Allowing an error, and passing a template for the error message.
+ // It will be filled with the reason for the error.
+ writer_.reset(new ZoneWriter(*segment_,
+ bind(&ZoneWriterTest::loadAction, this, _1),
+ Name("example.org"), RRClass::IN(), true));
+ writer_->load(&error_msg);
+ EXPECT_NE("", error_msg);
+
+ // In case of no error, the placeholder will be intact.
+ load_loader_throw_ = false;
+ error_msg.clear();
+ writer_.reset(new ZoneWriter(*segment_,
+ bind(&ZoneWriterTest::loadAction, this, _1),
+ Name("example.org"), RRClass::IN(), true));
+ writer_->load(&error_msg);
+ EXPECT_EQ("", error_msg);
+}
+
// Check the strong exception guarantee - if it throws, nothing happened
// to the content.
-TEST_F(ZoneWriterLocalTest, retry) {
+TEST_F(ZoneWriterTest, retry) {
// First attempt fails due to some exception.
load_throw_ = true;
EXPECT_THROW(writer_->load(), TestException);
@@ -219,7 +319,7 @@ TEST_F(ZoneWriterLocalTest, retry) {
}
// Check the writer defends itsefl when load action returns NULL
-TEST_F(ZoneWriterLocalTest, loadNull) {
+TEST_F(ZoneWriterTest, loadNull) {
load_null_ = true;
EXPECT_THROW(writer_->load(), isc::InvalidOperation);
@@ -231,10 +331,85 @@ TEST_F(ZoneWriterLocalTest, loadNull) {
}
// Check the object cleans up in case we forget it.
-TEST_F(ZoneWriterLocalTest, autoCleanUp) {
+TEST_F(ZoneWriterTest, autoCleanUp) {
// Load data and forget about it. It should get released
// when the writer itself is destroyed.
EXPECT_NO_THROW(writer_->load());
}
+// Used in the manyWrites test, encapsulating loadZoneData() to avoid
+// its signature ambiguity.
+ZoneData*
+loadZoneDataWrapper(isc::util::MemorySegment& segment, const RRClass& rrclass,
+ const Name& name, const std::string& filename)
+{
+ return (loadZoneData(segment, rrclass, name, filename));
+}
+
+// Check the behavior of creating many small zones. The main purpose of
+// test is to trigger MemorySegmentGrown exception in ZoneWriter::install.
+// There's no easy (if any) way to cause that reliably as it's highly
+// dependent on details of the underlying boost implementation and probably
+// also on the system behavior, but we'll try some promising scenario (it
+// in fact triggered the intended result at least on one environment).
+TEST_F(ZoneWriterTest, manyWrites) {
+#ifdef USE_SHARED_MEMORY
+ // First, make a fresh mapped file of a small size (so it'll be more likely
+ // to grow in the test.
+ const char* const mapped_file = TEST_DATA_BUILDDIR "/test.mapped";
+ unlink(mapped_file);
+ boost::scoped_ptr<isc::util::MemorySegmentMapped> segment(
+ new isc::util::MemorySegmentMapped(
+ mapped_file, isc::util::MemorySegmentMapped::CREATE_ONLY, 4096));
+ segment.reset();
+
+ // Then prepare a ZoneTableSegment of the 'mapped' type specifying the
+ // file we just created.
+ boost::scoped_ptr<ZoneTableSegment> zt_segment(
+ ZoneTableSegment::create(RRClass::IN(), "mapped"));
+ const isc::data::ConstElementPtr params =
+ isc::data::Element::fromJSON(
+ "{\"mapped-file\": \"" + std::string(mapped_file) + "\"}");
+ zt_segment->reset(ZoneTableSegment::READ_WRITE, params);
+#else
+ // Do the same test for the local segment, although there shouldn't be
+ // anything tricky in that case.
+ boost::scoped_ptr<ZoneTableSegment> zt_segment(
+ ZoneTableSegment::create(RRClass::IN(), "local"));
+#endif
+
+ // Now, create many small zones in the zone table with a ZoneWriter.
+ // We use larger origin names so it'll (hopefully) require the memory
+ // segment to grow while adding the name into the internal table.
+ const size_t zone_count = 10000; // arbitrary choice
+ for (size_t i = 0; i < zone_count; ++i) {
+ const Name origin(
+ boost::str(boost::format("%063u.%063u.%063u.example.org")
+ % i % i % i));
+ const LoadAction action = boost::bind(loadZoneDataWrapper, _1,
+ RRClass::IN(), origin,
+ TEST_DATA_DIR
+ "/template.zone");
+ ZoneWriter writer(*zt_segment, action, origin, RRClass::IN(), false);
+ writer.load();
+ writer.install();
+ writer.cleanup();
+
+ // Confirm it's been successfully added and can be actually found.
+ const ZoneTable::FindResult result =
+ zt_segment->getHeader().getTable()->findZone(origin);
+ EXPECT_EQ(isc::datasrc::result::SUCCESS, result.code);
+ EXPECT_NE(static_cast<const ZoneData*>(NULL), result.zone_data) <<
+ "unexpected find result: " + origin.toText();
+ }
+
+ // Make sure to close the segment before (possibly) removing the mapped
+ // file.
+ zt_segment.reset();
+
+#ifdef USE_SHARED_MEMORY
+ unlink(mapped_file);
+#endif
+}
+
}
diff --git a/src/lib/datasrc/tests/mock_client.cc b/src/lib/datasrc/tests/mock_client.cc
new file mode 100644
index 0000000..8cdc05d
--- /dev/null
+++ b/src/lib/datasrc/tests/mock_client.cc
@@ -0,0 +1,197 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/tests/mock_client.h>
+#include <datasrc/client.h>
+#include <datasrc/result.h>
+#include <datasrc/zone_iterator.h>
+#include <datasrc/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+
+#include <cc/data.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <vector>
+#include <set>
+#include <string>
+
+using namespace isc::dns;
+
+using boost::shared_ptr;
+using std::vector;
+using std::string;
+using std::set;
+
+namespace isc {
+namespace datasrc {
+namespace unittest {
+
+namespace {
+class Finder : public ZoneFinder {
+public:
+ Finder(const Name& origin) :
+ origin_(origin)
+ {}
+ Name getOrigin() const { return (origin_); }
+ // The rest is not to be called, so just have them
+ RRClass getClass() const {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ shared_ptr<Context> find(const Name&, const RRType&,
+ const FindOptions)
+ {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ shared_ptr<Context> findAll(const Name&,
+ vector<ConstRRsetPtr>&,
+ const FindOptions)
+ {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ FindNSEC3Result findNSEC3(const Name&, bool) {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+private:
+ Name origin_;
+};
+
+class Iterator : public ZoneIterator {
+public:
+ Iterator(const Name& origin, bool include_a) :
+ origin_(origin),
+ soa_(new RRset(origin_, RRClass::IN(), RRType::SOA(),
+ RRTTL(3600)))
+ {
+ // The RData here is bogus, but it is not used to anything. There
+ // just needs to be some.
+ soa_->addRdata(rdata::generic::SOA(Name::ROOT_NAME(),
+ Name::ROOT_NAME(),
+ 0, 0, 0, 0, 0));
+ rrsets_.push_back(soa_);
+
+ RRsetPtr rrset(new RRset(origin_, RRClass::IN(), RRType::NS(),
+ RRTTL(3600)));
+ rrset->addRdata(rdata::generic::NS(Name::ROOT_NAME()));
+ rrsets_.push_back(rrset);
+
+ if (include_a) {
+ // Dummy A rrset. This is used for checking zone data
+ // after reload.
+ rrset.reset(new RRset(Name("tstzonedata").concatenate(origin_),
+ RRClass::IN(), RRType::A(),
+ RRTTL(3600)));
+ rrset->addRdata(rdata::in::A("192.0.2.1"));
+ rrsets_.push_back(rrset);
+ }
+
+ rrsets_.push_back(ConstRRsetPtr());
+
+ it_ = rrsets_.begin();
+ }
+ virtual isc::dns::ConstRRsetPtr getNextRRset() {
+ ConstRRsetPtr result = *it_;
+ ++it_;
+ return (result);
+ }
+ virtual isc::dns::ConstRRsetPtr getSOA() const {
+ return (soa_);
+ }
+private:
+ const Name origin_;
+ const RRsetPtr soa_;
+ vector<ConstRRsetPtr> rrsets_;
+ vector<ConstRRsetPtr>::const_iterator it_;
+};
+}
+
+// A test data source. It pretends it has some zones.
+
+MockDataSourceClient::MockDataSourceClient(const char* zone_names[]) :
+ have_a_(true), use_baditerator_(true)
+{
+ for (const char** zone(zone_names); *zone; ++zone) {
+ zones.insert(Name(*zone));
+ }
+}
+
+// Constructor from configuration. The list of zones will be empty, but
+// it will keep the configuration inside for further inspection.
+MockDataSourceClient::MockDataSourceClient(
+ const string& type,
+ const data::ConstElementPtr& configuration) :
+ type_(type),
+ configuration_(configuration),
+ have_a_(true), use_baditerator_(true)
+{
+ EXPECT_NE("MasterFiles", type) << "MasterFiles is a special case "
+ "and it never should be created as a data source client";
+ if (configuration_->getType() == data::Element::list) {
+ for (size_t i(0); i < configuration_->size(); ++i) {
+ zones.insert(Name(configuration_->get(i)->stringValue()));
+ }
+ }
+}
+
+DataSourceClient::FindResult
+MockDataSourceClient::findZone(const Name& name) const {
+ if (zones.empty()) {
+ return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+ }
+ set<Name>::const_iterator it(zones.upper_bound(name));
+ if (it == zones.begin()) {
+ return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+ }
+ --it;
+ NameComparisonResult compar(it->compare(name));
+ const ZoneFinderPtr finder(new Finder(*it));
+ switch (compar.getRelation()) {
+ case NameComparisonResult::EQUAL:
+ return (FindResult(result::SUCCESS, finder));
+ case NameComparisonResult::SUPERDOMAIN:
+ return (FindResult(result::PARTIALMATCH, finder));
+ default:
+ return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+ }
+}
+
+// These methods are not used. They just need to be there to have
+// complete vtable.
+
+ZoneIteratorPtr
+MockDataSourceClient::getIterator(const Name& name, bool) const {
+ if (use_baditerator_ && name == Name("noiter.org")) {
+ isc_throw(isc::NotImplemented, "Asked not to be implemented");
+ } else if (use_baditerator_ && name == Name("null.org")) {
+ return (ZoneIteratorPtr());
+ } else {
+ FindResult result(findZone(name));
+ if (result.code == isc::datasrc::result::SUCCESS) {
+ return (ZoneIteratorPtr(new Iterator(name, have_a_)));
+ } else {
+ isc_throw(NoSuchZone, "No such zone");
+ }
+ }
+}
+
+} // end of unittest
+} // end of datasrc
+} // end of isc
diff --git a/src/lib/datasrc/tests/mock_client.h b/src/lib/datasrc/tests/mock_client.h
new file mode 100644
index 0000000..7a01440
--- /dev/null
+++ b/src/lib/datasrc/tests/mock_client.h
@@ -0,0 +1,83 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/client.h>
+
+#include <dns/dns_fwd.h>
+#include <dns/rrset.h>
+
+#include <cc/data.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <set>
+#include <vector>
+
+namespace isc {
+namespace datasrc {
+namespace unittest {
+
+// A test data source. It pretends it has some zones.
+class MockDataSourceClient : public DataSourceClient {
+public:
+ // Constructor from a list of zones.
+ MockDataSourceClient(const char* zone_names[]);
+
+ // Constructor from configuration. The list of zones will be empty, but
+ // it will keep the configuration inside for further inspection.
+ MockDataSourceClient(const std::string& type,
+ const data::ConstElementPtr& configuration);
+
+ virtual FindResult findZone(const dns::Name& name) const;
+ // These methods are not used. They just need to be there to have
+ // complete vtable.
+ virtual ZoneUpdaterPtr getUpdater(const dns::Name&, bool, bool) const {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ virtual std::pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
+ getJournalReader(const dns::Name&, uint32_t, uint32_t) const
+ {
+ isc_throw(isc::NotImplemented, "Not implemented");
+ }
+ virtual ZoneIteratorPtr getIterator(const dns::Name& name, bool) const;
+ void disableA() { have_a_ = false; }
+ void enableA() { have_a_ = true; }
+ void disableBadIterator() { use_baditerator_ = false; }
+ void enableBadIterator() { use_baditerator_ = true; }
+ void eraseZone(const dns::Name& zone_name) {
+ zones.erase(zone_name);
+ }
+
+ /// \brief Dynamically add a zone to the data source.
+ ///
+ /// \return true if the zone is newly added; false if it already exists.
+ bool insertZone(const dns::Name& zone_name) {
+ return (zones.insert(zone_name).second);
+ }
+ const std::string type_;
+ const data::ConstElementPtr configuration_;
+
+private:
+ std::set<dns::Name> zones;
+ bool have_a_; // control the iterator behavior whether to include A record
+ bool use_baditerator_; // whether to use bogus zone iterators for tests
+};
+
+} // end of unittest
+} // end of datasrc
+} // end of isc
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
index c263304..ce34d25 100644
--- a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
@@ -16,7 +16,7 @@
#include <datasrc/sqlite3_accessor.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <dns/rrclass.h>
@@ -806,7 +806,7 @@ const char* const new_data[] = {
};
const char* const deleted_data[] = {
// Existing data to be removed commonly used by some of the tests below
- "foo.bar.example.com.", "A", "192.0.2.1"
+ "foo.bar.example.com.", "A", "192.0.2.1", "com.example.bar.foo."
};
const char* const nsec3_data[DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT] = {
// example NSEC3 parameters. Using "apex_hash" just as a convenient
@@ -853,6 +853,7 @@ protected:
std::string add_columns[DatabaseAccessor::ADD_COLUMN_COUNT];
std::string add_nsec3_columns[DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT];
std::string del_params[DatabaseAccessor::DEL_PARAM_COUNT];
+ std::string del_nsec3_params[DatabaseAccessor::DEL_NSEC3_PARAM_COUNT];
std::string diff_params[DatabaseAccessor::DIFF_PARAM_COUNT];
vector<const char* const*> expected_stored; // placeholder for checkRecords
@@ -1192,8 +1193,9 @@ TEST_F(SQLite3Update, deleteNSEC3Record) {
// Delete it, and confirm that.
copy(nsec3_deleted_data,
- nsec3_deleted_data + DatabaseAccessor::DEL_PARAM_COUNT, del_params);
- accessor->deleteNSEC3RecordInZone(del_params);
+ nsec3_deleted_data + DatabaseAccessor::DEL_NSEC3_PARAM_COUNT,
+ del_nsec3_params);
+ accessor->deleteNSEC3RecordInZone(del_nsec3_params);
checkNSEC3Records(*accessor, zone_id, apex_hash, empty_stored);
// Commit the change, and confirm the deleted data still isn't there.
@@ -1222,6 +1224,7 @@ TEST_F(SQLite3Update, deleteNonexistent) {
// Replace the name with a non existent one, then try to delete it.
// nothing should happen.
del_params[DatabaseAccessor::DEL_NAME] = "no-such-name.example.com.";
+ del_params[DatabaseAccessor::DEL_RNAME] = "com.example.no-such-name.";
checkRecords(*accessor, zone_id, "no-such-name.example.com.",
empty_stored);
accessor->deleteRecordInZone(del_params);
@@ -1249,7 +1252,7 @@ TEST_F(SQLite3Update, invalidDelete) {
EXPECT_THROW(accessor->deleteRecordInZone(del_params), DataSourceError);
// Same for NSEC3.
- EXPECT_THROW(accessor->deleteNSEC3RecordInZone(del_params),
+ EXPECT_THROW(accessor->deleteNSEC3RecordInZone(del_nsec3_params),
DataSourceError);
}
@@ -1535,7 +1538,7 @@ TEST_F(SQLite3Update, addDiffWithUpdate) {
// the basic tests so far pass. But we check it in case we miss something.
const char* const old_a_record[] = {
- "dns01.example.com.", "A", "192.0.2.1"
+ "dns01.example.com.", "A", "192.0.2.1", "com.example.dns01."
};
const char* const new_a_record[] = {
"dns01.example.com.", "com.example.dns01.", "3600", "A", "",
@@ -1544,6 +1547,7 @@ TEST_F(SQLite3Update, addDiffWithUpdate) {
const char* const old_soa_record[] = {
"example.com.", "SOA",
"ns.example.com. admin.example.com. 1234 3600 1800 2419200 7200",
+ "com.example."
};
const char* const new_soa_record[] = {
"dns01.example.com.", "com.example.dns01.", "3600", "A", "",
diff --git a/src/lib/datasrc/tests/testdata/rrset_toWire2 b/src/lib/datasrc/tests/testdata/rrset_toWire2
index b9a6a15..743f968 100644
--- a/src/lib/datasrc/tests/testdata/rrset_toWire2
+++ b/src/lib/datasrc/tests/testdata/rrset_toWire2
@@ -18,7 +18,7 @@
# RDATA: 192.0.2.1
c0 00 02 01
#
-# 2nd RR: the owner name is compresed
+# 2nd RR: the owner name is compressed
c0 00
00 01 00 01
00 00 0e 10
diff --git a/src/lib/datasrc/tests/zone_finder_context_unittest.cc b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
index a5c8a8f..65e9f6c 100644
--- a/src/lib/datasrc/tests/zone_finder_context_unittest.cc
+++ b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
@@ -18,9 +18,13 @@
#include <dns/name.h>
#include <dns/rrclass.h>
+#include <cc/data.h>
+
#include <datasrc/zone_finder.h>
+#include <datasrc/cache_config.h>
#include <datasrc/memory/memory_client.h>
#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/zone_writer.h>
#include <datasrc/database.h>
#include <datasrc/sqlite3_accessor.h>
@@ -44,6 +48,7 @@ using namespace isc::data;
using namespace isc::util;
using namespace isc::dns;
using namespace isc::datasrc;
+using isc::data::Element;
using isc::datasrc::memory::InMemoryClient;
using isc::datasrc::memory::ZoneTableSegment;
using namespace isc::testutils;
@@ -63,14 +68,23 @@ typedef DataSourceClientPtr (*ClientCreator)(RRClass, const Name&);
// Creator for the in-memory client to be tested
DataSourceClientPtr
-createInMemoryClient(RRClass zclass, const Name& zname)
-{
- const ElementPtr config(Element::fromJSON("{}"));
+createInMemoryClient(RRClass zclass, const Name& zname) {
+ const internal::CacheConfig cache_conf(
+ "MasterFiles", NULL, *Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"params\":"
+ " {\"" + zname.toText() + "\": \"" +
+ string(TEST_ZONE_FILE) + "\"}}"), true);
shared_ptr<ZoneTableSegment> ztable_segment(
- ZoneTableSegment::create(*config, zclass));
+ ZoneTableSegment::create(zclass, cache_conf.getSegmentType()));
+ memory::ZoneWriter writer(*ztable_segment,
+ cache_conf.getLoadAction(zclass, zname),
+ zname, zclass, false);
+ writer.load();
+ writer.install();
+ writer.cleanup();
shared_ptr<InMemoryClient> client(new InMemoryClient(ztable_segment,
zclass));
- client->load(zname, TEST_ZONE_FILE);
return (client);
}
@@ -107,7 +121,7 @@ createSQLite3ClientWithNS(RRClass zclass, const Name& zname) {
}
// The test class. Its parameterized so we can share the test scnearios
-// for any concrete data source implementaitons.
+// for any concrete data source implementations.
class ZoneFinderContextTest :
public ::testing::TestWithParam<ClientCreator>
{
diff --git a/src/lib/datasrc/tests/zone_loader_unittest.cc b/src/lib/datasrc/tests/zone_loader_unittest.cc
index 4b42185..cd1999a 100644
--- a/src/lib/datasrc/tests/zone_loader_unittest.cc
+++ b/src/lib/datasrc/tests/zone_loader_unittest.cc
@@ -13,11 +13,13 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <datasrc/zone_loader.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/rrset_collection_base.h>
+#include <datasrc/cache_config.h>
#include <datasrc/memory/zone_table_segment.h>
#include <datasrc/memory/memory_client.h>
+#include <datasrc/memory/zone_writer.h>
#include <dns/rrclass.h>
#include <dns/name.h>
@@ -26,16 +28,20 @@
#include <util/memory_segment_local.h>
#include <exceptions/exceptions.h>
+#include <cc/data.h>
+
#include <gtest/gtest.h>
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/foreach.hpp>
+
#include <string>
#include <vector>
using namespace isc::dns;
using namespace isc::datasrc;
+using isc::data::Element;
using boost::shared_ptr;
using std::string;
using std::vector;
@@ -287,13 +293,42 @@ MockClient::getUpdater(const Name& name, bool replace, bool journaling) const {
class ZoneLoaderTest : public ::testing::Test {
protected:
ZoneLoaderTest() :
- rrclass_(RRClass::IN()),
- ztable_segment_(memory::ZoneTableSegment::
- create(isc::data::NullElement(), rrclass_)),
- source_client_(ztable_segment_, rrclass_)
- {}
+ rrclass_(RRClass::IN())
+ {
+ // Use ROOT_NAME as a placeholder; it will be ignored if filename is
+ // null.
+ prepareSource(Name::ROOT_NAME(), NULL);
+ }
void prepareSource(const Name& zone, const char* filename) {
- source_client_.load(zone, string(TEST_DATA_DIR) + "/" + filename);
+ // Cleanup the existing data in the right order
+ source_client_.reset();
+ ztable_segment_.reset();
+
+ // (re)configure zone table, then (re)construct the in-memory client
+ // with it.
+ string param_data;
+ if (filename) {
+ param_data = "\"" + zone.toText() + "\": \"" +
+ string(TEST_DATA_DIR) + "/" + filename + "\"";
+ }
+ const internal::CacheConfig cache_conf(
+ "MasterFiles", NULL, *Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"params\": {" + param_data + "}}"), true);
+ ztable_segment_.reset(memory::ZoneTableSegment::create(
+ rrclass_, cache_conf.getSegmentType()));
+ if (filename) {
+ boost::scoped_ptr<memory::ZoneWriter> writer(
+ new memory::ZoneWriter(*ztable_segment_,
+ cache_conf.getLoadAction(rrclass_,
+ zone),
+ zone, rrclass_, false));
+ writer->load();
+ writer->install();
+ writer->cleanup();
+ }
+ source_client_.reset(new memory::InMemoryClient(ztable_segment_,
+ rrclass_));
}
private:
const RRClass rrclass_;
@@ -305,7 +340,7 @@ private:
// But the shared pointer won't let us, will it?
shared_ptr<memory::ZoneTableSegment> ztable_segment_;
protected:
- memory::InMemoryClient source_client_;
+ boost::scoped_ptr<memory::InMemoryClient> source_client_;
// This one is mocked. It will help us see what is happening inside.
// Also, mocking it is simpler than setting up an sqlite3 client.
MockClient destination_client_;
@@ -314,7 +349,7 @@ protected:
// Use the loader to load an unsigned zone.
TEST_F(ZoneLoaderTest, copyUnsigned) {
prepareSource(Name::ROOT_NAME(), "root.zone");
- ZoneLoader loader(destination_client_, Name::ROOT_NAME(), source_client_);
+ ZoneLoader loader(destination_client_, Name::ROOT_NAME(), *source_client_);
// It gets the updater directly in the constructor
ASSERT_EQ(1, destination_client_.provided_updaters_.size());
EXPECT_EQ(Name::ROOT_NAME(), destination_client_.provided_updaters_[0]);
@@ -355,7 +390,7 @@ TEST_F(ZoneLoaderTest, copyUnsigned) {
// Try loading incrementally.
TEST_F(ZoneLoaderTest, copyUnsignedIncremental) {
prepareSource(Name::ROOT_NAME(), "root.zone");
- ZoneLoader loader(destination_client_, Name::ROOT_NAME(), source_client_);
+ ZoneLoader loader(destination_client_, Name::ROOT_NAME(), *source_client_);
// Try loading few RRs first.
loader.loadIncremental(10);
@@ -390,7 +425,7 @@ TEST_F(ZoneLoaderTest, copyUnsignedIncremental) {
TEST_F(ZoneLoaderTest, copySigned) {
prepareSource(Name("example.org"), "example.org.nsec3-signed");
ZoneLoader loader(destination_client_, Name("example.org"),
- source_client_);
+ *source_client_);
loader.load();
// All the RRs are there, including the ones in NSEC3 namespace
@@ -416,13 +451,13 @@ TEST_F(ZoneLoaderTest, copyMissingDestination) {
destination_client_.missing_zone_ = true;
prepareSource(Name::ROOT_NAME(), "root.zone");
EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
- source_client_), DataSourceError);
+ *source_client_), DataSourceError);
}
// If the source zone does not exist, it throws
TEST_F(ZoneLoaderTest, copyMissingSource) {
EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
- source_client_), DataSourceError);
+ *source_client_), DataSourceError);
}
// The class of the source and destination are different
@@ -430,7 +465,7 @@ TEST_F(ZoneLoaderTest, classMismatch) {
destination_client_.rrclass_ = RRClass::CH();
prepareSource(Name::ROOT_NAME(), "root.zone");
EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
- source_client_), isc::InvalidParameter);
+ *source_client_), isc::InvalidParameter);
}
// Load an unsigned zone, all at once
@@ -593,7 +628,7 @@ TEST_F(ZoneLoaderTest, loadCheckWarn) {
TEST_F(ZoneLoaderTest, copyCheckWarn) {
prepareSource(Name("example.org"), "checkwarn.zone");
ZoneLoader loader(destination_client_, Name("example.org"),
- source_client_);
+ *source_client_);
EXPECT_TRUE(loader.loadIncremental(10));
// The messages go to the log. We don't have an easy way to examine them.
// But the zone was committed and contains all 3 RRs
diff --git a/src/lib/datasrc/tests/zone_table_accessor_unittest.cc b/src/lib/datasrc/tests/zone_table_accessor_unittest.cc
new file mode 100644
index 0000000..e5164b5
--- /dev/null
+++ b/src/lib/datasrc/tests/zone_table_accessor_unittest.cc
@@ -0,0 +1,112 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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/zone_table_accessor_cache.h>
+#include <datasrc/cache_config.h>
+#include <datasrc/tests/mock_client.h>
+
+#include <exceptions/exceptions.h>
+
+#include <cc/data.h>
+
+#include <dns/name.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc;
+using namespace isc::datasrc::internal;
+using isc::data::Element;
+using isc::datasrc::unittest::MockDataSourceClient;
+
+namespace {
+
+// This test checks the abstract ZoneTableAccessor interface using
+// ZoneTableAccessorCache instances, thereby testing the top level interface
+// and the derived class behavior. If ZoneTableAccessorCache becomes more
+// complicated we may have to separate some test cases into dedicated test
+// fixture.
+class ZoneTableAccessorTest : public ::testing::Test {
+protected:
+ ZoneTableAccessorTest() :
+ // The paths of the zone files are dummy and don't even exist,
+ // but it doesn't matter in this test.
+ config_spec_(Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"params\": "
+ " {\"example.com\": \"/example-com.zone\","
+ " \"example.org\": \"/example-org.zone\"}"
+ "}")),
+ cache_config_("MasterFiles", NULL, *config_spec_, true),
+ accessor_(cache_config_)
+ {}
+
+private:
+ const isc::data::ConstElementPtr config_spec_;
+ const CacheConfig cache_config_;
+protected:
+ ZoneTableAccessorCache accessor_;
+};
+
+TEST_F(ZoneTableAccessorTest, iteratorFromCache) {
+ // Confirm basic iterator behavior.
+ ZoneTableAccessor::IteratorPtr it = accessor_.getIterator();
+ ASSERT_TRUE(it);
+ EXPECT_FALSE(it->isLast());
+ EXPECT_EQ(0, it->getCurrent().index); // index is always 0 for this version
+ EXPECT_EQ(Name("example.com"), it->getCurrent().origin);
+
+ it->next();
+ EXPECT_FALSE(it->isLast());
+ EXPECT_EQ(0, it->getCurrent().index);
+ EXPECT_EQ(Name("example.org"), it->getCurrent().origin);
+
+ it->next(); // shouldn't cause disruption
+ EXPECT_TRUE(it->isLast());
+
+ // getCurrent() and next() will be rejected once iterator reaches the end
+ EXPECT_THROW(it->getCurrent(), isc::InvalidOperation);
+ EXPECT_THROW(it->next(), isc::InvalidOperation);
+}
+
+TEST_F(ZoneTableAccessorTest, emptyTable) {
+ // Empty zone table is possible, while mostly useless.
+ const CacheConfig empty_config(
+ "MasterFiles", NULL, *Element::fromJSON(
+ "{\"cache-enable\": true, \"params\": {}}"), true);
+ ZoneTableAccessorCache accessor(empty_config);
+ ZoneTableAccessor::IteratorPtr it = accessor.getIterator();
+ ASSERT_TRUE(it);
+ EXPECT_TRUE(it->isLast());
+ EXPECT_THROW(it->getCurrent(), isc::InvalidOperation);
+ EXPECT_THROW(it->next(), isc::InvalidOperation);
+}
+
+TEST_F(ZoneTableAccessorTest, disabledTable) {
+ // Zone table based on disabled cache is effectively empty.
+ const char* zones[] = { "example.org.", "example.com.", NULL };
+ MockDataSourceClient mock_client(zones);
+ const CacheConfig mock_config(
+ "mock", &mock_client, *Element::fromJSON(
+ "{\"cache-enable\": false,"
+ " \"cache-zones\": [\"example.com\", \"example.org\"]}"), true);
+ ZoneTableAccessorCache accessor(mock_config);
+ ZoneTableAccessor::IteratorPtr it = accessor.getIterator();
+ ASSERT_TRUE(it);
+ EXPECT_TRUE(it->isLast());
+ EXPECT_THROW(it->getCurrent(), isc::InvalidOperation);
+ EXPECT_THROW(it->next(), isc::InvalidOperation);
+}
+
+}
diff --git a/src/lib/datasrc/zone_finder.cc b/src/lib/datasrc/zone_finder.cc
index b4240c0..70cb8bf 100644
--- a/src/lib/datasrc/zone_finder.cc
+++ b/src/lib/datasrc/zone_finder.cc
@@ -13,8 +13,9 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <datasrc/zone_finder.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
+#include <dns/rrclass.h>
#include <dns/rdata.h>
#include <dns/rrset.h>
#include <dns/rrtype.h>
diff --git a/src/lib/datasrc/zone_iterator.h b/src/lib/datasrc/zone_iterator.h
index e1c6929..bdf219a 100644
--- a/src/lib/datasrc/zone_iterator.h
+++ b/src/lib/datasrc/zone_iterator.h
@@ -27,7 +27,7 @@ namespace datasrc {
/**
* \brief Read-only iterator to a zone.
*
- * You can get an instance of (descendand of) ZoneIterator from
+ * You can get an instance of (descendant of) ZoneIterator from
* DataSourceClient::getIterator() method. The actual concrete implementation
* will be different depending on the actual data source used. This is the
* abstract interface.
diff --git a/src/lib/datasrc/zone_loader.cc b/src/lib/datasrc/zone_loader.cc
index d0f4a64..8044bc0 100644
--- a/src/lib/datasrc/zone_loader.cc
+++ b/src/lib/datasrc/zone_loader.cc
@@ -16,7 +16,7 @@
#include <datasrc/master_loader_callbacks.h>
#include <datasrc/client.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/zone_iterator.h>
#include <datasrc/zone.h>
#include <datasrc/logger.h>
diff --git a/src/lib/datasrc/zone_loader.h b/src/lib/datasrc/zone_loader.h
index 2a4559e..068dc35 100644
--- a/src/lib/datasrc/zone_loader.h
+++ b/src/lib/datasrc/zone_loader.h
@@ -15,7 +15,7 @@
#ifndef DATASRC_ZONE_LOADER_H
#define DATASRC_ZONE_LOADER_H
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <dns/master_loader.h>
diff --git a/src/lib/datasrc/zone_table_accessor.h b/src/lib/datasrc/zone_table_accessor.h
new file mode 100644
index 0000000..8e92d51
--- /dev/null
+++ b/src/lib/datasrc/zone_table_accessor.h
@@ -0,0 +1,215 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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_ZONE_TABLE_ACCESSOR_H
+#define DATASRC_ZONE_TABLE_ACCESSOR_H
+
+#include <dns/name.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <stdint.h>
+
+namespace isc {
+namespace datasrc {
+
+/// \brief Information of a zone stored in a data source zone table.
+///
+/// This is a straightforward composite type that represents an entry of
+/// the conceptual zone table referenced by \c ZoneTableAccessor.
+/// An object of this structure is specifically intended to be returned by
+/// \c ZoneTableIterator.
+///
+/// This is essentially a read-only tuple; only created by
+/// \c ZoneTableAccessor, and once created it will be immutable.
+///
+/// \note Once Trac #2144 is completed, this struct must be defined as
+/// non-assignable because it has a const member variable.
+struct ZoneSpec {
+ /// \brief Constructor.
+ ZoneSpec(uint32_t index_param, const dns::Name& origin_param) :
+ index(index_param), origin(origin_param)
+ {}
+
+ /// \brief Numeric zone index.
+ ///
+ /// In the current initial version, this field is just a placeholder.
+ /// In the future, we'll probably define it as a unique index in the table
+ /// for that particular zone so that applications can distinguish
+ /// and specify different zones efficiently. Until it's fixed, this field
+ /// shouldn't be used by applications.
+ const uint32_t index;
+
+ /// \brief The origin name of the zone.
+ const dns::Name origin;
+};
+
+/// \brief A simple iterator of zone table.
+///
+/// This is an abstract base class providing simple iteration operation
+/// over zones stored in a data source. A concrete object of this class
+/// is expected to be returned by \c ZoneTableAccessor::getIterator().
+///
+/// The interface is intentionally simplified and limited: it works
+/// "forward-only", i.e, only goes from begin to end one time; it's not
+/// copyable, assignable, nor comparable. For the latter reasons it's not
+/// compatible with standard iterator traits. It's simplified because it's
+/// not clear what kind of primitive can be used in specific data sources.
+/// In particular, iteration in a database-based data source would be very
+/// restrictive. So it's better to begin with minimal guaranteed features
+/// at the base class. If we find it possible to loosen the restriction
+/// as we implement more derived versions, we may extend the features later.
+///
+/// Likewise, this iterator does not guarantee the ordering of the zones
+/// returned by \c getCurrent(). It's probably possible to ensure some
+/// sorted order, but until we can be sure it's the case for many cases
+/// in practice, we'll not rely on it.
+///
+/// A concrete object of this class is created by specific derived
+/// implementation for the corresponding data source. The implementation
+/// must ensure the iterator is located at the "beginning" of the zone table,
+/// and that subsequent calls to \c next() go through all the zones
+/// one by one, until \c isLast() returns \c true. The implementation must
+/// support the concept of "empty table"; in that case \c isLast() will
+/// return \c true from the beginning.
+class ZoneTableIterator : boost::noncopyable {
+protected:
+ /// \brief The constructor.
+ ///
+ /// This class is not expected to be instantiated directly, so the
+ /// constructor is hidden from normal applications as protected.
+ ZoneTableIterator() {}
+
+public:
+ /// \brief The destructor.
+ virtual ~ZoneTableIterator() {}
+
+ /// \brief Return if the iterator reaches the end of the zone table.
+ virtual bool isLast() const = 0;
+
+ /// \brief Move the iterator to the next zone of the table.
+ ///
+ /// This method must not be called once the iterator reaches the end
+ /// of the table.
+ ///
+ /// \throw InvalidOperation called after reaching the end of table.
+ void next() {
+ // Perform common check, and delegate the actual work to the protected
+ // method.
+ if (isLast()) {
+ isc_throw(InvalidOperation,
+ "next() called on iterator beyond end of zone table");
+ }
+ nextImpl();
+ }
+
+ /// \brief Return the information of the zone at which the iterator is
+ /// currently located in the form of \c ZoneSpec.
+ ///
+ /// This method must not be called once the iterator reaches the end
+ /// of the zone table.
+ ///
+ /// \throw InvalidOperation called after reaching the end of table.
+ ///
+ /// \return Information of the "current" zone.
+ ZoneSpec getCurrent() const {
+ // Perform common check, and delegate the actual work to the protected
+ // method.
+ if (isLast()) {
+ isc_throw(InvalidOperation,
+ "getCurrent() called on iterator beyond "
+ "end of zone table");
+ }
+ return (getCurrentImpl());
+ }
+
+protected:
+ /// \brief Actual implementation of \c next().
+ ///
+ /// Each derived class must provide the implementation of \c next()
+ /// in its data source specific form, except for the common
+ /// validation check.
+ virtual void nextImpl() = 0;
+
+ /// \brief Actual implementation of \c getCurrent().
+ ///
+ /// Each derived class must provide the implementation of
+ /// \c getCurrent() in its data source specific form, except for the
+ /// common validation check.
+ virtual ZoneSpec getCurrentImpl() const = 0;
+};
+
+/// \brief An abstract accessor to conceptual zone table for a data source.
+///
+/// This is an abstract base class providing common interfaces to get access
+/// to a conceptual "zone table" corresponding to a specific data source.
+/// A zone table would contain a set of information about DNS zones stored in
+/// the data source. It's "conceptual" in that the actual form of the
+/// information is specific to the data source implementation.
+///
+/// The initial version of this class only provides one simple feature:
+/// iterating over the table so that an application can get a list of
+/// all zones of a specific data source (of a specific RR class). In
+/// future, this class will be extended so that, e.g., applications can
+/// add or remove zones.
+///
+/// \note It may make sense to move \c DataSourceClient::createZone()
+/// and \c DataSourceClient::deleteZone() to this class.
+class ZoneTableAccessor : boost::noncopyable {
+protected:
+ /// \brief The constructor.
+ ///
+ /// This class is not expected to be instantiated directly, so the
+ /// constructor is hidden from normal applications as protected.
+ ZoneTableAccessor() {}
+
+public:
+ /// \brief Shortcut type for a smart pointer of \c ZoneTableIterator
+ typedef boost::shared_ptr<ZoneTableIterator> IteratorPtr;
+
+ /// \brief The destructor.
+ virtual ~ZoneTableAccessor() {}
+
+ /// \brief Return a zone table iterator.
+ ///
+ /// In general, the specific implementation of the iterator object would
+ /// contain some form of reference to the underlying data source
+ /// (e.g., a database connection or a pointer to memory region), which
+ /// would be valid only until the object that created the instance of
+ /// the accessor is destroyed. The iterator must not be used beyond
+ /// the lifetime of such a creator object, and normally it's expected to
+ /// be even more ephemeral: it would be created by this method in a
+ /// single method or function and only used in that limited scope.
+ ///
+ /// \throw std::bad_alloc Memory allocation for the iterator object failed.
+ /// \throw Others There will be other cases as more implementations
+ /// are added (in this initial version, it's not really decided yet).
+ ///
+ /// \return A smart pointer to a newly created iterator object. Once
+ /// returned, the \c ZoneTableAccessor effectively releases its ownership.
+ virtual IteratorPtr getIterator() const = 0;
+};
+
+typedef boost::shared_ptr<ZoneTableAccessor> ZoneTableAccessorPtr;
+typedef boost::shared_ptr<const ZoneTableAccessor> ConstZoneTableAccessorPtr;
+
+}
+}
+
+#endif // DATASRC_ZONE_TABLE_ACCESSOR_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/zone_table_accessor_cache.cc b/src/lib/datasrc/zone_table_accessor_cache.cc
new file mode 100644
index 0000000..b1d26ac
--- /dev/null
+++ b/src/lib/datasrc/zone_table_accessor_cache.cc
@@ -0,0 +1,60 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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/zone_table_accessor_cache.h>
+#include <datasrc/cache_config.h>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace datasrc {
+namespace internal {
+
+namespace {
+// This is a straightforward wrapper of CacheConfig::ConstZoneIterator to
+// implement ZoneTableIterator interfaces.
+class ZoneTableIteratorCache : public ZoneTableIterator {
+public:
+ ZoneTableIteratorCache(const CacheConfig& config) :
+ it_(config.begin()),
+ it_end_(config.end())
+ {}
+
+ virtual void nextImpl() {
+ ++it_;
+ }
+
+ virtual ZoneSpec getCurrentImpl() const {
+ return (ZoneSpec(0, it_->first)); // index is always 0 in this version.
+ }
+
+ virtual bool isLast() const {
+ return (it_ == it_end_);
+ }
+
+private:
+ CacheConfig::ConstZoneIterator it_;
+ CacheConfig::ConstZoneIterator const it_end_;
+};
+}
+
+ZoneTableAccessor::IteratorPtr
+ZoneTableAccessorCache::getIterator() const {
+ return (ZoneTableAccessor::IteratorPtr(
+ new ZoneTableIteratorCache(config_)));
+}
+
+}
+}
+}
diff --git a/src/lib/datasrc/zone_table_accessor_cache.h b/src/lib/datasrc/zone_table_accessor_cache.h
new file mode 100644
index 0000000..314a9fd
--- /dev/null
+++ b/src/lib/datasrc/zone_table_accessor_cache.h
@@ -0,0 +1,76 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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_ZONE_TABLE_ACCESSOR_CACHE_H
+#define DATASRC_ZONE_TABLE_ACCESSOR_CACHE_H
+
+#include <datasrc/zone_table_accessor.h>
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace datasrc {
+namespace internal {
+class CacheConfig;
+
+/// \brief A \c ZoneTableAccessor implementation for in-memory cache.
+///
+/// This class implements the \c ZoneTableAccessor interface for in-memory
+/// cache. Its conceptual table consists of the zones that are specified
+/// to be loaded into memory in configuration. Note that these zones
+/// may or may not actually be loaded in memory. In fact, this class object
+/// is intended to be used by applications that load these zones into memory,
+/// so that the application can get a list of zones to be loaded. Also, even
+/// after loading, some zone may still not be loaded, e.g., due to an error
+/// in the corresponding zone file.
+///
+/// An object of this class is expected to be returned by
+/// \c ConfigurableClientList. Normal applications shouldn't instantiate
+/// this class directly. It's still defined to be publicly visible for
+/// testing purposes but, to clarify the intent, it's hidden in the
+/// "internal" namespace.
+class ZoneTableAccessorCache : public ZoneTableAccessor {
+public:
+ /// \brief Constructor.
+ ///
+ /// This class takes a \c CacheConfig object and holds it throughout
+ /// its lifetime. The caller must ensure that the configuration is
+ /// valid throughout the lifetime of this accessor object.
+ ///
+ /// \throw None
+ ///
+ /// \param config The cache configuration that the accessor refers to.
+ ZoneTableAccessorCache(const CacheConfig& config) : config_(config) {}
+
+ /// \brief In-memory cache version of \c getIterator().
+ ///
+ /// As returned from this version of iterator, \c ZoneSpec::index
+ /// will always be set to 0 at the moment.
+ ///
+ /// \throw None except std::bad_alloc in case of memory allocation failure
+ virtual IteratorPtr getIterator() const;
+
+private:
+ const CacheConfig& config_;
+};
+
+}
+}
+}
+
+#endif // DATASRC_ZONE_TABLE_ACCESSOR_CACHE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index f169fe6..31ecdee 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -23,9 +23,11 @@ libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
libb10_dhcp___la_SOURCES += iface_mgr_sun.cc
libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
+libb10_dhcp___la_SOURCES += option4_client_fqdn.cc option4_client_fqdn.h
libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
+libb10_dhcp___la_SOURCES += option6_client_fqdn.cc option6_client_fqdn.h
libb10_dhcp___la_SOURCES += option_int.h
libb10_dhcp___la_SOURCES += option_int_array.h
libb10_dhcp___la_SOURCES += option.cc option.h
@@ -33,8 +35,17 @@ libb10_dhcp___la_SOURCES += option_custom.cc option_custom.h
libb10_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
libb10_dhcp___la_SOURCES += option_space.cc option_space.h
+libb10_dhcp___la_SOURCES += option_string.cc option_string.h
+libb10_dhcp___la_SOURCES += protocol_util.cc protocol_util.h
libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
+libb10_dhcp___la_SOURCES += pkt_filter.h
+libb10_dhcp___la_SOURCES += pkt_filter_inet.cc pkt_filter_inet.h
+
+if OS_LINUX
+libb10_dhcp___la_SOURCES += pkt_filter_lpf.cc pkt_filter_lpf.h
+endif
+
libb10_dhcp___la_SOURCES += std_option_defs.h
libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
@@ -46,6 +57,36 @@ libb10_dhcp___la_LDFLAGS = -no-undefined -version-info 2:0:0
EXTRA_DIST = README libdhcp++.dox
+# Specify the headers for copying into the installation directory tree. User-
+# written libraries may need access to all libdhcp++ headers.
+libb10_dhcp___includedir = $(pkgincludedir)/dhcp
+libb10_dhcp___include_HEADERS = \
+ dhcp4.h \
+ dhcp6.h \
+ duid.h \
+ hwaddr.h \
+ iface_mgr.h \
+ libdhcp++.h \
+ option.h \
+ option4_addrlst.h \
+ option6_addrlst.h \
+ option6_ia.h \
+ option6_iaaddr.h \
+ option_custom.h \
+ option_data_types.h \
+ option_definition.h \
+ option_int.h \
+ option_int_array.h \
+ option_space.h \
+ option_string.h \
+ pkt4.h \
+ pkt6.h \
+ pkt_filter.h \
+ pkt_filter_inet.h \
+ pkt_filter_lpf.h \
+ protocol_util.h \
+ std_option_defs.h
+
if USE_CLANGPP
# Disable unused parameter warning caused by some of the
# Boost headers when compiling with clang.
diff --git a/src/lib/dhcp/dhcp6.h b/src/lib/dhcp/dhcp6.h
index 116019e..d5201a9 100644
--- a/src/lib/dhcp/dhcp6.h
+++ b/src/lib/dhcp/dhcp6.h
@@ -114,7 +114,7 @@ extern const int dhcpv6_type_name_max;
// Define hardware types
// Taken from http://www.iana.org/assignments/arp-parameters/
#define HWTYPE_ETHERNET 0x0001
-#define HWTYPE_INIFINIBAND 0x0020
+#define HWTYPE_INFINIBAND 0x0020
// Taken from http://www.iana.org/assignments/enterprise-numbers
#define ENTERPRISE_ID_ISC 2495
@@ -134,7 +134,7 @@ extern const int dhcpv6_type_name_max;
#define LQ_QUERY_OFFSET 17
/*
- * DHCPv6 well-known multicast addressess, from section 5.1 of RFC 3315
+ * DHCPv6 well-known multicast addresses, from section 5.1 of RFC 3315
*/
#define ALL_DHCP_RELAY_AGENTS_AND_SERVERS "ff02::1:2"
#define ALL_DHCP_SERVERS "ff05::1:3"
diff --git a/src/lib/dhcp/duid.cc b/src/lib/dhcp/duid.cc
index f1c8866..8570d26 100644
--- a/src/lib/dhcp/duid.cc
+++ b/src/lib/dhcp/duid.cc
@@ -28,20 +28,25 @@ namespace dhcp {
DUID::DUID(const std::vector<uint8_t>& duid) {
if (duid.size() > MAX_DUID_LEN) {
isc_throw(OutOfRange, "DUID too large");
- } else {
- duid_ = duid;
}
+ if (duid.empty()) {
+ isc_throw(OutOfRange, "Empty DUIDs are not allowed");
+ }
+ duid_ = duid;
}
DUID::DUID(const uint8_t* data, size_t len) {
if (len > MAX_DUID_LEN) {
isc_throw(OutOfRange, "DUID too large");
}
+ if (len == 0) {
+ isc_throw(OutOfRange, "Empty DUIDs/Client-ids not allowed");
+ }
duid_ = std::vector<uint8_t>(data, data + len);
}
-const std::vector<uint8_t> DUID::getDuid() const {
+std::vector<uint8_t> DUID::getDuid() const {
return (duid_);
}
@@ -83,15 +88,23 @@ bool DUID::operator!=(const DUID& other) const {
// Constructor based on vector<uint8_t>
ClientId::ClientId(const std::vector<uint8_t>& clientid)
: DUID(clientid) {
+ if (clientid.size() < MIN_CLIENT_ID_LEN) {
+ isc_throw(OutOfRange, "client-id is too short (" << clientid.size()
+ << "), at least 2 is required");
+ }
}
// Constructor based on C-style data
ClientId::ClientId(const uint8_t *clientid, size_t len)
: DUID(clientid, len) {
+ if (len < MIN_CLIENT_ID_LEN) {
+ isc_throw(OutOfRange, "client-id is too short (" << len
+ << "), at least 2 is required");
+ }
}
// Returns a copy of client-id data
-const std::vector<uint8_t> ClientId::getClientId() const {
+std::vector<uint8_t> ClientId::getClientId() const {
return (duid_);
}
diff --git a/src/lib/dhcp/duid.h b/src/lib/dhcp/duid.h
index 688885b..d20a58d 100644
--- a/src/lib/dhcp/duid.h
+++ b/src/lib/dhcp/duid.h
@@ -35,6 +35,11 @@ class DUID {
/// As defined in RFC3315, section 9.1
static const size_t MAX_DUID_LEN = 128;
+ /// @brief minimum duid size
+ /// There's no explicit minimal DUID size specified in RFC3315,
+ /// so let's use absolute minimum
+ static const size_t MIN_DUID_LEN = 1;
+
/// @brief specifies DUID type
typedef enum {
DUID_UNKNOWN = 0, ///< invalid/unknown type
@@ -58,7 +63,7 @@ class DUID {
/// returned it. In any case, this method should be used only sporadically.
/// If there are frequent uses, we must implement some other method
/// (e.g. storeSelf()) that will avoid data copying.
- const std::vector<uint8_t> getDuid() const;
+ std::vector<uint8_t> getDuid() const;
/// @brief Returns the DUID type
DUIDType getType() const;
@@ -88,6 +93,13 @@ typedef boost::shared_ptr<DUID> DuidPtr;
/// a client-id
class ClientId : public DUID {
public:
+
+ /// @brief Minimum size of a client ID
+ ///
+ /// Excerpt from RFC2132, section 9.14.
+ /// The code for this option is 61, and its minimum length is 2.
+ static const size_t MIN_CLIENT_ID_LEN = 2;
+
/// @brief Maximum size of a client ID
///
/// This is the same as the maximum size of the underlying DUID.
@@ -105,7 +117,7 @@ public:
ClientId(const uint8_t* clientid, size_t len);
/// @brief Returns reference to the client-id data
- const std::vector<uint8_t> getClientId() const;
+ std::vector<uint8_t> getClientId() const;
/// @brief Returns textual representation of a DUID (e.g. 00:01:02:03:ff)
std::string toText() const;
diff --git a/src/lib/dhcp/hwaddr.cc b/src/lib/dhcp/hwaddr.cc
index d19f2ad..eb23b44 100644
--- a/src/lib/dhcp/hwaddr.cc
+++ b/src/lib/dhcp/hwaddr.cc
@@ -14,9 +14,11 @@
#include <dhcp/hwaddr.h>
#include <dhcp/dhcp4.h>
+#include <exceptions/exceptions.h>
#include <iomanip>
#include <sstream>
#include <vector>
+#include <string.h>
namespace isc {
namespace dhcp {
@@ -27,10 +29,17 @@ HWAddr::HWAddr()
HWAddr::HWAddr(const uint8_t* hwaddr, size_t len, uint8_t htype)
:hwaddr_(hwaddr, hwaddr + len), htype_(htype) {
+ if (len > MAX_HWADDR_LEN) {
+ isc_throw(InvalidParameter, "hwaddr length exceeds MAX_HWADDR_LEN");
+ }
}
HWAddr::HWAddr(const std::vector<uint8_t>& hwaddr, uint8_t htype)
:hwaddr_(hwaddr), htype_(htype) {
+ if (hwaddr.size() > MAX_HWADDR_LEN) {
+ isc_throw(InvalidParameter,
+ "address vector size exceeds MAX_HWADDR_LEN");
+ }
}
std::string HWAddr::toText() const {
@@ -50,7 +59,7 @@ std::string HWAddr::toText() const {
}
bool HWAddr::operator==(const HWAddr& other) const {
- return ((this->htype_ == other.htype_) &&
+ return ((this->htype_ == other.htype_) &&
(this->hwaddr_ == other.hwaddr_));
}
diff --git a/src/lib/dhcp/hwaddr.h b/src/lib/dhcp/hwaddr.h
index 93b06a1..848ad23 100644
--- a/src/lib/dhcp/hwaddr.h
+++ b/src/lib/dhcp/hwaddr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -28,6 +28,12 @@ namespace dhcp {
struct HWAddr {
public:
+ /// @brief Size of an ethernet hardware address.
+ static const size_t ETHERNET_HWADDR_LEN = 6;
+
+ /// @brief Maximum size of a hardware address.
+ static const size_t MAX_HWADDR_LEN = 20;
+
/// @brief default constructor
HWAddr();
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
index a4f4659..128aafe 100644
--- a/src/lib/dhcp/iface_mgr.cc
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -22,6 +22,7 @@
#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter_inet.h>
#include <exceptions/exceptions.h>
#include <util/io/pktinfo_utilities.h>
@@ -47,32 +48,66 @@ IfaceMgr::instance() {
return (iface_mgr);
}
-IfaceMgr::Iface::Iface(const std::string& name, int ifindex)
+Iface::Iface(const std::string& name, int ifindex)
:name_(name), ifindex_(ifindex), mac_len_(0), hardware_type_(0),
flag_loopback_(false), flag_up_(false), flag_running_(false),
- flag_multicast_(false), flag_broadcast_(false), flags_(0)
+ flag_multicast_(false), flag_broadcast_(false), flags_(0),
+ inactive4_(false), inactive6_(false)
{
memset(mac_, 0, sizeof(mac_));
}
void
-IfaceMgr::Iface::closeSockets() {
- for (SocketCollection::iterator sock = sockets_.begin();
- sock != sockets_.end(); ++sock) {
- close(sock->sockfd_);
+Iface::closeSockets() {
+ // Close IPv4 sockets.
+ closeSockets(AF_INET);
+ // Close IPv6 sockets.
+ closeSockets(AF_INET6);
+}
+
+void
+Iface::closeSockets(const uint16_t family) {
+ // Check that the correect 'family' value has been specified.
+ // The possible values are AF_INET or AF_INET6. Note that, in
+ // the current code they are used to differentiate that the
+ // socket is used to transmit IPv4 or IPv6 traffic. However,
+ // the actual family types of the sockets may be different,
+ // e.g. for LPF we are using raw sockets of AF_PACKET family.
+ //
+ // @todo Consider replacing the AF_INET and AF_INET6 with some
+ // enum which will not be confused with the actual socket type.
+ if ((family != AF_INET) && (family != AF_INET6)) {
+ isc_throw(BadValue, "Invalid socket family " << family
+ << " specified when requested to close all sockets"
+ << " which belong to this family");
+ }
+ // Search for the socket of the specific type.
+ SocketCollection::iterator sock = sockets_.begin();
+ while (sock != sockets_.end()) {
+ if (sock->family_ == family) {
+ // Close and delete the socket and move to the
+ // next one.
+ close(sock->sockfd_);
+ sockets_.erase(sock++);
+
+ } else {
+ // Different type of socket. Let's move
+ // to the next one.
+ ++sock;
+
+ }
}
- sockets_.clear();
}
std::string
-IfaceMgr::Iface::getFullName() const {
+Iface::getFullName() const {
ostringstream tmp;
tmp << name_ << "/" << ifindex_;
return (tmp.str());
}
std::string
-IfaceMgr::Iface::getPlainMac() const {
+Iface::getPlainMac() const {
ostringstream tmp;
tmp.fill('0');
tmp << hex;
@@ -86,18 +121,18 @@ IfaceMgr::Iface::getPlainMac() const {
return (tmp.str());
}
-void IfaceMgr::Iface::setMac(const uint8_t* mac, size_t len) {
- if (len > IfaceMgr::MAX_MAC_LEN) {
+void Iface::setMac(const uint8_t* mac, size_t len) {
+ if (len > MAX_MAC_LEN) {
isc_throw(OutOfRange, "Interface " << getFullName()
<< " was detected to have link address of length "
<< len << ", but maximum supported length is "
- << IfaceMgr::MAX_MAC_LEN);
+ << MAX_MAC_LEN);
}
mac_len_ = len;
memcpy(mac_, mac, len);
}
-bool IfaceMgr::Iface::delAddress(const isc::asiolink::IOAddress& addr) {
+bool Iface::delAddress(const isc::asiolink::IOAddress& addr) {
for (AddressCollection::iterator a = addrs_.begin();
a!=addrs_.end(); ++a) {
if (*a==addr) {
@@ -108,7 +143,7 @@ bool IfaceMgr::Iface::delAddress(const isc::asiolink::IOAddress& addr) {
return (false);
}
-bool IfaceMgr::Iface::delSocket(uint16_t sockfd) {
+bool Iface::delSocket(uint16_t sockfd) {
list<SocketInfo>::iterator sock = sockets_.begin();
while (sock!=sockets_.end()) {
if (sock->sockfd_ == sockfd) {
@@ -124,7 +159,8 @@ bool IfaceMgr::Iface::delSocket(uint16_t sockfd) {
IfaceMgr::IfaceMgr()
:control_buf_len_(CMSG_SPACE(sizeof(struct in6_pktinfo))),
control_buf_(new char[control_buf_len_]),
- session_socket_(INVALID_SOCKET), session_callback_(NULL)
+ session_socket_(INVALID_SOCKET), session_callback_(NULL),
+ packet_filter_(new PktFilterInet())
{
try {
@@ -148,6 +184,14 @@ void IfaceMgr::closeSockets() {
}
}
+void
+IfaceMgr::closeSockets(const uint16_t family) {
+ for (IfaceCollection::iterator iface = ifaces_.begin();
+ iface != ifaces_.end(); ++iface) {
+ iface->closeSockets(family);
+ }
+}
+
IfaceMgr::~IfaceMgr() {
// control_buf_ is deleted automatically (scoped_ptr)
control_buf_len_ = 0;
@@ -155,6 +199,42 @@ IfaceMgr::~IfaceMgr() {
closeSockets();
}
+bool
+IfaceMgr::isDirectResponseSupported() const {
+ return (packet_filter_->isDirectResponseSupported());
+}
+
+void
+IfaceMgr::setPacketFilter(const boost::shared_ptr<PktFilter>& packet_filter) {
+ // Do not allow NULL pointer.
+ if (!packet_filter) {
+ isc_throw(InvalidPacketFilter, "NULL packet filter object specified");
+ }
+ // Different packet filters use different socket types. It does not make
+ // sense to allow the change of packet filter when there are IPv4 sockets
+ // open because they can't be used by the receive/send functions of the
+ // new packet filter. Below, we check that there are no open IPv4 sockets.
+ // If we find at least one, we have to fail. However, caller still has a
+ // chance to replace the packet filter if he closes sockets explicitly.
+ for (IfaceCollection::const_iterator iface = ifaces_.begin();
+ iface != ifaces_.end(); ++iface) {
+ const Iface::SocketCollection& sockets = iface->getSockets();
+ for (Iface::SocketCollection::const_iterator sock = sockets.begin();
+ sock != sockets.end(); ++sock) {
+ if (sock->family_ == AF_INET) {
+ // There is at least one socket open, so we have to fail.
+ isc_throw(PacketFilterChangeDenied,
+ "it is not allowed to set new packet"
+ << " filter when there are open IPv4 sockets - need"
+ << " to close them first");
+ }
+ }
+ }
+ // Everything is fine, so replace packet filter.
+ packet_filter_ = packet_filter;
+}
+
+
void IfaceMgr::stubDetectIfaces() {
string ifaceName;
const string v4addr("127.0.0.1"), v6addr("::1");
@@ -193,22 +273,36 @@ void IfaceMgr::stubDetectIfaces() {
addInterface(iface);
}
-bool IfaceMgr::openSockets4(const uint16_t port) {
+bool IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast) {
int sock;
int count = 0;
+// This option is used to bind sockets to particular interfaces.
+// This is currently the only way to discover on which interface
+// the broadcast packet has been received. If this option is
+// not supported then only one interface should be confugured
+// to listen for broadcast traffic.
+#ifdef SO_BINDTODEVICE
+ const bool bind_to_device = true;
+#else
+ const bool bind_to_device = false;
+#endif
+
+ int bcast_num = 0;
+
for (IfaceCollection::iterator iface = ifaces_.begin();
iface != ifaces_.end();
++iface) {
if (iface->flag_loopback_ ||
!iface->flag_up_ ||
- !iface->flag_running_) {
+ !iface->flag_running_ ||
+ iface->inactive4_) {
continue;
}
- AddressCollection addrs = iface->getAddresses();
- for (AddressCollection::iterator addr = addrs.begin();
+ Iface::AddressCollection addrs = iface->getAddresses();
+ for (Iface::AddressCollection::iterator addr = addrs.begin();
addr != addrs.end();
++addr) {
@@ -217,9 +311,40 @@ bool IfaceMgr::openSockets4(const uint16_t port) {
continue;
}
- sock = openSocket(iface->getName(), *addr, port);
+ // If selected interface is broadcast capable set appropriate
+ // options on the socket so as it can receive and send broadcast
+ // messages.
+ if (iface->flag_broadcast_ && use_bcast) {
+ // If our OS supports binding socket to a device we can listen
+ // for broadcast messages on multiple interfaces. Otherwise we
+ // bind to INADDR_ANY address but we can do it only once. Thus,
+ // if one socket has been bound we can't do it any further.
+ if (!bind_to_device && bcast_num > 0) {
+ isc_throw(SocketConfigError, "SO_BINDTODEVICE socket option is"
+ << " not supported on this OS; therefore, DHCP"
+ << " server can only listen broadcast traffic on"
+ << " a single interface");
+
+ } else {
+ // We haven't open any broadcast sockets yet, so we can
+ // open at least one more.
+ sock = openSocket(iface->getName(), *addr, port, true, true);
+ // Binding socket to an interface is not supported so we can't
+ // open any more broadcast sockets. Increase the number of
+ // opened broadcast sockets.
+ if (!bind_to_device) {
+ ++bcast_num;
+ }
+ }
+
+ } else {
+ // Not broadcast capable, do not set broadcast flags.
+ sock = openSocket(iface->getName(), *addr, port, false, false);
+
+ }
if (sock < 0) {
- isc_throw(SocketConfigError, "failed to open unicast socket");
+ isc_throw(SocketConfigError, "failed to open IPv4 socket"
+ << " supporting broadcast traffic");
}
count++;
@@ -238,12 +363,13 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
if (iface->flag_loopback_ ||
!iface->flag_up_ ||
- !iface->flag_running_) {
+ !iface->flag_running_ ||
+ iface->inactive6_) {
continue;
}
- AddressCollection addrs = iface->getAddresses();
- for (AddressCollection::iterator addr = addrs.begin();
+ Iface::AddressCollection addrs = iface->getAddresses();
+ for (Iface::AddressCollection::iterator addr = addrs.begin();
addr != addrs.end();
++addr) {
@@ -304,7 +430,7 @@ IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
iface!=ifaces_.end();
++iface) {
- const AddressCollection& addrs = iface->getAddresses();
+ const Iface::AddressCollection& addrs = iface->getAddresses();
out << "Detected interface " << iface->getFullName()
<< ", hwtype=" << iface->getHWType()
@@ -318,7 +444,7 @@ IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
<< ")" << endl;
out << " " << addrs.size() << " addr(s):";
- for (AddressCollection::const_iterator addr = addrs.begin();
+ for (Iface::AddressCollection::const_iterator addr = addrs.begin();
addr != addrs.end(); ++addr) {
out << " " << addr->toText();
}
@@ -326,7 +452,7 @@ IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
}
}
-IfaceMgr::Iface*
+Iface*
IfaceMgr::getIface(int ifindex) {
for (IfaceCollection::iterator iface=ifaces_.begin();
iface!=ifaces_.end();
@@ -338,7 +464,7 @@ IfaceMgr::getIface(int ifindex) {
return (NULL); // not found
}
-IfaceMgr::Iface*
+Iface*
IfaceMgr::getIface(const std::string& ifname) {
for (IfaceCollection::iterator iface=ifaces_.begin();
iface!=ifaces_.end();
@@ -351,13 +477,14 @@ IfaceMgr::getIface(const std::string& ifname) {
}
int IfaceMgr::openSocket(const std::string& ifname, const IOAddress& addr,
- const uint16_t port) {
+ const uint16_t port, const bool receive_bcast,
+ const bool send_bcast) {
Iface* iface = getIface(ifname);
if (!iface) {
isc_throw(BadValue, "There is no " << ifname << " interface present.");
}
if (addr.isV4()) {
- return openSocket4(*iface, addr, port);
+ return openSocket4(*iface, addr, port, receive_bcast, send_bcast);
} else if (addr.isV6()) {
return openSocket6(*iface, addr, port);
@@ -383,8 +510,8 @@ int IfaceMgr::openSocketFromIface(const std::string& ifname,
// Interface is now detected. Search for address on interface
// that matches address family (v6 or v4).
- AddressCollection addrs = iface->getAddresses();
- AddressCollection::iterator addr_it = addrs.begin();
+ Iface::AddressCollection addrs = iface->getAddresses();
+ Iface::AddressCollection::iterator addr_it = addrs.begin();
while (addr_it != addrs.end()) {
if (addr_it->getFamily() == family) {
// We have interface and address so let's open socket.
@@ -420,9 +547,9 @@ int IfaceMgr::openSocketFromAddress(const IOAddress& addr,
iface != ifaces_.end();
++iface) {
- AddressCollection addrs = iface->getAddresses();
+ Iface::AddressCollection addrs = iface->getAddresses();
- for (AddressCollection::iterator addr_it = addrs.begin();
+ for (Iface::AddressCollection::iterator addr_it = addrs.begin();
addr_it != addrs.end();
++addr_it) {
@@ -509,43 +636,6 @@ IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) {
return IOAddress(local_address);
}
-int IfaceMgr::openSocket4(Iface& iface, const IOAddress& addr, uint16_t port) {
-
- struct sockaddr_in addr4;
- memset(&addr4, 0, sizeof(sockaddr));
- addr4.sin_family = AF_INET;
- addr4.sin_port = htons(port);
-
- addr4.sin_addr.s_addr = htonl(addr);
- //addr4.sin_addr.s_addr = 0; // anyaddr: this will receive 0.0.0.0 => 255.255.255.255 traffic
- // addr4.sin_addr.s_addr = 0xffffffffu; // broadcast address. This will receive 0.0.0.0 => 255.255.255.255 as well
-
- int sock = socket(AF_INET, SOCK_DGRAM, 0);
- if (sock < 0) {
- isc_throw(SocketConfigError, "Failed to create UDP6 socket.");
- }
-
- if (bind(sock, (struct sockaddr *)&addr4, sizeof(addr4)) < 0) {
- close(sock);
- isc_throw(SocketConfigError, "Failed to bind socket " << sock << " to " << addr.toText()
- << "/port=" << port);
- }
-
- // if there is no support for IP_PKTINFO, we are really out of luck
- // it will be difficult to undersand, where this packet came from
-#if defined(IP_PKTINFO)
- int flag = 1;
- if (setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &flag, sizeof(flag)) != 0) {
- close(sock);
- isc_throw(SocketConfigError, "setsockopt: IP_PKTINFO: failed.");
- }
-#endif
-
- SocketInfo info(sock, addr, port);
- iface.addSocket(info);
-
- return (sock);
-}
int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port) {
@@ -620,6 +710,22 @@ int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port) {
return (sock);
}
+int IfaceMgr::openSocket4(Iface& iface, const IOAddress& addr,
+ const uint16_t port, const bool receive_bcast,
+ const bool send_bcast) {
+
+ // Skip checking if the packet_filter_ is non-NULL because this check
+ // has been already done when packet filter object was set.
+
+ int sock = packet_filter_->openSocket(iface, addr, port,
+ receive_bcast, send_bcast);
+
+ SocketInfo info(sock, addr, port);
+ iface.addSocket(info);
+
+ return (sock);
+}
+
bool
IfaceMgr::joinMulticast(int sock, const std::string& ifname,
const std::string & mcast) {
@@ -722,53 +828,17 @@ IfaceMgr::send(const Pkt6Ptr& pkt) {
}
bool
-IfaceMgr::send(const Pkt4Ptr& pkt)
-{
+IfaceMgr::send(const Pkt4Ptr& pkt) {
+
Iface* iface = getIface(pkt->getIface());
if (!iface) {
isc_throw(BadValue, "Unable to send Pkt4. Invalid interface ("
<< pkt->getIface() << ") specified.");
}
- memset(&control_buf_[0], 0, control_buf_len_);
-
-
- // Set the target address we're sending to.
- sockaddr_in to;
- memset(&to, 0, sizeof(to));
- to.sin_family = AF_INET;
- to.sin_port = htons(pkt->getRemotePort());
- to.sin_addr.s_addr = htonl(pkt->getRemoteAddr());
-
- struct msghdr m;
- // Initialize our message header structure.
- memset(&m, 0, sizeof(m));
- m.msg_name = &to;
- m.msg_namelen = sizeof(to);
-
- // Set the data buffer we're sending. (Using this wacky
- // "scatter-gather" stuff... we only have a single chunk
- // of data to send, so we declare a single vector entry.)
- struct iovec v;
- memset(&v, 0, sizeof(v));
- // iov_base field is of void * type. We use it for packet
- // transmission, so this buffer will not be modified.
- v.iov_base = const_cast<void *>(pkt->getBuffer().getData());
- v.iov_len = pkt->getBuffer().getLength();
- m.msg_iov = &v;
- m.msg_iovlen = 1;
-
- // call OS-specific routines (like setting interface index)
- os_send4(m, control_buf_, control_buf_len_, pkt);
-
- pkt->updateTimestamp();
-
- int result = sendmsg(getSocket(*pkt), &m, 0);
- if (result < 0) {
- isc_throw(SocketWriteError, "pkt4 send failed");
- }
-
- return (result);
+ // Skip checking if packet filter is non-NULL because it has been
+ // already checked when packet filter was set.
+ return (packet_filter_->send(*iface, getSocket(*pkt), pkt));
}
@@ -792,8 +862,8 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
/// provided set to indicated which sockets have something to read.
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
- const SocketCollection& socket_collection = iface->getSockets();
- for (SocketCollection::const_iterator s = socket_collection.begin();
+ const Iface::SocketCollection& socket_collection = iface->getSockets();
+ for (Iface::SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
// Only deal with IPv4 addresses.
@@ -848,8 +918,8 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
// Let's find out which interface/socket has the data
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
- const SocketCollection& socket_collection = iface->getSockets();
- for (SocketCollection::const_iterator s = socket_collection.begin();
+ const Iface::SocketCollection& socket_collection = iface->getSockets();
+ for (Iface::SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
if (FD_ISSET(s->sockfd_, &sockets)) {
candidate = &(*s);
@@ -866,64 +936,9 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
}
// Now we have a socket, let's get some data from it!
- struct sockaddr_in from_addr;
- uint8_t buf[RCVBUFSIZE];
-
- memset(&control_buf_[0], 0, control_buf_len_);
- memset(&from_addr, 0, sizeof(from_addr));
-
- // Initialize our message header structure.
- struct msghdr m;
- memset(&m, 0, sizeof(m));
-
- // Point so we can get the from address.
- m.msg_name = &from_addr;
- m.msg_namelen = sizeof(from_addr);
-
- struct iovec v;
- v.iov_base = static_cast<void*>(buf);
- v.iov_len = RCVBUFSIZE;
- m.msg_iov = &v;
- m.msg_iovlen = 1;
-
- // Getting the interface is a bit more involved.
- //
- // We set up some space for a "control message". We have
- // previously asked the kernel to give us packet
- // information (when we initialized the interface), so we
- // should get the destination address from that.
- m.msg_control = &control_buf_[0];
- m.msg_controllen = control_buf_len_;
-
- result = recvmsg(candidate->sockfd_, &m, 0);
- if (result < 0) {
- isc_throw(SocketReadError, "failed to receive UDP4 data");
- }
-
- // We have all data let's create Pkt4 object.
- Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(buf, result));
-
- pkt->updateTimestamp();
-
- unsigned int ifindex = iface->getIndex();
-
- IOAddress from(htonl(from_addr.sin_addr.s_addr));
- uint16_t from_port = htons(from_addr.sin_port);
-
- // Set receiving interface based on information, which socket was used to
- // receive data. OS-specific info (see os_receive4()) may be more reliable,
- // so this value may be overwritten.
- pkt->setIndex(ifindex);
- pkt->setIface(iface->getName());
- pkt->setRemoteAddr(from);
- pkt->setRemotePort(from_port);
- pkt->setLocalPort(candidate->port_);
-
- if (!os_receive4(m, pkt)) {
- isc_throw(SocketReadError, "unable to find pktinfo");
- }
-
- return (pkt);
+ // Skip checking if packet filter is non-NULL because it has been
+ // already checked when packet filter was set.
+ return (packet_filter_->receive(*iface, *candidate));
}
Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */ ) {
@@ -945,8 +960,8 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
/// provided set to indicated which sockets have something to read.
IfaceCollection::const_iterator iface;
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
- const SocketCollection& socket_collection = iface->getSockets();
- for (SocketCollection::const_iterator s = socket_collection.begin();
+ const Iface::SocketCollection& socket_collection = iface->getSockets();
+ for (Iface::SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
// Only deal with IPv6 addresses.
@@ -1001,8 +1016,8 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
// Let's find out which interface/socket has the data
for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
- const SocketCollection& socket_collection = iface->getSockets();
- for (SocketCollection::const_iterator s = socket_collection.begin();
+ const Iface::SocketCollection& socket_collection = iface->getSockets();
+ for (Iface::SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
if (FD_ISSET(s->sockfd_, &sockets)) {
candidate = &(*s);
@@ -1122,8 +1137,8 @@ uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
<< pkt.getIface());
}
- const SocketCollection& socket_collection = iface->getSockets();
- SocketCollection::const_iterator s;
+ const Iface::SocketCollection& socket_collection = iface->getSockets();
+ Iface::SocketCollection::const_iterator s;
for (s = socket_collection.begin(); s != socket_collection.end(); ++s) {
if ((s->family_ == AF_INET6) &&
(!s->addr_.getAddress().to_v6().is_multicast())) {
@@ -1145,8 +1160,8 @@ uint16_t IfaceMgr::getSocket(isc::dhcp::Pkt4 const& pkt) {
<< pkt.getIface());
}
- const SocketCollection& socket_collection = iface->getSockets();
- SocketCollection::const_iterator s;
+ const Iface::SocketCollection& socket_collection = iface->getSockets();
+ Iface::SocketCollection::const_iterator s;
for (s = socket_collection.begin(); s != socket_collection.end(); ++s) {
if (s->family_ == AF_INET) {
return (s->sockfd_);
diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h
index a669a6d..217524d 100644
--- a/src/lib/dhcp/iface_mgr.h
+++ b/src/lib/dhcp/iface_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// 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,6 +20,7 @@
#include <dhcp/dhcp6.h>
#include <dhcp/pkt4.h>
#include <dhcp/pkt6.h>
+#include <dhcp/pkt_filter.h>
#include <boost/noncopyable.hpp>
#include <boost/scoped_array.hpp>
@@ -38,6 +39,13 @@ public:
isc::Exception(file, line, what) { };
};
+/// @brief Exception thrown when it is not allowed to set new Packet Filter.
+class PacketFilterChangeDenied : public Exception {
+public:
+ PacketFilterChangeDenied(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
/// @brief IfaceMgr exception thrown thrown when socket opening
/// or configuration failed.
class SocketConfigError : public Exception {
@@ -62,7 +70,256 @@ public:
isc::Exception(file, line, what) { };
};
-/// @brief handles network interfaces, transmission and reception
+/// Holds information about socket.
+struct SocketInfo {
+ uint16_t sockfd_; /// socket descriptor
+ isc::asiolink::IOAddress addr_; /// bound address
+ uint16_t port_; /// socket port
+ uint16_t family_; /// IPv4 or IPv6
+
+ /// @brief SocketInfo constructor.
+ ///
+ /// @param sockfd socket descriptor
+ /// @param addr an address the socket is bound to
+ /// @param port a port the socket is bound to
+ SocketInfo(uint16_t sockfd, const isc::asiolink::IOAddress& addr,
+ uint16_t port)
+ :sockfd_(sockfd), addr_(addr), port_(port), family_(addr.getFamily()) { }
+};
+
+
+/// @brief Represents a single network interface
+///
+/// Iface structure represents network interface with all useful
+/// information, like name, interface index, MAC address and
+/// list of assigned addresses
+class Iface {
+public:
+
+ /// Maximum MAC address length (Infiniband uses 20 bytes)
+ static const unsigned int MAX_MAC_LEN = 20;
+
+ /// Type that defines list of addresses
+ typedef std::vector<isc::asiolink::IOAddress> AddressCollection;
+
+ /// @brief Type that holds a list of socket information.
+ ///
+ /// @warning The type of the container used here must guarantee
+ /// that the iterators do not invalidate when erase() is called.
+ /// This is because, the \ref closeSockets function removes
+ /// elements selectively by calling erase on the element to be
+ /// removed and further iterates through remaining elements.
+ ///
+ /// @todo: Add SocketCollectionConstIter type
+ typedef std::list<SocketInfo> SocketCollection;
+
+ /// @brief Iface constructor.
+ ///
+ /// Creates Iface object that represents network interface.
+ ///
+ /// @param name name of the interface
+ /// @param ifindex interface index (unique integer identifier)
+ Iface(const std::string& name, int ifindex);
+
+ /// @brief Closes all open sockets on interface.
+ void closeSockets();
+
+ /// @brief Closes all IPv4 or IPv6 sockets.
+ ///
+ /// This function closes sockets of the specific 'type' and closes them.
+ /// The 'type' of the socket indicates whether it is used to send IPv4
+ /// or IPv6 packets. The allowed values of the parameter are AF_INET and
+ /// AF_INET6 for IPv4 and IPv6 packets respectively. It is important
+ /// to realize that the actual types of sockets may be different than
+ /// AF_INET for IPv4 packets. This is because, historically the IfaceMgr
+ /// always used AF_INET sockets for IPv4 traffic. This is no longer the
+ /// case when the Direct IPv4 traffic must be supported. In order to support
+ /// direct traffic, the IfaceMgr operates on raw sockets, e.g. AF_PACKET
+ /// family sockets on Linux.
+ ///
+ /// @todo Replace the AF_INET and AF_INET6 values with an enum
+ /// which will not be confused with the actual socket type.
+ ///
+ /// @param family type of the sockets to be closed (AF_INET or AF_INET6)
+ ///
+ /// @throw BadValue if family value is different than AF_INET or AF_INET6.
+ void closeSockets(const uint16_t family);
+
+ /// @brief Returns full interface name as "ifname/ifindex" string.
+ ///
+ /// @return string with interface name
+ std::string getFullName() const;
+
+ /// @brief Returns link-layer address a plain text.
+ ///
+ /// @return MAC address as a plain text (string)
+ std::string getPlainMac() const;
+
+ /// @brief Sets MAC address of the interface.
+ ///
+ /// @param mac pointer to MAC address buffer
+ /// @param macLen length of mac address
+ void setMac(const uint8_t* mac, size_t macLen);
+
+ /// @brief Returns MAC length.
+ ///
+ /// @return length of MAC address
+ size_t getMacLen() const { return mac_len_; }
+
+ /// @brief Returns pointer to MAC address.
+ ///
+ /// Note: Returned pointer is only valid as long as the interface object
+ /// that returned it.
+ const uint8_t* getMac() const { return mac_; }
+
+ /// @brief Sets flag_*_ fields based on bitmask value returned by OS
+ ///
+ /// @note Implementation of this method is OS-dependent as bits have
+ /// different meaning on each OS.
+ ///
+ /// @param flags bitmask value returned by OS in interface detection
+ void setFlags(uint32_t flags);
+
+ /// @brief Returns interface index.
+ ///
+ /// @return interface index
+ uint16_t getIndex() const { return ifindex_; }
+
+ /// @brief Returns interface name.
+ ///
+ /// @return interface name
+ std::string getName() const { return name_; };
+
+ /// @brief Sets up hardware type of the interface.
+ ///
+ /// @param type hardware type
+ void setHWType(uint16_t type ) { hardware_type_ = type; }
+
+ /// @brief Returns hardware type of the interface.
+ ///
+ /// @return hardware type
+ uint16_t getHWType() const { return hardware_type_; }
+
+ /// @brief Returns all interfaces available on an interface.
+ ///
+ /// Care should be taken to not use this collection after Iface object
+ /// ceases to exist. That is easy in most cases as Iface objects are
+ /// created by IfaceMgr that is a singleton an is expected to be
+ /// available at all time. We may revisit this if we ever decide to
+ /// implement dynamic interface detection, but such fancy feature would
+ /// mostly be useful for clients with wifi/vpn/virtual interfaces.
+ ///
+ /// @return collection of addresses
+ const AddressCollection& getAddresses() const { return addrs_; }
+
+ /// @brief Adds an address to an interface.
+ ///
+ /// This only adds an address to collection, it does not physically
+ /// configure address on actual network interface.
+ ///
+ /// @param addr address to be added
+ void addAddress(const isc::asiolink::IOAddress& addr) {
+ addrs_.push_back(addr);
+ }
+
+ /// @brief Deletes an address from an interface.
+ ///
+ /// This only deletes address from collection, it does not physically
+ /// remove address configuration from actual network interface.
+ ///
+ /// @param addr address to be removed.
+ ///
+ /// @return true if removal was successful (address was in collection),
+ /// false otherwise
+ bool delAddress(const isc::asiolink::IOAddress& addr);
+
+ /// @brief Adds socket descriptor to an interface.
+ ///
+ /// @param sock SocketInfo structure that describes socket.
+ void addSocket(const SocketInfo& sock) {
+ sockets_.push_back(sock);
+ }
+
+ /// @brief Closes socket.
+ ///
+ /// Closes socket and removes corresponding SocketInfo structure
+ /// from an interface.
+ ///
+ /// @param sockfd socket descriptor to be closed/removed.
+ /// @return true if there was such socket, false otherwise
+ bool delSocket(uint16_t sockfd);
+
+ /// @brief Returns collection of all sockets added to interface.
+ ///
+ /// When new socket is created with @ref IfaceMgr::openSocket
+ /// it is added to sockets collection on particular interface.
+ /// If socket is opened by other means (e.g. function that does
+ /// not use @ref IfaceMgr::openSocket) it will not be available
+ /// in this collection. Note that functions like
+ /// @ref IfaceMgr::openSocketFromIface use
+ /// @ref IfaceMgr::openSocket internally.
+ /// The returned reference is only valid during the lifetime of
+ /// the IfaceMgr object that returned it.
+ ///
+ /// @return collection of sockets added to interface
+ const SocketCollection& getSockets() const { return sockets_; }
+
+protected:
+ /// Socket used to send data.
+ SocketCollection sockets_;
+
+ /// Network interface name.
+ std::string name_;
+
+ /// Interface index (a value that uniquely indentifies an interface).
+ int ifindex_;
+
+ /// List of assigned addresses.
+ AddressCollection addrs_;
+
+ /// Link-layer address.
+ uint8_t mac_[MAX_MAC_LEN];
+
+ /// Length of link-layer address (usually 6).
+ size_t mac_len_;
+
+ /// Hardware type.
+ uint16_t hardware_type_;
+
+public:
+ /// @todo: Make those fields protected once we start supporting more
+ /// than just Linux
+
+ /// Specifies if selected interface is loopback.
+ bool flag_loopback_;
+
+ /// Specifies if selected interface is up.
+ bool flag_up_;
+
+ /// Flag specifies if selected interface is running
+ /// (e.g. cable plugged in, wifi associated).
+ bool flag_running_;
+
+ /// Flag specifies if selected interface is multicast capable.
+ bool flag_multicast_;
+
+ /// Flag specifies if selected interface is broadcast capable.
+ bool flag_broadcast_;
+
+ /// Interface flags (this value is as is returned by OS,
+ /// it may mean different things on different OSes).
+ uint32_t flags_;
+
+ /// Indicates that IPv4 sockets should (true) or should not (false)
+ /// be opened on this interface.
+ bool inactive4_;
+
+ /// Indicates that IPv6 sockets should (true) or should not (false)
+ /// be opened on this interface.
+ bool inactive6_;
+};
+
+/// @brief Handles network interfaces, transmission and reception.
///
/// IfaceMgr is an interface manager class that detects available network
/// interfaces, configured addresses, link-local addresses, and provides
@@ -70,15 +327,9 @@ public:
///
class IfaceMgr : public boost::noncopyable {
public:
- /// type that defines list of addresses
- typedef std::vector<isc::asiolink::IOAddress> AddressCollection;
-
- /// defines callback used when commands are received over control session
+ /// Defines callback used when commands are received over control session.
typedef void (*SessionCallback) (void);
- /// maximum MAC address length (Infiniband uses 20 bytes)
- static const unsigned int MAX_MAC_LEN = 20;
-
/// @brief Packet reception buffer size
///
/// RFC3315 states that server responses may be
@@ -88,216 +339,11 @@ public:
/// we don't support packets larger than 1500.
static const uint32_t RCVBUFSIZE = 1500;
- /// Holds information about socket.
- struct SocketInfo {
- uint16_t sockfd_; /// socket descriptor
- isc::asiolink::IOAddress addr_; /// bound address
- uint16_t port_; /// socket port
- uint16_t family_; /// IPv4 or IPv6
-
- /// @brief SocketInfo constructor.
- ///
- /// @param sockfd socket descriptor
- /// @param addr an address the socket is bound to
- /// @param port a port the socket is bound to
- SocketInfo(uint16_t sockfd, const isc::asiolink::IOAddress& addr,
- uint16_t port)
- :sockfd_(sockfd), addr_(addr), port_(port), family_(addr.getFamily()) { }
- };
-
- /// type that holds a list of socket informations
- /// @todo: Add SocketCollectionConstIter type
- typedef std::list<SocketInfo> SocketCollection;
-
-
- /// @brief represents a single network interface
- ///
- /// Iface structure represents network interface with all useful
- /// information, like name, interface index, MAC address and
- /// list of assigned addresses
- class Iface {
- public:
- /// @brief Iface constructor.
- ///
- /// Creates Iface object that represents network interface.
- ///
- /// @param name name of the interface
- /// @param ifindex interface index (unique integer identifier)
- Iface(const std::string& name, int ifindex);
-
- /// @brief Closes all open sockets on interface.
- void closeSockets();
-
- /// @brief Returns full interface name as "ifname/ifindex" string.
- ///
- /// @return string with interface name
- std::string getFullName() const;
-
- /// @brief Returns link-layer address a plain text.
- ///
- /// @return MAC address as a plain text (string)
- std::string getPlainMac() const;
-
- /// @brief Sets MAC address of the interface.
- ///
- /// @param mac pointer to MAC address buffer
- /// @param macLen length of mac address
- void setMac(const uint8_t* mac, size_t macLen);
-
- /// @brief Returns MAC length.
- ///
- /// @return length of MAC address
- size_t getMacLen() const { return mac_len_; }
-
- /// @brief Returns pointer to MAC address.
- ///
- /// Note: Returned pointer is only valid as long as the interface object
- /// that returned it.
- const uint8_t* getMac() const { return mac_; }
-
- /// @brief Sets flag_*_ fields based on bitmask value returned by OS
- ///
- /// Note: Implementation of this method is OS-dependent as bits have
- /// different meaning on each OS.
- ///
- /// @param flags bitmask value returned by OS in interface detection
- void setFlags(uint32_t flags);
-
- /// @brief Returns interface index.
- ///
- /// @return interface index
- uint16_t getIndex() const { return ifindex_; }
-
- /// @brief Returns interface name.
- ///
- /// @return interface name
- std::string getName() const { return name_; };
-
- /// @brief Sets up hardware type of the interface.
- ///
- /// @param type hardware type
- void setHWType(uint16_t type ) { hardware_type_ = type; }
-
- /// @brief Returns hardware type of the interface.
- ///
- /// @return hardware type
- uint16_t getHWType() const { return hardware_type_; }
-
- /// @brief Returns all interfaces available on an interface.
- ///
- /// Care should be taken to not use this collection after Iface object
- /// ceases to exist. That is easy in most cases as Iface objects are
- /// created by IfaceMgr that is a singleton an is expected to be
- /// available at all time. We may revisit this if we ever decide to
- /// implement dynamic interface detection, but such fancy feature would
- /// mostly be useful for clients with wifi/vpn/virtual interfaces.
- ///
- /// @return collection of addresses
- const AddressCollection& getAddresses() const { return addrs_; }
-
- /// @brief Adds an address to an interface.
- ///
- /// This only adds an address to collection, it does not physically
- /// configure address on actual network interface.
- ///
- /// @param addr address to be added
- void addAddress(const isc::asiolink::IOAddress& addr) {
- addrs_.push_back(addr);
- }
-
- /// @brief Deletes an address from an interface.
- ///
- /// This only deletes address from collection, it does not physically
- /// remove address configuration from actual network interface.
- ///
- /// @param addr address to be removed.
- ///
- /// @return true if removal was successful (address was in collection),
- /// false otherwise
- bool delAddress(const isc::asiolink::IOAddress& addr);
-
- /// @brief Adds socket descriptor to an interface.
- ///
- /// @param sock SocketInfo structure that describes socket.
- void addSocket(const SocketInfo& sock)
- { sockets_.push_back(sock); }
-
- /// @brief Closes socket.
- ///
- /// Closes socket and removes corresponding SocketInfo structure
- /// from an interface.
- ///
- /// @param sockfd socket descriptor to be closed/removed.
- /// @return true if there was such socket, false otherwise
- bool delSocket(uint16_t sockfd);
-
- /// @brief Returns collection of all sockets added to interface.
- ///
- /// When new socket is created with @ref IfaceMgr::openSocket
- /// it is added to sockets collection on particular interface.
- /// If socket is opened by other means (e.g. function that does
- /// not use @ref IfaceMgr::openSocket) it will not be available
- /// in this collection. Note that functions like
- /// @ref IfaceMgr::openSocketFromIface use
- /// @ref IfaceMgr::openSocket internally.
- /// The returned reference is only valid during the lifetime of
- /// the IfaceMgr object that returned it.
- ///
- /// @return collection of sockets added to interface
- const SocketCollection& getSockets() const { return sockets_; }
-
- protected:
- /// socket used to sending data
- SocketCollection sockets_;
-
- /// network interface name
- std::string name_;
-
- /// interface index (a value that uniquely indentifies an interface)
- int ifindex_;
-
- /// list of assigned addresses
- AddressCollection addrs_;
-
- /// link-layer address
- uint8_t mac_[MAX_MAC_LEN];
-
- /// length of link-layer address (usually 6)
- size_t mac_len_;
-
- /// hardware type
- uint16_t hardware_type_;
-
- public:
- /// @todo: Make those fields protected once we start supporting more
- /// than just Linux
-
- /// specifies if selected interface is loopback
- bool flag_loopback_;
-
- /// specifies if selected interface is up
- bool flag_up_;
-
- /// flag specifies if selected interface is running
- /// (e.g. cable plugged in, wifi associated)
- bool flag_running_;
-
- /// flag specifies if selected interface is multicast capable
- bool flag_multicast_;
-
- /// flag specifies if selected interface is broadcast capable
- bool flag_broadcast_;
-
- /// interface flags (this value is as is returned by OS,
- /// it may mean different things on different OSes)
- uint32_t flags_;
- };
-
// TODO performance improvement: we may change this into
// 2 maps (ifindex-indexed and name-indexed) and
// also hide it (make it public make tests easier for now)
- /// type that holds a list of interfaces
+ /// Type that holds a list of interfaces.
typedef std::list<Iface> IfaceCollection;
/// IfaceMgr is a singleton class. This method returns reference
@@ -306,6 +352,16 @@ public:
/// @return the only existing instance of interface manager
static IfaceMgr& instance();
+ /// @brief Check if packet be sent directly to the client having no address.
+ ///
+ /// Checks if IfaceMgr can send DHCPv4 packet to the client
+ /// who hasn't got address assigned. If this is not supported
+ /// broadcast address should be used to send response to
+ /// the client.
+ ///
+ /// @return true if direct response is supported.
+ bool isDirectResponseSupported() const;
+
/// @brief Returns interface with specified interface index
///
/// @param ifindex index of searched interface
@@ -360,11 +416,10 @@ public:
/// @return a socket descriptor
uint16_t getSocket(const isc::dhcp::Pkt4& pkt);
- /// debugging method that prints out all available interfaces
+ /// Debugging method that prints out all available interfaces.
///
/// @param out specifies stream to print list of interfaces to
- void
- printIfaces(std::ostream& out = std::cout);
+ void printIfaces(std::ostream& out = std::cout);
/// @brief Sends an IPv6 packet.
///
@@ -434,6 +489,10 @@ public:
/// @param ifname name of the interface
/// @param addr address to be bound.
/// @param port UDP port.
+ /// @param receive_bcast configure IPv4 socket to receive broadcast messages.
+ /// This parameter is ignored for IPv6 sockets.
+ /// @param send_bcast configure IPv4 socket to send broadcast messages.
+ /// This parameter is ignored for IPv6 sockets.
///
/// Method will throw if socket creation, socket binding or multicast
/// join fails.
@@ -442,7 +501,9 @@ public:
/// group join were all successful.
int openSocket(const std::string& ifname,
const isc::asiolink::IOAddress& addr,
- const uint16_t port);
+ const uint16_t port,
+ const bool receive_bcast = false,
+ const bool send_bcast = false);
/// @brief Opens UDP/IP socket and binds it to interface specified.
///
@@ -504,20 +565,43 @@ public:
/// @return true if any sockets were open
bool openSockets6(const uint16_t port = DHCP6_SERVER_PORT);
- /// @brief Closes all open sockets.
- /// Is used in destructor, but also from Dhcpv4_srv and Dhcpv6_srv classes.
- void closeSockets();
-
/// Opens IPv4 sockets on detected interfaces.
/// Will throw exception if socket creation fails.
///
/// @param port specifies port number (usually DHCP4_SERVER_PORT)
+ /// @param use_bcast configure sockets to support broadcast messages.
///
/// @throw SocketOpenFailure if tried and failed to open socket.
/// @return true if any sockets were open
- bool openSockets4(const uint16_t port = DHCP4_SERVER_PORT);
+ bool openSockets4(const uint16_t port = DHCP4_SERVER_PORT,
+ const bool use_bcast = true);
- /// @brief returns number of detected interfaces
+ /// @brief Closes all open sockets.
+ /// Is used in destructor, but also from Dhcpv4Srv and Dhcpv6Srv classes.
+ void closeSockets();
+
+ /// @brief Closes all IPv4 or IPv6 sockets.
+ ///
+ /// This function closes sockets of the specific 'type' and closes them.
+ /// The 'type' of the socket indicates whether it is used to send IPv4
+ /// or IPv6 packets. The allowed values of the parameter are AF_INET and
+ /// AF_INET6 for IPv4 and IPv6 packets respectively. It is important
+ /// to realize that the actual types of sockets may be different than
+ /// AF_INET for IPv4 packets. This is because, historically the IfaceMgr
+ /// always used AF_INET sockets for IPv4 traffic. This is no longer the
+ /// case when the Direct IPv4 traffic must be supported. In order to support
+ /// direct traffic, the IfaceMgr operates on raw sockets, e.g. AF_PACKET
+ /// family sockets on Linux.
+ ///
+ /// @todo Replace the AF_INET and AF_INET6 values with an enum
+ /// which will not be confused with the actual socket type.
+ ///
+ /// @param family type of the sockets to be closed (AF_INET or AF_INET6)
+ ///
+ /// @throw BadValue if family value is different than AF_INET or AF_INET6.
+ void closeSockets(const uint16_t family);
+
+ /// @brief Returns number of detected interfaces.
///
/// @return number of detected interfaces
uint16_t countIfaces() { return ifaces_.size(); }
@@ -534,6 +618,40 @@ public:
session_callback_ = callback;
}
+ /// @brief Set Packet Filter object to handle send/receive packets.
+ ///
+ /// Packet Filters expose low-level functions handling sockets opening
+ /// and sending/receiving packets through those sockets. This function
+ /// sets custom Packet Filter (represented by a class derived from PktFilter)
+ /// to be used by IfaceMgr. Note that there must be no IPv4 sockets open
+ /// when this function is called. Call closeSockets(AF_INET) to close
+ /// all hanging IPv4 sockets opened by the current packet filter object.
+ ///
+ /// @param packet_filter new packet filter to be used by IfaceMgr to send/receive
+ /// packets and open sockets.
+ ///
+ /// @throw InvalidPacketFilter if provided packet filter object is NULL.
+ /// @throw PacketFilterChangeDenied if there are open IPv4 sockets
+ void setPacketFilter(const PktFilterPtr& packet_filter);
+
+ /// @brief Set Packet Filter object to handle send/receive packets.
+ ///
+ /// This function sets Packet Filter object to be used by IfaceMgr,
+ /// appropriate for the current OS. Setting the argument to 'true'
+ /// indicates that function should set a packet filter class
+ /// which supports direct responses to clients having no address
+ /// assigned yet. Filters picked by this function will vary, depending
+ /// on the OS being used. There is no guarantee that there is an
+ /// implementation that supports this feature on a particular OS.
+ /// If there isn't, the PktFilterInet object will be set. If the
+ /// argument is set to 'false', PktFilterInet object instance will
+ /// be set as the Packet Filter regrdaless of the OS type.
+ ///
+ /// @param direct_response_desired specifies whether the Packet Filter
+ /// object being set should support direct traffic to the host
+ /// not having address assigned.
+ void setMatchingPacketFilter(const bool direct_response_desired = false);
+
/// A value of socket descriptor representing "not specified" state.
static const int INVALID_SOCKET = -1;
@@ -557,9 +675,13 @@ protected:
/// @param iface reference to interface structure.
/// @param addr an address the created socket should be bound to
/// @param port a port that created socket should be bound to
+ /// @param receive_bcast configure socket to receive broadcast messages
+ /// @param send_bcast configure socket to send broadcast messages.
///
/// @return socket descriptor
- int openSocket4(Iface& iface, const isc::asiolink::IOAddress& addr, uint16_t port);
+ int openSocket4(Iface& iface, const isc::asiolink::IOAddress& addr,
+ const uint16_t port, const bool receive_bcast = false,
+ const bool send_bcast = false);
/// @brief Opens IPv6 socket.
///
@@ -585,7 +707,7 @@ protected:
///
/// This method will eventually detect available interfaces. For now
/// it offers stub implementation. First interface name and link-local
- /// IPv6 address is read from intefaces.txt file.
+ /// IPv6 address is read from interfaces.txt file.
void
detectIfaces();
@@ -594,7 +716,7 @@ protected:
/// This implementations reads a single line from interfaces.txt file
/// and pretends to detect such interface. First interface name and
/// link-local IPv6 address or IPv4 address is read from the
- /// intefaces.txt file.
+ /// interfaces.txt file.
void
stubDetectIfaces();
@@ -610,14 +732,14 @@ protected:
//int recvsock_; // TODO: should be fd_set eventually, but we have only
//int sendsock_; // 2 sockets for now. Will do for until next release
- // we can't use the same socket, as receiving socket
+ // We can't use the same socket, as receiving socket
// is bound to multicast address. And we all know what happens
// to people who try to use multicast as source address.
- /// length of the control_buf_ array
+ /// Length of the control_buf_ array
size_t control_buf_len_;
- /// control-buffer, used in transmission and reception
+ /// Control-buffer, used in transmission and reception.
boost::scoped_array<char> control_buf_;
/// @brief A wrapper for OS-specific operations before sending IPv4 packet
@@ -637,10 +759,10 @@ protected:
/// @return true if successful, false otherwise
bool os_receive4(struct msghdr& m, Pkt4Ptr& pkt);
- /// socket descriptor of the session socket
+ /// Socket descriptor of the session socket.
int session_socket_;
- /// a callback that will be called when data arrives over session_socket_
+ /// A callback that will be called when data arrives over session_socket_.
SessionCallback session_callback_;
private:
@@ -674,10 +796,20 @@ private:
/// @param remote_addr remote address to connect to
/// @param port port to be used
/// @return local address to be used to connect to remote address
- /// @throw isc::Unexpected if unable to indentify local address
+ /// @throw isc::Unexpected if unable to identify local address
isc::asiolink::IOAddress
getLocalAddress(const isc::asiolink::IOAddress& remote_addr,
const uint16_t port);
+
+ /// Holds instance of a class derived from PktFilter, used by the
+ /// IfaceMgr to open sockets and send/receive packets through these
+ /// sockets. It is possible to supply custom object using
+ /// setPacketFilter class. Various Packet Filters differ mainly by using
+ /// different types of sockets, e.g. SOCK_DGRAM, SOCK_RAW and different
+ /// families, e.g. AF_INET, AF_PACKET etc. Another possible type of
+ /// Packet Filter is the one used for unit testing, which doesn't
+ /// open sockets but rather mimics their behavior (mock object).
+ PktFilterPtr packet_filter_;
};
}; // namespace isc::dhcp
diff --git a/src/lib/dhcp/iface_mgr_bsd.cc b/src/lib/dhcp/iface_mgr_bsd.cc
index e3f11a1..2293877 100644
--- a/src/lib/dhcp/iface_mgr_bsd.cc
+++ b/src/lib/dhcp/iface_mgr_bsd.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011, 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -17,6 +17,7 @@
#if defined(OS_BSD)
#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter_inet.h>
#include <exceptions/exceptions.h>
using namespace std;
@@ -49,6 +50,14 @@ bool IfaceMgr::os_receive4(struct msghdr& /*m*/, Pkt4Ptr& /*pkt*/) {
return (true); // pretend that we have everything set up for reception.
}
+void
+IfaceMgr::setMatchingPacketFilter(const bool /* direct_response_desired */) {
+ // @todo Currently we ignore the preference to use direct traffic
+ // because it hasn't been implemented for BSD systems.
+ setPacketFilter(PktFilterPtr(new PktFilterInet()));
+}
+
+
} // end of isc::dhcp namespace
} // end of dhcp namespace
diff --git a/src/lib/dhcp/iface_mgr_linux.cc b/src/lib/dhcp/iface_mgr_linux.cc
index e7d5048..f31c353 100644
--- a/src/lib/dhcp/iface_mgr_linux.cc
+++ b/src/lib/dhcp/iface_mgr_linux.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -33,6 +33,8 @@
#include <asiolink/io_address.h>
#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter_inet.h>
+#include <dhcp/pkt_filter_lpf.h>
#include <exceptions/exceptions.h>
#include <util/io/sockaddr_util.h>
@@ -67,7 +69,7 @@ public:
/// interfaces) uses memory aliasing. Linux kernel returns a memory
/// blob that should be interpreted as series of nlmessages. There
/// are different nlmsg structures defined with varying size. They
-/// have one thing common - inital fields are laid out in the same
+/// have one thing common - initial fields are laid out in the same
/// way as nlmsghdr. Therefore different messages can be represented
/// as nlmsghdr with followed variable number of bytes that are
/// message-specific. The only reasonable way to represent this in
@@ -103,7 +105,7 @@ public:
void rtnl_send_request(int family, int type);
void rtnl_store_reply(NetlinkMessages& storage, const nlmsghdr* msg);
void parse_rtattr(RTattribPtrs& table, rtattr* rta, int len);
- void ipaddrs_get(IfaceMgr::Iface& iface, NetlinkMessages& addr_info);
+ void ipaddrs_get(Iface& iface, NetlinkMessages& addr_info);
void rtnl_process_reply(NetlinkMessages& info);
void release_list(NetlinkMessages& messages);
void rtnl_close_socket();
@@ -277,7 +279,7 @@ void Netlink::parse_rtattr(RTattribPtrs& table, struct rtattr* rta, int len)
///
/// @param iface interface representation (addresses will be added here)
/// @param addr_info collection of parsed netlink messages
-void Netlink::ipaddrs_get(IfaceMgr::Iface& iface, NetlinkMessages& addr_info) {
+void Netlink::ipaddrs_get(Iface& iface, NetlinkMessages& addr_info) {
uint8_t addr[V6ADDRESS_LEN];
RTattribPtrs rta_tb;
@@ -476,7 +478,7 @@ void IfaceMgr::detectIfaces() {
iface.setHWType(interface_info->ifi_type);
iface.setFlags(interface_info->ifi_flags);
- // Does inetface have LL_ADDR?
+ // Does interface have LL_ADDR?
if (attribs_table[IFLA_ADDRESS]) {
iface.setMac(static_cast<const uint8_t*>(RTA_DATA(attribs_table[IFLA_ADDRESS])),
RTA_PAYLOAD(attribs_table[IFLA_ADDRESS]));
@@ -500,7 +502,7 @@ void IfaceMgr::detectIfaces() {
/// on different OSes.
///
/// @param flags flags bitfield read from OS
-void IfaceMgr::Iface::setFlags(uint32_t flags) {
+void Iface::setFlags(uint32_t flags) {
flags_ = flags;
flag_loopback_ = flags & IFF_LOOPBACK;
@@ -510,56 +512,25 @@ void IfaceMgr::Iface::setFlags(uint32_t flags) {
flag_broadcast_ = flags & IFF_BROADCAST;
}
-void IfaceMgr::os_send4(struct msghdr& m, boost::scoped_array<char>& control_buf,
- size_t control_buf_len, const Pkt4Ptr& pkt) {
-
- // Setting the interface is a bit more involved.
- //
- // We have to create a "control message", and set that to
- // define the IPv4 packet information. We could set the
- // source address if we wanted, but we can safely let the
- // kernel decide what that should be.
- m.msg_control = &control_buf[0];
- m.msg_controllen = control_buf_len;
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
- cmsg->cmsg_level = IPPROTO_IP;
- cmsg->cmsg_type = IP_PKTINFO;
- cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
- struct in_pktinfo* pktinfo =(struct in_pktinfo *)CMSG_DATA(cmsg);
- memset(pktinfo, 0, sizeof(struct in_pktinfo));
- pktinfo->ipi_ifindex = pkt->getIndex();
- m.msg_controllen = cmsg->cmsg_len;
-}
-
-bool IfaceMgr::os_receive4(struct msghdr& m, Pkt4Ptr& pkt) {
- struct cmsghdr* cmsg;
- struct in_pktinfo* pktinfo;
- struct in_addr to_addr;
+void
+IfaceMgr::setMatchingPacketFilter(const bool direct_response_desired) {
+ if (direct_response_desired) {
+ setPacketFilter(PktFilterPtr(new PktFilterLPF()));
- memset(&to_addr, 0, sizeof(to_addr));
+ } else {
+ setPacketFilter(PktFilterPtr(new PktFilterInet()));
- cmsg = CMSG_FIRSTHDR(&m);
- while (cmsg != NULL) {
- if ((cmsg->cmsg_level == IPPROTO_IP) &&
- (cmsg->cmsg_type == IP_PKTINFO)) {
- pktinfo = (struct in_pktinfo*)CMSG_DATA(cmsg);
-
- pkt->setIndex(pktinfo->ipi_ifindex);
- pkt->setLocalAddr(IOAddress(htonl(pktinfo->ipi_addr.s_addr)));
- return (true);
+ }
+}
- // This field is useful, when we are bound to unicast
- // address e.g. 192.0.2.1 and the packet was sent to
- // broadcast. This will return broadcast address, not
- // the address we are bound to.
+void IfaceMgr::os_send4(struct msghdr&, boost::scoped_array<char>&,
+ size_t, const Pkt4Ptr&) {
+ return;
- // XXX: Perhaps we should uncomment this:
- // to_addr = pktinfo->ipi_spec_dst;
- }
- cmsg = CMSG_NXTHDR(&m, cmsg);
- }
+}
- return (false);
+bool IfaceMgr::os_receive4(struct msghdr&, Pkt4Ptr&) {
+ return (true);
}
} // end of isc::dhcp namespace
diff --git a/src/lib/dhcp/iface_mgr_sun.cc b/src/lib/dhcp/iface_mgr_sun.cc
index 5847906..46c4a97 100644
--- a/src/lib/dhcp/iface_mgr_sun.cc
+++ b/src/lib/dhcp/iface_mgr_sun.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011, 2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -17,6 +17,7 @@
#if defined(OS_SUN)
#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt_filter_inet.h>
#include <exceptions/exceptions.h>
using namespace std;
@@ -49,6 +50,13 @@ bool IfaceMgr::os_receive4(struct msghdr& /*m*/, Pkt4Ptr& /*pkt*/) {
return (true); // pretend that we have everything set up for reception.
}
+void
+IfaceMgr::setMatchingPacketFilter(const bool /* direct_response_desired */) {
+ // @todo Currently we ignore the preference to use direct traffic
+ // because it hasn't been implemented for Solaris.
+ setPacketFilter(PktFilterPtr(new PktFilterInet()));
+}
+
} // end of isc::dhcp namespace
} // end of dhcp namespace
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index 78f511d..697c33e 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -128,7 +128,9 @@ LibDHCP::optionFactory(Option::Universe u,
size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
- isc::dhcp::Option::OptionCollection& options) {
+ isc::dhcp::Option::OptionCollection& options,
+ size_t* relay_msg_offset /* = 0 */,
+ size_t* relay_msg_len /* = 0 */) {
size_t offset = 0;
size_t length = buf.size();
@@ -143,6 +145,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
while (offset + 4 <= length) {
uint16_t opt_type = isc::util::readUint16(&buf[offset]);
offset += 2;
+
uint16_t opt_len = isc::util::readUint16(&buf[offset]);
offset += 2;
@@ -151,6 +154,16 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
return (offset);
}
+ if (opt_type == D6O_RELAY_MSG && relay_msg_offset && relay_msg_len) {
+ // remember offset of the beginning of the relay-msg option
+ *relay_msg_offset = offset;
+ *relay_msg_len = opt_len;
+
+ // do not create that relay-msg option
+ offset += opt_len;
+ continue;
+ }
+
// Get all definitions with the particular option code. Note that option
// code is non-unique within this container however at this point we
// expect to get one option definition with the particular code. If more
@@ -193,7 +206,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
}
size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
- isc::dhcp::Option::OptionCollection& options) {
+ isc::dhcp::Option::OptionCollection& options) {
size_t offset = 0;
// Get the list of stdandard option definitions.
diff --git a/src/lib/dhcp/libdhcp++.dox b/src/lib/dhcp/libdhcp++.dox
index 194175a..eabc392 100644
--- a/src/lib/dhcp/libdhcp++.dox
+++ b/src/lib/dhcp/libdhcp++.dox
@@ -57,6 +57,53 @@ DHCPv6, but is rarely used in DHCPv4. isc::dhcp::Option::addOption(),
isc::dhcp::Option::delOption(), isc::dhcp::Option::getOption() can be used
for that purpose.
+ at section libdhcpRelay Relay v6 support in Pkt6
+
+DHCPv6 clients that are not connected to the same link as DHCPv6
+servers need relays to reach the server. Each relay receives a message
+on a client facing interface, encapsulates it into RELAY_MSG option
+and sends as RELAY_FORW message towards the server (or the next relay,
+which is closer to the server). This procedure can be repeated up to
+32 times. Kea is able to support up to 32 relays. Each traversed relay
+may add certain options. The most obvious example is interface-id
+option, but there may be other options as well. Each relay may add such
+an option, regardless of whether other relays added it before. Thanks
+to encapsulation, those options are separated and it is possible to
+differentiate which relay inserted specific instance of an option.
+
+Interface-id is used to identify a subnet (or interface) the original message
+came from and is used for that purpose on two occasions. First, the server
+uses the interface-id included by the first relay (the one closest to
+the client) to select appropriate subnet for a given request. Server includes
+that interface-id in its copy, when sending data back to the client.
+This will be used by the relay to choose proper interface when forwarding
+response towards the client.
+
+Pkt6 class has a public Pkt6::relay_info_ field, which is of type Pkt6::RelayInfo.
+This is a simple structure that represents the information in each RELAY_FORW
+or RELAY_REPL message. It is important to understand the order in which
+the data appear here. Consider the following network:
+
+\verbatim
+client-------relay1-----relay2-----relay3----server
+\endverbatim
+
+Client will transmit SOLICIT message. Relay1 will forward it as
+RELAY_FORW with SOLICIT in it. Relay2 forward it as RELAY_FORW with
+RELAY_FORW with SOLICIT in it. Finally the third relay will add yet
+another RELAY_FORW around it. The server will parse the packet and
+create Pkt6 object for it. Its relay_info_ will have 3
+elements. Packet parsing is done in reverse order, compare to the
+order the packet traversed in the network. The first element
+(relay_info_[0]) will represent relay3 information (the "last" relay or
+in other words the one closest to the server). The second element
+will represent relay2. The third element (relay_info_[2]) will represent
+the first relay (relay1) or in other words the one closest to the client.
+
+Packets sent by the server must maintain the same encapsulation order.
+This is easy to do - just copy data from client's message object into
+server's response object. See Pkt6::coyRelayInfo for details.
+
@section libdhcpIfaceMgr Interface Manager
Interface Manager (or IfaceMgr) is an abstraction layer about low-level
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index c6594b9..9d8bcab 100644
--- a/src/lib/dhcp/libdhcp++.h
+++ b/src/lib/dhcp/libdhcp++.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -61,7 +61,7 @@ public:
/// @param code option code.
///
/// @return true if the specified option is a standard option.
- /// @todo We arleady create option definitions for the subset if
+ /// @todo We already create option definitions for the subset if
/// standard options. We are aiming that this function checks
/// the presence of the standard option definition and if it finds
/// it, then the true value is returned. However, at this point
@@ -115,14 +115,27 @@ public:
/// @brief Parses provided buffer as DHCPv6 options and creates Option objects.
///
- /// Parses provided buffer and stores created Option objects
- /// in options container.
+ /// Parses provided buffer and stores created Option objects in options
+ /// container. The last two parameters are optional and are used in
+ /// relay parsing. If they are specified, relay-msg option is not created,
+ /// but rather those two parameters are specified to point out where
+ /// the relay-msg option resides and what is its length. This is perfromance
+ /// optimization that avoids unnecessary copying of potentially large
+ /// relay-msg option. It is not used for anything, except in the next
+ /// iteration its content will be treated as buffer to be parsed.
///
/// @param buf Buffer to be parsed.
/// @param options Reference to option container. Options will be
/// put here.
+ /// @param relay_msg_offset reference to a size_t structure. If specified,
+ /// offset to beginning of relay_msg option will be stored in it.
+ /// @param relay_msg_len reference to a size_t structure. If specified,
+ /// length of the relay_msg option will be stored in it.
+ /// @return offset to the first byte after last parsed option
static size_t unpackOptions6(const OptionBuffer& buf,
- isc::dhcp::Option::OptionCollection& options);
+ isc::dhcp::Option::OptionCollection& options,
+ size_t* relay_msg_offset = 0,
+ size_t* relay_msg_len = 0);
/// Registers factory method that produces options of specific option types.
///
diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc
index e06b163..cd6e313 100644
--- a/src/lib/dhcp/option.cc
+++ b/src/lib/dhcp/option.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -55,7 +55,7 @@ Option::Option(Universe u, uint16_t type, const OptionBuffer& data)
Option::Option(Universe u, uint16_t type, OptionBufferConstIter first,
OptionBufferConstIter last)
- :universe_(u), type_(type), data_(OptionBuffer(first,last)) {
+ :universe_(u), type_(type), data_(first, last) {
check();
}
@@ -121,7 +121,7 @@ Option::packOptions(isc::util::OutputBuffer& buf) {
void Option::unpack(OptionBufferConstIter begin,
OptionBufferConstIter end) {
- data_ = OptionBuffer(begin, end);
+ setData(begin, end);
}
void
@@ -274,13 +274,6 @@ void Option::setUint32(uint32_t value) {
writeUint32(value, &data_[0]);
}
-void Option::setData(const OptionBufferConstIter first,
- const OptionBufferConstIter last) {
- // We will copy entire option buffer, so we have to resize data_.
- data_.resize(std::distance(first, last));
- std::copy(first, last, data_.begin());
-}
-
bool Option::equal(const OptionPtr& other) const {
return ( (getType() == other->getType()) &&
(getData() == other->getData()) );
diff --git a/src/lib/dhcp/option.h b/src/lib/dhcp/option.h
index 553e825..9abb4b3 100644
--- a/src/lib/dhcp/option.h
+++ b/src/lib/dhcp/option.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -128,7 +128,7 @@ public:
/// @brief Constructor, used for received options.
///
- /// This contructor is similar to the previous one, but it does not take
+ /// This constructor is similar to the previous one, but it does not take
/// the whole vector<uint8_t>, but rather subset of it.
///
/// @todo This can be templated to use different containers, not just
@@ -280,10 +280,15 @@ public:
///
/// Option will be resized to length of buffer.
///
- /// @param first iterator pointing begining of buffer to copy.
+ /// @param first iterator pointing to beginning of buffer to copy.
/// @param last iterator pointing to end of buffer to copy.
- void setData(const OptionBufferConstIter first,
- const OptionBufferConstIter last);
+ ///
+ /// @tparam InputIterator type of the iterator representing the
+ /// limits of the buffer to be assigned to a data_ buffer.
+ template<typename InputIterator>
+ void setData(InputIterator first, InputIterator last) {
+ data_.assign(first, last);
+ }
/// just to force that every option has virtual dtor
virtual ~Option();
diff --git a/src/lib/dhcp/option4_client_fqdn.cc b/src/lib/dhcp/option4_client_fqdn.cc
new file mode 100644
index 0000000..7f93a50
--- /dev/null
+++ b/src/lib/dhcp/option4_client_fqdn.cc
@@ -0,0 +1,525 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <dhcp/dhcp4.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dns/labelsequence.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+#include <util/strutil.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Implements the logic for the Option6ClientFqdn class.
+///
+/// The purpose of the class is to separate the implementation details
+/// of the Option4ClientFqdn class from the interface. This implementation
+/// uses libdns classes to process FQDNs. At some point it may be
+/// desired to split libdhcp++ from libdns. In such case the
+/// implementation of this class may be changed. The declaration of the
+/// Option6ClientFqdn class holds the pointer to implementation, so
+/// the transition to a different implementation would not affect the
+/// header file.
+class Option4ClientFqdnImpl {
+public:
+ /// Holds flags carried by the option.
+ uint8_t flags_;
+ /// Holds RCODE1 and RCODE2 values.
+ Option4ClientFqdn::Rcode rcode1_;
+ Option4ClientFqdn::Rcode rcode2_;
+ /// Holds the pointer to a domain name carried in the option.
+ boost::shared_ptr<isc::dns::Name> domain_name_;
+ /// Indicates whether domain name is partial or fully qualified.
+ Option4ClientFqdn::DomainNameType domain_name_type_;
+
+ /// @brief Constructor, from domain name.
+ ///
+ /// @param flags A value of the flags option field.
+ /// @param rcode An object representing the RCODE1 and RCODE2 values.
+ /// @param domain_name A domain name carried by the option given in the
+ /// textual format.
+ /// @param name_type A value which indicates whether domain-name is partial
+ /// or fully qualified.
+ Option4ClientFqdnImpl(const uint8_t flags,
+ const Option4ClientFqdn::Rcode& rcode,
+ const std::string& domain_name,
+ const Option4ClientFqdn::DomainNameType name_type);
+
+ /// @brief Constructor, from wire data.
+ ///
+ /// @param first An iterator pointing to the begining of the option data
+ /// in the wire format.
+ /// @param last An iterator poiting to the end of the option data in the
+ /// wire format.
+ Option4ClientFqdnImpl(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Copy constructor.
+ ///
+ /// @param source An object being copied.
+ Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source);
+
+ /// @brief Assignment operator.
+ ///
+ /// @param source An object which is being assigned.
+ Option4ClientFqdnImpl& operator=(const Option4ClientFqdnImpl& source);
+
+ /// @brief Set a new domain name for the option.
+ ///
+ /// @param domain_name A new domain name to be assigned.
+ /// @param name_type A value which indicates whether the domain-name is
+ /// partial or fully qualified.
+ void setDomainName(const std::string& domain_name,
+ const Option4ClientFqdn::DomainNameType name_type);
+
+ /// @brief Check if flags are valid.
+ ///
+ /// In particular, this function checks if the N and S bits are not
+ /// set to 1 in the same time.
+ ///
+ /// @param flags A value carried by the flags field of the option.
+ /// @param check_mbz A boolean value which indicates if this function should
+ /// check if the MBZ bits are set (if true). This parameter should be set
+ /// to false when validating flags in the received message. This is because
+ /// server should ignore MBZ bits in received messages.
+ /// @throw InvalidOption6FqdnFlags if flags are invalid.
+ static void checkFlags(const uint8_t flags, const bool check_mbz);
+
+ /// @brief Parse the Option provided in the wire format.
+ ///
+ /// @param first An iterator pointing to the begining of the option data
+ /// in the wire format.
+ /// @param last An iterator poiting to the end of the option data in the
+ /// wire format.
+ void parseWireData(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Parse domain-name encoded in the canonical format.
+ ///
+ void parseCanonicalDomainName(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Parse domain-name emcoded in the deprecated ASCII format.
+ ///
+ /// @param first An iterator pointing to the begining of the option data
+ /// where domain-name is stored.
+ /// @param last An iterator poiting to the end of the option data where
+ /// domain-name is stored.
+ void parseASCIIDomainName(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+};
+
+Option4ClientFqdnImpl::
+Option4ClientFqdnImpl(const uint8_t flags,
+ const Option4ClientFqdn::Rcode& rcode,
+ const std::string& domain_name,
+ // cppcheck 1.57 complains that const enum value is not passed
+ // by reference. Note that, it accepts the non-const enum value
+ // to be passed by value. In both cases it is unneccessary to
+ // pass the enum by reference.
+ // cppcheck-suppress passedByValue
+ const Option4ClientFqdn::DomainNameType name_type)
+ : flags_(flags),
+ rcode1_(rcode),
+ rcode2_(rcode),
+ domain_name_(),
+ domain_name_type_(name_type) {
+
+ // Check if flags are correct. Also, check that MBZ bits are not set. If
+ // they are, throw exception.
+ checkFlags(flags_, true);
+ // Set domain name. It may throw an exception if domain name has wrong
+ // format.
+ setDomainName(domain_name, name_type);
+}
+
+Option4ClientFqdnImpl::Option4ClientFqdnImpl(OptionBufferConstIter first,
+ OptionBufferConstIter last)
+ : rcode1_(Option4ClientFqdn::RCODE_CLIENT()),
+ rcode2_(Option4ClientFqdn::RCODE_CLIENT()) {
+ parseWireData(first, last);
+ // Verify that flags value was correct. This constructor is used to parse
+ // incoming packet, so don't check MBZ bits. They are ignored because we
+ // don't want to discard the whole option because MBZ bits are set.
+ checkFlags(flags_, false);
+}
+
+Option4ClientFqdnImpl::
+Option4ClientFqdnImpl(const Option4ClientFqdnImpl& source)
+ : flags_(source.flags_),
+ rcode1_(source.rcode1_),
+ rcode2_(source.rcode2_),
+ domain_name_(),
+ domain_name_type_(source.domain_name_type_) {
+ if (source.domain_name_) {
+ domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+ }
+}
+
+Option4ClientFqdnImpl&
+// This assignment operator handles assignment to self, it copies all
+// required values.
+// cppcheck-suppress operatorEqToSelf
+Option4ClientFqdnImpl::operator=(const Option4ClientFqdnImpl& source) {
+ if (source.domain_name_) {
+ domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+
+ } else {
+ domain_name_.reset();
+ }
+
+ // Assignment is exception safe.
+ flags_ = source.flags_;
+ rcode1_ = source.rcode1_;
+ rcode2_ = source.rcode2_;
+ domain_name_type_ = source.domain_name_type_;
+
+ return (*this);
+}
+
+void
+Option4ClientFqdnImpl::
+setDomainName(const std::string& domain_name,
+ // cppcheck 1.57 complains that const enum value is not passed
+ // by reference. Note that, it accepts the non-const enum
+ // to be passed by value. In both cases it is unneccessary to
+ // pass the enum by reference.
+ // cppcheck-suppress passedByValue
+ const Option4ClientFqdn::DomainNameType name_type) {
+ // domain-name must be trimmed. Otherwise, string comprising spaces only
+ // would be treated as a fully qualified name.
+ std::string name = isc::util::str::trim(domain_name);
+ if (name.empty()) {
+ if (name_type == Option4ClientFqdn::FULL) {
+ isc_throw(InvalidOption4FqdnDomainName,
+ "fully qualified domain-name must not be empty"
+ << " when setting new domain-name for DHCPv4 Client"
+ << " FQDN Option");
+ }
+ // The special case when domain-name is empty is marked by setting the
+ // pointer to the domain-name object to NULL.
+ domain_name_.reset();
+
+ } else {
+ try {
+ domain_name_.reset(new isc::dns::Name(name));
+ domain_name_type_ = name_type;
+
+ } catch (const Exception& ex) {
+ isc_throw(InvalidOption4FqdnDomainName,
+ "invalid domain-name value '"
+ << domain_name << "' when setting new domain-name for"
+ << " DHCPv4 Client FQDN Option");
+
+ }
+ }
+}
+
+void
+Option4ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) {
+ // The Must Be Zero (MBZ) bits must not be set.
+ if (check_mbz && ((flags & ~Option4ClientFqdn::FLAG_MASK) != 0)) {
+ isc_throw(InvalidOption4FqdnFlags,
+ "invalid DHCPv4 Client FQDN Option flags: 0x"
+ << std::hex << static_cast<int>(flags) << std::dec);
+ }
+
+ // According to RFC 4702, section 2.1. if the N bit is 1, the S bit
+ // MUST be 0. Checking it here.
+ if ((flags & (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S))
+ == (Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S)) {
+ isc_throw(InvalidOption4FqdnFlags,
+ "both N and S flag of the DHCPv4 Client FQDN Option are set."
+ << " According to RFC 4702, if the N bit is 1 the S bit"
+ << " MUST be 0");
+ }
+}
+
+void
+Option4ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+
+ // Buffer must comprise at least one byte with the flags.
+ // The domain-name may be empty.
+ if (std::distance(first, last) < Option4ClientFqdn::FIXED_FIELDS_LEN) {
+ isc_throw(OutOfRange, "DHCPv4 Client FQDN Option ("
+ << DHO_FQDN << ") is truncated");
+ }
+
+ // Parse flags
+ flags_ = *(first++);
+
+ // Parse RCODE1 and RCODE2.
+ rcode1_ = Option4ClientFqdn::Rcode(*(first++));
+ rcode2_ = Option4ClientFqdn::Rcode(*(first++));
+
+ try {
+ if ((flags_ & Option4ClientFqdn::FLAG_E) != 0) {
+ parseCanonicalDomainName(first, last);
+
+ } else {
+ parseASCIIDomainName(first, last);
+
+ }
+ } catch (const Exception& ex) {
+ isc_throw(InvalidOption4FqdnDomainName,
+ "failed to parse the domain-name in DHCPv4 Client FQDN"
+ << " Option: " << ex.what());
+ }
+}
+
+void
+Option4ClientFqdnImpl::parseCanonicalDomainName(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+ // Parse domain-name if any.
+ if (std::distance(first, last) > 0) {
+ // The FQDN may comprise a partial domain-name. In this case it lacks
+ // terminating 0. If this is the case, we will need to add zero at
+ // the end because Name object constructor requires it.
+ if (*(last - 1) != 0) {
+ // Create temporary buffer and add terminating zero.
+ OptionBuffer buf(first, last);
+ buf.push_back(0);
+ // Reset domain name.
+ isc::util::InputBuffer name_buf(&buf[0], buf.size());
+ domain_name_.reset(new isc::dns::Name(name_buf));
+ // Terminating zero was missing, so set the domain-name type
+ // to partial.
+ domain_name_type_ = Option4ClientFqdn::PARTIAL;
+ } else {
+ // We are dealing with fully qualified domain name so there is
+ // no need to add terminating zero. Simply pass the buffer to
+ // Name object constructor.
+ isc::util::InputBuffer name_buf(&(*first),
+ std::distance(first, last));
+ domain_name_.reset(new isc::dns::Name(name_buf));
+ // Set the domain-type to fully qualified domain name.
+ domain_name_type_ = Option4ClientFqdn::FULL;
+ }
+ }
+}
+
+void
+Option4ClientFqdnImpl::parseASCIIDomainName(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+ if (std::distance(first, last) > 0) {
+ std::string domain_name(first, last);
+ domain_name_.reset(new isc::dns::Name(domain_name));
+ domain_name_type_ = domain_name[domain_name.length() - 1] == '.' ?
+ Option4ClientFqdn::FULL : Option4ClientFqdn::PARTIAL;
+ }
+}
+
+Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag, const Rcode& rcode)
+ : Option(Option::V4, DHO_FQDN),
+ impl_(new Option4ClientFqdnImpl(flag, rcode, "", PARTIAL)) {
+}
+
+Option4ClientFqdn::Option4ClientFqdn(const uint8_t flag,
+ const Rcode& rcode,
+ const std::string& domain_name,
+ const DomainNameType domain_name_type)
+ : Option(Option::V4, DHO_FQDN),
+ impl_(new Option4ClientFqdnImpl(flag, rcode, domain_name,
+ domain_name_type)) {
+}
+
+Option4ClientFqdn::Option4ClientFqdn(OptionBufferConstIter first,
+ OptionBufferConstIter last)
+ : Option(Option::V4, DHO_FQDN, first, last),
+ impl_(new Option4ClientFqdnImpl(first, last)) {
+}
+
+Option4ClientFqdn::~Option4ClientFqdn() {
+ delete(impl_);
+}
+
+Option4ClientFqdn::Option4ClientFqdn(const Option4ClientFqdn& source)
+ : Option(source),
+ impl_(new Option4ClientFqdnImpl(*source.impl_)) {
+}
+
+Option4ClientFqdn&
+// This assignment operator handles assignment to self, it uses copy
+// constructor of Option4ClientFqdnImpl to copy all required values.
+// cppcheck-suppress operatorEqToSelf
+Option4ClientFqdn::operator=(const Option4ClientFqdn& source) {
+ Option4ClientFqdnImpl* old_impl = impl_;
+ impl_ = new Option4ClientFqdnImpl(*source.impl_);
+ delete(old_impl);
+ return (*this);
+}
+
+bool
+Option4ClientFqdn::getFlag(const uint8_t flag) const {
+ // Caller should query for one of the: E, N, S or O flags. Any other value
+ /// is invalid and results in the exception.
+ if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N && flag != FLAG_E) {
+ isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
+ << " Option flag specified, expected E, N, S or O");
+ }
+
+ return ((impl_->flags_ & flag) != 0);
+}
+
+void
+Option4ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) {
+ // Check that flag is in range between 0x1 and 0x7. Although it is
+ // discouraged this check doesn't preclude the caller from setting
+ // multiple flags concurrently.
+ if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
+ isc_throw(InvalidOption4FqdnFlags, "invalid DHCPv4 Client FQDN"
+ << " Option flag " << std::hex
+ << static_cast<int>(flag) << std::dec
+ << "is being set. Expected combination of E, N, S and O");
+ }
+
+ // Copy the current flags into local variable. That way we will be able
+ // to test new flags settings before applying them.
+ uint8_t new_flag = impl_->flags_;
+ if (set_flag) {
+ new_flag |= flag;
+ } else {
+ new_flag &= ~flag;
+ }
+
+ // Check new flags. If they are valid, apply them. Also, check that MBZ
+ // bits are not set.
+ Option4ClientFqdnImpl::checkFlags(new_flag, true);
+ impl_->flags_ = new_flag;
+}
+
+void
+Option4ClientFqdn::setRcode(const Rcode& rcode) {
+ impl_->rcode1_ = rcode;
+ impl_->rcode2_ = rcode;
+}
+
+void
+Option4ClientFqdn::resetFlags() {
+ impl_->flags_ = 0;
+}
+
+std::string
+Option4ClientFqdn::getDomainName() const {
+ if (impl_->domain_name_) {
+ return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
+ PARTIAL));
+ }
+ // If an object holding domain-name is NULL it means that the domain-name
+ // is empty.
+ return ("");
+}
+
+void
+Option4ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const {
+ // If domain-name is empty, do nothing.
+ if (!impl_->domain_name_) {
+ return;
+ }
+
+ if (getFlag(FLAG_E)) {
+ // Domain name, encoded as a set of labels.
+ isc::dns::LabelSequence labels(*impl_->domain_name_);
+ if (labels.getDataLength() > 0) {
+ size_t read_len = 0;
+ const uint8_t* data = labels.getData(&read_len);
+ if (impl_->domain_name_type_ == PARTIAL) {
+ --read_len;
+ }
+ buf.writeData(data, read_len);
+ }
+
+ } else {
+ std::string domain_name = impl_->domain_name_->toText();
+ buf.writeData(&domain_name[0], domain_name.size());
+
+ }
+}
+
+void
+Option4ClientFqdn::setDomainName(const std::string& domain_name,
+ const DomainNameType domain_name_type) {
+ impl_->setDomainName(domain_name, domain_name_type);
+}
+
+void
+Option4ClientFqdn::resetDomainName() {
+ setDomainName("", PARTIAL);
+}
+
+Option4ClientFqdn::DomainNameType
+Option4ClientFqdn::getDomainNameType() const {
+ return (impl_->domain_name_type_);
+}
+
+void
+Option4ClientFqdn::pack(isc::util::OutputBuffer& buf) {
+ // Header = option code and length.
+ packHeader(buf);
+ // Flags field.
+ buf.writeUint8(impl_->flags_);
+ // RCODE1 and RCODE2
+ buf.writeUint8(impl_->rcode1_.getCode());
+ buf.writeUint8(impl_->rcode2_.getCode());
+ // Domain name.
+ packDomainName(buf);
+}
+
+void
+Option4ClientFqdn::unpack(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+ setData(first, last);
+ impl_->parseWireData(first, last);
+ // Check that the flags in the received option are valid. Ignore MBZ bits,
+ // because we don't want to discard the whole option because of MBZ bits
+ // being set.
+ impl_->checkFlags(impl_->flags_, false);
+}
+
+std::string
+Option4ClientFqdn::toText(int indent) {
+ std::ostringstream stream;
+ std::string in(indent, ' '); // base indentation
+ stream << in << "type=" << type_ << " (CLIENT_FQDN), "
+ << "flags: ("
+ << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", "
+ << "E=" << (getFlag(FLAG_E) ? "1" : "0") << ", "
+ << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", "
+ << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), "
+ << "domain-name='" << getDomainName() << "' ("
+ << (getDomainNameType() == PARTIAL ? "partial" : "full")
+ << ")";
+
+ return (stream.str());
+}
+
+uint16_t
+Option4ClientFqdn::len() {
+ uint16_t domain_name_length = 0;
+ // If domain name is partial, the NULL terminating character
+ // is not included and the option length have to be adjusted.
+ if (impl_->domain_name_) {
+ domain_name_length = impl_->domain_name_type_ == FULL ?
+ impl_->domain_name_->getLength() : impl_->domain_name_->getLength() - 1;
+ }
+
+ return (getHeaderLen() + FIXED_FIELDS_LEN + domain_name_length);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option4_client_fqdn.h b/src/lib/dhcp/option4_client_fqdn.h
new file mode 100644
index 0000000..fc0fb66
--- /dev/null
+++ b/src/lib/dhcp/option4_client_fqdn.h
@@ -0,0 +1,370 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 OPTION4_CLIENT_FQDN_H
+#define OPTION4_CLIENT_FQDN_H
+
+#include <dhcp/option.h>
+#include <dns/name.h>
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when invalid flags have been specified for
+/// DHCPv4 Client FQDN %Option.
+class InvalidOption4FqdnFlags : public Exception {
+public:
+ InvalidOption4FqdnFlags(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception thrown when invalid domain name is specified.
+class InvalidOption4FqdnDomainName : public Exception {
+public:
+ InvalidOption4FqdnDomainName(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// Forward declaration to implementation of @c Option4ClientFqdn class.
+class Option4ClientFqdnImpl;
+
+/// @brief Represents DHCPv4 Client FQDN %Option (code 81).
+///
+/// This option has been defined in the RFC 4702 and it has a following
+/// structure:
+/// - Code (1 octet) - option code (always equal to 81).
+/// - Len (1 octet) - a length of the option.
+/// - Flags (1 octet) - a field carrying "NEOS" flags described below.
+/// - RCODE1 (1 octet) - deprecated field which should be set to 0 by the client
+/// and set to 255 by the server.
+/// - RCODE2 (1 octet) - deprecated, should be used in the same way as RCODE1.
+/// - Domain Name - variable length field comprising partial or fully qualified
+/// domain name.
+///
+/// The flags field has the following structure:
+/// @code
+/// 0 1 2 3 4 5 6 7
+/// +-+-+-+-+-+-+-+-+
+/// | MBZ |N|E|O|S|
+/// +-+-+-+-+-+-+-+-+
+/// @endcode
+/// where:
+/// - N flag specifies whether server should (0) or should not (1) perform DNS
+/// Update,
+/// - E flag specifies encoding of the Domain Name field. If this flag is set
+/// to 1 it indicates canonical wire format without compression. 0 indicates
+/// the deprecated ASCII format.
+/// - O flag is set by the server to indicate that it has overridden client's
+/// preference set with the S bit.
+/// - S flag specifies whether server should (1) or should not (0) perform
+/// forward (FQDN-to-address) updates.
+///
+/// This class exposes a set of functions to modify flags and check their
+/// correctness.
+///
+/// Domain names being carried by DHCPv4 Client Fqdn %Option can be fully
+/// qualified or partial. Partial domain names are encoded similar to the
+/// fully qualified domain names, except that they lack terminating zero
+/// at the end of their wire representation (or lack of dot at the end, in
+/// case of ASCII encoding). It is also accepted to create an instance of
+/// this option which has empty domain-name. Clients use empty domain-names
+/// to indicate that server should generate complete fully qualified
+/// domain-name.
+///
+/// @warning: The RFC4702 section 2.3.1 states that the clients and servers
+/// should use character sets specified in RFC952, section 2.1 for ASCII-encoded
+/// domain-names. This class doesn't detect the character set violation for
+/// ASCII-encoded domain-name. It could be implemented in the future but it is
+/// not important now for two reasons:
+/// - ASCII encoding is deprecated
+/// - clients SHOULD obey restrictions but if they don't, server may still
+/// process the option
+///
+/// RFC 4702 mandates that the DHCP client sets RCODE1 and RCODE2 to 0 and that
+/// server sets them to 255. This class allows to set the value for these
+/// fields and both fields are always set to the same value. There is no way
+/// to set them separately (e.g. set different value for RCODE1 and RCODE2).
+/// However, there are no use cases which would require it.
+///
+/// <b>Design choice:</b> This class uses pimpl idiom to separate the interface
+/// from implementation specifics. Implementations may use different approaches
+/// to handle domain names (mostly validation of the domain-names). The existing
+/// @c isc::dns::Name class is a natural (and the simplest) choice to handle
+/// domain-names. Use of this class however, implies that libdhcp must be linked
+/// with libdns. At some point these libraries may need to be separated, i.e. to
+/// support compilation and use of standalone DHCP server. This will require
+/// that the part of implementation which deals with domain-names is modified to
+/// not use classes from libdns. These changes will be transparent for this
+/// interface.
+class Option4ClientFqdn : public Option {
+public:
+
+ ///
+ /// @name A set of constants used to identify and set bits in the flags field
+ //@{
+ static const uint8_t FLAG_S = 0x01; ///< Bit S
+ static const uint8_t FLAG_O = 0x02; ///< Bit O
+ static const uint8_t FLAG_E = 0x04; ///< Bit E
+ static const uint8_t FLAG_N = 0x08; ///< Bit N
+ //@}
+
+ /// @brief Mask which zeroes MBZ flag bits.
+ static const uint8_t FLAG_MASK = 0xF;
+
+ /// @brief Represents the value of one of the RCODE1 or RCODE2 fields.
+ ///
+ /// Typically, RCODE values are set to 255 by the server and to 0 by the
+ /// clients (as per RFC 4702).
+ class Rcode {
+ public:
+ Rcode(const uint8_t rcode)
+ : rcode_(rcode) { }
+
+ /// @brief Returns the value of the RCODE.
+ ///
+ /// Returned value can be directly used to create the on-wire format
+ /// of the DHCPv4 Client FQDN %Option.
+ uint8_t getCode() const {
+ return (rcode_);
+ }
+
+ private:
+ uint8_t rcode_;
+ };
+
+
+ /// @brief Type of the domain-name: partial or full.
+ enum DomainNameType {
+ PARTIAL,
+ FULL
+ };
+
+ /// @brief The size in bytes of the fixed fields within DHCPv4 Client Fqdn
+ /// %Option.
+ ///
+ /// The fixed fields are:
+ /// - Flags
+ /// - RCODE1
+ /// - RCODE2
+ static const uint16_t FIXED_FIELDS_LEN = 3;
+
+ /// @brief Constructor, creates option instance using flags and domain name.
+ ///
+ /// This constructor is used to create an instance of the option which will
+ /// be included in outgoing messages.
+ ///
+ /// Note that the RCODE values are encapsulated by the Rcode object (not a
+ /// simple uint8_t value). This helps to prevent a caller from confusing the
+ /// flags value with rcode value (both are uint8_t values). For example:
+ /// if caller swaps the two, it will be detected in the compilation time.
+ /// Also, this API encourages the caller to use two predefined functions:
+ /// @c RCODE_SERVER and @c RCODE_CLIENT to set the value of RCODE. These
+ /// functions generate objects which represent the only valid values to be
+ /// be passed to the constructor (255 and 0 respectively). Other
+ /// values should not be used. However, it is still possible that the other
+ /// entity (client or server) sends the option with invalid value. Although,
+ /// the RCODE values are ignored, there should be a way to represent such
+ /// invalid RCODE value. The Rcode class is capable of representing it.
+ ///
+ /// @param flags a combination of flags to be stored in flags field.
+ /// @param rcode @c Rcode object representing a value for RCODE1 and RCODE2
+ /// fields of the option. Both fields are assigned the same value
+ /// encapsulated by the parameter.
+ /// @param domain_name a name to be stored in the domain-name field.
+ /// @param domain_name_type indicates if the domain name is partial
+ /// or full.
+ /// @throw InvalidOption4FqdnFlags if value of the flags field is wrong.
+ /// @throw InvalidOption4FqdnDomainName if the domain-name is invalid.
+ explicit Option4ClientFqdn(const uint8_t flags,
+ const Rcode& rcode,
+ const std::string& domain_name,
+ const DomainNameType domain_name_type = FULL);
+
+ /// @brief Constructor, creates option instance with empty domain name.
+ ///
+ /// This constructor creates an instance of the option with empty
+ /// domain-name. This domain-name is marked partial.
+ ///
+ /// @param flags a combination of flags to be stored in flags field.
+ /// @param rcode @c Rcode object representing a value for RCODE1 and RCODE2
+ /// fields. Both fields are assigned the same value encapsulated by this
+ /// parameter.
+ /// @throw InvalidOption4FqdnFlags if value of the flags field is invalid.
+ Option4ClientFqdn(const uint8_t flags, const Rcode& rcode);
+
+ /// @brief Constructor, creates an option instance from part of the buffer.
+ ///
+ /// This constructor is mainly used to parse options in the received
+ /// messages. Function parameters specify buffer bounds from which the
+ /// option should be created. The size of the buffer chunk, specified by
+ /// the constructor's parameters should be equal or larger than the size
+ /// of the option. Otherwise, constructor will throw an exception.
+ ///
+ /// @param first the lower bound of the buffer to create option from.
+ /// @param last the upper bound of the buffer to create option from.
+ /// @throw InvalidOption4FqdnFlags if value of the flags field is invalid.
+ /// @throw InvalidOption4FqdnDomainName if the domain-name carried by the
+ /// option is invalid.
+ /// @throw OutOfRange if the option is truncated.
+ explicit Option4ClientFqdn(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Copy constructor
+ Option4ClientFqdn(const Option4ClientFqdn& source);
+
+ /// @brief Destructor
+ virtual ~Option4ClientFqdn();
+
+ /// @brief Assignment operator
+ Option4ClientFqdn& operator=(const Option4ClientFqdn& source);
+
+ /// @brief Checks if the specified flag of the DHCPv4 Client FQDN %Option
+ /// is set.
+ ///
+ /// @param flag A value specifying a bit within flags field to be checked.
+ /// It must be one of the following @c FLAG_S, @c FLAG_E, @c FLAG_O,
+ /// @c FLAG_N.
+ ///
+ /// @return true if the bit of the specified flags bit is set, false
+ /// otherwise.
+ /// @throw InvalidOption4ClientFlags if specified flag which value is to be
+ /// returned is invalid (is not one of the FLAG_S, FLAG_N, FLAG_O).
+ bool getFlag(const uint8_t flag) const;
+
+ /// @brief Modifies the value of the specified DHCPv4 Client Fqdn %Option
+ /// flag.
+ ///
+ /// @param flag A value specifying a bit within flags field to be set. It
+ /// must be one of the following @c FLAG_S, @c FLAG_E, @c FLAG_O, @c FLAG_N.
+ /// @param set a boolean value which indicates whether flag should be
+ /// set (true), or cleared (false).
+ /// @throw InvalidOption4ClientFlags if specified flag which value is to be
+ /// set is invalid (is not one of the FLAG_S, FLAG_N, FLAG_O).
+ void setFlag(const uint8_t flag, const bool set);
+
+ /// @brief Sets the flag field value to 0.
+ void resetFlags();
+
+ /// @brief Set Rcode value.
+ ///
+ /// @param rcode An @c Rcode object representing value of RCODE1 and RCODE2.
+ /// Both fields are assigned the same value.
+ void setRcode(const Rcode& rcode);
+
+ /// @brief Returns the domain-name in the text format.
+ ///
+ /// If domain-name is partial, it lacks the dot at the end (e.g. myhost).
+ /// If domain-name is fully qualified, it has the dot at the end (e.g.
+ /// myhost.example.com.).
+ ///
+ /// @return domain-name in the text format.
+ std::string getDomainName() const;
+
+ /// @brief Writes domain-name in the wire format into a buffer.
+ ///
+ /// The data being written are appended at the end of the buffer.
+ ///
+ /// @param [out] buf buffer where domain-name will be written.
+ void packDomainName(isc::util::OutputBuffer& buf) const;
+
+ /// @brief Set new domain-name.
+ ///
+ /// @param domain_name domain name field value in the text format.
+ /// @param domain_name_type type of the domain name: partial or fully
+ /// qualified.
+ /// @throw InvalidOption4FqdnDomainName if the specified domain-name is
+ /// invalid.
+ void setDomainName(const std::string& domain_name,
+ const DomainNameType domain_name_type);
+
+ /// @brief Set empty domain-name.
+ ///
+ /// This function is equivalent to @c Option6ClientFqdn::setDomainName
+ /// with empty partial domain-name. It is exception safe.
+ void resetDomainName();
+
+ /// @brief Returns enumerator value which indicates whether domain-name is
+ /// partial or full.
+ ///
+ /// @return An enumerator value indicating whether domain-name is partial
+ /// or full.
+ DomainNameType getDomainNameType() const;
+
+ /// @brief Writes option in the wire format into a buffer.
+ ///
+ /// @param [out] buf output buffer where option data will be stored.
+ virtual void pack(isc::util::OutputBuffer& buf);
+
+ /// @brief Parses option from the received buffer.
+ ///
+ /// Method creates an instance of the DHCPv4 Client FQDN %Option from the
+ /// wire format. Parameters specify the bounds of the buffer to read option
+ /// data from. The size of the buffer limited by the specified parameters
+ /// should be equal or larger than size of the option (including its
+ /// header). Otherwise exception will be thrown.
+ ///
+ /// @param first lower bound of the buffer to parse option from.
+ /// @param last upper bound of the buffer to parse option from.
+ virtual void unpack(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Returns string representation of the option.
+ ///
+ /// The string returned by the method comprises the bit value of each
+ /// option flag and the domain-name.
+ ///
+ /// @param indent number of spaces before printed text.
+ ///
+ /// @return string with text representation.
+ virtual std::string toText(int indent = 0);
+
+ /// @brief Returns length of the complete option (data length +
+ /// DHCPv4 option header).
+ ///
+ /// @return length of the option.
+ virtual uint16_t len();
+
+ ///
+ /// @name Well known Rcode declarations for DHCPv4 Client FQDN %Option
+ ///
+ //@{
+ /// @brief Rcode being set by the server.
+ inline static const Rcode& RCODE_SERVER() {
+ static Rcode rcode(255);
+ return (rcode);
+ }
+
+ /// @brief Rcode being set by the client.
+ inline static const Rcode& RCODE_CLIENT() {
+ static Rcode rcode(0);
+ return (rcode);
+ }
+ //@}
+
+private:
+
+ /// @brief A pointer to the implementation.
+ Option4ClientFqdnImpl* impl_;
+};
+
+/// A pointer to the @c Option4ClientFqdn object.
+typedef boost::shared_ptr<Option4ClientFqdn> Option4ClientFqdnPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION4_CLIENT_FQDN_H
diff --git a/src/lib/dhcp/option6_client_fqdn.cc b/src/lib/dhcp/option6_client_fqdn.cc
new file mode 100644
index 0000000..761acae
--- /dev/null
+++ b/src/lib/dhcp/option6_client_fqdn.cc
@@ -0,0 +1,455 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <dhcp/dhcp6.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dns/labelsequence.h>
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+#include <util/strutil.h>
+#include <sstream>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Implements the logic for the Option6ClientFqdn class.
+///
+/// The purpose of the class is to separate the implementation details
+/// of the Option6ClientFqdn class from the interface. This implementation
+/// uses b10-libdns classes to process FQDNs. At some point it may be
+/// desired to split b10-libdhcp++ from b10-libdns. In such case the
+/// implementation of this class may be changed. The declaration of the
+/// Option6ClientFqdn class holds the pointer to implementation, so
+/// the transition to a different implementation would not affect the
+/// header file.
+class Option6ClientFqdnImpl {
+public:
+ /// Holds flags carried by the option.
+ uint8_t flags_;
+ /// Holds the pointer to a domain name carried in the option.
+ boost::shared_ptr<isc::dns::Name> domain_name_;
+ /// Indicates whether domain name is partial or fully qualified.
+ Option6ClientFqdn::DomainNameType domain_name_type_;
+
+ /// @brief Constructor, from domain name.
+ ///
+ /// @param flags A value of the flags option field.
+ /// @param domain_name A domain name carried by the option given in the
+ /// textual format.
+ /// @param name_type A value which indicates whether domain-name
+ /// is partial of fully qualified.
+ Option6ClientFqdnImpl(const uint8_t flags,
+ const std::string& domain_name,
+ const Option6ClientFqdn::DomainNameType name_type);
+
+ /// @brief Constructor, from wire data.
+ ///
+ /// @param first An iterator pointing to the begining of the option data
+ /// in the wire format.
+ /// @param last An iterator poiting to the end of the option data in the
+ /// wire format.
+ Option6ClientFqdnImpl(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Copy constructor.
+ ///
+ /// @param source An object being copied.
+ Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source);
+
+ /// @brief Assignment operator.
+ ///
+ /// @param source An object which is being assigned.
+ Option6ClientFqdnImpl& operator=(const Option6ClientFqdnImpl& source);
+
+ /// @brief Set a new domain name for the option.
+ ///
+ /// @param domain_name A new domain name to be assigned.
+ /// @param name_type A value which indicates whether the domain-name is
+ /// partial or fully qualified.
+ void setDomainName(const std::string& domain_name,
+ const Option6ClientFqdn::DomainNameType name_type);
+
+ /// @brief Check if flags are valid.
+ ///
+ /// In particular, this function checks if the N and S bits are not
+ /// set to 1 in the same time.
+ ///
+ /// @param flags A value carried by the flags field of the option.
+ /// @param check_mbz A boolean value which indicates if this function should
+ /// check if the MBZ bits are set (if true). This parameter should be set
+ /// to false when validating flags in the received message. This is because
+ /// server should ignore MBZ bits in received messages.
+ /// @throw InvalidOption6FqdnFlags if flags are invalid.
+ static void checkFlags(const uint8_t flags, const bool check_mbz);
+
+ /// @brief Parse the Option provided in the wire format.
+ ///
+ /// @param first An iterator pointing to the begining of the option data
+ /// in the wire format.
+ /// @param last An iterator poiting to the end of the option data in the
+ /// wire format.
+ void parseWireData(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+};
+
+Option6ClientFqdnImpl::
+Option6ClientFqdnImpl(const uint8_t flags,
+ const std::string& domain_name,
+ // cppcheck 1.57 complains that const enum value is not
+ // passed by reference. Note that it accepts the non-const
+ // enum to be passed by value. In both cases it is
+ // unnecessary to pass the enum by reference.
+ // cppcheck-suppress passedByValue
+ const Option6ClientFqdn::DomainNameType name_type)
+ : flags_(flags),
+ domain_name_(),
+ domain_name_type_(name_type) {
+
+ // Check if flags are correct. Also check if MBZ bits are set.
+ checkFlags(flags_, true);
+ // Set domain name. It may throw an exception if domain name has wrong
+ // format.
+ setDomainName(domain_name, name_type);
+}
+
+Option6ClientFqdnImpl::Option6ClientFqdnImpl(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+ parseWireData(first, last);
+ // Verify that flags value was correct. Do not check if MBZ bits are
+ // set because we should ignore those bits in received message.
+ checkFlags(flags_, false);
+}
+
+Option6ClientFqdnImpl::
+Option6ClientFqdnImpl(const Option6ClientFqdnImpl& source)
+ : flags_(source.flags_),
+ domain_name_(),
+ domain_name_type_(source.domain_name_type_) {
+ if (source.domain_name_) {
+ domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+ }
+}
+
+Option6ClientFqdnImpl&
+// This assignment operator handles assignment to self, it copies all
+// required values.
+// cppcheck-suppress operatorEqToSelf
+Option6ClientFqdnImpl::operator=(const Option6ClientFqdnImpl& source) {
+ if (source.domain_name_) {
+ domain_name_.reset(new isc::dns::Name(*source.domain_name_));
+
+ } else {
+ domain_name_.reset();
+
+ }
+
+ // This assignment should be exception safe.
+ flags_ = source.flags_;
+ domain_name_type_ = source.domain_name_type_;
+
+ return (*this);
+}
+
+void
+Option6ClientFqdnImpl::
+setDomainName(const std::string& domain_name,
+ // cppcheck 1.57 complains that const enum value is not
+ // passed by reference. Note that it accepts the non-const
+ // enum to be passed by value. In both cases it is
+ // unnecessary to pass the enum by reference.
+ // cppcheck-suppress passedByValue
+ const Option6ClientFqdn::DomainNameType name_type) {
+ // domain-name must be trimmed. Otherwise, string comprising spaces only
+ // would be treated as a fully qualified name.
+ std::string name = isc::util::str::trim(domain_name);
+ if (name.empty()) {
+ if (name_type == Option6ClientFqdn::FULL) {
+ isc_throw(InvalidOption6FqdnDomainName,
+ "fully qualified domain-name must not be empty"
+ << " when setting new domain-name for DHCPv6 Client"
+ << " FQDN Option");
+ }
+ // The special case when domain-name is empty is marked by setting the
+ // pointer to the domain-name object to NULL.
+ domain_name_.reset();
+
+ } else {
+ try {
+ domain_name_.reset(new isc::dns::Name(name, true));
+ domain_name_type_ = name_type;
+
+ } catch (const Exception& ex) {
+ isc_throw(InvalidOption6FqdnDomainName, "invalid domain-name value '"
+ << domain_name << "' when setting new domain-name for"
+ << " DHCPv6 Client FQDN Option");
+
+ }
+ }
+}
+
+void
+Option6ClientFqdnImpl::checkFlags(const uint8_t flags, const bool check_mbz) {
+ // The Must Be Zero (MBZ) bits must not be set.
+ if (check_mbz && ((flags & ~Option6ClientFqdn::FLAG_MASK) != 0)) {
+ isc_throw(InvalidOption6FqdnFlags,
+ "invalid DHCPv6 Client FQDN Option flags: 0x"
+ << std::hex << static_cast<int>(flags) << std::dec);
+ }
+
+ // According to RFC 4704, section 4.1. if the N bit is 1, the S bit
+ // MUST be 0. Checking it here.
+ if ((flags & (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S))
+ == (Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S)) {
+ isc_throw(InvalidOption6FqdnFlags,
+ "both N and S flag of the DHCPv6 Client FQDN Option are set."
+ << " According to RFC 4704, if the N bit is 1 the S bit"
+ << " MUST be 0");
+ }
+}
+
+void
+Option6ClientFqdnImpl::parseWireData(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+
+ // Buffer must comprise at least one byte with the flags.
+ // The domain-name may be empty.
+ if (std::distance(first, last) < Option6ClientFqdn::FLAG_FIELD_LEN) {
+ isc_throw(OutOfRange, "DHCPv6 Client FQDN Option ("
+ << D6O_CLIENT_FQDN << ") is truncated. Minimal option"
+ << " size is " << Option6ClientFqdn::FLAG_FIELD_LEN
+ << ", got option with size " << std::distance(first, last));
+ }
+
+ // Parse flags
+ flags_ = *(first++);
+
+ // Parse domain-name if any.
+ if (std::distance(first, last) > 0) {
+ // The FQDN may comprise a partial domain-name. In this case it lacks
+ // terminating 0. If this is the case, we will need to add zero at
+ // the end because Name object constructor requires it.
+ if (*(last - 1) != 0) {
+ // Create temporary buffer and add terminating zero.
+ OptionBuffer buf(first, last);
+ buf.push_back(0);
+ // Reset domain name.
+ isc::util::InputBuffer name_buf(&buf[0], buf.size());
+ try {
+ domain_name_.reset(new isc::dns::Name(name_buf, true));
+ } catch (const Exception& ex) {
+ isc_throw(InvalidOption6FqdnDomainName, "failed to parse"
+ "partial domain-name from wire format");
+ }
+ // Terminating zero was missing, so set the domain-name type
+ // to partial.
+ domain_name_type_ = Option6ClientFqdn::PARTIAL;
+ } else {
+ // We are dealing with fully qualified domain name so there is
+ // no need to add terminating zero. Simply pass the buffer to
+ // Name object constructor.
+ isc::util::InputBuffer name_buf(&(*first),
+ std::distance(first, last));
+ try {
+ domain_name_.reset(new isc::dns::Name(name_buf, true));
+ } catch (const Exception& ex) {
+ isc_throw(InvalidOption6FqdnDomainName, "failed to parse"
+ "fully qualified domain-name from wire format");
+ }
+ // Set the domain-type to fully qualified domain name.
+ domain_name_type_ = Option6ClientFqdn::FULL;
+ }
+ }
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag)
+ : Option(Option::V6, D6O_CLIENT_FQDN),
+ impl_(new Option6ClientFqdnImpl(flag, "", PARTIAL)) {
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const uint8_t flag,
+ const std::string& domain_name,
+ const DomainNameType domain_name_type)
+ : Option(Option::V6, D6O_CLIENT_FQDN),
+ impl_(new Option6ClientFqdnImpl(flag, domain_name, domain_name_type)) {
+}
+
+Option6ClientFqdn::Option6ClientFqdn(OptionBufferConstIter first,
+ OptionBufferConstIter last)
+ : Option(Option::V6, D6O_CLIENT_FQDN, first, last),
+ impl_(new Option6ClientFqdnImpl(first, last)) {
+}
+
+Option6ClientFqdn::~Option6ClientFqdn() {
+ delete(impl_);
+}
+
+Option6ClientFqdn::Option6ClientFqdn(const Option6ClientFqdn& source)
+ : Option(source),
+ impl_(new Option6ClientFqdnImpl(*source.impl_)) {
+}
+
+Option6ClientFqdn&
+// This assignment operator handles assignment to self, it uses copy
+// constructor of Option6ClientFqdnImpl to copy all required values.
+// cppcheck-suppress operatorEqToSelf
+Option6ClientFqdn::operator=(const Option6ClientFqdn& source) {
+ Option6ClientFqdnImpl* old_impl = impl_;
+ impl_ = new Option6ClientFqdnImpl(*source.impl_);
+ delete(old_impl);
+ return (*this);
+}
+
+bool
+Option6ClientFqdn::getFlag(const uint8_t flag) const {
+ // Caller should query for one of the: N, S or O flags. Any other
+ // value is invalid.
+ if (flag != FLAG_S && flag != FLAG_O && flag != FLAG_N) {
+ isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN"
+ << " Option flag specified, expected N, S or O");
+ }
+
+ return ((impl_->flags_ & flag) != 0);
+}
+
+void
+Option6ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) {
+ // Check that flag is in range between 0x1 and 0x7. Note that this
+ // allows to set or clear multiple flags concurrently. Setting
+ // concurrent bits is discouraged (see header file) but it is not
+ // checked here so it will work.
+ if (((flag & ~FLAG_MASK) != 0) || (flag == 0)) {
+ isc_throw(InvalidOption6FqdnFlags, "invalid DHCPv6 Client FQDN"
+ << " Option flag " << std::hex
+ << static_cast<int>(flag) << std::dec
+ << "is being set. Expected: N, S or O");
+ }
+
+ // Copy the current flags into local variable. That way we will be able
+ // to test new flags settings before applying them.
+ uint8_t new_flag = impl_->flags_;
+ if (set_flag) {
+ new_flag |= flag;
+ } else {
+ new_flag &= ~flag;
+ }
+
+ // Check new flags. If they are valid, apply them.
+ Option6ClientFqdnImpl::checkFlags(new_flag, true);
+ impl_->flags_ = new_flag;
+}
+
+void
+Option6ClientFqdn::resetFlags() {
+ impl_->flags_ = 0;
+}
+
+std::string
+Option6ClientFqdn::getDomainName() const {
+ if (impl_->domain_name_) {
+ return (impl_->domain_name_->toText(impl_->domain_name_type_ ==
+ PARTIAL));
+ }
+ // If an object holding domain-name is NULL it means that the domain-name
+ // is empty.
+ return ("");
+}
+
+void
+Option6ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const {
+ // There is nothing to do if domain-name is empty.
+ if (!impl_->domain_name_) {
+ return;
+ }
+
+ // Domain name, encoded as a set of labels.
+ isc::dns::LabelSequence labels(*impl_->domain_name_);
+ if (labels.getDataLength() > 0) {
+ size_t read_len = 0;
+ const uint8_t* data = labels.getData(&read_len);
+ if (impl_->domain_name_type_ == PARTIAL) {
+ --read_len;
+ }
+ buf.writeData(data, read_len);
+ }
+}
+
+void
+Option6ClientFqdn::setDomainName(const std::string& domain_name,
+ const DomainNameType domain_name_type) {
+ impl_->setDomainName(domain_name, domain_name_type);
+}
+
+void
+Option6ClientFqdn::resetDomainName() {
+ setDomainName("", PARTIAL);
+}
+
+Option6ClientFqdn::DomainNameType
+Option6ClientFqdn::getDomainNameType() const {
+ return (impl_->domain_name_type_);
+}
+
+void
+Option6ClientFqdn::pack(isc::util::OutputBuffer& buf) {
+ // Header = option code and length.
+ packHeader(buf);
+ // Flags field.
+ buf.writeUint8(impl_->flags_);
+ // Domain name.
+ packDomainName(buf);
+}
+
+void
+Option6ClientFqdn::unpack(OptionBufferConstIter first,
+ OptionBufferConstIter last) {
+ setData(first, last);
+ impl_->parseWireData(first, last);
+ // Check that the flags in the received option are valid. Ignore MBZ bits
+ // because we don't want to discard the whole option because of MBZ bits
+ // being set.
+ impl_->checkFlags(impl_->flags_, false);
+}
+
+std::string
+Option6ClientFqdn::toText(int indent) {
+ std::ostringstream stream;
+ std::string in(indent, ' '); // base indentation
+ stream << in << "type=" << type_ << "(CLIENT_FQDN)" << ", "
+ << "flags: ("
+ << "N=" << (getFlag(FLAG_N) ? "1" : "0") << ", "
+ << "O=" << (getFlag(FLAG_O) ? "1" : "0") << ", "
+ << "S=" << (getFlag(FLAG_S) ? "1" : "0") << "), "
+ << "domain-name='" << getDomainName() << "' ("
+ << (getDomainNameType() == PARTIAL ? "partial" : "full")
+ << ")";
+
+ return (stream.str());
+}
+
+uint16_t
+Option6ClientFqdn::len() {
+ uint16_t domain_name_length = 0;
+ if (impl_->domain_name_) {
+ // If domain name is partial, the NULL terminating character
+ // is not included and the option. Length has to be adjusted.
+ domain_name_length = impl_->domain_name_type_ == FULL ?
+ impl_->domain_name_->getLength() :
+ impl_->domain_name_->getLength() - 1;
+ }
+ return (getHeaderLen() + FLAG_FIELD_LEN + domain_name_length);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option6_client_fqdn.h b/src/lib/dhcp/option6_client_fqdn.h
new file mode 100644
index 0000000..f67e7d5
--- /dev/null
+++ b/src/lib/dhcp/option6_client_fqdn.h
@@ -0,0 +1,271 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 OPTION6_CLIENT_FQDN_H
+#define OPTION6_CLIENT_FQDN_H
+
+#include <dhcp/option.h>
+#include <dns/name.h>
+
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when invalid flags have been specified for
+/// DHCPv6 Client Fqdn %Option.
+class InvalidOption6FqdnFlags : public Exception {
+public:
+ InvalidOption6FqdnFlags(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Exception thrown when invalid domain name is specified.
+class InvalidOption6FqdnDomainName : public Exception {
+public:
+ InvalidOption6FqdnDomainName(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// Forward declaration to implementation of @c Option6ClientFqdn class.
+class Option6ClientFqdnImpl;
+
+/// @brief Represents DHCPv6 Client FQDN %Option (code 39).
+///
+/// This option has been defined in the RFC 4704 and it has a following
+/// structure:
+/// - option-code = 39 (2 octets)
+/// - option-len (2 octets)
+/// - flags (1 octet)
+/// - domain-name - variable length field comprising partial or fully qualified
+/// domain name.
+///
+/// The flags field has the following structure:
+/// @code
+/// 0 1 2 3 4 5 6 7
+/// +-+-+-+-+-+-+-+-+
+/// | MBZ |N|O|S|
+/// +-+-+-+-+-+-+-+-+
+/// @endcode
+/// where:
+/// - N flag specifies whether server should (0) or should not (1) perform DNS
+/// Update,
+/// - O flag is set by the server to indicate that it has overridden client's
+/// preference set with the S bit.
+/// - S flag specifies whether server should (1) or should not (0) perform
+/// forward (FQDN-to-address) updates.
+///
+/// This class exposes a set of functions to modify flags and check their
+/// correctness.
+///
+/// Domain names being carried by DHCPv6 Client Fqdn %Option can be fully
+/// qualified or partial. Partial domain names are encoded similar to the
+/// fully qualified domain names, except that they lack terminating zero
+/// at the end of their wire representation. It is also accepted to create an
+/// instance of this option which has empty domain-name. Clients use empty
+/// domain-names to indicate that server should generate complete fully
+/// qualified domain-name.
+///
+/// <b>Design choice:</b> This class uses pimpl idiom to separate the interface
+/// from implementation specifics. Implementations may use different approaches
+/// to handle domain names (mostly validation of the domain-names). The existing
+/// @c isc::dns::Name class is a natural (and the simplest) choice to handle
+/// domain-names. Use of this class however, implies that libdhcp must be linked
+/// with libdns. At some point these libraries may need to be separated, i.e. to
+/// support compilation and use of standalone DHCP server. This will require
+/// that the part of implementation which deals with domain-names is modified to
+/// not use classes from libdns. These changes will be transparent for this
+/// interface.
+class Option6ClientFqdn : public Option {
+public:
+
+ ///
+ ///@name A set of constants setting respective bits in 'flags' field
+ //@{
+ static const uint8_t FLAG_S = 0x01; ///< S bit.
+ static const uint8_t FLAG_O = 0x02; ///< O bit.
+ static const uint8_t FLAG_N = 0x04; ///< N bit.
+ //@}
+
+ /// @brief Mask which zeroes MBZ flag bits.
+ static const uint8_t FLAG_MASK = 0x7;
+
+ /// @brief The length of the flag field within DHCPv6 Client Fqdn %Option.
+ static const uint16_t FLAG_FIELD_LEN = 1;
+
+ /// @brief Type of the domain-name: partial or full.
+ enum DomainNameType {
+ PARTIAL,
+ FULL
+ };
+
+ /// @brief Constructor, creates option instance using flags and domain name.
+ ///
+ /// This constructor is used to create instance of the option which will be
+ /// included in outgoing messages.
+ ///
+ /// @param flags a combination of flag bits to be stored in flags field.
+ /// @param domain_name a name to be stored in the domain-name field.
+ /// @param domain_name_type indicates if the domain name is partial
+ /// or full.
+ explicit Option6ClientFqdn(const uint8_t flags,
+ const std::string& domain_name,
+ const DomainNameType domain_name_type = FULL);
+
+ /// @brief Constructor, creates option instance using flags.
+ ///
+ /// This constructor creates an instance of the option with empty
+ /// domain-name. This domain-name is marked partial.
+ ///
+ /// @param flags A combination of flag bits to be stored in flags field.
+ Option6ClientFqdn(const uint8_t flags);
+
+ /// @brief Constructor, creates an option instance from part of the buffer.
+ ///
+ /// This constructor is mainly used to parse options in the received
+ /// messages. Function parameters specify buffer bounds from which the
+ /// option should be created. The size of the buffer chunk, specified by
+ /// the constructor's parameters should be equal or larger than the size
+ /// of the option. Otherwise, constructor will throw an exception.
+ ///
+ /// @param first the lower bound of the buffer to create option from.
+ /// @param last the upper bound of the buffer to create option from.
+ explicit Option6ClientFqdn(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Copy constructor
+ Option6ClientFqdn(const Option6ClientFqdn& source);
+
+ /// @brief Destructor
+ virtual ~Option6ClientFqdn();
+
+ /// @brief Assignment operator
+ Option6ClientFqdn& operator=(const Option6ClientFqdn& source);
+
+ /// @brief Checks if the specified flag of the DHCPv6 Client FQDN %Option
+ /// is set.
+ ///
+ /// This method checks the single bit of flags field. Therefore, a caller
+ /// should use one of the: @c FLAG_S, @c FLAG_N, @c FLAG_O constants as
+ /// an argument of the function. Attempt to use any other value (including
+ /// combinations of these constants) will result in exception.
+ ///
+ /// @param flag A value specifying the flags bit to be checked. It can be
+ /// one of the following: @c FLAG_S, @c FLAG_N, @c FLAG_O.
+ ///
+ /// @return true if the bit of the specified flag is set, false otherwise.
+ bool getFlag(const uint8_t flag) const;
+
+ /// @brief Modifies the value of the specified DHCPv6 Client Fqdn %Option
+ /// flag.
+ ///
+ /// This method sets the single bit of flags field. Therefore, a caller
+ /// should use one of the: @c FLAG_S, @c FLAG_N, @c FLAG_O constants as
+ /// an argument of the function. Attempt to use any other value (including
+ /// combinations of these constants) will result in exception.
+ ///
+ /// @param flag A value specifying the flags bit to be modified. It can
+ /// be one of the following: @c FLAG_S, @c FLAG_N, @c FLAG_O.
+ /// @param set a boolean value which indicates whether flag should be
+ /// set (true), or cleared (false).
+ void setFlag(const uint8_t flag, const bool set);
+
+ /// @brief Sets the flag field value to 0.
+ void resetFlags();
+
+ /// @brief Returns the domain-name in the text format.
+ ///
+ /// If domain-name is partial, it lacks the dot at the end (e.g. myhost).
+ /// If domain-name is fully qualified, it has the dot at the end (e.g.
+ /// myhost.example.com.).
+ ///
+ /// @return domain-name in the text format.
+ std::string getDomainName() const;
+
+ /// @brief Writes domain-name in the wire format into a buffer.
+ ///
+ /// The data being written are appended at the end of the buffer.
+ ///
+ /// @param [out] buf buffer where domain-name will be written.
+ void packDomainName(isc::util::OutputBuffer& buf) const;
+
+ /// @brief Set new domain-name.
+ ///
+ /// @param domain_name domain name field value in the text format.
+ /// @param domain_name_type type of the domain name: partial or fully
+ /// qualified.
+ void setDomainName(const std::string& domain_name,
+ const DomainNameType domain_name_type);
+
+ /// @brief Set empty domain-name.
+ ///
+ /// This function is equivalent to @c Option6ClientFqdn::setDomainName
+ /// with empty partial domain-name. It is exception safe.
+ void resetDomainName();
+
+ /// @brief Returns enumerator value which indicates whether domain-name is
+ /// partial or full.
+ ///
+ /// @return An enumerator value indicating whether domain-name is partial
+ /// or full.
+ DomainNameType getDomainNameType() const;
+
+ /// @brief Writes option in the wire format into a buffer.
+ ///
+ /// @param [out] buf output buffer where option data will be stored.
+ virtual void pack(isc::util::OutputBuffer& buf);
+
+ /// @brief Parses option from the received buffer.
+ ///
+ /// Method creates an instance of the DHCPv6 Client FQDN %Option from the
+ /// wire format. Parameters specify the bounds of the buffer to read option
+ /// data from. The size of the buffer limited by the specified parameters
+ /// should be equal or larger than size of the option (including its
+ /// header). Otherwise exception will be thrown.
+ ///
+ /// @param first lower bound of the buffer to parse option from.
+ /// @param last upper bound of the buffer to parse option from.
+ virtual void unpack(OptionBufferConstIter first,
+ OptionBufferConstIter last);
+
+ /// @brief Returns string representation of the option.
+ ///
+ /// The string returned by the method comprises the bit value of each
+ /// option flag and the domain-name.
+ ///
+ /// @param indent number of spaces before printed text.
+ ///
+ /// @return string with text representation.
+ virtual std::string toText(int indent = 0);
+
+ /// @brief Returns length of the complete option (data length +
+ /// DHCPv6 option header).
+ ///
+ /// @return length of the option.
+ virtual uint16_t len();
+
+private:
+
+ /// @brief A pointer to the implementation.
+ Option6ClientFqdnImpl* impl_;
+};
+
+/// A pointer to the @c Option6ClientFqdn object.
+typedef boost::shared_ptr<Option6ClientFqdn> Option6ClientFqdnPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION6_CLIENT_FQDN_H
diff --git a/src/lib/dhcp/option6_ia.h b/src/lib/dhcp/option6_ia.h
index 32f3667..e4e4d11 100644
--- a/src/lib/dhcp/option6_ia.h
+++ b/src/lib/dhcp/option6_ia.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -16,12 +16,17 @@
#define OPTION_IA_H
#include <dhcp/option.h>
-
+#include <boost/shared_ptr.hpp>
#include <stdint.h>
namespace isc {
namespace dhcp {
+class Option6IA;
+
+/// A pointer to the @c Option6IA object.
+typedef boost::shared_ptr<Option6IA> Option6IAPtr;
+
class Option6IA: public Option {
public:
diff --git a/src/lib/dhcp/option6_iaaddr.h b/src/lib/dhcp/option6_iaaddr.h
index 28c5abc..cb85bed 100644
--- a/src/lib/dhcp/option6_iaaddr.h
+++ b/src/lib/dhcp/option6_iaaddr.h
@@ -17,10 +17,16 @@
#include <asiolink/io_address.h>
#include <dhcp/option.h>
+#include <boost/shared_ptr.hpp>
namespace isc {
namespace dhcp {
+class Option6IAAddr;
+
+/// A pointer to the @c isc::dhcp::Option6IAAddr object.
+typedef boost::shared_ptr<Option6IAAddr> Option6IAAddrPtr;
+
class Option6IAAddr: public Option {
public:
diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc
index 3d2a1a9..12145fc 100644
--- a/src/lib/dhcp/option_custom.cc
+++ b/src/lib/dhcp/option_custom.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -28,30 +28,20 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
}
OptionCustom::OptionCustom(const OptionDefinition& def,
- Universe u,
- const OptionBuffer& data)
+ Universe u,
+ const OptionBuffer& data)
: Option(u, def.getCode(), data.begin(), data.end()),
definition_(def) {
- // It is possible that no data is provided if an option
- // is being created on a server side. In such case a bunch
- // of buffers with default values is first created and then
- // the values are replaced using writeXXX functions. Thus
- // we need to detect that no data has been specified and
- // take a different code path.
- if (!data_.empty()) {
- createBuffers(data_);
- } else {
- createBuffers();
- }
+ createBuffers(getData());
}
OptionCustom::OptionCustom(const OptionDefinition& def,
- Universe u,
- OptionBufferConstIter first,
- OptionBufferConstIter last)
+ Universe u,
+ OptionBufferConstIter first,
+ OptionBufferConstIter last)
: Option(u, def.getCode(), first, last),
definition_(def) {
- createBuffers(data_);
+ createBuffers(getData());
}
void
@@ -230,7 +220,7 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
// 1 byte larger than the size of the string
// representation of this FQDN.
data_size = fqdn.size() + 1;
- } else {
+ } else if ( (*field == OPT_BINARY_TYPE) || (*field == OPT_STRING_TYPE) ) {
// In other case we are dealing with string or binary value
// which size can't be determined. Thus we consume the
// remaining part of the buffer for it. Note that variable
@@ -238,14 +228,11 @@ OptionCustom::createBuffers(const OptionBuffer& data_buf) {
// that the validate() function in OptionDefinition object
// should have checked wheter it is a case for this option.
data_size = std::distance(data, data_buf.end());
- }
- if (data_size == 0) {
+ } else {
// If we reached the end of buffer we assume that this option is
// truncated because there is no remaining data to initialize
// an option field.
- if (data_size == 0) {
- isc_throw(OutOfRange, "option buffer truncated");
- }
+ isc_throw(OutOfRange, "option buffer truncated");
}
} else {
// Our data field requires that there is a certain chunk of
@@ -520,9 +507,7 @@ OptionCustom::writeString(const std::string& text, const uint32_t index) {
void
OptionCustom::unpack(OptionBufferConstIter begin,
OptionBufferConstIter end) {
- data_ = OptionBuffer(begin, end);
- // Chop the buffer stored in data_ into set of sub buffers.
- createBuffers(data_);
+ initialize(begin, end);
}
uint16_t
@@ -546,15 +531,13 @@ OptionCustom::len() {
return (length);
}
-void OptionCustom::setData(const OptionBufferConstIter first,
- const OptionBufferConstIter last) {
- // We will copy entire option buffer, so we have to resize data_.
- data_.resize(std::distance(first, last));
- std::copy(first, last, data_.begin());
+void OptionCustom::initialize(const OptionBufferConstIter first,
+ const OptionBufferConstIter last) {
+ setData(first, last);
// Chop the data_ buffer into set of buffers that represent
// option fields data.
- createBuffers(data_);
+ createBuffers(getData());
}
std::string OptionCustom::toText(int indent) {
diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h
index c25347b..a7d2b95 100644
--- a/src/lib/dhcp/option_custom.h
+++ b/src/lib/dhcp/option_custom.h
@@ -205,7 +205,7 @@ public:
// is consistent with an option definition.
checkDataType<T>(index);
// When we created the buffer we have checked that it has a
- // valid size so this condition here should be always fulfiled.
+ // valid size so this condition here should be always fulfilled.
assert(buffers_[index].size() == OptionDataTypeTraits<T>::len);
// Read an integer value.
return (OptionDataTypeUtil::readInt<T>(buffers_[index]));
@@ -278,10 +278,10 @@ public:
///
/// Option will be resized to length of buffer.
///
- /// @param first iterator pointing begining of buffer to copy.
+ /// @param first iterator pointing to beginning of buffer to copy.
/// @param last iterator pointing to end of buffer to copy.
- void setData(const OptionBufferConstIter first,
- const OptionBufferConstIter last);
+ void initialize(const OptionBufferConstIter first,
+ const OptionBufferConstIter last);
private:
@@ -310,6 +310,7 @@ private:
///
/// @throw isc::dhcp::InvalidDataType if the type is invalid.
template<typename T>
+ // cppcheck-suppress unusedPrivateFunction
void checkDataType(const uint32_t index) const;
/// @brief Check if data field index is valid.
@@ -336,6 +337,11 @@ private:
std::string dataFieldToText(const OptionDataType data_type,
const uint32_t index) const;
+ /// Make this function private as we don't want it to be invoked
+ /// on OptionCustom object. We rather want that initialize to
+ /// be called instead.
+ using Option::setData;
+
/// Option definition used to create an option.
OptionDefinition definition_;
diff --git a/src/lib/dhcp/option_data_types.cc b/src/lib/dhcp/option_data_types.cc
index a567b7e..3c55ada 100644
--- a/src/lib/dhcp/option_data_types.cc
+++ b/src/lib/dhcp/option_data_types.cc
@@ -170,7 +170,7 @@ OptionDataTypeUtil::writeBinary(const std::string& hex_str,
bool
OptionDataTypeUtil::readBool(const std::vector<uint8_t>& buf) {
- if (buf.size() < 1) {
+ if (buf.empty()) {
isc_throw(BadDataTypeCast, "unable to read the buffer as boolean"
<< " value. Invalid buffer size " << buf.size());
}
@@ -212,9 +212,10 @@ OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& buf) {
void
OptionDataTypeUtil::writeFqdn(const std::string& fqdn,
- std::vector<uint8_t>& buf) {
+ std::vector<uint8_t>& buf,
+ bool downcase) {
try {
- isc::dns::Name name(fqdn);
+ isc::dns::Name name(fqdn, downcase);
isc::dns::LabelSequence labels(name);
if (labels.getDataLength() > 0) {
size_t read_len = 0;
diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h
index e53fa6e..35d6a1f 100644
--- a/src/lib/dhcp/option_data_types.h
+++ b/src/lib/dhcp/option_data_types.h
@@ -366,11 +366,14 @@ public:
///
/// @param fqdn fully qualified domain name to be written.
/// @param [out] buf output buffer.
+ /// @param downcase indicates if the FQDN should be converted to lower
+ /// case (if true). By default it is not converted.
///
/// @throw isc::dhcp::BadDataTypeCast if provided FQDN
/// is invalid.
static void writeFqdn(const std::string& fqdn,
- std::vector<uint8_t>& buf);
+ std::vector<uint8_t>& buf,
+ const bool downcase = false);
/// @brief Read string value from a buffer.
///
@@ -405,7 +408,7 @@ private:
///
/// This function is used by some of the public static functions
/// to create an instance of OptionDataTypeUtil class.
- /// When instance is called it calls the class'es constructor
+ /// When this instance is called it calls the classes constructor
/// and initializes some of the private data members.
///
/// @return instance of OptionDataTypeUtil singleton.
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index 59ff022..be24021 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -12,16 +12,20 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/option4_addrlst.h>
+#include <dhcp/option4_client_fqdn.h>
#include <dhcp/option6_addrlst.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_client_fqdn.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_definition.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/option_space.h>
+#include <dhcp/option_string.h>
#include <util/encode/hex.h>
#include <util/strutil.h>
#include <boost/algorithm/string/classification.hpp>
@@ -100,7 +104,8 @@ OptionDefinition::addRecordField(const OptionDataType data_type) {
if (data_type >= OPT_RECORD_TYPE ||
data_type == OPT_ANY_ADDRESS_TYPE ||
data_type == OPT_EMPTY_TYPE) {
- isc_throw(isc::BadValue, "attempted to add invalid data type to the record.");
+ isc_throw(isc::BadValue,
+ "attempted to add invalid data type to the record.");
}
record_fields_.push_back(data_type);
}
@@ -109,8 +114,6 @@ OptionPtr
OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
OptionBufferConstIter begin,
OptionBufferConstIter end) const {
- validate();
-
try {
switch(type_) {
case OPT_EMPTY_TYPE:
@@ -128,19 +131,23 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
factoryInteger<int8_t>(u, type, begin, end));
case OPT_UINT16_TYPE:
- return (array_type_ ? factoryIntegerArray<uint16_t>(u, type, begin, end) :
+ return (array_type_ ?
+ factoryIntegerArray<uint16_t>(u, type, begin, end) :
factoryInteger<uint16_t>(u, type, begin, end));
case OPT_INT16_TYPE:
- return (array_type_ ? factoryIntegerArray<uint16_t>(u, type, begin, end) :
+ return (array_type_ ?
+ factoryIntegerArray<uint16_t>(u, type, begin, end) :
factoryInteger<int16_t>(u, type, begin, end));
case OPT_UINT32_TYPE:
- return (array_type_ ? factoryIntegerArray<uint32_t>(u, type, begin, end) :
+ return (array_type_ ?
+ factoryIntegerArray<uint32_t>(u, type, begin, end) :
factoryInteger<uint32_t>(u, type, begin, end));
case OPT_INT32_TYPE:
- return (array_type_ ? factoryIntegerArray<uint32_t>(u, type, begin, end) :
+ return (array_type_ ?
+ factoryIntegerArray<uint32_t>(u, type, begin, end) :
factoryInteger<int32_t>(u, type, begin, end));
case OPT_IPV4_ADDRESS_TYPE:
@@ -162,6 +169,9 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
}
break;
+ case OPT_STRING_TYPE:
+ return (OptionPtr(new OptionString(u, type, begin, end)));
+
default:
if (u == Option::V6) {
if ((code_ == D6O_IA_NA || code_ == D6O_IA_PD) &&
@@ -181,10 +191,18 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
// option only for the same reasons as described in
// for IA_NA and IA_PD above.
return (factoryIAAddr6(type, begin, end));
+ } else if (code_ == D6O_CLIENT_FQDN && haveClientFqdnFormat()) {
+ // FQDN option requires special processing. Thus, there is
+ // a specialized class to handle it.
+ return (OptionPtr(new Option6ClientFqdn(begin, end)));
+ }
+ } else {
+ if ((code_ == DHO_FQDN) && haveFqdn4Format()) {
+ return (OptionPtr(new Option4ClientFqdn(begin, end)));
}
}
}
- return (OptionPtr(new OptionCustom(*this, u, OptionBuffer(begin, end))));
+ return (OptionPtr(new OptionCustom(*this, u, begin, end)));
} catch (const Exception& ex) {
isc_throw(InvalidOptionValue, ex.what());
@@ -200,8 +218,6 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
OptionPtr
OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
const std::vector<std::string>& values) const {
- validate();
-
OptionBuffer buf;
if (!array_type_ && type_ != OPT_RECORD_TYPE) {
if (values.empty()) {
@@ -228,14 +244,6 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
}
void
-OptionDefinition::sanityCheckUniverse(const Option::Universe expected_universe,
- const Option::Universe actual_universe) {
- if (expected_universe != actual_universe) {
- isc_throw(isc::BadValue, "invalid universe specified for the option");
- }
-}
-
-void
OptionDefinition::validate() const {
using namespace boost::algorithm;
@@ -272,10 +280,12 @@ OptionDefinition::validate() const {
// it no way to tell when other data fields begin.
err_str << "array of strings is not a valid option definition.";
} else if (type_ == OPT_BINARY_TYPE) {
- err_str << "array of binary values is not a valid option definition.";
+ err_str << "array of binary values is not"
+ << " a valid option definition.";
} else if (type_ == OPT_EMPTY_TYPE) {
- err_str << "array of empty value is not a valid option definition.";
+ err_str << "array of empty value is not"
+ << " a valid option definition.";
}
@@ -283,33 +293,34 @@ OptionDefinition::validate() const {
// At least two data fields should be added to the record. Otherwise
// non-record option definition could be used.
if (getRecordFields().size() < 2) {
- err_str << "invalid number of data fields: " << getRecordFields().size()
+ err_str << "invalid number of data fields: "
+ << getRecordFields().size()
<< " specified for the option of type 'record'. Expected at"
<< " least 2 fields.";
} else {
// If the number of fields is valid we have to check if their order
// is valid too. We check that string or binary data fields are not
- // laid before other fields. But we allow that they are laid at the end of
- // an option.
+ // laid before other fields. But we allow that they are laid at the
+ // end of an option.
const RecordFieldsCollection& fields = getRecordFields();
for (RecordFieldsConstIter it = fields.begin();
it != fields.end(); ++it) {
if (*it == OPT_STRING_TYPE &&
it < fields.end() - 1) {
- err_str << "string data field can't be laid before data fields"
- << " of other types.";
+ err_str << "string data field can't be laid before data"
+ << " fields of other types.";
break;
}
if (*it == OPT_BINARY_TYPE &&
it < fields.end() - 1) {
- err_str << "binary data field can't be laid before data fields"
- << " of other types.";
+ err_str << "binary data field can't be laid before data"
+ << " fields of other types.";
}
/// Empty type is not allowed within a record.
if (*it == OPT_EMPTY_TYPE) {
- err_str << "empty data type can't be stored as a field in an"
- << " option record.";
+ err_str << "empty data type can't be stored as a field in"
+ << " an option record.";
break;
}
}
@@ -349,13 +360,34 @@ OptionDefinition::haveIAAddr6Format() const {
return (haveIAx6Format(OPT_IPV6_ADDRESS_TYPE));
}
+bool
+OptionDefinition::haveFqdn4Format() const {
+ return (haveType(OPT_RECORD_TYPE) &&
+ record_fields_.size() == 4 &&
+ record_fields_[0] == OPT_UINT8_TYPE &&
+ record_fields_[1] == OPT_UINT8_TYPE &&
+ record_fields_[2] == OPT_UINT8_TYPE &&
+ record_fields_[3] == OPT_FQDN_TYPE);
+}
+
+bool
+OptionDefinition::haveClientFqdnFormat() const {
+ return (haveType(OPT_RECORD_TYPE) &&
+ (record_fields_.size() == 2) &&
+ (record_fields_[0] == OPT_UINT8_TYPE) &&
+ (record_fields_[1] == OPT_FQDN_TYPE));
+}
+
template<typename T>
-T OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str) const {
+T
+OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str)
+ const {
// Lexical cast in case of our data types make sense only
// for uintX_t, intX_t and bool type.
if (!OptionDataTypeTraits<T>::integer_type &&
OptionDataTypeTraits<T>::type != OPT_BOOLEAN_TYPE) {
- isc_throw(BadDataTypeCast, "unable to do lexical cast to non-integer and"
+ isc_throw(BadDataTypeCast,
+ "unable to do lexical cast to non-integer and"
<< " non-boolean data type");
}
// We use the 64-bit value here because it has wider range than
@@ -372,16 +404,18 @@ T OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str) cons
if (OptionDataTypeTraits<T>::integer_type) {
data_type_str = "integer";
}
- isc_throw(BadDataTypeCast, "unable to do lexical cast to " << data_type_str
- << " data type for value " << value_str << ": " << ex.what());
+ isc_throw(BadDataTypeCast, "unable to do lexical cast to "
+ << data_type_str << " data type for value "
+ << value_str << ": " << ex.what());
}
// Perform range checks for integer values only (exclude bool values).
if (OptionDataTypeTraits<T>::integer_type) {
if (result > numeric_limits<T>::max() ||
result < numeric_limits<T>::min()) {
isc_throw(BadDataTypeCast, "unable to do lexical cast for value "
- << value_str << ". This value is expected to be in the range of "
- << numeric_limits<T>::min() << ".." << numeric_limits<T>::max());
+ << value_str << ". This value is expected to be"
+ << " in the range of " << numeric_limits<T>::min()
+ << ".." << numeric_limits<T>::max());
}
}
return (static_cast<T>(result));
@@ -403,30 +437,37 @@ OptionDefinition::writeToBuffer(const std::string& value,
// That way we actually waste 7 bits but it seems to be the
// simpler way to encode boolean.
// @todo Consider if any other encode methods can be used.
- OptionDataTypeUtil::writeBool(lexicalCastWithRangeCheck<bool>(value), buf);
+ OptionDataTypeUtil::writeBool(lexicalCastWithRangeCheck<bool>(value),
+ buf);
return;
case OPT_INT8_TYPE:
- OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<int8_t>(value),
+ OptionDataTypeUtil::writeInt<uint8_t>
+ (lexicalCastWithRangeCheck<int8_t>(value),
buf);
return;
case OPT_INT16_TYPE:
- OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<int16_t>(value),
+ OptionDataTypeUtil::writeInt<uint16_t>
+ (lexicalCastWithRangeCheck<int16_t>(value),
buf);
return;
case OPT_INT32_TYPE:
- OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<int32_t>(value),
+ OptionDataTypeUtil::writeInt<uint32_t>
+ (lexicalCastWithRangeCheck<int32_t>(value),
buf);
return;
case OPT_UINT8_TYPE:
- OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<uint8_t>(value),
+ OptionDataTypeUtil::writeInt<uint8_t>
+ (lexicalCastWithRangeCheck<uint8_t>(value),
buf);
return;
case OPT_UINT16_TYPE:
- OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<uint16_t>(value),
+ OptionDataTypeUtil::writeInt<uint16_t>
+ (lexicalCastWithRangeCheck<uint16_t>(value),
buf);
return;
case OPT_UINT32_TYPE:
- OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<uint32_t>(value),
+ OptionDataTypeUtil::writeInt<uint32_t>
+ (lexicalCastWithRangeCheck<uint32_t>(value),
buf);
return;
case OPT_IPV4_ADDRESS_TYPE:
@@ -434,7 +475,8 @@ OptionDefinition::writeToBuffer(const std::string& value,
{
asiolink::IOAddress address(value);
if (!address.isV4() && !address.isV6()) {
- isc_throw(BadDataTypeCast, "provided address " << address.toText()
+ isc_throw(BadDataTypeCast, "provided address "
+ << address.toText()
<< " is not a valid IPv4 or IPv6 address.");
}
OptionDataTypeUtil::writeAddress(address, buf);
@@ -462,7 +504,8 @@ OptionPtr
OptionDefinition::factoryAddrList4(uint16_t type,
OptionBufferConstIter begin,
OptionBufferConstIter end) {
- boost::shared_ptr<Option4AddrLst> option(new Option4AddrLst(type, begin, end));
+ boost::shared_ptr<Option4AddrLst> option(new Option4AddrLst(type, begin,
+ end));
return (option);
}
@@ -470,7 +513,8 @@ OptionPtr
OptionDefinition::factoryAddrList6(uint16_t type,
OptionBufferConstIter begin,
OptionBufferConstIter end) {
- boost::shared_ptr<Option6AddrLst> option(new Option6AddrLst(type, begin, end));
+ boost::shared_ptr<Option6AddrLst> option(new Option6AddrLst(type, begin,
+ end));
return (option);
}
@@ -494,8 +538,9 @@ OptionDefinition::factoryIA6(uint16_t type,
OptionBufferConstIter begin,
OptionBufferConstIter end) {
if (std::distance(begin, end) < Option6IA::OPTION6_IA_LEN) {
- isc_throw(isc::OutOfRange, "input option buffer has invalid size, expected "
- "at least " << Option6IA::OPTION6_IA_LEN << " bytes");
+ isc_throw(isc::OutOfRange, "input option buffer has invalid size,"
+ << " expected at least " << Option6IA::OPTION6_IA_LEN
+ << " bytes");
}
boost::shared_ptr<Option6IA> option(new Option6IA(type, begin, end));
return (option);
@@ -506,10 +551,12 @@ OptionDefinition::factoryIAAddr6(uint16_t type,
OptionBufferConstIter begin,
OptionBufferConstIter end) {
if (std::distance(begin, end) < Option6IAAddr::OPTION6_IAADDR_LEN) {
- isc_throw(isc::OutOfRange, "input option buffer has invalid size, expected "
- " at least " << Option6IAAddr::OPTION6_IAADDR_LEN << " bytes");
+ isc_throw(isc::OutOfRange,
+ "input option buffer has invalid size, expected at least "
+ << Option6IAAddr::OPTION6_IAADDR_LEN << " bytes");
}
- boost::shared_ptr<Option6IAAddr> option(new Option6IAAddr(type, begin, end));
+ boost::shared_ptr<Option6IAAddr> option(new Option6IAAddr(type, begin,
+ end));
return (option);
}
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index df5def7..884a4ad 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -167,7 +167,7 @@ public:
///
/// This constructor sets the name of the option space that is
/// encapsulated by this option. The encapsulated option space
- /// indentifies sub-options that are carried within this option.
+ /// identifies sub-options that are carried within this option.
/// This constructor does not allow to set array indicator
/// because options comprising an array of data fields must
/// not be used with sub-options.
@@ -186,7 +186,7 @@ public:
///
/// This constructor sets the name of the option space that is
/// encapsulated by this option. The encapsulated option space
- /// indentifies sub-options that are carried within this option.
+ /// identifies sub-options that are carried within this option.
/// This constructor does not allow to set array indicator
/// because options comprising an array of data fields must
/// not be used with sub-options.
@@ -255,6 +255,13 @@ public:
/// @brief Check if the option definition is valid.
///
+ /// Note that it is a responsibility of the code that created
+ /// the OptionDefinition object to validate that it is valid.
+ /// This function will not be called internally anywhere in this
+ /// class to verify that the option definition is valid. Using
+ /// invalid option definition to create an instance of the
+ /// DHCP option leads to undefined behavior.
+ ///
/// @throw MalformedOptionDefinition option definition is invalid.
void validate() const;
@@ -268,19 +275,43 @@ public:
/// @return true if specified format is IAADDR option format.
bool haveIAAddr6Format() const;
+ /// @brief Check if specified format is OPTION_CLIENT_FQDN option format.
+ ///
+ /// @return true of specified format is OPTION_CLIENT_FQDN option format,
+ /// false otherwise.
+ bool haveClientFqdnFormat() const;
+
+ /// @brief Check if option has format of the DHCPv4 Client FQDN
+ /// %Option.
+ ///
+ /// The encoding of the domain-name carried by the FQDN option is
+ /// conditional and is specified in the flags field of the option.
+ /// The domain-name can be encoded in the ASCII format or canonical
+ /// wire format. The ASCII format is deprecated, therefore canonical
+ /// format is selected for the FQDN option definition and this function
+ /// returns true if the option definition comprises the domain-name
+ /// field encoded in canonical format.
+ ///
+ /// @return true if option has the format of DHCPv4 Client FQDN
+ /// %Option.
+ bool haveFqdn4Format() const;
+
/// @brief Option factory.
///
/// This function creates an instance of DHCP option using
/// provided chunk of buffer. This function may be used to
/// create option which is to be sent in the outgoing packet.
///
+ /// @warning calling this function on invalid option definition
+ /// yields undefined behavior. Use \ref validate to test that
+ /// the option definition is valid.
+ ///
/// @param u option universe (V4 or V6).
/// @param type option type.
/// @param begin beginning of the option buffer.
/// @param end end of the option buffer.
///
/// @return instance of the DHCP option.
- /// @throw MalformedOptionDefinition if option definition is invalid.
/// @throw InvalidOptionValue if data for the option is invalid.
OptionPtr optionFactory(Option::Universe u, uint16_t type,
OptionBufferConstIter begin,
@@ -292,12 +323,15 @@ public:
/// whole provided buffer. This function may be used to
/// create option which is to be sent in the outgoing packet.
///
+ /// @warning calling this function on invalid option definition
+ /// yields undefined behavior. Use \ref validate to test that
+ /// the option definition is valid.
+ ///
/// @param u option universe (V4 or V6).
/// @param type option type.
/// @param buf option buffer.
///
/// @return instance of the DHCP option.
- /// @throw MalformedOptionDefinition if option definition is invalid.
/// @throw InvalidOptionValue if data for the option is invalid.
OptionPtr optionFactory(Option::Universe u, uint16_t type,
const OptionBuffer& buf = OptionBuffer()) const;
@@ -316,12 +350,15 @@ public:
/// must be tokenized into the vector of string values and this vector
/// can be supplied to this function.
///
+ /// @warning calling this function on invalid option definition
+ /// yields undefined behavior. Use \ref validate to test that
+ /// the option definition is valid.
+ ///
/// @param u option universe (V4 or V6).
/// @param type option type.
/// @param values a vector of values to be used to set data for an option.
///
/// @return instance of the DHCP option.
- /// @throw MalformedOptionDefinition if option definition is invalid.
/// @throw InvalidOptionValue if data for the option is invalid.
OptionPtr optionFactory(Option::Universe u, uint16_t type,
const std::vector<std::string>& values) const;
@@ -485,15 +522,6 @@ private:
void writeToBuffer(const std::string& value, const OptionDataType type,
OptionBuffer& buf) const;
- /// @brief Sanity check universe value.
- ///
- /// @param expected_universe expected universe value.
- /// @param actual_universe actual universe value.
- ///
- /// @throw isc::BadValue if expected universe and actual universe don't match.
- static inline void sanityCheckUniverse(const Option::Universe expected_universe,
- const Option::Universe actual_universe);
-
/// Option name.
std::string name_;
/// Option code.
@@ -515,7 +543,7 @@ private:
/// using two indexes:
/// - sequenced: used to access elements in the order they have
/// been added to the container
-/// - option code: used to search defintions of options
+/// - option code: used to search definitions of options
/// with a specified option code (aka option type).
/// Note that this container can hold multiple options with the
/// same code. For this reason, the latter index can be used to
@@ -555,7 +583,7 @@ typedef boost::shared_ptr<OptionDefContainer> OptionDefContainerPtr;
typedef OptionDefContainer::nth_index<1>::type OptionDefContainerTypeIndex;
/// Pair of iterators to represent the range of options definitions
/// having the same option type value. The first element in this pair
-/// represents the begining of the range, the second element
+/// represents the beginning of the range, the second element
/// represents the end.
typedef std::pair<OptionDefContainerTypeIndex::const_iterator,
OptionDefContainerTypeIndex::const_iterator> OptionDefContainerTypeRange;
diff --git a/src/lib/dhcp/option_int.h b/src/lib/dhcp/option_int.h
index ebb1641..9a4cfb1 100644
--- a/src/lib/dhcp/option_int.h
+++ b/src/lib/dhcp/option_int.h
@@ -56,7 +56,7 @@ public:
/// @brief Constructor.
///
- /// This constructor creates option from a buffer. This construtor
+ /// This constructor creates option from a buffer. This constructor
/// may throw exception if \ref unpack function throws during buffer
/// parsing.
///
diff --git a/src/lib/dhcp/option_int_array.h b/src/lib/dhcp/option_int_array.h
index 1babee5..a87e9b5 100644
--- a/src/lib/dhcp/option_int_array.h
+++ b/src/lib/dhcp/option_int_array.h
@@ -102,7 +102,7 @@ public:
/// @brief Constructor.
///
- /// This constructor creates option from a buffer. This construtor
+ /// This constructor creates option from a buffer. This constructor
/// may throw exception if \ref unpack function throws during buffer
/// parsing.
///
diff --git a/src/lib/dhcp/option_string.cc b/src/lib/dhcp/option_string.cc
new file mode 100644
index 0000000..6a8001a
--- /dev/null
+++ b/src/lib/dhcp/option_string.cc
@@ -0,0 +1,86 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <dhcp/option_string.h>
+
+namespace isc {
+namespace dhcp {
+
+OptionString::OptionString(const Option::Universe u, const uint16_t type,
+ const std::string& value)
+ : Option(u, type) {
+ // Try to assign the provided string value. This will throw exception
+ // if the provided value is empty.
+ setValue(value);
+}
+
+OptionString::OptionString(const Option::Universe u, const uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end)
+ : Option(u, type) {
+ // Decode the data. This will throw exception if the buffer is
+ // truncated.
+ unpack(begin, end);
+}
+
+std::string
+OptionString::getValue() const {
+ const OptionBuffer& data = getData();
+ return (std::string(data.begin(), data.end()));
+}
+
+void
+OptionString::setValue(const std::string& value) {
+ // Sanity check that the string value is at least one byte long.
+ // This is a requirement for all currently defined options which
+ // carry a string value.
+ if (value.empty()) {
+ isc_throw(isc::OutOfRange, "string value carried by the option '"
+ << getType() << "' must not be empty");
+ }
+
+ setData(value.begin(), value.end());
+}
+
+
+uint16_t
+OptionString::len() {
+ return (getHeaderLen() + getData().size());
+}
+
+void
+OptionString::pack(isc::util::OutputBuffer& buf) {
+ // Pack option header.
+ packHeader(buf);
+ // Pack data.
+ const OptionBuffer& data = getData();
+ buf.writeData(&data[0], data.size());
+
+ // That's it. We don't pack any sub-options here, because this option
+ // must not contain sub-options.
+}
+
+void
+OptionString::unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ if (std::distance(begin, end) == 0) {
+ isc_throw(isc::OutOfRange, "failed to parse an option '"
+ << getType() << "' holding string value"
+ << " - empty value is not accepted");
+ }
+ setData(begin, end);
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option_string.h b/src/lib/dhcp/option_string.h
new file mode 100644
index 0000000..3d0aa9a
--- /dev/null
+++ b/src/lib/dhcp/option_string.h
@@ -0,0 +1,114 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 OPTION_STRING_H
+#define OPTION_STRING_H
+
+#include <dhcp/option.h>
+#include <util/buffer.h>
+
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Class which represents an option carrying a single string value.
+///
+/// This class represents an option carrying a single string value.
+/// Currently this class imposes that the minimal length of the carried
+/// string is 1.
+///
+/// @todo In the future this class may be extended with some more string
+/// content checks and encoding methods if required.
+class OptionString : public Option {
+public:
+
+ /// @brief Constructor, used to create options to be sent.
+ ///
+ /// This constructor creates an instance of option which carries a
+ /// string value specified as constructor's parameter. This constructor
+ /// is most often used to create an instance of an option which will
+ /// be sent in the outgoing packet.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option code.
+ /// @param value a string value to be carried by the option.
+ ///
+ /// @throw isc::OutOfRange if provided string is empty.
+ OptionString(const Option::Universe u, const uint16_t type,
+ const std::string& value);
+
+ /// @brief Constructor, used for receiving options.
+ ///
+ /// This constructor creates an instance of the option from the provided
+ /// chunk of buffer. This buffer may hold the data received on the wire.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option code.
+ /// @param begin iterator pointing to the first byte of the buffer chunk.
+ /// @param end iterator pointing to the last byte of the buffer chunk.
+ ///
+ /// @throw isc::OutOfRange if provided buffer is truncated.
+ OptionString(const Option::Universe u, const uint16_t type,
+ OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Returns length of the whole option, including header.
+ ///
+ /// @return length of the whole option.
+ virtual uint16_t len();
+
+ /// @brief Returns the string value held by the option.
+ ///
+ /// @return string value held by the option.
+ std::string getValue() const;
+
+ /// @brief Sets the string value to be held by the option.
+ ///
+ /// @param value string value to be set.
+ ///
+ /// @throw isc::OutOfRange if a string value to be set is empty.
+ void setValue(const std::string& value);
+
+ /// @brief Creates on-wire format of the option.
+ ///
+ /// This function creates on-wire format of the option and appends it to
+ /// the data existing in the provided buffer. The internal buffer's pointer
+ /// is moved to the end of stored data.
+ ///
+ /// @param [out] buf output buffer where the option will be stored.
+ virtual void pack(isc::util::OutputBuffer& buf);
+
+ /// @brief Decodes option data from the provided buffer.
+ ///
+ /// This function decodes option data from the provided buffer. Note that
+ /// it does not decode the option code and length, so the iterators must
+ /// point to the begining and end of the option payload respectively.
+ /// The size of the decoded payload must be at least 1 byte.
+ ///
+ /// @param begin the iterator pointing to the option payload.
+ /// @param end the iterator pointing to the end of the option payload.
+ ///
+ /// @throw isc::OutOfRange if provided buffer is truncated.
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+};
+
+/// Pointer to the OptionString object.
+typedef boost::shared_ptr<OptionString> OptionStringPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION_STRING_H
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc
index 0592807..67c0ae5 100644
--- a/src/lib/dhcp/pkt4.cc
+++ b/src/lib/dhcp/pkt4.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -33,7 +33,8 @@ namespace dhcp {
const IOAddress DEFAULT_ADDRESS("0.0.0.0");
Pkt4::Pkt4(uint8_t msg_type, uint32_t transid)
- :local_addr_(DEFAULT_ADDRESS),
+ :buffer_out_(DHCPV4_PKT_HDR_LEN),
+ local_addr_(DEFAULT_ADDRESS),
remote_addr_(DEFAULT_ADDRESS),
iface_(""),
ifindex_(0),
@@ -48,8 +49,7 @@ Pkt4::Pkt4(uint8_t msg_type, uint32_t transid)
ciaddr_(DEFAULT_ADDRESS),
yiaddr_(DEFAULT_ADDRESS),
siaddr_(DEFAULT_ADDRESS),
- giaddr_(DEFAULT_ADDRESS),
- bufferOut_(DHCPV4_PKT_HDR_LEN)
+ giaddr_(DEFAULT_ADDRESS)
{
memset(sname_, 0, MAX_SNAME_LEN);
memset(file_, 0, MAX_FILE_LEN);
@@ -58,7 +58,8 @@ Pkt4::Pkt4(uint8_t msg_type, uint32_t transid)
}
Pkt4::Pkt4(const uint8_t* data, size_t len)
- :local_addr_(DEFAULT_ADDRESS),
+ :buffer_out_(0), // not used, this is RX packet
+ local_addr_(DEFAULT_ADDRESS),
remote_addr_(DEFAULT_ADDRESS),
iface_(""),
ifindex_(0),
@@ -72,8 +73,7 @@ Pkt4::Pkt4(const uint8_t* data, size_t len)
ciaddr_(DEFAULT_ADDRESS),
yiaddr_(DEFAULT_ADDRESS),
siaddr_(DEFAULT_ADDRESS),
- giaddr_(DEFAULT_ADDRESS),
- bufferOut_(0) // not used, this is RX packet
+ giaddr_(DEFAULT_ADDRESS)
{
if (len < DHCPV4_PKT_HDR_LEN) {
isc_throw(OutOfRange, "Truncated DHCPv4 packet (len=" << len
@@ -102,55 +102,60 @@ Pkt4::len() {
return (length);
}
-bool
+void
Pkt4::pack() {
if (!hwaddr_) {
isc_throw(InvalidOperation, "Can't build Pkt4 packet. HWAddr not set.");
}
- size_t hw_len = hwaddr_->hwaddr_.size();
-
- bufferOut_.writeUint8(op_);
- bufferOut_.writeUint8(hwaddr_->htype_);
- bufferOut_.writeUint8(hw_len < MAX_CHADDR_LEN ? hw_len : MAX_CHADDR_LEN);
- bufferOut_.writeUint8(hops_);
- bufferOut_.writeUint32(transid_);
- bufferOut_.writeUint16(secs_);
- bufferOut_.writeUint16(flags_);
- bufferOut_.writeUint32(ciaddr_);
- bufferOut_.writeUint32(yiaddr_);
- bufferOut_.writeUint32(siaddr_);
- bufferOut_.writeUint32(giaddr_);
-
-
- if (hw_len <= MAX_CHADDR_LEN) {
- // write up to 16 bytes of the hardware address (CHADDR field is 16
- // bytes long in DHCPv4 message).
- bufferOut_.writeData(&hwaddr_->hwaddr_[0],
- (hw_len < MAX_CHADDR_LEN ? hw_len : MAX_CHADDR_LEN) );
- hw_len = MAX_CHADDR_LEN - hw_len;
- } else {
- hw_len = MAX_CHADDR_LEN;
+ try {
+ size_t hw_len = hwaddr_->hwaddr_.size();
+
+ buffer_out_.writeUint8(op_);
+ buffer_out_.writeUint8(hwaddr_->htype_);
+ buffer_out_.writeUint8(hw_len < MAX_CHADDR_LEN ?
+ hw_len : MAX_CHADDR_LEN);
+ buffer_out_.writeUint8(hops_);
+ buffer_out_.writeUint32(transid_);
+ buffer_out_.writeUint16(secs_);
+ buffer_out_.writeUint16(flags_);
+ buffer_out_.writeUint32(ciaddr_);
+ buffer_out_.writeUint32(yiaddr_);
+ buffer_out_.writeUint32(siaddr_);
+ buffer_out_.writeUint32(giaddr_);
+
+
+ if (hw_len <= MAX_CHADDR_LEN) {
+ // write up to 16 bytes of the hardware address (CHADDR field is 16
+ // bytes long in DHCPv4 message).
+ buffer_out_.writeData(&hwaddr_->hwaddr_[0],
+ (hw_len < MAX_CHADDR_LEN ?
+ hw_len : MAX_CHADDR_LEN) );
+ hw_len = MAX_CHADDR_LEN - hw_len;
+ } else {
+ hw_len = MAX_CHADDR_LEN;
+ }
+
+ // write (len) bytes of padding
+ vector<uint8_t> zeros(hw_len, 0);
+ buffer_out_.writeData(&zeros[0], hw_len);
+ // buffer_out_.writeData(chaddr_, MAX_CHADDR_LEN);
+
+ buffer_out_.writeData(sname_, MAX_SNAME_LEN);
+ buffer_out_.writeData(file_, MAX_FILE_LEN);
+
+ // write DHCP magic cookie
+ buffer_out_.writeUint32(DHCP_OPTIONS_COOKIE);
+
+ LibDHCP::packOptions(buffer_out_, options_);
+
+ // add END option that indicates end of options
+ // (End option is very simple, just a 255 octet)
+ buffer_out_.writeUint8(DHO_END);
+ } catch(const Exception& e) {
+ // An exception is thrown and message will be written to Logger
+ isc_throw(InvalidOperation, e.what());
}
-
- // write (len) bytes of padding
- vector<uint8_t> zeros(hw_len, 0);
- bufferOut_.writeData(&zeros[0], hw_len);
- // bufferOut_.writeData(chaddr_, MAX_CHADDR_LEN);
-
- bufferOut_.writeData(sname_, MAX_SNAME_LEN);
- bufferOut_.writeData(file_, MAX_FILE_LEN);
-
- // write DHCP magic cookie
- bufferOut_.writeUint32(DHCP_OPTIONS_COOKIE);
-
- LibDHCP::packOptions(bufferOut_, options_);
-
- // add END option that indicates end of options
- // (End option is very simple, just a 255 octet)
- bufferOut_.writeUint8(DHO_END);
-
- return (true);
}
void
@@ -254,7 +259,7 @@ void Pkt4::setType(uint8_t dhcp_type) {
}
void Pkt4::repack() {
- bufferOut_.writeData(&data_[0], data_.size());
+ buffer_out_.writeData(&data_[0], data_.size());
}
std::string
@@ -276,8 +281,24 @@ Pkt4::toText() {
}
void
-Pkt4::setHWAddr(uint8_t hType, uint8_t hlen,
+Pkt4::setHWAddr(uint8_t htype, uint8_t hlen,
const std::vector<uint8_t>& mac_addr) {
+ setHWAddrMember(htype, hlen, mac_addr, hwaddr_);
+}
+
+void
+Pkt4::setHWAddr(const HWAddrPtr& addr) {
+ if (!addr) {
+ isc_throw(BadValue, "Setting DHCPv4 chaddr field to NULL"
+ << " is forbidden");
+ }
+ hwaddr_ = addr;
+}
+
+void
+Pkt4::setHWAddrMember(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr,
+ HWAddrPtr& hw_addr) {
/// @todo Rewrite this once support for client-identifier option
/// is implemented (ticket 1228?)
if (hlen > MAX_CHADDR_LEN) {
@@ -288,15 +309,37 @@ Pkt4::setHWAddr(uint8_t hType, uint8_t hlen,
isc_throw(OutOfRange, "Invalid HW Address specified");
}
- hwaddr_.reset(new HWAddr(mac_addr, hType));
+ hw_addr.reset(new HWAddr(mac_addr, htype));
}
void
-Pkt4::setHWAddr(const HWAddrPtr& addr) {
+Pkt4::setLocalHWAddr(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr) {
+ setHWAddrMember(htype, hlen, mac_addr, local_hwaddr_);
+}
+
+void
+Pkt4::setLocalHWAddr(const HWAddrPtr& addr) {
if (!addr) {
- isc_throw(BadValue, "Setting hw address to NULL is forbidden");
+ isc_throw(BadValue, "Setting local HW address to NULL is"
+ << " forbidden.");
}
- hwaddr_ = addr;
+ local_hwaddr_ = addr;
+}
+
+void
+Pkt4::setRemoteHWAddr(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr) {
+ setHWAddrMember(htype, hlen, mac_addr, remote_hwaddr_);
+}
+
+void
+Pkt4::setRemoteHWAddr(const HWAddrPtr& addr) {
+ if (!addr) {
+ isc_throw(BadValue, "Setting remote HW address to NULL is"
+ << " forbidden.");
+ }
+ remote_hwaddr_ = addr;
}
void
diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h
index 664686b..a64734b 100644
--- a/src/lib/dhcp/pkt4.h
+++ b/src/lib/dhcp/pkt4.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -47,6 +47,10 @@ public:
/// specifies DHCPv4 packet header length (fixed part)
const static size_t DHCPV4_PKT_HDR_LEN = 236;
+ /// Mask for the value of flags field in the DHCPv4 message
+ /// to check whether client requested broadcast response.
+ const static uint16_t FLAG_BROADCAST_MASK = 0x8000;
+
/// Constructor, used in replying to a message.
///
/// @param msg_type type of message (e.g. DHCPDISOVER=1)
@@ -66,10 +70,10 @@ public:
///
/// Prepares on-wire format of message and all its options.
/// Options must be stored in options_ field.
- /// Output buffer will be stored in bufferOut_.
+ /// Output buffer will be stored in buffer_out_.
///
- /// @return true if packing procedure was successful
- bool
+ /// @throw InvalidOperation if packing fails
+ void
pack();
/// @brief Parses on-wire form of DHCPv4 packet.
@@ -99,7 +103,7 @@ public:
///
/// This is mostly a diagnostic function. It is being used for sending
/// received packet. Received packet is stored in bufferIn_, but
- /// transmitted data is stored in bufferOut_. If we want to send packet
+ /// transmitted data is stored in buffer_out_. If we want to send packet
/// that we just received, a copy between those two buffers is necessary.
void repack();
@@ -267,10 +271,10 @@ public:
///
/// Note: mac_addr must be a buffer of at least hlen bytes.
///
- /// @param hType hardware type (will be sent in htype field)
+ /// @param htype hardware type (will be sent in htype field)
/// @param hlen hardware length (will be sent in hlen field)
/// @param mac_addr pointer to hardware address
- void setHWAddr(uint8_t hType, uint8_t hlen,
+ void setHWAddr(uint8_t htype, uint8_t hlen,
const std::vector<uint8_t>& mac_addr);
/// @brief Sets hardware address
@@ -303,11 +307,12 @@ public:
/// is only valid till Pkt4 object is valid.
///
/// RX packet or TX packet before pack() will return buffer with
- /// zero length
+ /// zero length. This buffer is returned as non-const, so hooks
+ /// framework (and user's callouts) can modify them if needed
///
/// @return reference to output buffer
- const isc::util::OutputBuffer&
- getBuffer() const { return (bufferOut_); };
+ isc::util::OutputBuffer&
+ getBuffer() { return (buffer_out_); };
/// @brief Add an option.
///
@@ -363,6 +368,72 @@ public:
/// @return interface index
uint32_t getIndex() const { return (ifindex_); };
+ /// @brief Sets remote HW address.
+ ///
+ /// Sets the destination HW address for the outgoing packet
+ /// or source HW address for the incoming packet. When this
+ /// is an outgoing packet this address will be used to construct
+ /// the link layer header.
+ ///
+ /// @note mac_addr must be a buffer of at least hlen bytes.
+ ///
+ /// @param htype hardware type (will be sent in htype field)
+ /// @param hlen hardware length (will be sent in hlen field)
+ /// @param mac_addr pointer to hardware address
+ void setRemoteHWAddr(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr);
+
+ /// @brief Sets remote HW address.
+ ///
+ /// Sets hardware address from an existing HWAddr structure.
+ /// The remote address is a destination address for outgoing
+ /// packet and source address for incoming packet. When this
+ /// is an outgoing packet, this address will be used to
+ /// construct the link layer header.
+ ///
+ /// @param addr structure representing HW address.
+ ///
+ /// @throw BadValue if addr is null
+ void setRemoteHWAddr(const HWAddrPtr& addr);
+
+ /// @brief Returns the remote HW address.
+ ///
+ /// @return remote HW address.
+ HWAddrPtr getRemoteHWAddr() const {
+ return (remote_hwaddr_);
+ }
+
+ /// @brief Sets local HW address.
+ ///
+ /// Sets the source HW address for the outgoing packet or
+ /// destination HW address for the incoming packet.
+ ///
+ /// @note mac_addr must be a buffer of at least hlen bytes.
+ ///
+ /// @param htype hardware type (will be sent in htype field)
+ /// @param hlen hardware length (will be sent in hlen field)
+ /// @param mac_addr pointer to hardware address
+ void setLocalHWAddr(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr);
+
+ /// @brief Sets local HW address.
+ ///
+ /// Sets hardware address from an existing HWAddr structure.
+ /// The local address is a source address for outgoing
+ /// packet and destination address for incoming packet.
+ ///
+ /// @param addr structure representing HW address.
+ ///
+ /// @throw BadValue if addr is null
+ void setLocalHWAddr(const HWAddrPtr& addr);
+
+ /// @brief Returns local HW address.
+ ///
+ /// @return local HW addr.
+ HWAddrPtr getLocalHWAddr() const {
+ return (local_hwaddr_);
+ }
+
/// @brief Sets remote address.
///
/// @param remote specifies remote address
@@ -419,6 +490,55 @@ public:
/// @throw isc::Unexpected if timestamp update failed
void updateTimestamp();
+ /// Output buffer (used during message transmission)
+ ///
+ /// @warning This public member is accessed by derived
+ /// classes directly. One of such derived classes is
+ /// @ref perfdhcp::PerfPkt4. The impact on derived clasess'
+ /// behavior must be taken into consideration before making
+ /// changes to this member such as access scope restriction or
+ /// data format change etc. This field is also public, because
+ /// it may be modified by callouts (which are written in C++ now,
+ /// but we expect to also have them in Python, so any accesibility
+ /// methods would overly complicate things here and degrade
+ /// performance).
+ isc::util::OutputBuffer buffer_out_;
+
+ /// @brief That's the data of input buffer used in RX packet.
+ ///
+ /// @note Note that InputBuffer does not store the data itself, but just
+ /// expects that data will be valid for the whole life of InputBuffer.
+ /// Therefore we need to keep the data around.
+ ///
+ /// @warning This public member is accessed by derived
+ /// classes directly. One of such derived classes is
+ /// @ref perfdhcp::PerfPkt4. The impact on derived clasess'
+ /// behavior must be taken into consideration before making
+ /// changes to this member such as access scope restriction or
+ /// data format change etc. This field is also public, because
+ /// it may be modified by callouts (which are written in C++ now,
+ /// but we expect to also have them in Python, so any accesibility
+ /// methods would overly complicate things here and degrade
+ /// performance).
+ std::vector<uint8_t> data_;
+
+private:
+
+ /// @brief Generic method that validates and sets HW address.
+ ///
+ /// This is a generic method used by all modifiers of this class
+ /// which set class members representing HW address.
+ ///
+ /// @param htype hardware type.
+ /// @param hlen hardware length.
+ /// @param mac_addr pointer to actual hardware address.
+ /// @param [out] hw_addr pointer to a class member to be modified.
+ ///
+ /// @trow isc::OutOfRange if invalid HW address specified.
+ void setHWAddrMember(const uint8_t htype, const uint8_t hlen,
+ const std::vector<uint8_t>& mac_addr,
+ HWAddrPtr& hw_addr);
+
protected:
/// converts DHCP message type to BOOTP op type
@@ -429,6 +549,12 @@ protected:
uint8_t
DHCPTypeToBootpType(uint8_t dhcpType);
+ /// local HW address (dst if receiving packet, src if sending packet)
+ HWAddrPtr local_hwaddr_;
+
+ // remote HW address (src if receiving packet, dst if sending packet)
+ HWAddrPtr remote_hwaddr_;
+
/// local address (dst if receiving packet, src if sending packet)
isc::asiolink::IOAddress local_addr_;
@@ -441,7 +567,7 @@ protected:
/// @brief interface index
///
/// Each network interface has assigned unique ifindex. It is functional
- /// equvalent of name, but sometimes more useful, e.g. when using crazy
+ /// equivalent of name, but sometimes more useful, e.g. when using crazy
/// systems that allow spaces in interface names e.g. MS Windows)
uint32_t ifindex_;
@@ -498,29 +624,6 @@ protected:
// end of real DHCPv4 fields
- /// output buffer (used during message transmission)
- ///
- /// @warning This protected member is accessed by derived
- /// classes directly. One of such derived classes is
- /// @ref perfdhcp::PerfPkt4. The impact on derived clasess'
- /// behavior must be taken into consideration before making
- /// changes to this member such as access scope restriction or
- /// data format change etc.
- isc::util::OutputBuffer bufferOut_;
-
- /// that's the data of input buffer used in RX packet. Note that
- /// InputBuffer does not store the data itself, but just expects that
- /// data will be valid for the whole life of InputBuffer. Therefore we
- /// need to keep the data around.
- ///
- /// @warning This protected member is accessed by derived
- /// classes directly. One of such derived classes is
- /// @ref perfdhcp::PerfPkt4. The impact on derived clasess'
- /// behavior must be taken into consideration before making
- /// changes to this member such as access scope restriction or
- /// data format change etc.
- std::vector<uint8_t> data_;
-
/// collection of options present in this message
///
/// @warning This protected member is accessed by derived
@@ -533,6 +636,7 @@ protected:
/// packet timestamp
boost::posix_time::ptime timestamp_;
+
}; // Pkt4 class
typedef boost::shared_ptr<Pkt4> Pkt4Ptr;
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
index c3a98bf..9ace94a 100644
--- a/src/lib/dhcp/pkt6.cc
+++ b/src/lib/dhcp/pkt6.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -21,10 +21,17 @@
#include <sstream>
using namespace std;
+using namespace isc::asiolink;
namespace isc {
namespace dhcp {
+Pkt6::RelayInfo::RelayInfo()
+ :msg_type_(0), hop_count_(0), linkaddr_("::"), peeraddr_("::"), relay_msg_len_(0) {
+ // interface_id_, subscriber_id_, remote_id_ initialized to NULL
+ // echo_options_ initialized to empty collection
+}
+
Pkt6::Pkt6(const uint8_t* buf, uint32_t buf_len, DHCPv6Proto proto /* = UDP */) :
proto_(proto),
msg_type_(0),
@@ -54,9 +61,117 @@ Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/) :
}
uint16_t Pkt6::len() {
+ if (relay_info_.empty()) {
+ return (directLen());
+ } else {
+ // Unfortunately we need to re-calculate relay size every time, because
+ // we need to make sure that once a new option is added, its extra size
+ // is reflected in Pkt6::len().
+ calculateRelaySizes();
+ return (relay_info_[0].relay_msg_len_ + getRelayOverhead(relay_info_[0]));
+ }
+}
+
+OptionPtr Pkt6::getAnyRelayOption(uint16_t opt_type, RelaySearchOrder order) {
+
+ if (relay_info_.empty()) {
+ // There's no relay info, this is a direct message
+ return (OptionPtr());
+ }
+
+ int start = 0; // First relay to check
+ int end = 0; // Last relay to check
+ int direction = 0; // How we going to iterate: forward or backward?
+
+ switch (order) {
+ case RELAY_SEARCH_FROM_CLIENT:
+ // Search backwards
+ start = relay_info_.size() - 1;
+ end = 0;
+ direction = -1;
+ break;
+ case RELAY_SEARCH_FROM_SERVER:
+ // Search forward
+ start = 0;
+ end = relay_info_.size() - 1;
+ direction = 1;
+ break;
+ case RELAY_GET_FIRST:
+ // Look at the innermost relay only
+ start = relay_info_.size() - 1;
+ end = start;
+ direction = 1;
+ break;
+ case RELAY_GET_LAST:
+ // Look at the outermost relay only
+ start = 0;
+ end = 0;
+ direction = 1;
+ }
+
+ // This is a tricky loop. It must go from start to end, but it must work in
+ // both directions (start > end; or start < end). We can't use regular
+ // exit condition, because we don't know whether to use i <= end or i >= end.
+ // That's why we check if in the next iteration we would go past the
+ // list (end + direction). It is similar to STL concept of end pointing
+ // to a place after the last element
+ for (int i = start; i != end + direction; i += direction) {
+ OptionPtr opt = getRelayOption(opt_type, i);
+ if (opt) {
+ return (opt);
+ }
+ }
+
+ // We iterated over specified relays and haven't found what we were
+ // looking for
+ return (OptionPtr());
+}
+
+
+OptionPtr Pkt6::getRelayOption(uint16_t opt_type, uint8_t relay_level) {
+ if (relay_level >= relay_info_.size()) {
+ isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)."
+ << " There is no info about " << relay_level + 1 << " relay.");
+ }
+
+ for (Option::OptionCollection::iterator it = relay_info_[relay_level].options_.begin();
+ it != relay_info_[relay_level].options_.end(); ++it) {
+ if ((*it).second->getType() == opt_type) {
+ return (it->second);
+ }
+ }
+
+ return (OptionPtr());
+}
+
+uint16_t Pkt6::getRelayOverhead(const RelayInfo& relay) const {
+ uint16_t len = DHCPV6_RELAY_HDR_LEN // fixed header
+ + Option::OPTION6_HDR_LEN; // header of the relay-msg option
+
+ for (Option::OptionCollection::const_iterator opt = relay.options_.begin();
+ opt != relay.options_.end(); ++opt) {
+ len += (opt->second)->len();
+ }
+
+ return (len);
+}
+
+uint16_t Pkt6::calculateRelaySizes() {
+
+ uint16_t len = directLen(); // start with length of all options
+
+ for (int relay_index = relay_info_.size(); relay_index > 0; --relay_index) {
+ relay_info_[relay_index - 1].relay_msg_len_ = len;
+ len += getRelayOverhead(relay_info_[relay_index - 1]);
+ }
+
+ return (len);
+}
+
+uint16_t Pkt6::directLen() const {
uint16_t length = DHCPV6_PKT_HDR_LEN; // DHCPv6 header
- for (Option::OptionCollection::iterator it = options_.begin();
+ for (Option::OptionCollection::const_iterator it = options_.begin();
it != options_.end();
++it) {
length += (*it).second->len();
@@ -66,22 +181,67 @@ uint16_t Pkt6::len() {
}
-bool
+void
Pkt6::pack() {
switch (proto_) {
case UDP:
- return packUDP();
+ packUDP();
+ break;
case TCP:
- return packTCP();
+ packTCP();
+ break;
default:
isc_throw(BadValue, "Invalid protocol specified (non-TCP, non-UDP)");
}
- return (false); // never happens
}
-bool
+void
Pkt6::packUDP() {
try {
+
+ // is this a relayed packet?
+ if (!relay_info_.empty()) {
+
+ // calculate size needed for each relay (if there is only one relay,
+ // then it will be equal to "regular" length + relay-forw header +
+ // size of relay-msg option header + possibly size of interface-id
+ // option (if present). If there is more than one relay, the whole
+ // process is called iteratively for each relay.
+ calculateRelaySizes();
+
+ // Now for each relay, we need to...
+ for (vector<RelayInfo>::iterator relay = relay_info_.begin();
+ relay != relay_info_.end(); ++relay) {
+
+ // build relay-forw/relay-repl header (see RFC3315, section 7)
+ bufferOut_.writeUint8(relay->msg_type_);
+ bufferOut_.writeUint8(relay->hop_count_);
+ bufferOut_.writeData(&(relay->linkaddr_.toBytes()[0]),
+ isc::asiolink::V6ADDRESS_LEN);
+ bufferOut_.writeData(&relay->peeraddr_.toBytes()[0],
+ isc::asiolink::V6ADDRESS_LEN);
+
+ // store every option in this relay scope. Usually that will be
+ // only interface-id, but occasionally other options may be
+ // present here as well (vendor-opts for Cable modems,
+ // subscriber-id, remote-id, options echoed back from Echo
+ // Request Option, etc.)
+ for (Option::OptionCollection::const_iterator opt =
+ relay->options_.begin();
+ opt != relay->options_.end(); ++opt) {
+ (opt->second)->pack(bufferOut_);
+ }
+
+ // and include header relay-msg option. Its payload will be
+ // generated in the next iteration (if there are more relays)
+ // or outside the loop (if there are no more relays and the
+ // payload is a direct message)
+ bufferOut_.writeUint16(D6O_RELAY_MSG);
+ bufferOut_.writeUint16(relay->relay_msg_len_);
+ }
+
+ }
+
// DHCPv6 header: message-type (1 octect) + transaction id (3 octets)
bufferOut_.writeUint8(msg_type_);
// store 3-octet transaction-id
@@ -93,16 +253,15 @@ Pkt6::packUDP() {
LibDHCP::packOptions(bufferOut_, options_);
}
catch (const Exception& e) {
- /// @todo: throw exception here once we turn this function to void.
- return (false);
+ // An exception is thrown and message will be written to Logger
+ isc_throw(InvalidOperation, e.what());
}
- return (true);
}
-bool
+void
Pkt6::packTCP() {
/// TODO Implement this function.
- isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover)"
+ isc_throw(NotImplemented, "DHCPv6 over TCP (bulk leasequery and failover)"
"not implemented yet.");
}
@@ -127,12 +286,43 @@ Pkt6::unpackUDP() {
return (false);
}
msg_type_ = data_[0];
- transid_ = ( (data_[1]) << 16 ) +
- ((data_[2]) << 8) + (data_[3]);
+ switch (msg_type_) {
+ case DHCPV6_SOLICIT:
+ case DHCPV6_ADVERTISE:
+ case DHCPV6_REQUEST:
+ case DHCPV6_CONFIRM:
+ case DHCPV6_RENEW:
+ case DHCPV6_REBIND:
+ case DHCPV6_REPLY:
+ case DHCPV6_DECLINE:
+ case DHCPV6_RECONFIGURE:
+ case DHCPV6_INFORMATION_REQUEST:
+ default: // assume that uknown messages are not using relay format
+ {
+ return (unpackMsg(data_.begin(), data_.end()));
+ }
+ case DHCPV6_RELAY_FORW:
+ case DHCPV6_RELAY_REPL:
+ return (unpackRelayMsg());
+ }
+}
+
+bool
+Pkt6::unpackMsg(OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end) {
+ if (std::distance(begin, end) < 4) {
+ // truncated message (less than 4 bytes)
+ return (false);
+ }
+
+ msg_type_ = *begin++;
+
+ transid_ = ( (*begin++) << 16 ) +
+ ((*begin++) << 8) + (*begin++);
transid_ = transid_ & 0xffffff;
try {
- OptionBuffer opt_buffer(data_.begin() + 4, data_.end());
+ OptionBuffer opt_buffer(begin, end);
LibDHCP::unpackOptions6(opt_buffer, options_);
} catch (const Exception& e) {
@@ -143,6 +333,97 @@ Pkt6::unpackUDP() {
}
bool
+Pkt6::unpackRelayMsg() {
+
+ // we use offset + bufsize, because we want to avoid creating unnecessary
+ // copies. There may be up to 32 relays. While using InputBuffer would
+ // be probably a bit cleaner, copying data up to 32 times is unacceptable
+ // price here. Hence a single buffer with offets and lengths.
+ size_t bufsize = data_.size();
+ size_t offset = 0;
+
+ while (bufsize >= DHCPV6_RELAY_HDR_LEN) {
+
+ RelayInfo relay;
+
+ size_t relay_msg_offset = 0;
+ size_t relay_msg_len = 0;
+
+ // parse fixed header first (first 34 bytes)
+ relay.msg_type_ = data_[offset++];
+ relay.hop_count_ = data_[offset++];
+ relay.linkaddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]);
+ offset += isc::asiolink::V6ADDRESS_LEN;
+ relay.peeraddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]);
+ offset += isc::asiolink::V6ADDRESS_LEN;
+ bufsize -= DHCPV6_RELAY_HDR_LEN; // 34 bytes (1+1+16+16)
+
+ try {
+ // parse the rest as options
+ OptionBuffer opt_buffer(&data_[offset], &data_[offset+bufsize]);
+ LibDHCP::unpackOptions6(opt_buffer, relay.options_, &relay_msg_offset,
+ &relay_msg_len);
+
+ /// @todo: check that each option appears at most once
+ //relay.interface_id_ = options->getOption(D6O_INTERFACE_ID);
+ //relay.subscriber_id_ = options->getOption(D6O_SUBSCRIBER_ID);
+ //relay.remote_id_ = options->getOption(D6O_REMOTE_ID);
+
+ if (relay_msg_offset == 0 || relay_msg_len == 0) {
+ isc_throw(BadValue, "Mandatory relay-msg option missing");
+ }
+
+ // store relay information parsed so far
+ addRelayInfo(relay);
+
+ /// @todo: implement ERO here
+
+ if (relay_msg_len >= bufsize) {
+ // length of the relay_msg option extends beyond end of the message
+ isc_throw(Unexpected, "Relay-msg option is truncated.");
+ return false;
+ }
+ uint8_t inner_type = data_[offset + relay_msg_offset];
+ offset += relay_msg_offset; // offset is relative
+ bufsize = relay_msg_len; // length is absolute
+
+ if ( (inner_type != DHCPV6_RELAY_FORW) &&
+ (inner_type != DHCPV6_RELAY_REPL)) {
+ // Ok, the inner message is not encapsulated, let's decode it
+ // directly
+ return (unpackMsg(data_.begin() + offset, data_.begin() + offset
+ + relay_msg_len));
+ }
+
+ // Oh well, there's inner relay-forw or relay-repl inside. Let's
+ // unpack it as well. The next loop iteration will take care
+ // of that.
+ } catch (const Exception& e) {
+ /// @todo: throw exception here once we turn this function to void.
+ return (false);
+ }
+ }
+
+ if ( (offset == data_.size()) && (bufsize == 0) ) {
+ // message has been parsed completely
+ return (true);
+ }
+
+ /// @todo: log here that there are additional unparsed bytes
+ return (true);
+}
+
+void
+Pkt6::addRelayInfo(const RelayInfo& relay) {
+ if (relay_info_.size() > 32) {
+ isc_throw(BadValue, "Massage cannot be encapsulated more than 32 times");
+ }
+
+ /// @todo: Implement type checks here (e.g. we could receive relay-forw in relay-repl)
+ relay_info_.push_back(relay);
+}
+
+bool
Pkt6::unpackTCP() {
isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover) "
"not implemented yet.");
@@ -258,5 +539,33 @@ const char* Pkt6::getName() const {
return (getName(getType()));
}
+void Pkt6::copyRelayInfo(const Pkt6Ptr& question) {
+
+ // We use index rather than iterator, because we need that as a parameter
+ // passed to getRelayOption()
+ for (int i = 0; i < question->relay_info_.size(); ++i) {
+ RelayInfo info;
+ info.msg_type_ = DHCPV6_RELAY_REPL;
+ info.hop_count_ = question->relay_info_[i].hop_count_;
+ info.linkaddr_ = question->relay_info_[i].linkaddr_;
+ info.peeraddr_ = question->relay_info_[i].peeraddr_;
+
+ // Is there an interface-id option in this nesting level?
+ // If there is, we need to echo it back
+ OptionPtr opt = question->getRelayOption(D6O_INTERFACE_ID, i);
+ // taken from question->RelayInfo_[i].options_
+ if (opt) {
+ info.options_.insert(make_pair(opt->getType(), opt));
+ }
+
+ /// @todo: Implement support for ERO (Echo Request Option, RFC4994)
+
+ // Add this relay-forw info (client's message) to our relay-repl
+ // message (server's response)
+ relay_info_.push_back(info);
+ }
+}
+
+
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index 6ffea2b..a7f95c6 100644
--- a/src/lib/dhcp/pkt6.h
+++ b/src/lib/dhcp/pkt6.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -30,17 +30,67 @@ namespace isc {
namespace dhcp {
+class Pkt6;
+typedef boost::shared_ptr<Pkt6> Pkt6Ptr;
+
class Pkt6 {
public:
- /// specifes DHCPv6 packet header length
+ /// specifies non-relayed DHCPv6 packet header length (over UDP)
const static size_t DHCPV6_PKT_HDR_LEN = 4;
+ /// specifies relay DHCPv6 packet header length (over UDP)
+ const static size_t DHCPV6_RELAY_HDR_LEN = 34;
+
/// DHCPv6 transport protocol
enum DHCPv6Proto {
UDP = 0, // most packets are UDP
TCP = 1 // there are TCP DHCPv6 packets (bulk leasequery, failover)
};
+ /// @brief defines relay search pattern
+ ///
+ /// Defines order in which options are searched in a message that
+ /// passed through mulitple relays. RELAY_SEACH_FROM_CLIENT will
+ /// start search from the relay that was the closest to the client
+ /// (i.e. innermost in the encapsulated message, which also means
+ /// this was the first relay that forwarded packet received by the
+ /// server and this will be the last relay that will handle the
+ /// response that server sent towards the client.).
+ /// RELAY_SEARCH_FROM_SERVER is the opposite. This will be the
+ /// relay closest to the server (i.e. outermost in the encapsulated
+ /// message, which also means it was the last relay that relayed
+ /// the received message and will be the first one to process
+ /// server's response). RELAY_GET_FIRST will try to get option from
+ /// the first relay only (closest to the client), RELAY_GET_LAST will
+ /// try to get option form the the last relay (closest to the server).
+ enum RelaySearchOrder {
+ RELAY_SEARCH_FROM_CLIENT = 1,
+ RELAY_SEARCH_FROM_SERVER = 2,
+ RELAY_GET_FIRST = 3,
+ RELAY_GET_LAST = 4
+ };
+
+ /// @brief structure that describes a single relay information
+ ///
+ /// Client sends messages. Each relay along its way will encapsulate the message.
+ /// This structure represents all information added by a single relay.
+ struct RelayInfo {
+
+ /// @brief default constructor
+ RelayInfo();
+ uint8_t msg_type_; ///< message type (RELAY-FORW oro RELAY-REPL)
+ uint8_t hop_count_; ///< number of traversed relays (up to 32)
+ isc::asiolink::IOAddress linkaddr_;///< fixed field in relay-forw/relay-reply
+ isc::asiolink::IOAddress peeraddr_;///< fixed field in relay-forw/relay-reply
+
+ /// @brief length of the relay_msg_len
+ /// Used when calculating length during pack/unpack
+ uint16_t relay_msg_len_;
+
+ /// options received from a specified relay, except relay-msg option
+ isc::dhcp::Option::OptionCollection options_;
+ };
+
/// Constructor, used in replying to a message
///
/// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...)
@@ -66,8 +116,10 @@ public:
/// Output buffer will be stored in data_. Length
/// will be set in data_len_.
///
- /// @return true if packing procedure was successful
- bool pack();
+ /// @throw BadValue if packet protocol is invalid, InvalidOperation
+ /// if packing fails, or NotImplemented if protocol is TCP (IPv6 over TCP is
+ /// not yet supported).
+ void pack();
/// @brief Dispatch method that handles binary packet parsing.
///
@@ -89,12 +141,6 @@ public:
/// @return reference to output buffer
const isc::util::OutputBuffer& getBuffer() const { return (bufferOut_); };
-
- /// @brief Returns reference to input buffer.
- ///
- /// @return reference to input buffer
- const OptionBuffer& getData() const { return(data_); }
-
/// @brief Returns protocol of this packet (UDP or TCP).
///
/// @return protocol type
@@ -160,6 +206,35 @@ public:
/// @return pointer to found option (or NULL)
OptionPtr getOption(uint16_t type);
+ /// @brief returns option inserted by relay
+ ///
+ /// Returns an option from specified relay scope (inserted by a given relay
+ /// if this is received packet or to be decapsulated by a given relay if
+ /// this is a transmitted packet). nesting_level specifies which relay
+ /// scope is to be used. 0 is the outermost encapsulation (relay closest to
+ /// the server). pkt->relay_info_.size() - 1 is the innermost encapsulation
+ /// (relay closest to the client).
+ ///
+ /// @throw isc::OutOfRange if nesting level has invalid value.
+ ///
+ /// @param option_code code of the requested option
+ /// @param nesting_level see description above
+ ///
+ /// @return pointer to the option (or NULL if there is no such option)
+ OptionPtr getRelayOption(uint16_t option_code, uint8_t nesting_level);
+
+ /// @brief Return first instance of a specified option
+ ///
+ /// When a client's packet traverses multiple relays, each passing relay may
+ /// insert extra options. This method allows the specific instance of a given
+ /// option to be obtained (e.g. closest to the client, closest to the server,
+ /// etc.) See @ref RelaySearchOrder for a detailed description.
+ ///
+ /// @param option_code searched option
+ /// @param order option search order (see @ref RelaySearchOrder)
+ /// @return option pointer (or NULL if no option matches specified criteria)
+ OptionPtr getAnyRelayOption(uint16_t option_code, RelaySearchOrder order);
+
/// @brief Returns all instances of specified type.
///
/// Returns all instances of options of the specified type. DHCPv6 protocol
@@ -246,7 +321,7 @@ public:
/// @brief Returns packet timestamp.
///
/// Returns packet timestamp value updated when
- /// packet is received or send.
+ /// packet is received or sent.
///
/// @return packet timestamp.
const boost::posix_time::ptime& getTimestamp() const { return timestamp_; }
@@ -259,9 +334,17 @@ public:
/// @return interface name
void setIface(const std::string& iface ) { iface_ = iface; };
+ /// @brief add information about one traversed relay
+ ///
+ /// This adds information about one traversed relay, i.e.
+ /// one relay-forw or relay-repl level of encapsulation.
+ ///
+ /// @param relay structure with necessary relay information
+ void addRelayInfo(const RelayInfo& relay);
+
/// collection of options present in this message
///
- /// @warning This protected member is accessed by derived
+ /// @warning This public member is accessed by derived
/// classes directly. One of such derived classes is
/// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
/// behavior must be taken into consideration before making
@@ -305,18 +388,47 @@ public:
/// be freed by the caller.
const char* getName() const;
+ /// @brief copies relay information from client's packet to server's response
+ ///
+ /// This information is not simply copied over. Some parameter are
+ /// removed, msg_type_is updated (RELAY-FORW => RELAY-REPL), etc.
+ ///
+ /// @param question client's packet
+ void copyRelayInfo(const Pkt6Ptr& question);
+
+ /// relay information
+ ///
+ /// this is a public field. Otherwise we hit one of the two problems:
+ /// we return reference to an internal field (and that reference could
+ /// be potentially used past Pkt6 object lifetime causing badness) or
+ /// we return a copy (which is inefficient and also causes any updates
+ /// to be impossible). Therefore public field is considered the best
+ /// (or least bad) solution.
+ std::vector<RelayInfo> relay_info_;
+
+
+ /// unparsed data (in received packets)
+ ///
+ /// @warning This public member is accessed by derived
+ /// classes directly. One of such derived classes is
+ /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
+ /// behavior must be taken into consideration before making
+ /// changes to this member such as access scope restriction or
+ /// data format change etc.
+ OptionBuffer data_;
+
protected:
/// Builds on wire packet for TCP transmission.
///
/// TODO This function is not implemented yet.
///
- /// @return true, if build was successful
- bool packTCP();
+ /// @throw NotImplemented, IPv6 over TCP is not yet supported.
+ void packTCP();
/// Builds on wire packet for UDP transmission.
///
- /// @return true, if build was successful
- bool packUDP();
+ /// @throw InvalidOperation if packing fails
+ void packUDP();
/// @brief Parses on-wire form of TCP DHCPv6 packet.
///
@@ -340,6 +452,44 @@ protected:
/// @return true, if build was successful
bool unpackUDP();
+ /// @brief unpacks direct (non-relayed) message
+ ///
+ /// This method unpacks specified buffer range as a direct
+ /// (e.g. solicit or request) message. This method is called from
+ /// unpackUDP() when received message is detected to be direct.
+ ///
+ /// @param begin start of the buffer
+ /// @param end end of the buffer
+ /// @return true if parsing was successful and there are no leftover bytes
+ bool unpackMsg(OptionBuffer::const_iterator begin,
+ OptionBuffer::const_iterator end);
+
+ /// @brief unpacks relayed message (RELAY-FORW or RELAY-REPL)
+ ///
+ /// This method is called from unpackUDP() when received message
+ /// is detected to be relay-message. It goes iteratively over
+ /// all relays (if there are multiple encapsulation levels).
+ ///
+ /// @return true if parsing was successful
+ bool unpackRelayMsg();
+
+ /// @brief calculates overhead introduced in specified relay
+ ///
+ /// It is used when calculating message size and packing message
+ /// @param relay RelayInfo structure that holds information about relay
+ /// @return number of bytes needed to store relay information
+ uint16_t getRelayOverhead(const RelayInfo& relay) const;
+
+ /// @brief calculates overhead for all relays defined for this message
+ /// @return number of bytes needed to store all relay information
+ uint16_t calculateRelaySizes();
+
+ /// @brief calculates size of the message as if it was not relayed at all
+ ///
+ /// This is equal to len() if the message was not relayed.
+ /// @return number of bytes required to store the message
+ uint16_t directLen() const;
+
/// UDP (usually) or TCP (bulk leasequery or failover)
DHCPv6Proto proto_;
@@ -349,23 +499,13 @@ protected:
/// DHCPv6 transaction-id
uint32_t transid_;
- /// unparsed data (in received packets)
- ///
- /// @warning This protected member is accessed by derived
- /// classes directly. One of such derived classes is
- /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
- /// behavior must be taken into consideration before making
- /// changes to this member such as access scope restriction or
- /// data format change etc.
- OptionBuffer data_;
-
/// name of the network interface the packet was received/to be sent over
std::string iface_;
/// @brief interface index
///
/// interface index (each network interface has assigned unique ifindex
- /// it is functional equvalent of name, but sometimes more useful, e.g.
+ /// it is functional equivalent of name, but sometimes more useful, e.g.
/// when using crazy systems that allow spaces in interface names
/// e.g. windows
int ifindex_;
@@ -396,8 +536,6 @@ protected:
boost::posix_time::ptime timestamp_;
}; // Pkt6 class
-typedef boost::shared_ptr<Pkt6> Pkt6Ptr;
-
} // isc::dhcp namespace
} // isc namespace
diff --git a/src/lib/dhcp/pkt_filter.h b/src/lib/dhcp/pkt_filter.h
new file mode 100644
index 0000000..204b25e
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter.h
@@ -0,0 +1,111 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 PKT_FILTER_H
+#define PKT_FILTER_H
+
+#include <dhcp/pkt4.h>
+#include <asiolink/io_address.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when invalid packet filter object specified.
+class InvalidPacketFilter : public Exception {
+public:
+ InvalidPacketFilter(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// Forward declaration to the structure describing a socket.
+struct SocketInfo;
+
+/// Forward declaration to the class representing interface
+class Iface;
+
+/// @brief Abstract packet handling class
+///
+/// This class represents low level method to send and receive DHCP packet.
+/// Different methods, represented by classes derived from this class, use
+/// different socket families and socket types. Also, various packet filtering
+/// methods can be implemented by derived classes, e.g. Linux Packet
+/// Filtering (LPF) or Berkeley Packet Filtering (BPF).
+///
+/// Low-level code operating on sockets may require special privileges to execute.
+/// For example: opening raw socket or opening socket on low port number requires
+/// root privileges. This makes it impossible or very hard to unit test the IfaceMgr.
+/// In order to overcome this problem, it is recommended to create mock object derived
+/// from this class that mimics the behavior of the real packet handling class making
+/// IfaceMgr testable.
+class PktFilter {
+public:
+
+ /// @brief Virtual Destructor
+ virtual ~PktFilter() { }
+
+ /// @brief Check if packet can be sent to the host without address directly.
+ ///
+ /// Checks if the Packet Filter class has capability to send a packet
+ /// directly to the client having no address assigned. This capability
+ /// is used by DHCPv4 servers which respond to the clients they assign
+ /// addresses to. Not all classes derived from PktFilter support this
+ /// because it requires injection of the destination host HW address to
+ /// the link layer header of the packet.
+ ///
+ /// @return true of the direct response is supported.
+ virtual bool isDirectResponseSupported() const = 0;
+
+ /// @brief Open socket.
+ ///
+ /// @param iface interface descriptor
+ /// @param addr address on the interface to be used to send packets.
+ /// @param port port number.
+ /// @param receive_bcast configure socket to receive broadcast messages
+ /// @param send_bcast configure socket to send broadcast messages.
+ ///
+ /// @return created socket's descriptor
+ virtual int openSocket(const Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast) = 0;
+
+ /// @brief Receive packet over specified socket.
+ ///
+ /// @param iface interface
+ /// @param socket_info structure holding socket information
+ ///
+ /// @return Received packet
+ virtual Pkt4Ptr receive(const Iface& iface,
+ const SocketInfo& socket_info) = 0;
+
+ /// @brief Send packet over specified socket.
+ ///
+ /// @param iface interface to be used to send packet
+ /// @param sockfd socket descriptor
+ /// @param pkt packet to be sent
+ ///
+ /// @return result of sending the packet. It is 0 if successful.
+ virtual int send(const Iface& iface, uint16_t sockfd,
+ const Pkt4Ptr& pkt) = 0;
+};
+
+/// Pointer to a PktFilter object.
+typedef boost::shared_ptr<PktFilter> PktFilterPtr;
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_H
diff --git a/src/lib/dhcp/pkt_filter_inet.cc b/src/lib/dhcp/pkt_filter_inet.cc
new file mode 100644
index 0000000..62695e5
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_inet.cc
@@ -0,0 +1,252 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_inet.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+PktFilterInet::PktFilterInet()
+ : control_buf_len_(CMSG_SPACE(sizeof(struct in6_pktinfo))),
+ control_buf_(new char[control_buf_len_])
+{
+}
+
+int PktFilterInet::openSocket(const Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast) {
+
+ struct sockaddr_in addr4;
+ memset(&addr4, 0, sizeof(sockaddr));
+ addr4.sin_family = AF_INET;
+ addr4.sin_port = htons(port);
+
+ // If we are to receive broadcast messages we have to bind
+ // to "ANY" address.
+ if (receive_bcast && iface.flag_broadcast_) {
+ addr4.sin_addr.s_addr = INADDR_ANY;
+ } else {
+ addr4.sin_addr.s_addr = htonl(addr);
+ }
+
+ int sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ isc_throw(SocketConfigError, "Failed to create UDP6 socket.");
+ }
+
+#ifdef SO_BINDTODEVICE
+ if (receive_bcast && iface.flag_broadcast_) {
+ // Bind to device so as we receive traffic on a specific interface.
+ if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, iface.getName().c_str(),
+ iface.getName().length() + 1) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to set SO_BINDTODEVICE option"
+ << " on socket " << sock);
+ }
+ }
+#endif
+
+ if (send_bcast && iface.flag_broadcast_) {
+ // Enable sending to broadcast address.
+ int flag = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag)) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to set SO_BROADCAST option"
+ << " on socket " << sock);
+ }
+ }
+
+ if (bind(sock, (struct sockaddr *)&addr4, sizeof(addr4)) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to bind socket " << sock << " to " << addr.toText()
+ << "/port=" << port);
+ }
+
+ // if there is no support for IP_PKTINFO, we are really out of luck
+ // it will be difficult to undersand, where this packet came from
+#if defined(IP_PKTINFO)
+ int flag = 1;
+ if (setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &flag, sizeof(flag)) != 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "setsockopt: IP_PKTINFO: failed.");
+ }
+#endif
+
+ return (sock);
+
+}
+
+Pkt4Ptr
+PktFilterInet::receive(const Iface& iface, const SocketInfo& socket_info) {
+ struct sockaddr_in from_addr;
+ uint8_t buf[IfaceMgr::RCVBUFSIZE];
+
+ memset(&control_buf_[0], 0, control_buf_len_);
+ memset(&from_addr, 0, sizeof(from_addr));
+
+ // Initialize our message header structure.
+ struct msghdr m;
+ memset(&m, 0, sizeof(m));
+
+ // Point so we can get the from address.
+ m.msg_name = &from_addr;
+ m.msg_namelen = sizeof(from_addr);
+
+ struct iovec v;
+ v.iov_base = static_cast<void*>(buf);
+ v.iov_len = IfaceMgr::RCVBUFSIZE;
+ m.msg_iov = &v;
+ m.msg_iovlen = 1;
+
+ // Getting the interface is a bit more involved.
+ //
+ // We set up some space for a "control message". We have
+ // previously asked the kernel to give us packet
+ // information (when we initialized the interface), so we
+ // should get the destination address from that.
+ m.msg_control = &control_buf_[0];
+ m.msg_controllen = control_buf_len_;
+
+ int result = recvmsg(socket_info.sockfd_, &m, 0);
+ if (result < 0) {
+ isc_throw(SocketReadError, "failed to receive UDP4 data");
+ }
+
+ // We have all data let's create Pkt4 object.
+ Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(buf, result));
+
+ pkt->updateTimestamp();
+
+ unsigned int ifindex = iface.getIndex();
+
+ IOAddress from(htonl(from_addr.sin_addr.s_addr));
+ uint16_t from_port = htons(from_addr.sin_port);
+
+ // Set receiving interface based on information, which socket was used to
+ // receive data. OS-specific info (see os_receive4()) may be more reliable,
+ // so this value may be overwritten.
+ pkt->setIndex(ifindex);
+ pkt->setIface(iface.getName());
+ pkt->setRemoteAddr(from);
+ pkt->setRemotePort(from_port);
+ pkt->setLocalPort(socket_info.port_);
+
+// In the future the OS-specific code may be abstracted to a different
+// file but for now we keep it here because there is no code yet, which
+// is specific to non-Linux systems.
+#if defined (IP_PKTINFO) && defined (OS_LINUX)
+ struct cmsghdr* cmsg;
+ struct in_pktinfo* pktinfo;
+ struct in_addr to_addr;
+
+ memset(&to_addr, 0, sizeof(to_addr));
+
+ cmsg = CMSG_FIRSTHDR(&m);
+ while (cmsg != NULL) {
+ if ((cmsg->cmsg_level == IPPROTO_IP) &&
+ (cmsg->cmsg_type == IP_PKTINFO)) {
+ pktinfo = (struct in_pktinfo*)CMSG_DATA(cmsg);
+
+ pkt->setIndex(pktinfo->ipi_ifindex);
+ pkt->setLocalAddr(IOAddress(htonl(pktinfo->ipi_addr.s_addr)));
+ break;
+
+ // This field is useful, when we are bound to unicast
+ // address e.g. 192.0.2.1 and the packet was sent to
+ // broadcast. This will return broadcast address, not
+ // the address we are bound to.
+
+ // XXX: Perhaps we should uncomment this:
+ // to_addr = pktinfo->ipi_spec_dst;
+ }
+ cmsg = CMSG_NXTHDR(&m, cmsg);
+ }
+#endif
+
+ return (pkt);
+}
+
+int
+PktFilterInet::send(const Iface&, uint16_t sockfd,
+ const Pkt4Ptr& pkt) {
+ memset(&control_buf_[0], 0, control_buf_len_);
+
+ // Set the target address we're sending to.
+ sockaddr_in to;
+ memset(&to, 0, sizeof(to));
+ to.sin_family = AF_INET;
+ to.sin_port = htons(pkt->getRemotePort());
+ to.sin_addr.s_addr = htonl(pkt->getRemoteAddr());
+
+ struct msghdr m;
+ // Initialize our message header structure.
+ memset(&m, 0, sizeof(m));
+ m.msg_name = &to;
+ m.msg_namelen = sizeof(to);
+
+ // Set the data buffer we're sending. (Using this wacky
+ // "scatter-gather" stuff... we only have a single chunk
+ // of data to send, so we declare a single vector entry.)
+ struct iovec v;
+ memset(&v, 0, sizeof(v));
+ // iov_base field is of void * type. We use it for packet
+ // transmission, so this buffer will not be modified.
+ v.iov_base = const_cast<void *>(pkt->getBuffer().getData());
+ v.iov_len = pkt->getBuffer().getLength();
+ m.msg_iov = &v;
+ m.msg_iovlen = 1;
+
+// In the future the OS-specific code may be abstracted to a different
+// file but for now we keep it here because there is no code yet, which
+// is specific to non-Linux systems.
+#if defined (IP_PKTINFO) && defined (OS_LINUX)
+ // Setting the interface is a bit more involved.
+ //
+ // We have to create a "control message", and set that to
+ // define the IPv4 packet information. We could set the
+ // source address if we wanted, but we can safely let the
+ // kernel decide what that should be.
+ m.msg_control = &control_buf_[0];
+ m.msg_controllen = control_buf_len_;
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
+ cmsg->cmsg_level = IPPROTO_IP;
+ cmsg->cmsg_type = IP_PKTINFO;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+ struct in_pktinfo* pktinfo =(struct in_pktinfo *)CMSG_DATA(cmsg);
+ memset(pktinfo, 0, sizeof(struct in_pktinfo));
+ pktinfo->ipi_ifindex = pkt->getIndex();
+ m.msg_controllen = cmsg->cmsg_len;
+#endif
+
+ pkt->updateTimestamp();
+
+ int result = sendmsg(sockfd, &m, 0);
+ if (result < 0) {
+ isc_throw(SocketWriteError, "pkt4 send failed");
+ }
+
+ return (result);
+}
+
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/pkt_filter_inet.h b/src/lib/dhcp/pkt_filter_inet.h
new file mode 100644
index 0000000..95c9224
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_inet.h
@@ -0,0 +1,90 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 PKT_FILTER_INET_H
+#define PKT_FILTER_INET_H
+
+#include <dhcp/pkt_filter.h>
+#include <boost/scoped_array.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Packet handling class using AF_INET socket family
+///
+/// This class provides methods to send and recive packet via socket using
+/// AF_INET family and SOCK_DGRAM type.
+class PktFilterInet : public PktFilter {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Allocates control buffer.
+ PktFilterInet();
+
+ /// @brief Check if packet can be sent to the host without address directly.
+ ///
+ /// This Packet Filter sends packets through AF_INET datagram sockets, so
+ /// it can't inject the HW address of the destionation host into the packet.
+ /// Therefore this class does not support direct responses.
+ ///
+ /// @return false always.
+ virtual bool isDirectResponseSupported() const {
+ return (false);
+ }
+
+ /// @brief Open socket.
+ ///
+ /// @param iface interface descriptor
+ /// @param addr address on the interface to be used to send packets.
+ /// @param port port number.
+ /// @param receive_bcast configure socket to receive broadcast messages
+ /// @param send_bcast configure socket to send broadcast messages.
+ ///
+ /// @return created socket's descriptor
+ virtual int openSocket(const Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast);
+
+ /// @brief Receive packet over specified socket.
+ ///
+ /// @param iface interface
+ /// @param socket_info structure holding socket information
+ ///
+ /// @return Received packet
+ virtual Pkt4Ptr receive(const Iface& iface, const SocketInfo& socket_info);
+
+ /// @brief Send packet over specified socket.
+ ///
+ /// @param iface interface to be used to send packet
+ /// @param sockfd socket descriptor
+ /// @param pkt packet to be sent
+ ///
+ /// @return result of sending a packet. It is 0 if successful.
+ virtual int send(const Iface& iface, uint16_t sockfd,
+ const Pkt4Ptr& pkt);
+
+private:
+ /// Length of the control_buf_ array.
+ size_t control_buf_len_;
+ /// Control buffer, used in transmission and reception.
+ boost::scoped_array<char> control_buf_;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_INET_H
diff --git a/src/lib/dhcp/pkt_filter_lpf.cc b/src/lib/dhcp/pkt_filter_lpf.cc
new file mode 100644
index 0000000..e0964d5
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_lpf.cc
@@ -0,0 +1,268 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_lpf.h>
+#include <dhcp/protocol_util.h>
+#include <exceptions/exceptions.h>
+#include <linux/filter.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <net/ethernet.h>
+
+namespace {
+
+using namespace isc::dhcp;
+
+/// The following structure defines a Berkely Packet Filter program to perform
+/// packet filtering. The program operates on Ethernet packets. To help with
+/// interpretation of the program, for the types of Ethernet packets we are
+/// interested in, the header layout is:
+///
+/// 6 bytes Destination Ethernet Address
+/// 6 bytes Source Ethernet Address
+/// 2 bytes Ethernet packet type
+///
+/// 20 bytes Fixed part of IP header
+/// variable Variable part of IP header
+///
+/// 2 bytes UDP Source port
+/// 2 bytes UDP destination port
+/// 4 bytes Rest of UDP header
+///
+/// @todo We may want to extend the filter to receive packets sent
+/// to the particular IP address assigned to the interface or
+/// broadcast address.
+struct sock_filter dhcp_sock_filter [] = {
+ // Make sure this is an IP packet: check the half-word (two bytes)
+ // at offset 12 in the packet (the Ethernet packet type). If it
+ // is, advance to the next instruction. If not, advance 8
+ // instructions (which takes execution to the last instruction in
+ // the sequence: "drop it").
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_PACKET_TYPE_OFFSET),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8),
+
+ // Make sure it's a UDP packet. The IP protocol is at offset
+ // 9 in the IP header so, adding the Ethernet packet header size
+ // of 14 bytes gives an absolute byte offset in the packet of 23.
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, ETHERNET_HEADER_LEN + IP_PROTO_TYPE_OFFSET),
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
+
+ // Make sure this isn't a fragment by checking that the fragment
+ // offset field in the IP header is zero. This field is the
+ // least-significant 13 bits in the bytes at offsets 6 and 7 in
+ // the IP header, so the half-word at offset 20 (6 + size of
+ // Ethernet header) is loaded and an appropriate mask applied.
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_HEADER_LEN + IP_FLAGS_OFFSET),
+ BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+
+ // Get the IP header length. This is achieved by the following
+ // (special) instruction that, given the offset of the start
+ // of the IP header (offset 14) loads the IP header length.
+ BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, ETHERNET_HEADER_LEN),
+
+ // Make sure it's to the right port. The following instruction
+ // adds the previously extracted IP header length to the given
+ // offset to locate the correct byte. The given offset of 16
+ // comprises the length of the Ethernet header (14) plus the offset
+ // of the UDP destination port (2) within the UDP header.
+ BPF_STMT(BPF_LD + BPF_H + BPF_IND, ETHERNET_HEADER_LEN + UDP_DEST_PORT),
+ // The following instruction tests against the default DHCP server port,
+ // but the action port is actually set in PktFilterLPF::openSocket().
+ // N.B. The code in that method assumes that this instruction is at
+ // offset 8 in the program. If this is changed, openSocket() must be
+ // updated.
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP4_SERVER_PORT, 0, 1),
+
+ // If we passed all the tests, ask for the whole packet.
+ BPF_STMT(BPF_RET + BPF_K, (u_int)-1),
+
+ // Otherwise, drop it.
+ BPF_STMT(BPF_RET + BPF_K, 0),
+};
+
+}
+
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+int
+PktFilterLPF::openSocket(const Iface& iface, const isc::asiolink::IOAddress&,
+ const uint16_t port, const bool,
+ const bool) {
+
+ int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
+ if (sock < 0) {
+ isc_throw(SocketConfigError, "Failed to create raw LPF socket");
+ }
+
+ // Create socket filter program. This program will only allow incoming UDP
+ // traffic which arrives on the specific (DHCP) port). It will also filter
+ // out all fragmented packets.
+ struct sock_fprog filter_program;
+ memset(&filter_program, 0, sizeof(filter_program));
+
+ filter_program.filter = dhcp_sock_filter;
+ filter_program.len = sizeof(dhcp_sock_filter) / sizeof(struct sock_filter);
+ // Override the default port value.
+ dhcp_sock_filter[8].k = port;
+ // Apply the filter.
+ if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter_program,
+ sizeof(filter_program)) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to install packet filtering program"
+ << " on the socket " << sock);
+ }
+
+ struct sockaddr_ll sa;
+ memset(&sa, 0, sizeof(sockaddr_ll));
+ sa.sll_family = AF_PACKET;
+ sa.sll_ifindex = iface.getIndex();
+
+ // For raw sockets we construct IP headers on our own, so we don't bind
+ // socket to IP address but to the interface. We will later use the
+ // Linux Packet Filtering to filter out these packets that we are
+ // interested in.
+ if (bind(sock, reinterpret_cast<const struct sockaddr*>(&sa),
+ sizeof(sa)) < 0) {
+ close(sock);
+ isc_throw(SocketConfigError, "Failed to bind LPF socket '" << sock
+ << "' to interface '" << iface.getName() << "'");
+ }
+
+ return (sock);
+
+}
+
+Pkt4Ptr
+PktFilterLPF::receive(const Iface& iface, const SocketInfo& socket_info) {
+ uint8_t raw_buf[IfaceMgr::RCVBUFSIZE];
+ int data_len = read(socket_info.sockfd_, raw_buf, sizeof(raw_buf));
+ // If negative value is returned by read(), it indicates that an
+ // error occured. If returned value is 0, no data was read from the
+ // socket. In both cases something has gone wrong, because we expect
+ // that a chunk of data is there. We signal the lack of data by
+ // returing an empty packet.
+ if (data_len <= 0) {
+ return Pkt4Ptr();
+ }
+
+ InputBuffer buf(raw_buf, data_len);
+
+ // @todo: This is awkward way to solve the chicken and egg problem
+ // whereby we don't know the offset where DHCP data start in the
+ // received buffer when we create the packet object. In general case,
+ // the IP header has variable length. The information about its length
+ // is stored in one of its fields. Therefore, we have to decode the
+ // packet to get the offset of the DHCP data. The dummy object is
+ // created so as we can pass it to the functions which decode IP stack
+ // and find actual offset of the DHCP data.
+ // Once we find the offset we can create another Pkt4 object from
+ // the reminder of the input buffer and set the IP addresses and
+ // ports from the dummy packet. We should consider doing it
+ // in some more elegant way.
+ Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0));
+
+ // Decode ethernet, ip and udp headers.
+ decodeEthernetHeader(buf, dummy_pkt);
+ decodeIpUdpHeader(buf, dummy_pkt);
+
+ // Read the DHCP data.
+ std::vector<uint8_t> dhcp_buf;
+ buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition());
+
+ // Decode DHCP data into the Pkt4 object.
+ Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(&dhcp_buf[0], dhcp_buf.size()));
+
+ // Set the appropriate packet members using data collected from
+ // the decoded headers.
+ pkt->setIndex(iface.getIndex());
+ pkt->setIface(iface.getName());
+ pkt->setLocalAddr(dummy_pkt->getLocalAddr());
+ pkt->setRemoteAddr(dummy_pkt->getRemoteAddr());
+ pkt->setLocalPort(dummy_pkt->getLocalPort());
+ pkt->setRemotePort(dummy_pkt->getRemotePort());
+ pkt->setLocalHWAddr(dummy_pkt->getLocalHWAddr());
+ pkt->setRemoteHWAddr(dummy_pkt->getRemoteHWAddr());
+
+ return (pkt);
+}
+
+int
+PktFilterLPF::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) {
+
+ OutputBuffer buf(14);
+
+ HWAddrPtr hwaddr(new HWAddr(iface.getMac(), iface.getMacLen(),
+ iface.getHWType()));
+ pkt->setLocalHWAddr(hwaddr);
+
+
+ // Ethernet frame header.
+ // Note that we don't validate whether HW addresses in 'pkt'
+ // are valid because they are checked by the function called.
+ writeEthernetHeader(pkt, buf);
+
+ // This object represents broadcast address. We will compare the
+ // local packet address with it a few lines below. Having static
+ // variable guarantees that this object is created only once, not
+ // every time this function is called.
+ static const isc::asiolink::IOAddress bcast_addr("255.255.255.255");
+
+ // It is likely that the local address in pkt object is set to
+ // broadcast address. This is the case if server received the
+ // client's packet on broadcast address. Therefore, we need to
+ // correct it here and assign the actual source address.
+ if (pkt->getLocalAddr() == bcast_addr) {
+ const Iface::SocketCollection& sockets = iface.getSockets();
+ for (Iface::SocketCollection::const_iterator it = sockets.begin();
+ it != sockets.end(); ++it) {
+ if (sockfd == it->sockfd_) {
+ pkt->setLocalAddr(it->addr_);
+ }
+ }
+ }
+
+ // IP and UDP header
+ writeIpUdpHeader(pkt, buf);
+
+ // DHCPv4 message
+ buf.writeData(pkt->getBuffer().getData(), pkt->getBuffer().getLength());
+
+ sockaddr_ll sa;
+ sa.sll_family = AF_PACKET;
+ sa.sll_ifindex = iface.getIndex();
+ sa.sll_protocol = htons(ETH_P_IP);
+ sa.sll_halen = 6;
+
+ int result = sendto(sockfd, buf.getData(), buf.getLength(), 0,
+ reinterpret_cast<const struct sockaddr*>(&sa),
+ sizeof(sockaddr_ll));
+ if (result < 0) {
+ isc_throw(SocketWriteError, "failed to send DHCPv4 packet, errno="
+ << errno << " (check errno.h)");
+ }
+
+ return (0);
+
+}
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/pkt_filter_lpf.h b/src/lib/dhcp/pkt_filter_lpf.h
new file mode 100644
index 0000000..d36719f
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_lpf.h
@@ -0,0 +1,85 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 PKT_FILTER_LPF_H
+#define PKT_FILTER_LPF_H
+
+#include <dhcp/pkt_filter.h>
+
+#include <util/buffer.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Packet handling class using Linux Packet Filtering
+///
+/// This class provides methods to send and recive packet using raw sockets
+/// and Linux Packet Filtering.
+///
+/// @warning This class is not implemented yet. Therefore all functions
+/// currently throw isc::NotImplemented exception.
+class PktFilterLPF : public PktFilter {
+public:
+
+ /// @brief Check if packet can be sent to the host without address directly.
+ ///
+ /// This class supports direct responses to the host without address.
+ ///
+ /// @return true always.
+ virtual bool isDirectResponseSupported() const {
+ return (true);
+ }
+
+ /// @brief Open socket.
+ ///
+ /// @param iface interface descriptor
+ /// @param addr address on the interface to be used to send packets.
+ /// @param port port number.
+ /// @param receive_bcast configure socket to receive broadcast messages
+ /// @param send_bcast configure socket to send broadcast messages.
+ ///
+ /// @throw isc::NotImplemented always
+ /// @return created socket's descriptor
+ virtual int openSocket(const Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast);
+
+ /// @brief Receive packet over specified socket.
+ ///
+ /// @param iface interface
+ /// @param socket_info structure holding socket information
+ ///
+ /// @throw isc::NotImplemented always
+ /// @return Received packet
+ virtual Pkt4Ptr receive(const Iface& iface, const SocketInfo& socket_info);
+
+ /// @brief Send packet over specified socket.
+ ///
+ /// @param iface interface to be used to send packet
+ /// @param sockfd socket descriptor
+ /// @param pkt packet to be sent
+ ///
+ /// @throw isc::NotImplemented always
+ /// @return result of sending a packet. It is 0 if successful.
+ virtual int send(const Iface& iface, uint16_t sockfd,
+ const Pkt4Ptr& pkt);
+
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_LPF_H
diff --git a/src/lib/dhcp/protocol_util.cc b/src/lib/dhcp/protocol_util.cc
new file mode 100644
index 0000000..d93f8c4
--- /dev/null
+++ b/src/lib/dhcp/protocol_util.cc
@@ -0,0 +1,243 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/protocol_util.h>
+#include <boost/static_assert.hpp>
+// in_systm.h is required on some some BSD systems
+// complaining that n_time is undefined but used
+// in ip.h.
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace isc {
+namespace dhcp {
+
+void
+decodeEthernetHeader(InputBuffer& buf, Pkt4Ptr& pkt) {
+ // The size of the buffer to be parsed must not be lower
+ // then the size of the Ethernet frame header.
+ if (buf.getLength() - buf.getPosition() < ETHERNET_HEADER_LEN) {
+ isc_throw(InvalidPacketHeader, "size of ethernet header in received "
+ << "packet is invalid, expected at least "
+ << ETHERNET_HEADER_LEN << " bytes, received "
+ << buf.getLength() - buf.getPosition() << " bytes");
+ }
+ // Packet object must not be NULL. We want to output some values
+ // to this object.
+ if (!pkt) {
+ isc_throw(BadValue, "NULL packet object provided when parsing ethernet"
+ " frame header");
+ }
+
+ // The size of the single address is always lower then the size of
+ // the header that holds this address. Otherwise, it is a programming
+ // error that we want to detect in the compilation time.
+ BOOST_STATIC_ASSERT(ETHERNET_HEADER_LEN > HWAddr::ETHERNET_HWADDR_LEN);
+
+ // Remember initial position.
+ size_t start_pos = buf.getPosition();
+
+ // Read the destination HW address.
+ std::vector<uint8_t> dest_addr;
+ buf.readVector(dest_addr, HWAddr::ETHERNET_HWADDR_LEN);
+ pkt->setLocalHWAddr(HWTYPE_ETHERNET, HWAddr::ETHERNET_HWADDR_LEN, dest_addr);
+ // Read the source HW address.
+ std::vector<uint8_t> src_addr;
+ buf.readVector(src_addr, HWAddr::ETHERNET_HWADDR_LEN);
+ pkt->setRemoteHWAddr(HWTYPE_ETHERNET, HWAddr::ETHERNET_HWADDR_LEN, src_addr);
+ // Move the buffer read pointer to the end of the Ethernet frame header.
+ buf.setPosition(start_pos + ETHERNET_HEADER_LEN);
+}
+
+void
+decodeIpUdpHeader(InputBuffer& buf, Pkt4Ptr& pkt) {
+ // The size of the buffer must be at least equal to the minimal size of
+ // the IPv4 packet header plus UDP header length.
+ if (buf.getLength() - buf.getPosition() < MIN_IP_HEADER_LEN + UDP_HEADER_LEN) {
+ isc_throw(InvalidPacketHeader, "the total size of the IP and UDP headers in "
+ << "received packet is invalid, expected at least "
+ << MIN_IP_HEADER_LEN + UDP_HEADER_LEN
+ << " bytes, received " << buf.getLength() - buf.getPosition()
+ << " bytes");
+ }
+
+ // Packet object must not be NULL.
+ if (!pkt) {
+ isc_throw(BadValue, "NULL packet object provided when parsing IP and UDP"
+ " packet headers");
+ }
+
+ BOOST_STATIC_ASSERT(IP_SRC_ADDR_OFFSET < MIN_IP_HEADER_LEN);
+
+ // Remember initial position of the read pointer.
+ size_t start_pos = buf.getPosition();
+
+ // Read IP header length (mask most significant bits as they indicate IP version).
+ uint8_t ip_len = buf.readUint8() & 0xF;
+ // IP length is the number of 4 byte chunks that construct IPv4 header.
+ // It must not be lower than 5 because first 20 bytes are fixed.
+ if (ip_len < 5) {
+ isc_throw(InvalidPacketHeader, "Value of the length of the IP header must not be"
+ << " lower than 5 words. The length of the received header is "
+ << ip_len << ".");
+ }
+
+ // Seek to the position of source IP address.
+ buf.setPosition(start_pos + IP_SRC_ADDR_OFFSET);
+ // Read source address.
+ pkt->setRemoteAddr(IOAddress(buf.readUint32()));
+ // Read destination address.
+ pkt->setLocalAddr(IOAddress(buf.readUint32()));
+
+ // Skip IP header options (if any) to start of the
+ // UDP header.
+ buf.setPosition(start_pos + ip_len * 4);
+
+ // Read source port from UDP header.
+ pkt->setRemotePort(buf.readUint16());
+ // Read destination port from UDP header.
+ pkt->setLocalPort(buf.readUint16());
+
+ // Set the pointer position to the first byte o the
+ // UDP payload (DHCP packet).
+ buf.setPosition(start_pos + ip_len * 4 + UDP_HEADER_LEN);
+}
+
+void
+writeEthernetHeader(const Pkt4Ptr& pkt, OutputBuffer& out_buf) {
+ // Set destination HW address.
+ HWAddrPtr remote_addr = pkt->getRemoteHWAddr();
+ if (remote_addr) {
+ if (remote_addr->hwaddr_.size() == HWAddr::ETHERNET_HWADDR_LEN) {
+ out_buf.writeData(&remote_addr->hwaddr_[0],
+ HWAddr::ETHERNET_HWADDR_LEN);
+ } else {
+ isc_throw(BadValue, "invalid size of the remote HW address "
+ << remote_addr->hwaddr_.size() << " when constructing"
+ << " an ethernet frame header; expected size is"
+ << " " << HWAddr::ETHERNET_HWADDR_LEN);
+ }
+ } else {
+ // HW address has not been specified. This is possible when receiving
+ // packet through a logical interface (e.g. lo). In such cases, we
+ // don't want to fail but rather provide a default HW address, which
+ // consists of zeros.
+ out_buf.writeData(&std::vector<uint8_t>(HWAddr::ETHERNET_HWADDR_LEN)[0],
+ HWAddr::ETHERNET_HWADDR_LEN);
+ }
+
+ // Set source HW address.
+ HWAddrPtr local_addr = pkt->getLocalHWAddr();
+ if (local_addr) {
+ if (local_addr->hwaddr_.size() == HWAddr::ETHERNET_HWADDR_LEN) {
+ out_buf.writeData(&local_addr->hwaddr_[0],
+ HWAddr::ETHERNET_HWADDR_LEN);
+ } else {
+ isc_throw(BadValue, "invalid size of the local HW address "
+ << local_addr->hwaddr_.size() << " when constructing"
+ << " an ethernet frame header; expected size is"
+ << " " << HWAddr::ETHERNET_HWADDR_LEN);
+ }
+ } else {
+ // Provide default HW address.
+ out_buf.writeData(&std::vector<uint8_t>(HWAddr::ETHERNET_HWADDR_LEN)[0],
+ HWAddr::ETHERNET_HWADDR_LEN);
+ }
+
+ // Type IP.
+ out_buf.writeUint16(ETHERNET_TYPE_IP);
+}
+
+void
+writeIpUdpHeader(const Pkt4Ptr& pkt, util::OutputBuffer& out_buf) {
+
+ out_buf.writeUint8(0x45); // IP version 4, IP header length 5
+ out_buf.writeUint8(IPTOS_LOWDELAY); // DSCP and ECN
+ out_buf.writeUint16(28 + pkt->getBuffer().getLength()); // Total length.
+ out_buf.writeUint16(0); // Identification
+ out_buf.writeUint16(0x4000); // Disable fragmentation.
+ out_buf.writeUint8(128); // TTL
+ out_buf.writeUint8(IPPROTO_UDP); // Protocol UDP.
+ out_buf.writeUint16(0); // Temporarily set checksum to 0.
+ out_buf.writeUint32(pkt->getLocalAddr()); // Source address.
+ out_buf.writeUint32(pkt->getRemoteAddr()); // Destination address.
+
+ // Calculate pseudo header checksum. It will be necessary to compute
+ // UDP checksum.
+ // Get the UDP length. This includes udp header's and data length.
+ uint32_t udp_len = 8 + pkt->getBuffer().getLength();
+ // The magic number "8" indicates the offset where the source address
+ // is stored in the buffer. This offset is counted here from the
+ // current tail of the buffer. Starting from this offset we calculate
+ // the checksum using 8 following bytes of data. This will include
+ // 4 bytes of source address and 4 bytes of destination address.
+ // The IPPROTO_UDP and udp_len are also added up to the checksum.
+ uint16_t pseudo_hdr_checksum =
+ calcChecksum(static_cast<const uint8_t*>(out_buf.getData()) + out_buf.getLength() - 8,
+ 8, IPPROTO_UDP + udp_len);
+
+ // Calculate IP header checksum.
+ uint16_t ip_checksum = ~calcChecksum(static_cast<const uint8_t*>(out_buf.getData())
+ + out_buf.getLength() - 20, 20);
+ // Write checksum in the IP header. The offset of the checksum is 10 bytes
+ // back from the tail of the current buffer.
+ out_buf.writeUint16At(ip_checksum, out_buf.getLength() - 10);
+
+ // Start UDP header.
+ out_buf.writeUint16(pkt->getLocalPort()); // Source port.
+ out_buf.writeUint16(pkt->getRemotePort()); // Destination port.
+ out_buf.writeUint16(udp_len); // Length of the header and data.
+
+ // Checksum is calculated from the contents of UDP header, data and pseudo ip header.
+ // The magic number "6" indicates that the UDP header starts at offset 6 from the
+ // tail of the current buffer. These 6 bytes contain source and destination port
+ // as well as the length of the header.
+ uint16_t udp_checksum =
+ ~calcChecksum(static_cast<const uint8_t*>(out_buf.getData()) + out_buf.getLength() - 6, 6,
+ calcChecksum(static_cast<const uint8_t*>(pkt->getBuffer().getData()),
+ pkt->getBuffer().getLength(),
+ pseudo_hdr_checksum));
+ // Write UDP checksum.
+ out_buf.writeUint16(udp_checksum);
+}
+
+uint16_t
+calcChecksum(const uint8_t* buf, const uint32_t buf_size, uint32_t sum) {
+ uint32_t i;
+ for (i = 0; i < (buf_size & ~1U); i += 2) {
+ uint16_t chunk = buf[i] << 8 | buf[i + 1];
+ sum += chunk;
+ if (sum > 0xFFFF) {
+ sum -= 0xFFFF;
+ }
+ }
+ // If one byte has left, we also need to add it to the checksum.
+ if (i < buf_size) {
+ sum += buf[i] << 8;
+ if (sum > 0xFFFF) {
+ sum -= 0xFFFF;
+ }
+ }
+
+ return (sum);
+
+}
+
+}
+}
diff --git a/src/lib/dhcp/protocol_util.h b/src/lib/dhcp/protocol_util.h
new file mode 100644
index 0000000..b3f8085
--- /dev/null
+++ b/src/lib/dhcp/protocol_util.h
@@ -0,0 +1,153 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 PROTOCOL_UTIL_H
+#define PROTOCOL_UTIL_H
+
+#include <dhcp/pkt4.h>
+#include <util/buffer.h>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when error occured during parsing packet's headers.
+///
+/// This exception is thrown when parsing link, Internet or Transport layer
+/// header has failed.
+class InvalidPacketHeader : public Exception {
+public:
+ InvalidPacketHeader(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// Size of the Ethernet frame header.
+static const size_t ETHERNET_HEADER_LEN = 14;
+/// Offset of the 2-byte word in the Ethernet packet which
+/// holds the type of the protocol it encapsulates.
+static const size_t ETHERNET_PACKET_TYPE_OFFSET = 12;
+/// This value is held in the Ethertype field of Ethernet frame
+/// and indicates that an IP packet is encapsulated with this
+/// frame. In the standard headers, there is an ETHERTYPE_IP,
+/// constant which serves the same purpose. However, it is more
+/// convenient to have our constant because we avoid
+/// inclusion of additional headers, which have different names
+/// and locations on different OSes.
+static const uint16_t ETHERNET_TYPE_IP = 0x0800;
+
+/// Minimal IPv4 header length.
+static const size_t MIN_IP_HEADER_LEN = 20;
+/// Offset in the IP header where the flags field starts.
+static const size_t IP_FLAGS_OFFSET = 6;
+/// Offset of the byte in IP header which holds the type
+/// of the protocol it encapsulates.
+static const size_t IP_PROTO_TYPE_OFFSET = 9;
+/// Offset of source address in the IPv4 header.
+static const size_t IP_SRC_ADDR_OFFSET = 12;
+
+/// UDP header length.
+static const size_t UDP_HEADER_LEN = 8;
+/// Offset within UDP header where destination port is held.
+static const size_t UDP_DEST_PORT = 2;
+
+/// @brief Decode the Ethernet header.
+///
+/// This function reads Ethernet frame header from the provided
+/// buffer at the current read position. The source HW address
+/// is read from the header and assigned as client address in
+/// the pkt object. The buffer read pointer is set to the end
+/// of the Ethernet frame header if read was successful.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param buf input buffer holding header to be parsed.
+/// @param [out] pkt packet object receiving HW source address read from header.
+///
+/// @throw InvalidPacketHeader if packet header is truncated
+/// @throw BadValue if pkt object is NULL.
+void decodeEthernetHeader(util::InputBuffer& buf, Pkt4Ptr& pkt);
+
+/// @brief Decode IP and UDP header.
+///
+/// This function reads IP and UDP headers from the provided buffer
+/// at the current read position. The source and destination IP
+/// addresses and ports and read from these headers and stored in
+/// the appropriate members of the pkt object.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param buf input buffer holding headers to be parsed.
+/// @param [out] pkt packet object where IP addresses and ports
+/// are stored.
+///
+/// @throw InvalidPacketHeader if packet header is truncated
+/// @throw BadValue if pkt object is NULL.
+void decodeIpUdpHeader(util::InputBuffer& buf, Pkt4Ptr& pkt);
+
+/// @brief Writes ethernet frame header into a buffer.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param pkt packet object holding source and destination HW address.
+/// @param [out] out_buf buffer where a header is written.
+void writeEthernetHeader(const Pkt4Ptr& pkt,
+ util::OutputBuffer& out_buf);
+
+/// @brief Writes both IP and UDP header into output buffer
+///
+/// This utility function assembles IP and UDP packet headers for the
+/// provided DHCPv4 message. The source and destination addreses and
+/// ports stored in the pkt object are copied as source and destination
+/// addresses and ports into IP/UDP headers.
+///
+/// @warning This function does not check that the provided 'pkt'
+/// pointer is valid. Caller must make sure that pointer is
+/// allocated.
+///
+/// @param pkt DHCPv4 packet to be sent in IP packet
+/// @param [out] out_buf buffer where an IP header is written
+void writeIpUdpHeader(const Pkt4Ptr& pkt, util::OutputBuffer& out_buf);
+
+/// @brief Calculates checksum for provided buffer
+///
+/// This function returns the sum of 16-bit values from the provided
+/// buffer. If the third parameter is specified, it indicates the
+/// initial checksum value. This parameter can be a result of
+/// calcChecksum function's invocation on different data buffer.
+/// The IP or UDP checksum value is a complement of the result returned
+/// by this function. However, this function does not compute complement
+/// of the summed values. It must be calculated outside of this function
+/// before writing the value to the packet buffer.
+///
+/// The IP header checksum calculation algorithm has been defined in
+/// <a href="https://tools.ietf.org/html/rfc791#page-14">RFC 791</a>
+///
+/// @param buf buffer for which the checksum is calculated.
+/// @param buf_size size of the buffer for which checksum is calculated.
+/// @param sum initial checksum value, other values will be added to it.
+///
+/// @return calculated checksum.
+uint16_t calcChecksum(const uint8_t* buf, const uint32_t buf_size,
+ uint32_t sum = 0);
+
+}
+}
+#endif // PROTOCOL_UTIL_H
diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h
index 839a5d9..00acdab 100644
--- a/src/lib/dhcp/std_option_defs.h
+++ b/src/lib/dhcp/std_option_defs.h
@@ -62,7 +62,8 @@ struct OptionDefParams {
// RFC 1035, section 3.1. The latter could be handled
// by OPT_FQDN_TYPE but we can't use it here because
// clients may request ASCII encoding.
-RECORD_DECL(FQDN_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_STRING_TYPE);
+RECORD_DECL(FQDN_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_UINT8_TYPE,
+ OPT_FQDN_TYPE);
/// @brief Definitions of standard DHCPv4 options.
const OptionDefParams OPTION_DEF_PARAMS4[] = {
@@ -84,7 +85,7 @@ const OptionDefParams OPTION_DEF_PARAMS4[] = {
{ "host-name", DHO_HOST_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
{ "boot-size", DHO_BOOT_SIZE, OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
{ "merit-dump", DHO_MERIT_DUMP, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
- { "domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" },
+ { "domain-name", DHO_DOMAIN_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
{ "swap-server", DHO_SWAP_SERVER, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
{ "root-path", DHO_ROOT_PATH, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
{ "extensions-path", DHO_EXTENSIONS_PATH, OPT_STRING_TYPE,
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index c868553..3baac04 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -31,7 +31,9 @@ libdhcp___unittests_SOURCES += hwaddr_unittest.cc
libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
+libdhcp___unittests_SOURCES += option4_client_fqdn_unittest.cc
libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc
+libdhcp___unittests_SOURCES += option6_client_fqdn_unittest.cc
libdhcp___unittests_SOURCES += option6_ia_unittest.cc
libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
libdhcp___unittests_SOURCES += option_int_unittest.cc
@@ -41,8 +43,16 @@ libdhcp___unittests_SOURCES += option_definition_unittest.cc
libdhcp___unittests_SOURCES += option_custom_unittest.cc
libdhcp___unittests_SOURCES += option_unittest.cc
libdhcp___unittests_SOURCES += option_space_unittest.cc
+libdhcp___unittests_SOURCES += option_string_unittest.cc
libdhcp___unittests_SOURCES += pkt4_unittest.cc
libdhcp___unittests_SOURCES += pkt6_unittest.cc
+libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc
+
+if OS_LINUX
+libdhcp___unittests_SOURCES += pkt_filter_lpf_unittest.cc
+endif
+
+libdhcp___unittests_SOURCES += protocol_util_unittest.cc
libdhcp___unittests_SOURCES += duid_unittest.cc
libdhcp___unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
diff --git a/src/lib/dhcp/tests/duid_unittest.cc b/src/lib/dhcp/tests/duid_unittest.cc
index de20e51..36499c2 100644
--- a/src/lib/dhcp/tests/duid_unittest.cc
+++ b/src/lib/dhcp/tests/duid_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -37,6 +37,15 @@ using boost::scoped_ptr;
namespace {
+// This is a workaround for strange linking problems with gtest:
+// libdhcp___unittests-duid_unittest.o: In function `Compare<long unsigned int, long unsigned int>':
+// ~/gtest-1.6.0/include/gtest/gtest.h:1353: undefined reference to `isc::dhcp::ClientId::MAX_CLIENT_ID_LE'N
+// collect2: ld returned 1 exit status
+
+const size_t MAX_DUID_LEN = DUID::MAX_DUID_LEN;
+const size_t MAX_CLIENT_ID_LEN = DUID::MAX_DUID_LEN;
+
+
// This test verifies if the constructors are working as expected
// and process passed parameters.
TEST(DuidTest, constructor) {
@@ -61,21 +70,20 @@ TEST(DuidTest, constructor) {
// This test verifies if DUID size restrictions are implemented
// properly.
TEST(DuidTest, size) {
- const int MAX_DUID_SIZE = 128;
- uint8_t data[MAX_DUID_SIZE + 1];
+ uint8_t data[MAX_DUID_LEN + 1];
vector<uint8_t> data2;
- for (uint8_t i = 0; i < MAX_DUID_SIZE + 1; ++i) {
+ for (uint8_t i = 0; i < MAX_DUID_LEN + 1; ++i) {
data[i] = i;
- if (i < MAX_DUID_SIZE)
+ if (i < MAX_DUID_LEN)
data2.push_back(i);
}
- ASSERT_EQ(data2.size(), MAX_DUID_SIZE);
+ ASSERT_EQ(data2.size(), MAX_DUID_LEN);
- scoped_ptr<DUID> duidmaxsize1(new DUID(data, MAX_DUID_SIZE));
+ scoped_ptr<DUID> duidmaxsize1(new DUID(data, MAX_DUID_LEN));
scoped_ptr<DUID> duidmaxsize2(new DUID(data2));
EXPECT_THROW(
- scoped_ptr<DUID> toolarge1(new DUID(data, MAX_DUID_SIZE + 1)),
+ scoped_ptr<DUID> toolarge1(new DUID(data, MAX_DUID_LEN + 1)),
OutOfRange);
// that's one too much
@@ -84,6 +92,16 @@ TEST(DuidTest, size) {
EXPECT_THROW(
scoped_ptr<DUID> toolarge2(new DUID(data2)),
OutOfRange);
+
+ // empty duids are not allowed
+ vector<uint8_t> empty;
+ EXPECT_THROW(
+ scoped_ptr<DUID> emptyDuid(new DUID(empty)),
+ OutOfRange);
+
+ EXPECT_THROW(
+ scoped_ptr<DUID> emptyDuid2(new DUID(data, 0)),
+ OutOfRange);
}
// This test verifies if the implementation supports all defined
@@ -146,7 +164,7 @@ TEST(ClientIdTest, constructor) {
uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
vector<uint8_t> data2(data1, data1 + sizeof(data1));
- // checks for C-style construtor (uint8_t * + len)
+ // checks for C-style constructor (uint8_t * + len)
scoped_ptr<ClientId> id1(new ClientId(data1, sizeof(data1)));
vector<uint8_t> vecdata = id1->getClientId();
EXPECT_TRUE(data2 == vecdata);
@@ -157,6 +175,51 @@ TEST(ClientIdTest, constructor) {
EXPECT_TRUE(data2 == vecdata);
}
+// Check that client-id sizes are reasonable
+TEST(ClientIdTest, size) {
+ uint8_t data[MAX_CLIENT_ID_LEN + 1];
+ vector<uint8_t> data2;
+ for (uint8_t i = 0; i < MAX_CLIENT_ID_LEN + 1; ++i) {
+ data[i] = i;
+ if (i < MAX_CLIENT_ID_LEN)
+ data2.push_back(i);
+ }
+ ASSERT_EQ(data2.size(), MAX_CLIENT_ID_LEN);
+
+ scoped_ptr<ClientId> duidmaxsize1(new ClientId(data, MAX_CLIENT_ID_LEN));
+ scoped_ptr<ClientId> duidmaxsize2(new ClientId(data2));
+
+ EXPECT_THROW(
+ scoped_ptr<ClientId> toolarge1(new ClientId(data, MAX_CLIENT_ID_LEN + 1)),
+ OutOfRange);
+
+ // that's one too much
+ data2.push_back(128);
+
+ EXPECT_THROW(
+ scoped_ptr<ClientId> toolarge2(new ClientId(data2)),
+ OutOfRange);
+
+ // empty client-ids are not allowed
+ vector<uint8_t> empty;
+ EXPECT_THROW(
+ scoped_ptr<ClientId> empty_client_id1(new ClientId(empty)),
+ OutOfRange);
+
+ EXPECT_THROW(
+ scoped_ptr<ClientId> empty_client_id2(new ClientId(data, 0)),
+ OutOfRange);
+
+ // client-id must be at least 2 bytes long
+ vector<uint8_t> shorty(1,17); // just a single byte with value 17
+ EXPECT_THROW(
+ scoped_ptr<ClientId> too_short_client_id1(new ClientId(shorty)),
+ OutOfRange);
+ EXPECT_THROW(
+ scoped_ptr<ClientId> too_short_client_id1(new ClientId(data, 1)),
+ OutOfRange);
+}
+
// This test checks if the comparison operators are sane.
TEST(ClientIdTest, operators) {
uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
diff --git a/src/lib/dhcp/tests/hwaddr_unittest.cc b/src/lib/dhcp/tests/hwaddr_unittest.cc
index 144d62d..bf2eb9a 100644
--- a/src/lib/dhcp/tests/hwaddr_unittest.cc
+++ b/src/lib/dhcp/tests/hwaddr_unittest.cc
@@ -40,9 +40,11 @@ TEST(HWAddrTest, constructor) {
const uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
const uint8_t htype = HTYPE_ETHER;
-
vector<uint8_t> data2(data1, data1 + sizeof(data1));
+ // Over the limit data
+ vector<uint8_t> big_data_vector(HWAddr::MAX_HWADDR_LEN + 1, 0);
+
scoped_ptr<HWAddr> hwaddr1(new HWAddr(data1, sizeof(data1), htype));
scoped_ptr<HWAddr> hwaddr2(new HWAddr(data2, htype));
scoped_ptr<HWAddr> hwaddr3(new HWAddr());
@@ -55,6 +57,13 @@ TEST(HWAddrTest, constructor) {
EXPECT_EQ(0, hwaddr3->hwaddr_.size());
EXPECT_EQ(htype, hwaddr3->htype_);
+
+ // Check that over the limit data length throws exception
+ EXPECT_THROW(HWAddr(&big_data_vector[0], big_data_vector.size(), HTYPE_ETHER),
+ InvalidParameter);
+
+ // Check that over the limit vector throws exception
+ EXPECT_THROW(HWAddr(big_data_vector, HTYPE_ETHER), InvalidParameter);
}
// This test checks if the comparison operators are sane.
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index d72ef9e..488ecb3 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -18,6 +18,7 @@
#include <dhcp/dhcp4.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/pkt6.h>
+#include <dhcp/pkt_filter.h>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
@@ -33,6 +34,7 @@ using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
+using boost::scoped_ptr;
namespace {
@@ -44,45 +46,118 @@ char LOOPBACK[BUF_SIZE] = "lo";
const uint16_t PORT1 = 10547; // V6 socket
const uint16_t PORT2 = 10548; // V4 socket
-// On some systems measured duration of receive6() and
-// receive4() appears to be shorter than select() timeout.
-// called by these functions. This may be the case
-// if different ime resolutions are used by these functions.
-// For such cases we set the tolerance of 0.01s.
+// On some systems measured duration of receive6() and receive4() appears to be
+// shorter than select() timeout. This may be the case if different time
+// resolutions are used by these functions. For such cases we set the
+// tolerance to 0.01s.
const uint32_t TIMEOUT_TOLERANCE = 10000;
+/// Mock object implementing PktFilter class. It is used by
+/// IfaceMgrTest::setPacketFilter to verify that IfaceMgr::setPacketFilter
+/// sets this object as a handler for opening sockets. This dummy
+/// class simply records that openSocket function was called by
+/// the IfaceMgr as expected.
+///
+/// @todo This class currently doesn't verify that send/receive functions
+/// were called. In order to test it, there is a need to supply dummy
+/// function performing select() on certain sockets. The system select()
+/// call will fail when dummy socket descriptor is provided and thus
+/// TestPktFilter::receive will never be called. The appropriate extension
+/// to IfaceMgr is planned along with implementation of other "Packet
+/// Filters" such as these supporting Linux Packet Filtering and
+/// Berkley Packet Filtering.
+class TestPktFilter : public PktFilter {
+public:
+
+ /// Constructor
+ TestPktFilter()
+ : open_socket_called_(false) {
+ }
+
+ virtual bool isDirectResponseSupported() const {
+ return (false);
+ }
+
+ /// Pretends to open socket. Only records a call to this function.
+ /// This function returns fake socket descriptor (always the same).
+ /// Note that the returned value has been selected to be unique
+ /// (because real values are rather less than 255). Values greater
+ /// than 255 are not recommended because they cause warnings to be
+ /// reported by Valgrind when invoking close() on them.
+ virtual int openSocket(const Iface&,
+ const isc::asiolink::IOAddress&,
+ const uint16_t,
+ const bool,
+ const bool) {
+ open_socket_called_ = true;
+ return (255);
+ }
+
+ /// Does nothing
+ virtual Pkt4Ptr receive(const Iface&,
+ const SocketInfo&) {
+ return (Pkt4Ptr());
+ }
+
+ /// Does nothing
+ virtual int send(const Iface&, uint16_t, const Pkt4Ptr&) {
+ return (0);
+ }
+
+ /// Holds the information whether openSocket was called on this
+ /// object after its creation.
+ bool open_socket_called_;
+};
class NakedIfaceMgr: public IfaceMgr {
- // "naked" Interface Manager, exposes internal fields
+ // "Naked" Interface Manager, exposes internal fields
public:
- NakedIfaceMgr() { }
+ NakedIfaceMgr() {
+ }
IfaceCollection & getIfacesLst() { return ifaces_; }
};
-// dummy class for now, but this will be expanded when needed
+// Dummy class for now, but this will be expanded when needed
class IfaceMgrTest : public ::testing::Test {
public:
- // these are empty for now, but let's keep them around
+ // These are empty for now, but let's keep them around
IfaceMgrTest() {
}
~IfaceMgrTest() {
}
+ // Get ther number of IPv4 or IPv6 sockets on the loopback interface
+ int getOpenSocketsCount(const Iface& iface, uint16_t family) const {
+ // Get all sockets.
+ Iface::SocketCollection sockets = iface.getSockets();
+
+ // Loop through sockets and try to find the ones which match the
+ // specified type.
+ int sockets_count = 0;
+ for (Iface::SocketCollection::const_iterator sock = sockets.begin();
+ sock != sockets.end(); ++sock) {
+ // Match found, increase the counter.
+ if (sock->family_ == family) {
+ ++sockets_count;
+ }
+ }
+ return (sockets_count);
+ }
+
};
-// We need some known interface to work reliably. Loopback interface
-// is named lo on Linux and lo0 on BSD boxes. We need to find out
-// which is available. This is not a real test, but rather a workaround
-// that will go away when interface detection is implemented.
+// We need some known interface to work reliably. Loopback interface is named
+// lo on Linux and lo0 on BSD boxes. We need to find out which is available.
+// This is not a real test, but rather a workaround that will go away when
+// interface detection is implemented.
// NOTE: At this stage of development, write access to current directory
// during running tests is required.
TEST_F(IfaceMgrTest, loDetect) {
- // poor man's interface detection
- // it will go away as soon as proper interface detection
- // is implemented
+ // Poor man's interface detection. It will go away as soon as proper
+ // interface detection is implemented
if (if_nametoindex("lo") > 0) {
snprintf(LOOPBACK, BUF_SIZE - 1, "lo");
} else if (if_nametoindex("lo0") > 0) {
@@ -103,7 +178,7 @@ TEST_F(IfaceMgrTest, loDetect) {
#if 0
TEST_F(IfaceMgrTest, dhcp6Sniffer) {
- // testing socket operation in a portable way is tricky
+ // Testing socket operation in a portable way is tricky
// without interface detection implemented
unlink("interfaces.txt");
@@ -112,13 +187,13 @@ TEST_F(IfaceMgrTest, dhcp6Sniffer) {
interfaces << "eth0 fe80::21e:8cff:fe9b:7349";
interfaces.close();
- NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+ boost::scoped_ptr<NakedIfaceMgr> ifacemgr = new NakedIfaceMgr();
- Pkt6* pkt = NULL;
+ Pkt6Ptr pkt;
int cnt = 0;
cout << "---8X-----------------------------------------" << endl;
while (true) {
- pkt = ifacemgr->receive();
+ pkt.reset(ifacemgr->receive());
cout << "// this code is autogenerated. Do NOT edit." << endl;
cout << "// Received " << pkt->data_len_ << " bytes packet:" << endl;
@@ -146,31 +221,86 @@ TEST_F(IfaceMgrTest, dhcp6Sniffer) {
cout << " return (pkt);" << endl;
cout << "}" << endl << endl;
- delete pkt;
+ pkt.reset();
}
cout << "---8X-----------------------------------------" << endl;
- // never happens. Infinite loop is infinite
- delete pkt;
- delete ifacemgr;
+ // Never happens. Infinite loop is infinite
}
#endif
TEST_F(IfaceMgrTest, basic) {
- // checks that IfaceManager can be instantiated
+ // Checks that IfaceManager can be instantiated
IfaceMgr & ifacemgr = IfaceMgr::instance();
ASSERT_TRUE(&ifacemgr != 0);
}
-TEST_F(IfaceMgrTest, ifaceClass) {
- // basic tests for Iface inner class
- IfaceMgr::Iface* iface = new IfaceMgr::Iface("eth5", 7);
+// This test verifies that sockets can be closed selectively, i.e. all
+// IPv4 sockets can be closed first and all IPv6 sockets remain open.
+TEST_F(IfaceMgrTest, closeSockets) {
+ // Will be using local loopback addresses for this test.
+ IOAddress loaddr("127.0.0.1");
+ IOAddress loaddr6("::1");
+
+ // Create instance of IfaceMgr.
+ boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
+ ASSERT_TRUE(iface_mgr);
+
+ // Out constructor does not detect interfaces by itself. We need
+ // to create one and add.
+ int ifindex = if_nametoindex(LOOPBACK);
+ ASSERT_GT(ifindex, 0);
+ Iface lo_iface(LOOPBACK, ifindex);
+ iface_mgr->getIfacesLst().push_back(lo_iface);
+
+ // Create set of V4 and V6 sockets on the loopback interface.
+ // They must differ by a port they are bound to.
+ for (int i = 0; i < 6; ++i) {
+ // Every other socket will be IPv4.
+ if (i % 2) {
+ ASSERT_NO_THROW(
+ iface_mgr->openSocket(LOOPBACK, loaddr, 10000 + i)
+ );
+ } else {
+ ASSERT_NO_THROW(
+ iface_mgr->openSocket(LOOPBACK, loaddr6, 10000 + i)
+ );
+ }
+ }
+
+ // At the end we should have 3 IPv4 and 3 IPv6 sockets open.
+ Iface* iface = iface_mgr->getIface(LOOPBACK);
+ ASSERT_TRUE(iface != NULL);
+
+ int v4_sockets_count = getOpenSocketsCount(*iface, AF_INET);
+ ASSERT_EQ(3, v4_sockets_count);
+ int v6_sockets_count = getOpenSocketsCount(*iface, AF_INET6);
+ ASSERT_EQ(3, v6_sockets_count);
+
+ // Let's try to close only IPv4 sockets.
+ ASSERT_NO_THROW(iface_mgr->closeSockets(AF_INET));
+ v4_sockets_count = getOpenSocketsCount(*iface, AF_INET);
+ EXPECT_EQ(0, v4_sockets_count);
+ // The IPv6 sockets should remain open.
+ v6_sockets_count = getOpenSocketsCount(*iface, AF_INET6);
+ EXPECT_EQ(3, v6_sockets_count);
+
+ // Let's try to close IPv6 sockets.
+ ASSERT_NO_THROW(iface_mgr->closeSockets(AF_INET6));
+ v4_sockets_count = getOpenSocketsCount(*iface, AF_INET);
+ EXPECT_EQ(0, v4_sockets_count);
+ // They should have been closed now.
+ v6_sockets_count = getOpenSocketsCount(*iface, AF_INET6);
+ EXPECT_EQ(0, v6_sockets_count);
+}
- EXPECT_STREQ("eth5/7", iface->getFullName().c_str());
+TEST_F(IfaceMgrTest, ifaceClass) {
+ // Basic tests for Iface inner class
- delete iface;
+ Iface iface("eth5", 7);
+ EXPECT_STREQ("eth5/7", iface.getFullName().c_str());
}
// TODO: Implement getPlainMac() test as soon as interface detection
@@ -178,18 +308,18 @@ TEST_F(IfaceMgrTest, ifaceClass) {
TEST_F(IfaceMgrTest, getIface) {
cout << "Interface checks. Please ignore socket binding errors." << endl;
- NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
- // interface name, ifindex
- IfaceMgr::Iface iface1("lo1", 100);
- IfaceMgr::Iface iface2("eth9", 101);
- IfaceMgr::Iface iface3("en3", 102);
- IfaceMgr::Iface iface4("e1000g4", 103);
+ // Interface name, ifindex
+ Iface iface1("lo1", 100);
+ Iface iface2("eth9", 101);
+ Iface iface3("en3", 102);
+ Iface iface4("e1000g4", 103);
cout << "This test assumes that there are less than 100 network interfaces"
<< " in the tested system and there are no lo1, eth9, en3, e1000g4"
<< " or wifi15 interfaces present." << endl;
- // note: real interfaces may be detected as well
+ // Note: real interfaces may be detected as well
ifacemgr->getIfacesLst().push_back(iface1);
ifacemgr->getIfacesLst().push_back(iface2);
ifacemgr->getIfacesLst().push_back(iface3);
@@ -204,25 +334,22 @@ TEST_F(IfaceMgrTest, getIface) {
}
- // check that interface can be retrieved by ifindex
- IfaceMgr::Iface* tmp = ifacemgr->getIface(102);
+ // Check that interface can be retrieved by ifindex
+ Iface* tmp = ifacemgr->getIface(102);
ASSERT_TRUE(tmp != NULL);
EXPECT_EQ("en3", tmp->getName());
EXPECT_EQ(102, tmp->getIndex());
- // check that interface can be retrieved by name
+ // Check that interface can be retrieved by name
tmp = ifacemgr->getIface("lo1");
ASSERT_TRUE(tmp != NULL);
EXPECT_EQ("lo1", tmp->getName());
EXPECT_EQ(100, tmp->getIndex());
- // check that non-existing interfaces are not returned
+ // Check that non-existing interfaces are not returned
EXPECT_EQ(static_cast<void*>(NULL), ifacemgr->getIface("wifi15") );
-
- delete ifacemgr;
-
}
TEST_F(IfaceMgrTest, receiveTimeout6) {
@@ -231,15 +358,15 @@ TEST_F(IfaceMgrTest, receiveTimeout6) {
<< " Test will block for a few seconds when waiting"
<< " for timeout to occur." << std::endl;
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// Open socket on the lo interface.
IOAddress loAddr("::1");
int socket1 = 0;
ASSERT_NO_THROW(
socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, 10547)
);
- // Socket is open if its descriptor is greater than zero.
- ASSERT_GT(socket1, 0);
+ // Socket is open if result is non-negative.
+ ASSERT_GE(socket1, 0);
// Remember when we call receive6().
ptime start_time = microsec_clock::universal_time();
@@ -283,15 +410,15 @@ TEST_F(IfaceMgrTest, receiveTimeout4) {
<< " Test will block for a few seconds when waiting"
<< " for timeout to occur." << std::endl;
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// Open socket on the lo interface.
IOAddress loAddr("127.0.0.1");
int socket1 = 0;
ASSERT_NO_THROW(
socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, 10067)
);
- // Socket is open if its descriptor is greater than zero.
- ASSERT_GT(socket1, 0);
+ // Socket is open if returned value is non-negative.
+ ASSERT_GE(socket1, 0);
Pkt4Ptr pkt;
// Remember when we call receive4().
@@ -330,31 +457,31 @@ TEST_F(IfaceMgrTest, receiveTimeout4) {
}
TEST_F(IfaceMgrTest, multipleSockets) {
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
- // container for initialized socket descriptors
+ // Container for initialized socket descriptors
std::list<uint16_t> init_sockets;
- // create socket #1
+ // Create socket #1
int socket1 = 0;
ASSERT_NO_THROW(
socket1 = ifacemgr->openSocketFromIface(LOOPBACK, PORT1, AF_INET);
);
- ASSERT_GT(socket1, 0);
+ ASSERT_GE(socket1, 0);
init_sockets.push_back(socket1);
- // create socket #2
+ // Create socket #2
IOAddress loAddr("127.0.0.1");
int socket2 = 0;
ASSERT_NO_THROW(
socket2 = ifacemgr->openSocketFromRemoteAddress(loAddr, PORT2);
);
- ASSERT_GT(socket2, 0);
+ ASSERT_GE(socket2, 0);
init_sockets.push_back(socket2);
// Get loopback interface. If we don't find one we are unable to run
// this test but we don't want to fail.
- IfaceMgr::Iface* iface_ptr = ifacemgr->getIface(LOOPBACK);
+ Iface* iface_ptr = ifacemgr->getIface(LOOPBACK);
if (iface_ptr == NULL) {
cout << "Local loopback interface not found. Skipping test. " << endl;
return;
@@ -362,7 +489,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
// Once sockets have been sucessfully opened, they are supposed to
// be on the list. Here we start to test if all expected sockets
// are on the list and no other (unexpected) socket is there.
- IfaceMgr::SocketCollection sockets = iface_ptr->getSockets();
+ Iface::SocketCollection sockets = iface_ptr->getSockets();
int matched_sockets = 0;
for (std::list<uint16_t>::iterator init_sockets_it =
init_sockets.begin();
@@ -379,7 +506,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
EXPECT_EQ(EWOULDBLOCK, errno);
// Apart from the ability to use the socket we want to make
// sure that socket on the list is the one that we created.
- for (IfaceMgr::SocketCollection::const_iterator socket_it =
+ for (Iface::SocketCollection::const_iterator socket_it =
sockets.begin(); socket_it != sockets.end(); ++socket_it) {
if (*init_sockets_it == socket_it->sockfd_) {
// This socket is the one that we created.
@@ -388,7 +515,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
}
}
}
- // all created sockets have been matched if this condition works.
+ // All created sockets have been matched if this condition works.
EXPECT_EQ(sockets.size(), matched_sockets);
// closeSockets() is the other function that we want to test. It
@@ -396,7 +523,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
// them anymore communication.
ifacemgr->closeSockets();
- // closed sockets are supposed to be removed from the list
+ // Closed sockets are supposed to be removed from the list
sockets = iface_ptr->getSockets();
ASSERT_EQ(0, sockets.size());
@@ -418,27 +545,27 @@ TEST_F(IfaceMgrTest, multipleSockets) {
}
TEST_F(IfaceMgrTest, sockets6) {
- // testing socket operation in a portable way is tricky
- // without interface detection implemented
+ // Testing socket operation in a portable way is tricky
+ // without interface detection implemented.
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
IOAddress loAddr("::1");
Pkt6 pkt6(DHCPV6_SOLICIT, 123);
pkt6.setIface(LOOPBACK);
- // bind multicast socket to port 10547
+ // Bind multicast socket to port 10547
int socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, 10547);
- EXPECT_GT(socket1, 0); // socket > 0
+ EXPECT_GE(socket1, 0); // socket >= 0
EXPECT_EQ(socket1, ifacemgr->getSocket(pkt6));
- // bind unicast socket to port 10548
+ // Bind unicast socket to port 10548
int socket2 = ifacemgr->openSocket(LOOPBACK, loAddr, 10548);
- EXPECT_GT(socket2, 0);
+ EXPECT_GE(socket2, 0);
- // removed code for binding socket twice to the same address/port
+ // Removed code for binding socket twice to the same address/port
// as it caused problems on some platforms (e.g. Mac OS X)
// Close sockets here because the following tests will want to
@@ -463,15 +590,15 @@ TEST_F(IfaceMgrTest, sockets6) {
}
TEST_F(IfaceMgrTest, socketsFromIface) {
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// Open v6 socket on loopback interface and bind to port
int socket1 = 0;
EXPECT_NO_THROW(
socket1 = ifacemgr->openSocketFromIface(LOOPBACK, PORT1, AF_INET6);
);
- // Socket descriptor must be positive integer
- EXPECT_GT(socket1, 0);
+ // Socket descriptor must be non-negative integer
+ EXPECT_GE(socket1, 0);
close(socket1);
// Open v4 socket on loopback interface and bind to different port
@@ -479,8 +606,8 @@ TEST_F(IfaceMgrTest, socketsFromIface) {
EXPECT_NO_THROW(
socket2 = ifacemgr->openSocketFromIface(LOOPBACK, PORT2, AF_INET);
);
- // socket descriptor must be positive integer
- EXPECT_GT(socket2, 0);
+ // socket descriptor must be non-negative integer
+ EXPECT_GE(socket2, 0);
close(socket2);
// Close sockets here because the following tests will want to
@@ -499,7 +626,7 @@ TEST_F(IfaceMgrTest, socketsFromIface) {
TEST_F(IfaceMgrTest, socketsFromAddress) {
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// Open v6 socket on loopback interface and bind to port
int socket1 = 0;
@@ -507,8 +634,8 @@ TEST_F(IfaceMgrTest, socketsFromAddress) {
EXPECT_NO_THROW(
socket1 = ifacemgr->openSocketFromAddress(loAddr6, PORT1);
);
- // socket descriptor must be positive integer
- EXPECT_GT(socket1, 0);
+ // socket descriptor must be non-negative integer
+ EXPECT_GE(socket1, 0);
// Open v4 socket on loopback interface and bind to different port
int socket2 = 0;
@@ -517,7 +644,7 @@ TEST_F(IfaceMgrTest, socketsFromAddress) {
socket2 = ifacemgr->openSocketFromAddress(loAddr, PORT2);
);
// socket descriptor must be positive integer
- EXPECT_GT(socket2, 0);
+ EXPECT_GE(socket2, 0);
// Close sockets here because the following tests will want to
// open sockets on the same ports.
@@ -534,7 +661,7 @@ TEST_F(IfaceMgrTest, socketsFromAddress) {
}
TEST_F(IfaceMgrTest, socketsFromRemoteAddress) {
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// Open v6 socket to connect to remote address.
// Loopback address is the only one that we know
@@ -544,7 +671,7 @@ TEST_F(IfaceMgrTest, socketsFromRemoteAddress) {
EXPECT_NO_THROW(
socket1 = ifacemgr->openSocketFromRemoteAddress(loAddr6, PORT1);
);
- EXPECT_GT(socket1, 0);
+ EXPECT_GE(socket1, 0);
// Open v4 socket to connect to remote address.
int socket2 = 0;
@@ -552,25 +679,17 @@ TEST_F(IfaceMgrTest, socketsFromRemoteAddress) {
EXPECT_NO_THROW(
socket2 = ifacemgr->openSocketFromRemoteAddress(loAddr, PORT2);
);
- EXPECT_GT(socket2, 0);
+ EXPECT_GE(socket2, 0);
// Close sockets here because the following tests will want to
// open sockets on the same ports.
ifacemgr->closeSockets();
- // The following test is currently disabled for OSes other than
- // Linux because interface detection is not implemented on them.
- // @todo enable this test for all OSes once interface detection
- // is implemented.
-#if defined(OS_LINUX)
- // Open v4 socket to connect to broadcast address.
- int socket3 = 0;
- IOAddress bcastAddr("255.255.255.255");
- EXPECT_NO_THROW(
- socket3 = ifacemgr->openSocketFromRemoteAddress(bcastAddr, PORT2);
- );
- EXPECT_GT(socket3, 0);
-#endif
+ // There used to be a check here that verified the ability to open
+ // suitable socket for sending broadcast request. However,
+ // there is no guarantee for such test to work on all systems
+ // because some systems may have no broadcast capable interfaces at all.
+ // Thus, this check has been removed.
// Do not call closeSockets() because it is called by IfaceMgr's
// virtual destructor.
@@ -582,19 +701,19 @@ TEST_F(IfaceMgrTest, DISABLED_sockets6Mcast) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
- NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
IOAddress loAddr("::1");
IOAddress mcastAddr("ff02::1:2");
// bind multicast socket to port 10547
int socket1 = ifacemgr->openSocket(LOOPBACK, mcastAddr, 10547);
- EXPECT_GT(socket1, 0); // socket > 0
+ EXPECT_GE(socket1, 0); // socket > 0
// expect success. This address/port is already bound, but
// we are using SO_REUSEADDR, so we can bind it twice
int socket2 = ifacemgr->openSocket(LOOPBACK, mcastAddr, 10547);
- EXPECT_GT(socket2, 0);
+ EXPECT_GE(socket2, 0);
// there's no good way to test negative case here.
// we would need non-multicast interface. We will be able
@@ -603,8 +722,6 @@ TEST_F(IfaceMgrTest, DISABLED_sockets6Mcast) {
close(socket1);
close(socket2);
-
- delete ifacemgr;
}
TEST_F(IfaceMgrTest, sendReceive6) {
@@ -612,7 +729,7 @@ TEST_F(IfaceMgrTest, sendReceive6) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// let's assume that every supported OS have lo interface
IOAddress loAddr("::1");
@@ -622,8 +739,8 @@ TEST_F(IfaceMgrTest, sendReceive6) {
socket2 = ifacemgr->openSocket(LOOPBACK, loAddr, 10546);
);
- EXPECT_GT(socket1, 0);
- EXPECT_GT(socket2, 0);
+ EXPECT_GE(socket1, 0);
+ EXPECT_GE(socket2, 0);
// prepare dummy payload
@@ -649,15 +766,15 @@ TEST_F(IfaceMgrTest, sendReceive6) {
ASSERT_TRUE(rcvPkt); // received our own packet
// let's check that we received what was sent
- ASSERT_EQ(sendPkt->getData().size(), rcvPkt->getData().size());
- EXPECT_EQ(0, memcmp(&sendPkt->getData()[0], &rcvPkt->getData()[0],
- rcvPkt->getData().size()));
+ ASSERT_EQ(sendPkt->data_.size(), rcvPkt->data_.size());
+ EXPECT_EQ(0, memcmp(&sendPkt->data_[0], &rcvPkt->data_[0],
+ rcvPkt->data_.size()));
EXPECT_EQ(sendPkt->getRemoteAddr().toText(), rcvPkt->getRemoteAddr().toText());
// since we opened 2 sockets on the same interface and none of them is multicast,
// none is preferred over the other for sending data, so we really should not
- // assume the one or the other will always be choosen for sending data. Therefore
+ // assume the one or the other will always be chosen for sending data. Therefore
// we should accept both values as source ports.
EXPECT_TRUE((rcvPkt->getRemotePort() == 10546) || (rcvPkt->getRemotePort() == 10547));
@@ -673,18 +790,16 @@ TEST_F(IfaceMgrTest, sendReceive4) {
// testing socket operation in a portable way is tricky
// without interface detection implemented
- boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// let's assume that every supported OS have lo interface
IOAddress loAddr("127.0.0.1");
- int socket1 = 0, socket2 = 0;
+ int socket1 = 0;
EXPECT_NO_THROW(
socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, DHCP4_SERVER_PORT + 10000);
- socket2 = ifacemgr->openSocket(LOOPBACK, loAddr, DHCP4_SERVER_PORT + 10000 + 1);
);
EXPECT_GE(socket1, 0);
- EXPECT_GE(socket2, 0);
boost::shared_ptr<Pkt4> sendPkt(new Pkt4(DHCPDISCOVER, 1234) );
@@ -717,7 +832,7 @@ TEST_F(IfaceMgrTest, sendReceive4) {
boost::shared_ptr<Pkt4> rcvPkt;
- EXPECT_EQ(true, ifacemgr->send(sendPkt));
+ EXPECT_NO_THROW(ifacemgr->send(sendPkt));
ASSERT_NO_THROW(rcvPkt = ifacemgr->receive4(10));
ASSERT_TRUE(rcvPkt); // received our own packet
@@ -749,20 +864,144 @@ TEST_F(IfaceMgrTest, sendReceive4) {
// since we opened 2 sockets on the same interface and none of them is multicast,
// none is preferred over the other for sending data, so we really should not
- // assume the one or the other will always be choosen for sending data. We should
+ // assume the one or the other will always be chosen for sending data. We should
// skip checking source port of sent address.
- // try to receive data over the closed socket. Closed socket's descriptor is
- // still being hold by IfaceMgr which will try to use it to receive data.
+ // Close the socket. Further we will test if errors are reported
+ // properly on attempt to use closed soscket.
close(socket1);
+
+// Warning: kernel bug on FreeBSD. The following code checks that attempt to
+// read through invalid descriptor will result in exception. The reason why
+// this failure is expected is that select() function should result in EBADF
+// error when invalid descriptor is passed to it. In particular, closed socket
+// descriptor is invalid. On the following OS:
+//
+// 8.1-RELEASE FreeBSD 8.1-RELEASE #0: Mon Jul 19 02:55:53 UTC 2010
+//
+// calling select() using invalid descriptor results in timeout and eventually
+// value of 0 is returned. This has been identified and reported as a bug in
+// FreeBSD: http://www.freebsd.org/cgi/query-pr.cgi?pr=155606
+//
+// @todo: This part of the test is currently disabled on all BSD systems as it was
+// the quick fix. We need a more elegant (config-based) solution to disable
+// this check on affected systems only. The ticket has been submited for this
+// work: http://bind10.isc.org/ticket/2971
+#ifndef OS_BSD
EXPECT_THROW(ifacemgr->receive4(10), SocketReadError);
+#endif
+
EXPECT_THROW(ifacemgr->send(sendPkt), SocketWriteError);
}
+// Verifies that it is possible to set custom packet filter object
+// to handle sockets opening and send/receive operation.
+TEST_F(IfaceMgrTest, setPacketFilter) {
+
+ // Create an instance of IfaceMgr.
+ boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
+ ASSERT_TRUE(iface_mgr);
+
+ // Try to set NULL packet filter object and make sure it is rejected.
+ boost::shared_ptr<TestPktFilter> custom_packet_filter;
+ EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter),
+ isc::dhcp::InvalidPacketFilter);
+
+ // Create valid object and check if it can be set.
+ custom_packet_filter.reset(new TestPktFilter());
+ ASSERT_TRUE(custom_packet_filter);
+ ASSERT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter));
+
+ // Try to open socket using IfaceMgr. It should call the openSocket() function
+ // on the packet filter object we have set.
+ IOAddress loAddr("127.0.0.1");
+ int socket1 = 0;
+ EXPECT_NO_THROW(
+ socket1 = iface_mgr->openSocket(LOOPBACK, loAddr, DHCP4_SERVER_PORT + 10000);
+ );
+
+ // Check that openSocket function was called.
+ EXPECT_TRUE(custom_packet_filter->open_socket_called_);
+ // This function always returns fake socket descriptor equal to 255.
+ EXPECT_EQ(255, socket1);
+
+ // Replacing current packet filter object while there are IPv4
+ // sockets open is not allowed!
+ EXPECT_THROW(iface_mgr->setPacketFilter(custom_packet_filter),
+ PacketFilterChangeDenied);
+
+ // So, let's close the open IPv4 sockets and retry. Now it should succeed.
+ iface_mgr->closeSockets(AF_INET);
+ EXPECT_NO_THROW(iface_mgr->setPacketFilter(custom_packet_filter));
+}
+
+#if defined OS_LINUX
+
+// This Linux specific test checks whether it is possible to use
+// IfaceMgr to figure out which Pakcket Filter object should be
+// used when direct responses to hosts, having no address assigned
+// are desired or not desired.
+TEST_F(IfaceMgrTest, setMatchingPacketFilter) {
+
+ // Create an instance of IfaceMgr.
+ boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
+ ASSERT_TRUE(iface_mgr);
+
+ // Let IfaceMgr figure out which Packet Filter to use when
+ // direct response capability is not desired. It should pick
+ // PktFilterInet.
+ EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(false));
+ // The PktFilterInet is supposed to report lack of direct
+ // response capability.
+ EXPECT_FALSE(iface_mgr->isDirectResponseSupported());
+
+ // There is working implementation of direct responses on Linux
+ // in PktFilterLPF. It uses Linux Packet Filtering as underlying
+ // mechanism. When direct responses are desired the object of
+ // this class should be set.
+ EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(true));
+ // This object should report that direct responses are supported.
+ EXPECT_TRUE(iface_mgr->isDirectResponseSupported());
+}
+
+#else
+
+// This non-Linux specific test checks whether it is possible to use
+// IfaceMgr to figure out which Pakcket Filter object should be
+// used when direct responses to hosts, having no address assigned
+// are desired or not desired. Since direct responses aren't supported
+// on systems other than Linux the function under test should always
+// set object of PktFilterInet type as current Packet Filter. This
+// object does not support direct responses. Once implementation is
+// added on non-Linux systems the OS specific version of the test
+// will be removed.
+TEST_F(IfaceMgrTest, setMatchingPacketFilter) {
+
+ // Create an instance of IfaceMgr.
+ boost::scoped_ptr<NakedIfaceMgr> iface_mgr(new NakedIfaceMgr());
+ ASSERT_TRUE(iface_mgr);
+
+ // Let IfaceMgr figure out which Packet Filter to use when
+ // direct response capability is not desired. It should pick
+ // PktFilterInet.
+ EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(false));
+ // The PktFilterInet is supposed to report lack of direct
+ // response capability.
+ EXPECT_FALSE(iface_mgr->isDirectResponseSupported());
+
+ // On non-Linux systems, we are missing the direct traffic
+ // implementation. Therefore, we expect that PktFilterInet
+ // object will be set.
+ EXPECT_NO_THROW(iface_mgr->setMatchingPacketFilter(true));
+ // This object should report lack of direct response capability.
+ EXPECT_FALSE(iface_mgr->isDirectResponseSupported());
+}
+
+#endif
TEST_F(IfaceMgrTest, socket4) {
- NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
// Let's assume that every supported OS have lo interface.
IOAddress loAddr("127.0.0.1");
@@ -773,7 +1012,7 @@ TEST_F(IfaceMgrTest, socket4) {
socket1 = ifacemgr->openSocket(LOOPBACK, loAddr, DHCP4_SERVER_PORT + 10000);
);
- EXPECT_GT(socket1, 0);
+ EXPECT_GE(socket1, 0);
Pkt4 pkt(DHCPDISCOVER, 1234);
pkt.setIface(LOOPBACK);
@@ -782,23 +1021,19 @@ TEST_F(IfaceMgrTest, socket4) {
EXPECT_EQ(socket1, ifacemgr->getSocket(pkt));
close(socket1);
-
- delete ifacemgr;
}
// Test the Iface structure itself
TEST_F(IfaceMgrTest, iface) {
- IfaceMgr::Iface* iface = NULL;
- EXPECT_NO_THROW(
- iface = new IfaceMgr::Iface("eth0",1);
- );
+ boost::scoped_ptr<Iface> iface;
+ EXPECT_NO_THROW(iface.reset(new Iface("eth0",1)));
EXPECT_EQ("eth0", iface->getName());
EXPECT_EQ(1, iface->getIndex());
EXPECT_EQ("eth0/1", iface->getFullName());
// Let's make a copy of this address collection.
- IfaceMgr::AddressCollection addrs = iface->getAddresses();
+ Iface::AddressCollection addrs = iface->getAddresses();
EXPECT_EQ(0, addrs.size());
@@ -822,19 +1057,17 @@ TEST_F(IfaceMgrTest, iface) {
EXPECT_EQ(0, addrs.size());
- EXPECT_NO_THROW(
- delete iface;
- );
+ EXPECT_NO_THROW(iface.reset());
}
TEST_F(IfaceMgrTest, iface_methods) {
- IfaceMgr::Iface iface("foo", 1234);
+ Iface iface("foo", 1234);
iface.setHWType(42);
EXPECT_EQ(42, iface.getHWType());
- uint8_t mac[IfaceMgr::MAX_MAC_LEN+10];
- for (int i = 0; i < IfaceMgr::MAX_MAC_LEN + 10; i++)
+ uint8_t mac[Iface::MAX_MAC_LEN+10];
+ for (int i = 0; i < Iface::MAX_MAC_LEN + 10; i++)
mac[i] = 255 - i;
EXPECT_EQ("foo", iface.getName());
@@ -843,7 +1076,7 @@ TEST_F(IfaceMgrTest, iface_methods) {
// MAC is too long. Exception should be thrown and
// MAC length should not be set.
EXPECT_THROW(
- iface.setMac(mac, IfaceMgr::MAX_MAC_LEN + 1),
+ iface.setMac(mac, Iface::MAX_MAC_LEN + 1),
OutOfRange
);
@@ -851,34 +1084,34 @@ TEST_F(IfaceMgrTest, iface_methods) {
EXPECT_EQ(0, iface.getMacLen());
// Setting maximum length MAC should be ok.
- iface.setMac(mac, IfaceMgr::MAX_MAC_LEN);
+ iface.setMac(mac, Iface::MAX_MAC_LEN);
// For some reason constants cannot be used directly in EXPECT_EQ
// as this produces linking error.
- size_t len = IfaceMgr::MAX_MAC_LEN;
+ size_t len = Iface::MAX_MAC_LEN;
EXPECT_EQ(len, iface.getMacLen());
EXPECT_EQ(0, memcmp(mac, iface.getMac(), iface.getMacLen()));
}
TEST_F(IfaceMgrTest, socketInfo) {
- // check that socketinfo for IPv4 socket is functional
- IfaceMgr::SocketInfo sock1(7, IOAddress("192.0.2.56"), DHCP4_SERVER_PORT + 7);
+ // Check that socketinfo for IPv4 socket is functional
+ SocketInfo sock1(7, IOAddress("192.0.2.56"), DHCP4_SERVER_PORT + 7);
EXPECT_EQ(7, sock1.sockfd_);
EXPECT_EQ("192.0.2.56", sock1.addr_.toText());
EXPECT_EQ(AF_INET, sock1.family_);
EXPECT_EQ(DHCP4_SERVER_PORT + 7, sock1.port_);
- // check that socketinfo for IPv6 socket is functional
- IfaceMgr::SocketInfo sock2(9, IOAddress("2001:db8:1::56"), DHCP4_SERVER_PORT + 9);
+ // Check that socketinfo for IPv6 socket is functional
+ SocketInfo sock2(9, IOAddress("2001:db8:1::56"), DHCP4_SERVER_PORT + 9);
EXPECT_EQ(9, sock2.sockfd_);
EXPECT_EQ("2001:db8:1::56", sock2.addr_.toText());
EXPECT_EQ(AF_INET6, sock2.family_);
EXPECT_EQ(DHCP4_SERVER_PORT + 9, sock2.port_);
- // now let's test if IfaceMgr handles socket info properly
- NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
- IfaceMgr::Iface* loopback = ifacemgr->getIface(LOOPBACK);
+ // Now let's test if IfaceMgr handles socket info properly
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+ Iface* loopback = ifacemgr->getIface(LOOPBACK);
ASSERT_TRUE(loopback);
loopback->addSocket(sock1);
loopback->addSocket(sock2);
@@ -891,14 +1124,14 @@ TEST_F(IfaceMgrTest, socketInfo) {
BadValue
);
- // try to send over non-existing interface
+ // Try to send over non-existing interface
pkt6.setIface("nosuchinterface45");
EXPECT_THROW(
ifacemgr->getSocket(pkt6),
BadValue
);
- // this will work
+ // This will work
pkt6.setIface(LOOPBACK);
EXPECT_EQ(9, ifacemgr->getSocket(pkt6));
@@ -908,13 +1141,13 @@ TEST_F(IfaceMgrTest, socketInfo) {
);
EXPECT_EQ(true, deleted);
- // it should throw again, there's no usable socket anymore
+ // It should throw again, there's no usable socket anymore
EXPECT_THROW(
ifacemgr->getSocket(pkt6),
Unexpected
);
- // repeat for pkt4
+ // Repeat for pkt4
Pkt4 pkt4(DHCPDISCOVER, 1);
// pkt4 does not have interface set yet.
@@ -943,8 +1176,6 @@ TEST_F(IfaceMgrTest, socketInfo) {
ifacemgr->getSocket(pkt4),
Unexpected
);
-
- delete ifacemgr;
}
#if defined(OS_LINUX)
@@ -952,10 +1183,10 @@ TEST_F(IfaceMgrTest, socketInfo) {
/// @brief parses text representation of MAC address
///
/// This function parses text representation of a MAC address and stores
-/// it in binary format. Text format is expecte to be separate with
+/// it in binary format. Text format is expected to be separate with
/// semicolons, e.g. f4:6d:04:96:58:f2
///
-/// TODO: IfaceMgr::Iface::mac_ uses uint8_t* type, should be vector<uint8_t>
+/// TODO: Iface::mac_ uses uint8_t* type, should be vector<uint8_t>
///
/// @param textMac string with MAC address to parse
/// @param mac pointer to output buffer
@@ -967,7 +1198,7 @@ size_t parse_mac(const std::string& textMac, uint8_t* mac, size_t macLen) {
tmp.flags(ios::hex);
int i = 0;
uint8_t octet = 0; // output octet
- uint8_t byte; // parsed charater from text representation
+ uint8_t byte; // parsed character from text representation
while (!tmp.eof()) {
tmp >> byte; // hex value
@@ -1045,7 +1276,7 @@ void parse_ifconfig(const std::string& textFile, IfaceMgr::IfaceCollection& ifac
string name = line.substr(0, offset);
// sadly, ifconfig does not return ifindex
- ifaces.push_back(IfaceMgr::Iface(name, 0));
+ ifaces.push_back(Iface(name, 0));
iface = ifaces.end();
--iface; // points to the last element
@@ -1057,8 +1288,8 @@ void parse_ifconfig(const std::string& textFile, IfaceMgr::IfaceCollection& ifac
mac = line.substr(offset, string::npos);
mac = mac.substr(0, mac.find_first_of(" "));
- uint8_t buf[IfaceMgr::MAX_MAC_LEN];
- int mac_len = parse_mac(mac, buf, IfaceMgr::MAX_MAC_LEN);
+ uint8_t buf[Iface::MAX_MAC_LEN];
+ int mac_len = parse_mac(mac, buf, Iface::MAX_MAC_LEN);
iface->setMac(buf, mac_len);
}
}
@@ -1070,14 +1301,17 @@ void parse_ifconfig(const std::string& textFile, IfaceMgr::IfaceCollection& ifac
// Ubuntu style format: inet6 addr: ::1/128 Scope:Host
addr = line.substr(line.find("addr:") + 6, string::npos);
} else {
- // Gentoo style format: inet6 fe80::6ef0:49ff:fe96:ba17 prefixlen 64 scopeid 0x20<link>
+ // Gentoo style format: inet6 fe80::6ef0:49ff:fe96:ba17
+ // prefixlen 64 scopeid 0x20<link>
addr = line.substr(line.find("inet6") + 6, string::npos);
}
- // handle Ubuntu format: inet6 addr: fe80::f66d:4ff:fe96:58f2/64 Scope:Link
+ // Handle Ubuntu format: inet6 addr: fe80::f66d:4ff:fe96:58f2/64
+ // Scope:Link
addr = addr.substr(0, addr.find("/"));
- // handle inet6 fe80::ca3a:35ff:fed4:8f1d prefixlen 64 scopeid 0x20<link>
+ // Handle inet6 fe80::ca3a:35ff:fed4:8f1d prefixlen 64
+ // scopeid 0x20<link>
addr = addr.substr(0, addr.find(" "));
IOAddress a(addr);
iface->addAddress(a);
@@ -1096,7 +1330,7 @@ void parse_ifconfig(const std::string& textFile, IfaceMgr::IfaceCollection& ifac
IOAddress a(addr);
iface->addAddress(a);
} else if(line.find("Metric") != string::npos) {
- // flags
+ // Flags
if (line.find("UP") != string::npos) {
iface->flag_up_ = true;
}
@@ -1117,15 +1351,16 @@ void parse_ifconfig(const std::string& textFile, IfaceMgr::IfaceCollection& ifac
}
-// This test compares implemented detection routines to output of "ifconfig -a" command.
-// It is far from perfect, but it is able to verify that interface names, flags,
-// MAC address, IPv4 and IPv6 addresses are detected properly. Interface list completeness
-// (check that each interface is reported, i.e. no missing or extra interfaces) and
-// address completeness is verified.
+// This test compares implemented detection routines to output of "ifconfig
+// -a" command. It is far from perfect, but it is able to verify that
+// interface names, flags, MAC address, IPv4 and IPv6 addresses are detected
+// properly. Interface list completeness (check that each interface is reported,
+// i.e. no missing or extra interfaces) and address completeness is verified.
//
// Things that are not tested:
// - ifindex (ifconfig does not print it out)
-// - address scopes and lifetimes (we don't need it, so it is not implemented in IfaceMgr)
+// - address scopes and lifetimes (we don't need it, so it is not implemented
+// in IfaceMgr)
// TODO: temporarily disabled, see ticket #1529
TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
@@ -1139,7 +1374,7 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
ASSERT_EQ(0, result);
- // list of interfaces parsed from ifconfig
+ // List of interfaces parsed from ifconfig
IfaceMgr::IfaceCollection parsedIfaces;
ASSERT_NO_THROW(
@@ -1165,15 +1400,15 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
cout << " BROADCAST";
}
cout << ", addrs:";
- const IfaceMgr::AddressCollection& addrs = i->getAddresses();
- for (IfaceMgr::AddressCollection::const_iterator a= addrs.begin();
+ const Iface::AddressCollection& addrs = i->getAddresses();
+ for (Iface::AddressCollection::const_iterator a= addrs.begin();
a != addrs.end(); ++a) {
cout << a->toText() << " ";
}
cout << endl;
}
- // Ok, now we have 2 lists of interfaces. Need to compare them
+ // OK, now we have 2 lists of interfaces. Need to compare them
ASSERT_EQ(detectedIfaces.size(), parsedIfaces.size());
// TODO: This could could probably be written simple with find()
@@ -1191,14 +1426,14 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
cout << "Checking interface " << detected->getName() << endl;
- // start with checking flags
+ // Start with checking flags
EXPECT_EQ(detected->flag_loopback_, i->flag_loopback_);
EXPECT_EQ(detected->flag_up_, i->flag_up_);
EXPECT_EQ(detected->flag_running_, i->flag_running_);
EXPECT_EQ(detected->flag_multicast_, i->flag_multicast_);
EXPECT_EQ(detected->flag_broadcast_, i->flag_broadcast_);
- // skip MAC comparison for loopback as netlink returns MAC
+ // Skip MAC comparison for loopback as netlink returns MAC
// 00:00:00:00:00:00 for lo
if (!detected->flag_loopback_) {
ASSERT_EQ(detected->getMacLen(), i->getMacLen());
@@ -1207,14 +1442,14 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
EXPECT_EQ(detected->getAddresses().size(), i->getAddresses().size());
- // now compare addresses
- const IfaceMgr::AddressCollection& addrs = detected->getAddresses();
- for (IfaceMgr::AddressCollection::const_iterator addr = addrs.begin();
+ // Now compare addresses
+ const Iface::AddressCollection& addrs = detected->getAddresses();
+ for (Iface::AddressCollection::const_iterator addr = addrs.begin();
addr != addrs.end(); ++addr) {
bool addr_found = false;
- const IfaceMgr::AddressCollection& addrs2 = detected->getAddresses();
- for (IfaceMgr::AddressCollection::const_iterator a = addrs2.begin();
+ const Iface::AddressCollection& addrs2 = detected->getAddresses();
+ for (Iface::AddressCollection::const_iterator a = addrs2.begin();
a != addrs2.end(); ++a) {
if (*addr != *a) {
continue;
@@ -1230,7 +1465,7 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
<< " matched with 'ifconfig -a' output." << endl;
}
}
- if (!found) { // corresponding interface was not found
+ if (!found) { // Corresponding interface was not found
FAIL();
}
}
@@ -1247,14 +1482,14 @@ void my_callback(void) {
}
TEST_F(IfaceMgrTest, controlSession) {
- // tests if extra control socket and its callback can be passed and
+ // Tests if extra control socket and its callback can be passed and
// it is supported properly by receive4() method.
callback_ok = false;
- NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
- // create pipe and register it as extra socket
+ // Create pipe and register it as extra socket
int pipefd[2];
EXPECT_TRUE(pipe(pipefd) == 0);
EXPECT_NO_THROW(ifacemgr->set_session_socket(pipefd[0], my_callback));
@@ -1280,8 +1515,6 @@ TEST_F(IfaceMgrTest, controlSession) {
// There was some data, so this time callback should be called
EXPECT_TRUE(callback_ok);
- delete ifacemgr;
-
// close both pipe ends
close(pipefd[1]);
close(pipefd[0]);
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index 1a87b13..f7f5ffc 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -18,12 +18,15 @@
#include <dhcp/dhcp6.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option4_addrlst.h>
+#include <dhcp/option4_client_fqdn.h>
#include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_client_fqdn.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
+#include <dhcp/option_string.h>
#include <util/buffer.h>
#include <gtest/gtest.h>
@@ -52,8 +55,7 @@ public:
/// @param buf option-buffer
static OptionPtr genericOptionFactory(Option::Universe u, uint16_t type,
const OptionBuffer& buf) {
- Option* option = new Option(u, type, buf);
- return OptionPtr(option);
+ return (OptionPtr(new Option(u, type, buf)));
}
/// @brief Test DHCPv4 option definition.
@@ -62,9 +64,9 @@ public:
/// option has been initialized correctly.
///
/// @param code option code.
- /// @param begin iterator pointing a begining of a buffer to
+ /// @param begin iterator pointing at beginning of a buffer to
/// be used to create option instance.
- /// @param end iterator pointing an end of a buffer to be
+ /// @param end iterator pointing at end of a buffer to be
/// used to create option instance.
/// @param expected_type type of the option created by the
/// factory function returned by the option definition.
@@ -86,9 +88,9 @@ public:
/// option has been initialized correctly.
///
/// @param code option code.
- /// @param begin iterator pointing a begining of a buffer to
+ /// @param begin iterator pointing at beginning of a buffer to
/// be used to create option instance.
- /// @param end iterator pointing an end of a buffer to be
+ /// @param end iterator pointing at end of a buffer to be
/// used to create option instance.
/// @param expected_type type of the option created by the
/// factory function returned by the option definition.
@@ -111,9 +113,9 @@ private:
/// option has been initialized correctly.
///
/// @param code option code.
- /// @param begin iterator pointing a begining of a buffer to
+ /// @param begin iterator pointing at beginning of a buffer to
/// be used to create option instance.
- /// @param end iterator pointing an end of a buffer to be
+ /// @param end iterator pointing at end of a buffer to be
/// used to create option instance.
/// @param expected_type type of the option created by the
/// factory function returned by the option definition.
@@ -265,11 +267,11 @@ TEST_F(LibDhcpTest, packOptions6) {
OptionPtr opt4(new Option(Option::V6, 6, buf.begin() + 8, buf.begin() + 12));
OptionPtr opt5(new Option(Option::V6, 8, buf.begin() + 12, buf.begin() + 14));
- opts.insert(pair<int, OptionPtr >(opt1->getType(), opt1));
- opts.insert(pair<int, OptionPtr >(opt1->getType(), opt2));
- opts.insert(pair<int, OptionPtr >(opt1->getType(), opt3));
- opts.insert(pair<int, OptionPtr >(opt1->getType(), opt4));
- opts.insert(pair<int, OptionPtr >(opt1->getType(), opt5));
+ opts.insert(make_pair(opt1->getType(), opt1));
+ opts.insert(make_pair(opt1->getType(), opt2));
+ opts.insert(make_pair(opt1->getType(), opt3));
+ opts.insert(make_pair(opt1->getType(), opt4));
+ opts.insert(make_pair(opt1->getType(), opt5));
OutputBuffer assembled(512);
@@ -373,7 +375,7 @@ TEST_F(LibDhcpTest, unpackOptions6) {
/// is no restriction on the data length being carried by them.
/// For simplicity, we assign data of the length 3 for each
/// of them.
-static uint8_t v4Opts[] = {
+static uint8_t v4_opts[] = {
12, 3, 0, 1, 2, // Hostname
60, 3, 10, 11, 12, // Class Id
14, 3, 20, 21, 22, // Merit Dump File
@@ -404,18 +406,18 @@ TEST_F(LibDhcpTest, packOptions4) {
opts.insert(make_pair(opt1->getType(), opt4));
opts.insert(make_pair(opt1->getType(), opt5));
- vector<uint8_t> expVect(v4Opts, v4Opts + sizeof(v4Opts));
+ vector<uint8_t> expVect(v4_opts, v4_opts + sizeof(v4_opts));
OutputBuffer buf(100);
EXPECT_NO_THROW(LibDHCP::packOptions(buf, opts));
- ASSERT_EQ(buf.getLength(), sizeof(v4Opts));
- EXPECT_EQ(0, memcmp(v4Opts, buf.getData(), sizeof(v4Opts)));
+ ASSERT_EQ(buf.getLength(), sizeof(v4_opts));
+ EXPECT_EQ(0, memcmp(v4_opts, buf.getData(), sizeof(v4_opts)));
}
TEST_F(LibDhcpTest, unpackOptions4) {
- vector<uint8_t> v4packed(v4Opts, v4Opts + sizeof(v4Opts));
+ vector<uint8_t> v4packed(v4_opts, v4_opts + sizeof(v4_opts));
isc::dhcp::Option::OptionCollection options; // list of options
ASSERT_NO_THROW(
@@ -424,38 +426,43 @@ TEST_F(LibDhcpTest, unpackOptions4) {
isc::dhcp::Option::OptionCollection::const_iterator x = options.find(12);
ASSERT_FALSE(x == options.end()); // option 1 should exist
- EXPECT_EQ(12, x->second->getType()); // this should be option 12
- ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
- EXPECT_EQ(5, x->second->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4Opts+2, 3)); // data len=3
+ // Option 12 holds a string so let's cast it to an appropriate type.
+ OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x->second);
+ ASSERT_TRUE(option12);
+ EXPECT_EQ(12, option12->getType()); // this should be option 12
+ ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3
+ EXPECT_EQ(5, option12->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&option12->getValue()[0], v4_opts + 2, 3)); // data len=3
x = options.find(60);
ASSERT_FALSE(x == options.end()); // option 2 should exist
EXPECT_EQ(60, x->second->getType()); // this should be option 60
ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->second->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4Opts+7, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 7, 3)); // data len=3
x = options.find(14);
ASSERT_FALSE(x == options.end()); // option 3 should exist
- EXPECT_EQ(14, x->second->getType()); // this should be option 14
- ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
- EXPECT_EQ(5, x->second->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4Opts+12, 3)); // data len=3
+ OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x->second);
+ ASSERT_TRUE(option14);
+ EXPECT_EQ(14, option14->getType()); // this should be option 14
+ ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3
+ EXPECT_EQ(5, option14->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&option14->getValue()[0], v4_opts + 12, 3)); // data len=3
x = options.find(254);
ASSERT_FALSE(x == options.end()); // option 3 should exist
EXPECT_EQ(254, x->second->getType()); // this should be option 254
ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->second->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4Opts+17, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 17, 3)); // data len=3
x = options.find(128);
ASSERT_FALSE(x == options.end()); // option 3 should exist
EXPECT_EQ(128, x->second->getType()); // this should be option 254
ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->second->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4Opts+22, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 22, 3)); // data len=3
x = options.find(0);
EXPECT_TRUE(x == options.end()); // option 0 not found
@@ -533,7 +540,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
// It will be used to create most of the options.
std::vector<uint8_t> buf(48, 1);
OptionBufferConstIter begin = buf.begin();
- OptionBufferConstIter end = buf.begin();
+ OptionBufferConstIter end = buf.end();
LibDhcpTest::testStdOptionDefs4(DHO_SUBNET_MASK, begin, end,
typeid(OptionCustom));
@@ -569,25 +576,25 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
typeid(Option4AddrLst));
LibDhcpTest::testStdOptionDefs4(DHO_HOST_NAME, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_BOOT_SIZE, begin, begin + 2,
typeid(OptionInt<uint16_t>));
LibDhcpTest::testStdOptionDefs4(DHO_MERIT_DUMP, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_NAME, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_SWAP_SERVER, begin, end,
typeid(OptionCustom));
LibDhcpTest::testStdOptionDefs4(DHO_ROOT_PATH, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_EXTENSIONS_PATH, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_IP_FORWARDING, begin, end,
typeid(OptionCustom));
@@ -653,7 +660,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
typeid(OptionCustom));
LibDhcpTest::testStdOptionDefs4(DHO_NIS_DOMAIN, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_NIS_SERVERS, begin, end,
typeid(Option4AddrLst));
@@ -675,7 +682,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
typeid(OptionInt<uint8_t>));
LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_SCOPE, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_FONT_SERVERS, begin, end,
typeid(Option4AddrLst));
@@ -702,7 +709,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
typeid(Option));
LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MESSAGE, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MAX_MESSAGE_SIZE, begin, begin + 2,
typeid(OptionInt<uint16_t>));
@@ -720,7 +727,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
typeid(Option));
LibDhcpTest::testStdOptionDefs4(DHO_NWIP_DOMAIN_NAME, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs4(DHO_NWIP_SUBOPTIONS, begin, end,
typeid(Option));
@@ -728,8 +735,8 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
LibDhcpTest::testStdOptionDefs4(DHO_USER_CLASS, begin, end,
typeid(Option));
- LibDhcpTest::testStdOptionDefs4(DHO_FQDN, begin, end,
- typeid(OptionCustom));
+ LibDhcpTest::testStdOptionDefs4(DHO_FQDN, begin, begin + 3,
+ typeid(Option4ClientFqdn));
LibDhcpTest::testStdOptionDefs4(DHO_DHCP_AGENT_OPTIONS, begin, end,
typeid(Option), "dhcp-agent-options-space");
@@ -900,7 +907,8 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
typeid(Option));
LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_FQDN, client_fqdn_buf.begin(),
- client_fqdn_buf.end(), typeid(OptionCustom));
+ client_fqdn_buf.end(),
+ typeid(Option6ClientFqdn));
LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, begin, end,
typeid(Option6AddrLst));
@@ -909,10 +917,10 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
typeid(Option6AddrLst));
LibDhcpTest::testStdOptionDefs6(D6O_NEW_POSIX_TIMEZONE, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs6(D6O_NEW_TZDB_TIMEZONE, begin, end,
- typeid(OptionCustom));
+ typeid(OptionString));
LibDhcpTest::testStdOptionDefs6(D6O_ERO, begin, end,
typeid(OptionIntArray<uint16_t>));
diff --git a/src/lib/dhcp/tests/option4_addrlst_unittest.cc b/src/lib/dhcp/tests/option4_addrlst_unittest.cc
index 0c1d9e6..767d690 100644
--- a/src/lib/dhcp/tests/option4_addrlst_unittest.cc
+++ b/src/lib/dhcp/tests/option4_addrlst_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -21,6 +21,7 @@
#include <util/buffer.h>
#include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
#include <iostream>
#include <sstream>
@@ -32,6 +33,7 @@ using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
using namespace isc::util;
+using boost::scoped_ptr;
namespace {
@@ -62,7 +64,7 @@ class Option4AddrLstTest : public ::testing::Test {
protected:
Option4AddrLstTest():
- vec_(vector<uint8_t>(300,0)) // 300 bytes long filled with 0s
+ vec_(vector<uint8_t>(300, 0)) // 300 bytes long filled with 0s
{
sampleAddrs_.push_back(IOAddress("192.0.2.3"));
sampleAddrs_.push_back(IOAddress("255.255.255.0"));
@@ -79,13 +81,13 @@ TEST_F(Option4AddrLstTest, parse1) {
memcpy(&vec_[0], sampledata, sizeof(sampledata));
- // just one address
- Option4AddrLst* opt1 = 0;
+ // Just one address
+ scoped_ptr<Option4AddrLst> opt1;
EXPECT_NO_THROW(
- opt1 = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
- vec_.begin(),
- vec_.begin()+4);
- // use just first address (4 bytes), not the whole
+ opt1.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
+ vec_.begin(),
+ vec_.begin()+4));
+ // Use just first address (4 bytes), not the whole
// sampledata
);
@@ -99,26 +101,21 @@ TEST_F(Option4AddrLstTest, parse1) {
EXPECT_EQ("192.0.2.3", addrs[0].toText());
- EXPECT_NO_THROW(
- delete opt1;
- opt1 = 0;
- );
-
- // 1 address
+ EXPECT_NO_THROW(opt1.reset());
}
TEST_F(Option4AddrLstTest, parse4) {
- vector<uint8_t> buffer(300,0); // 300 bytes long filled with 0s
+ vector<uint8_t> buffer(300, 0); // 300 bytes long filled with 0s
memcpy(&buffer[0], sampledata, sizeof(sampledata));
// 4 addresses
- Option4AddrLst* opt4 = 0;
+ scoped_ptr<Option4AddrLst> opt4;
EXPECT_NO_THROW(
- opt4 = new Option4AddrLst(254,
- buffer.begin(),
- buffer.begin()+sizeof(sampledata));
+ opt4.reset(new Option4AddrLst(254,
+ buffer.begin(),
+ buffer.begin()+sizeof(sampledata)));
);
EXPECT_EQ(Option::V4, opt4->getUniverse());
@@ -134,17 +131,15 @@ TEST_F(Option4AddrLstTest, parse4) {
EXPECT_EQ("0.0.0.0", addrs[2].toText());
EXPECT_EQ("127.0.0.1", addrs[3].toText());
- EXPECT_NO_THROW(
- delete opt4;
- opt4 = 0;
- );
+ EXPECT_NO_THROW(opt4.reset());
}
TEST_F(Option4AddrLstTest, assembly1) {
- Option4AddrLst* opt = 0;
+ scoped_ptr<Option4AddrLst> opt;
EXPECT_NO_THROW(
- opt = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, IOAddress("192.0.2.3"));
+ opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
+ IOAddress("192.0.2.3")));
);
EXPECT_EQ(Option::V4, opt->getUniverse());
EXPECT_EQ(DHO_DOMAIN_NAME_SERVERS, opt->getType());
@@ -163,28 +158,21 @@ TEST_F(Option4AddrLstTest, assembly1) {
EXPECT_EQ(0, memcmp(expected1, buf.getData(), 6));
- EXPECT_NO_THROW(
- delete opt;
- opt = 0;
- );
+ EXPECT_NO_THROW(opt.reset());
// This is old-fashioned option. We don't serve IPv6 types here!
EXPECT_THROW(
- opt = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, IOAddress("2001:db8::1")),
+ opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS,
+ IOAddress("2001:db8::1"))),
BadValue
);
- if (opt) {
- // test failed. Execption was not thrown, but option was created instead.
- delete opt;
- }
}
TEST_F(Option4AddrLstTest, assembly4) {
-
- Option4AddrLst* opt = 0;
+ scoped_ptr<Option4AddrLst> opt;
EXPECT_NO_THROW(
- opt = new Option4AddrLst(254, sampleAddrs_);
+ opt.reset(new Option4AddrLst(254, sampleAddrs_));
);
EXPECT_EQ(Option::V4, opt->getUniverse());
EXPECT_EQ(254, opt->getType());
@@ -206,27 +194,21 @@ TEST_F(Option4AddrLstTest, assembly4) {
ASSERT_EQ(0, memcmp(expected4, buf.getData(), 18));
- EXPECT_NO_THROW(
- delete opt;
- opt = 0;
- );
+ EXPECT_NO_THROW(opt.reset());
// This is old-fashioned option. We don't serve IPv6 types here!
sampleAddrs_.push_back(IOAddress("2001:db8::1"));
EXPECT_THROW(
- opt = new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, sampleAddrs_),
+ opt.reset(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, sampleAddrs_)),
BadValue
);
- if (opt) {
- // test failed. Execption was not thrown, but option was created instead.
- delete opt;
- }
}
TEST_F(Option4AddrLstTest, setAddress) {
- Option4AddrLst* opt = 0;
+
+ scoped_ptr<Option4AddrLst> opt;
EXPECT_NO_THROW(
- opt = new Option4AddrLst(123, IOAddress("1.2.3.4"));
+ opt.reset(new Option4AddrLst(123, IOAddress("1.2.3.4")));
);
opt->setAddress(IOAddress("192.0.255.255"));
@@ -240,17 +222,15 @@ TEST_F(Option4AddrLstTest, setAddress) {
BadValue
);
- EXPECT_NO_THROW(
- delete opt;
- );
+ EXPECT_NO_THROW(opt.reset());
}
TEST_F(Option4AddrLstTest, setAddresses) {
- Option4AddrLst* opt = 0;
+ scoped_ptr<Option4AddrLst> opt;
EXPECT_NO_THROW(
- opt = new Option4AddrLst(123); // empty list
+ opt.reset(new Option4AddrLst(123)); // Empty list
);
opt->setAddresses(sampleAddrs_);
@@ -269,9 +249,7 @@ TEST_F(Option4AddrLstTest, setAddresses) {
BadValue
);
- EXPECT_NO_THROW(
- delete opt;
- );
+ EXPECT_NO_THROW(opt.reset());
}
} // namespace
diff --git a/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc
new file mode 100644
index 0000000..cdd7c64
--- /dev/null
+++ b/src/lib/dhcp/tests/option4_client_fqdn_unittest.cc
@@ -0,0 +1,959 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dns/name.h>
+#include <util/buffer.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+namespace {
+
+using namespace isc;
+using namespace isc::dhcp;
+
+// This test verifies that constructor accepts empty partial domain-name but
+// does not accept empty fully qualified domain name.
+TEST(Option4ClientFqdnTest, constructEmptyName) {
+ // Create an instance of the source option.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ // Constructor should not accept empty fully qualified domain name.
+ EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "",
+ Option4ClientFqdn::FULL),
+ InvalidOption4FqdnDomainName);
+ // This check is similar to previous one, but using domain-name comprising
+ // a single space character. This should be treated as empty domain-name.
+ EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ " ",
+ Option4ClientFqdn::FULL),
+ InvalidOption4FqdnDomainName);
+
+ // Try different constructor.
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER()))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that copy constructor makes a copy of the option and
+// the source option instance can be deleted (both instances don't share
+// any resources).
+TEST(Option4ClientFqdnTest, copyConstruct) {
+ // Create an instance of the source option.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost.example.com",
+ Option4ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+
+ // Use copy constructor to create a second instance of the option.
+ boost::scoped_ptr<Option4ClientFqdn> option_copy;
+ ASSERT_NO_THROW(
+ option_copy.reset(new Option4ClientFqdn(*option))
+ );
+ ASSERT_TRUE(option_copy);
+
+ // Copy construction should result in no shared resources between
+ // two objects. In particular, pointer to implementation should not
+ // be shared. Thus, we can release the source object now.
+ option.reset();
+
+ // Verify that all parameters have been copied to the target object.
+ EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example.com.", option_copy->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option_copy->getDomainNameType());
+
+ // Do another test with different parameters to verify that parameters
+ // change when copied object is changed.
+
+ // Create an option with different parameters.
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "example",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+
+ // Call copy-constructor to copy the option.
+ ASSERT_NO_THROW(
+ option_copy.reset(new Option4ClientFqdn(*option))
+ );
+ ASSERT_TRUE(option_copy);
+
+ option.reset();
+
+ EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_TRUE(option_copy->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option_copy->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("example", option_copy->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option_copy->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with the domain-name
+// encoded in the canonical format is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWire) {
+ // The E flag sets the domain-name format to canonical.
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with the domain-name
+// encoded in the ASCII format is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWireASCII) {
+ // The E flag is set to zero which indicates that the domain name
+ // is encoded in the ASCII format. The "dot" character at the end
+ // indicates that the domain-name is fully qualified.
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_S, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 109, 121, 104, 111, 115, 116, 46, // myhost.
+ 101, 120, 97, 109, 112, 108, 101, 46, // example.
+ 99, 111, 109, 46 // com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that truncated option is rejected.
+TEST(Option4ClientFqdnTest, constructFromWireTruncated) {
+ // Empty buffer is invalid. It should be at least one octet long.
+ OptionBuffer in_buf;
+ for (uint8_t i = 0; i < 3; ++i) {
+ EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+ OutOfRange) << "Test of the truncated buffer failed for"
+ << " buffer length " << static_cast<int>(i);
+ in_buf.push_back(0);
+ }
+
+ // Buffer is now 3 bytes long, so it should not fail now.
+ EXPECT_NO_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()));
+}
+
+// This test verifies that exception is thrown when invalid domain-name
+// in canonical format is carried in the option.
+TEST(Option4ClientFqdnTest, constructFromWireInvalidName) {
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 5, 99, 111, 109, 0 // com. (invalid label length 5)
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+ InvalidOption4FqdnDomainName);
+}
+
+// This test verifies that exception is thrown when invalid domain-name
+// in ASCII format is carried in the option.
+TEST(Option4ClientFqdnTest, constructFromWireInvalidASCIIName) {
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_S, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 109, 121, 104, 111, 115, 116, 46, 46, // myhost.. (double dot!)
+ 101, 120, 97, 109, 112, 108, 101, 46, // example.
+ 99, 111, 109, 46 // com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+ InvalidOption4FqdnDomainName);
+}
+
+// This test verifies that the option in the on-wire format with partial
+// domain-name encoded in canonical format is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWirePartial) {
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_N | Option4ClientFqdn:: FLAG_E, // flags
+ 255, // RCODE1
+ 255, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116 // myhost
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with partial
+// domain-name encoded in ASCII format is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWirePartialASCII) {
+ // There is no "dot" character at the end, so the domain-name is partial.
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_N, // flags
+ 255, // RCODE1
+ 255, // RCODE2
+ 109, 121, 104, 111, 115, 116, 46, // myhost.
+ 101, 120, 97, 109, 112, 108, 101 // example
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with empty
+// domain-name is parsed correctly.
+TEST(Option4ClientFqdnTest, constructFromWireEmpty) {
+ // Initialize the 3-byte long buffer. All bytes initialized to 0:
+ // Flags, RCODE1 and RCODE2.
+ OptionBuffer in_buf(3, 0);
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ // domain-name field should be empty because on-wire data comprised
+ // flags field only.
+ EXPECT_TRUE(option->getDomainName().empty());
+}
+
+// This test verifies that assignment operator can be used to assign one
+// instance of the option to another.
+TEST(Option4ClientFqdnTest, assignment) {
+ // Usually the smart pointer is used to declare options and call
+ // constructor within assert. Thanks to this approach, the option instance
+ // is in the function scope and only initialization is done within assert.
+ // In this particular test we can't use smart pointers because we are
+ // testing assignment operator like this:
+ //
+ // option2 = option;
+ //
+ // The two asserts below do not create the instances that we will used to
+ // test assignment. They just attempt to create instances of the options
+ // with the same parameters as those that will be created for the actual
+ // assignment test. If these asserts do not fail, we can create options
+ // for the assignment test, do not surround them with asserts and be sure
+ // they will not throw.
+ ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost.example.com",
+ Option4ClientFqdn::FULL));
+
+ ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_N |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL));
+
+ // Create options with the same parameters as tested above.
+
+ // Create first option.
+ Option4ClientFqdn option(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost.example.com",
+ Option4ClientFqdn::FULL);
+
+ // Verify that the values have been set correctly.
+ ASSERT_TRUE(option.getFlag(Option4ClientFqdn::FLAG_S));
+ ASSERT_TRUE(option.getFlag(Option4ClientFqdn::FLAG_E));
+ ASSERT_FALSE(option.getFlag(Option4ClientFqdn::FLAG_O));
+ ASSERT_FALSE(option.getFlag(Option4ClientFqdn::FLAG_N));
+ ASSERT_EQ("myhost.example.com.", option.getDomainName());
+ ASSERT_EQ(Option4ClientFqdn::FULL, option.getDomainNameType());
+
+ // Create a second option.
+ Option4ClientFqdn option2(Option4ClientFqdn::FLAG_N |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL);
+
+ // Verify tha the values have been set correctly.
+ ASSERT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_S));
+ ASSERT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_E));
+ ASSERT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O));
+ ASSERT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_N));
+ ASSERT_EQ("myhost", option2.getDomainName());
+ ASSERT_EQ(Option4ClientFqdn::PARTIAL, option2.getDomainNameType());
+
+
+ // Make the assignment.
+ ASSERT_NO_THROW(option2 = option);
+
+ // Both options should now have the same values.
+ EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ(option.getDomainName(), option2.getDomainName());
+ EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
+
+ // Make self-assignment.
+ ASSERT_NO_THROW(option2 = option2);
+ EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option2.getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option2.getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_EQ(option.getDomainName(), option2.getDomainName());
+ EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
+}
+
+// This test verifies that constructor will throw an exception if invalid
+// DHCPv6 Client FQDN Option flags are specified.
+TEST(Option4ClientFqdnTest, constructInvalidFlags) {
+ // First, check that constructor does not throw an exception when
+ // valid flags values are provided. That way we eliminate the issue
+ // that constructor always throws exception.
+ uint8_t flags = 0;
+ ASSERT_NO_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"));
+
+ // Invalid flags: The maximal value is 0xF when all flag bits are set
+ // (00001111b). The flag value of 0x18 sets the bit from the Must Be
+ // Zero (MBZ) bitset (00011000b).
+ flags = 0x18;
+ EXPECT_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"),
+ InvalidOption4FqdnFlags);
+
+ // According to RFC 4702, section 2.1. if the N bit is set the S bit MUST
+ // be zero. If both are set, constructor is expected to throw.
+ flags = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S;
+ EXPECT_THROW(Option4ClientFqdn(flags, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"),
+ InvalidOption4FqdnFlags);
+}
+
+// This test verifies that constructor which parses option from on-wire format
+// will throw exception if parsed flags field is invalid.
+TEST(Option4ClientFqdnTest, constructFromWireInvalidFlags) {
+ // Create a buffer which holds flags field only. Set valid flag field at
+ // at first to make sure that constructor doesn't always throw an exception.
+ OptionBuffer in_buf(3, 0);
+ in_buf[0] = Option4ClientFqdn::FLAG_S;
+ ASSERT_NO_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()));
+
+ // Replace the flags with invalid value and verify that constructor throws
+ // appropriate exception.
+ in_buf[0] = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_S;
+ EXPECT_THROW(Option4ClientFqdn(in_buf.begin(), in_buf.end()),
+ InvalidOption4FqdnFlags);
+}
+
+// This test verifies that if invalid domain name is used the constructor
+// will throw appropriate exception.
+TEST(Option4ClientFqdnTest, constructInvalidName) {
+ // First, check that constructor does not throw when valid domain name
+ // is specified. That way we eliminate the possibility that constructor
+ // always throws exception.
+ ASSERT_NO_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"));
+
+ // Specify invalid domain name and expect that exception is thrown.
+ EXPECT_THROW(Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "my...host.example.com"),
+ InvalidOption4FqdnDomainName);
+
+ // Do the same test for the domain-name in ASCII format.
+ ASSERT_NO_THROW(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"));
+
+ EXPECT_THROW(Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+ "my...host.example.com"),
+ InvalidOption4FqdnDomainName);
+}
+
+// This test verifies that getFlag throws an exception if flag value other
+// than N, E, O, S was specified.
+TEST(Option4ClientFqdnTest, getFlag) {
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // The value of 0x3 (binary 0011) is invalid because it specifies two bits
+ // in the flags field which value is to be checked.
+ EXPECT_THROW(option->getFlag(0x3), InvalidOption4FqdnFlags);
+}
+
+// This test verifies that flags can be modified and that incorrect flags
+// are rejected.
+TEST(Option4ClientFqdnTest, setFlag) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(0,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // All flags should be set to 0 initially.
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E));
+
+ // Set E = 1
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_E, true));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+
+ // Set N = 1
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, true));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_N));
+
+ // Set O = 1
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_O, true));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+
+ // Set S = 1, this should throw exception because S and N must not
+ // be set in the same time.
+ ASSERT_THROW(option->setFlag(Option4ClientFqdn::FLAG_S, true),
+ InvalidOption4FqdnFlags);
+
+ // Set E = 0
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, false));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+
+ // Set N = 0
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, false));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+
+ // Set S = 1, this should not result in exception because N has been
+ // cleared.
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_S, true));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+
+ // Set N = 1, this should result in exception because S = 1
+ ASSERT_THROW(option->setFlag(Option4ClientFqdn::FLAG_N, true),
+ InvalidOption4FqdnFlags);
+
+ // Set O = 0
+ ASSERT_NO_THROW(option->setFlag(Option4ClientFqdn::FLAG_O, false));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+
+ // Try out of bounds settings.
+ uint8_t flags = 0;
+ ASSERT_THROW(option->setFlag(flags, true), InvalidOption4FqdnFlags);
+
+ flags = 0x18;
+ ASSERT_THROW(option->setFlag(flags, true), InvalidOption4FqdnFlags);
+}
+
+// This test verifies that flags field of the option is set to 0 when resetFlags
+// function is called.
+TEST(Option4ClientFqdnTest, resetFlags) {
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_O |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com",
+ Option4ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+
+ // Check that flags we set in the constructor are set.
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ ASSERT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ ASSERT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+
+ option->resetFlags();
+
+ // After reset, all flags should be 0.
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_E));
+}
+
+// This test verifies that current domain-name can be replaced with a new
+// domain-name.
+TEST(Option4ClientFqdnTest, setDomainName) {
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_SERVER(),
+ "myhost.example.com",
+ Option4ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+ ASSERT_EQ("myhost.example.com.", option->getDomainName());
+ ASSERT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+ // Partial domain-name.
+ ASSERT_NO_THROW(option->setDomainName("myhost",
+ Option4ClientFqdn::PARTIAL));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ // Fully qualified domain-name.
+ ASSERT_NO_THROW(option->setDomainName("example.com",
+ Option4ClientFqdn::FULL));
+ EXPECT_EQ("example.com.", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+ // Empty domain name (partial). This should be successful.
+ ASSERT_NO_THROW(option->setDomainName("", Option4ClientFqdn::PARTIAL));
+ EXPECT_TRUE(option->getDomainName().empty());
+
+ // Fully qualified domain-names must not be empty.
+ EXPECT_THROW(option->setDomainName("", Option4ClientFqdn::FULL),
+ InvalidOption4FqdnDomainName);
+ EXPECT_THROW(option->setDomainName(" ", Option4ClientFqdn::FULL),
+ InvalidOption4FqdnDomainName);
+}
+
+// This test verifies that current domain-name can be reset to empty one.
+TEST(Option4ClientFqdnTest, resetDomainName) {
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com",
+ Option4ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+ ASSERT_EQ("myhost.example.com.", option->getDomainName());
+ ASSERT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+ // Set the domain-name to empty one.
+ ASSERT_NO_THROW(option->resetDomainName());
+ EXPECT_TRUE(option->getDomainName().empty());
+}
+
+// This test verifies on-wire format of the option is correctly created.
+TEST(Option4ClientFqdnTest, pack) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E;
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 81, 23, // header
+ Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+TEST(Option4ClientFqdnTest, packASCII) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option4ClientFqdn::FLAG_S;
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 81, 23, // header
+ Option4ClientFqdn::FLAG_S, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 109, 121, 104, 111, 115, 116, 46, // myhost.
+ 101, 120, 97, 109, 112, 108, 101, 46, // example.
+ 99, 111, 109, 46 // com.
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+
+}
+
+// This test verifies on-wire format of the option with partial domain name
+// is correctly created.
+TEST(Option4ClientFqdnTest, packPartial) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E;
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 81, 10, // header
+ Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116 // myhost
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies that it is possible to encode option with empty
+// domain-name in the on-wire format.
+TEST(Option4ClientFqdnTest, packEmpty) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E;
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT()))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 81, 3, // header
+ Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+ 0, // RCODE1
+ 0 // RCODE2
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies that on-wire option data holding fully qualified domain
+// name is parsed correctly.
+TEST(Option4ClientFqdnTest, unpack) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ // Make sure that the parameters have been set correctly. Later in this
+ // test we will check that they will be replaced with new values when
+ // unpack is called.
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Initialize new values from the on-wire format.
+ ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+ // Check that new values are correct.
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that on-wire option data holding partial domain name
+// is parsed correctly.
+TEST(Option4ClientFqdnTest, unpackPartial) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+ // Make sure that the parameters have been set correctly. Later in this
+ // test we will check that they will be replaced with new values when
+ // unpack is called.
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::FULL, option->getDomainNameType());
+
+ const uint8_t in_data[] = {
+ Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_E, // flags
+ 0, // RCODE1
+ 0, // RCODE2
+ 6, 109, 121, 104, 111, 115, 116 // myhost
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Initialize new values from the on-wire format.
+ ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+ // Check that new values are correct.
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getFlag(Option4ClientFqdn::FLAG_E));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option4ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option4ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the empty buffer is rejected when decoding an option
+// from on-wire format.
+TEST(Option4ClientFqdnTest, unpackTruncated) {
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O |
+ Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT()))
+ );
+ ASSERT_TRUE(option);
+
+ // Empty buffer is invalid. It should be at least 1 octet long.
+ OptionBuffer in_buf;
+ EXPECT_THROW(option->unpack(in_buf.begin(), in_buf.end()), OutOfRange);
+}
+
+// This test verifies that string representation of the option returned by
+// toText method is correctly formatted.
+TEST(Option4ClientFqdnTest, toText) {
+ // Create option instance. Check that constructor doesn't throw.
+ uint8_t flags = Option4ClientFqdn::FLAG_N | Option4ClientFqdn::FLAG_O |
+ Option4ClientFqdn::FLAG_E;
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // The base indentation of the option will be set to 2. It should appear
+ // as follows.
+ std::string ref_string =
+ " type=81 (CLIENT_FQDN), flags: (N=1, E=1, O=1, S=0), "
+ "domain-name='myhost.example.com.' (full)";
+ const int indent = 2;
+ EXPECT_EQ(ref_string, option->toText(indent));
+
+ // Create another option with different parameters:
+ // - flags set to 0
+ // - domain-name is now partial, not fully qualified
+ // Also, remove base indentation.
+ flags = 0;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(flags,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ref_string =
+ "type=81 (CLIENT_FQDN), flags: (N=0, E=0, O=0, S=0), "
+ "domain-name='myhost' (partial)";
+ EXPECT_EQ(ref_string, option->toText());
+}
+
+// This test verifies that the correct length of the option in on-wire
+// format is returned.
+TEST(Option4ClientFqdnTest, len) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option4ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+ // This option comprises a header (2 octets), flag field (1 octet),
+ // RCODE1 and RCODE2 (2 octets) and wire representation of the
+ // domain name (length equal to the length of the string representation
+ // of the domain name + 1).
+ EXPECT_EQ(25, option->len());
+
+ // Let's check that the size will change when domain name of a different
+ // size is used.
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "example.com"))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_EQ(18, option->len());
+
+ ASSERT_NO_THROW(
+ option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+ Option4ClientFqdn::RCODE_CLIENT(),
+ "myhost",
+ Option4ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_EQ(12, option->len());
+ }
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option6_addrlst_unittest.cc b/src/lib/dhcp/tests/option6_addrlst_unittest.cc
index 96db59c..35a7829 100644
--- a/src/lib/dhcp/tests/option6_addrlst_unittest.cc
+++ b/src/lib/dhcp/tests/option6_addrlst_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -21,6 +21,7 @@
#include <util/buffer.h>
#include <gtest/gtest.h>
+#include <boost/scoped_ptr.hpp>
#include <iostream>
#include <sstream>
@@ -32,6 +33,7 @@ using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
using namespace isc::util;
+using boost::scoped_ptr;
namespace {
class Option6AddrLstTest : public ::testing::Test {
@@ -47,7 +49,7 @@ public:
TEST_F(Option6AddrLstTest, basic) {
- // Limiting tests to just a 2001:db8::/32 as is *wrong*.
+ // Limiting tests to just a 2001:db8::/32 is *wrong*.
// Good tests check corner cases as well.
// ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff checks
// for integer overflow.
@@ -110,10 +112,11 @@ TEST_F(Option6AddrLstTest, basic) {
memcpy(&buf_[0], sampledata, 48);
- // just a single address
- Option6AddrLst* opt1 = 0;
+ // Just a single address
+ scoped_ptr<Option6AddrLst> opt1;
EXPECT_NO_THROW(
- opt1 = new Option6AddrLst(D6O_NAME_SERVERS, buf_.begin(), buf_.begin() + 16 );
+ opt1.reset(new Option6AddrLst(D6O_NAME_SERVERS,
+ buf_.begin(), buf_.begin() + 16));
);
EXPECT_EQ(Option::V6, opt1->getUniverse());
@@ -125,16 +128,17 @@ TEST_F(Option6AddrLstTest, basic) {
IOAddress addr = addrs[0];
EXPECT_EQ("2001:db8:1::dead:beef", addr.toText());
- // pack this option
+ // Pack this option
opt1->pack(outBuf_);
EXPECT_EQ(20, outBuf_.getLength());
EXPECT_EQ(0, memcmp(expected1, outBuf_.getData(), 20));
- // two addresses
- Option6AddrLst* opt2 = 0;
+ // Two addresses
+ scoped_ptr<Option6AddrLst> opt2;
EXPECT_NO_THROW(
- opt2 = new Option6AddrLst(D6O_SIP_SERVERS_ADDR, buf_.begin(), buf_.begin() + 32);
+ opt2.reset(new Option6AddrLst(D6O_SIP_SERVERS_ADDR,
+ buf_.begin(), buf_.begin() + 32));
);
EXPECT_EQ(D6O_SIP_SERVERS_ADDR, opt2->getType());
EXPECT_EQ(36, opt2->len());
@@ -143,17 +147,18 @@ TEST_F(Option6AddrLstTest, basic) {
EXPECT_EQ("2001:db8:1::dead:beef", addrs[0].toText());
EXPECT_EQ("ff02::face:b00c", addrs[1].toText());
- // pack this option
+ // Pack this option
outBuf_.clear();
opt2->pack(outBuf_);
EXPECT_EQ(36, outBuf_.getLength() );
EXPECT_EQ(0, memcmp(expected2, outBuf_.getData(), 36));
- // three addresses
- Option6AddrLst* opt3 = 0;
+ // Three addresses
+ scoped_ptr<Option6AddrLst> opt3;
EXPECT_NO_THROW(
- opt3 = new Option6AddrLst(D6O_NIS_SERVERS, buf_.begin(), buf_.begin() + 48);
+ opt3.reset(new Option6AddrLst(D6O_NIS_SERVERS,
+ buf_.begin(), buf_.begin() + 48));
);
EXPECT_EQ(D6O_NIS_SERVERS, opt3->getType());
@@ -164,25 +169,23 @@ TEST_F(Option6AddrLstTest, basic) {
EXPECT_EQ("ff02::face:b00c", addrs[1].toText());
EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", addrs[2].toText());
- // pack this option
+ // Pack this option
outBuf_.clear();
opt3->pack(outBuf_);
EXPECT_EQ(52, outBuf_.getLength());
EXPECT_EQ(0, memcmp(expected3, outBuf_.getData(), 52));
- EXPECT_NO_THROW(
- delete opt1;
- delete opt2;
- delete opt3;
- );
+ EXPECT_NO_THROW(opt1.reset());
+ EXPECT_NO_THROW(opt2.reset());
+ EXPECT_NO_THROW(opt3.reset());
}
TEST_F(Option6AddrLstTest, constructors) {
- Option6AddrLst* opt1 = 0;
+ scoped_ptr<Option6AddrLst> opt1;
EXPECT_NO_THROW(
- opt1 = new Option6AddrLst(1234, IOAddress("::1"));
+ opt1.reset(new Option6AddrLst(1234, IOAddress("::1")));
);
EXPECT_EQ(Option::V6, opt1->getUniverse());
EXPECT_EQ(1234, opt1->getType());
@@ -195,9 +198,9 @@ TEST_F(Option6AddrLstTest, constructors) {
addrs.push_back(IOAddress(string("fe80::1234")));
addrs.push_back(IOAddress(string("2001:db8:1::baca")));
- Option6AddrLst* opt2 = 0;
+ scoped_ptr<Option6AddrLst> opt2;
EXPECT_NO_THROW(
- opt2 = new Option6AddrLst(5678, addrs);
+ opt2.reset(new Option6AddrLst(5678, addrs));
);
Option6AddrLst::AddressContainer check = opt2->getAddresses();
@@ -205,16 +208,14 @@ TEST_F(Option6AddrLstTest, constructors) {
EXPECT_EQ("fe80::1234", check[0].toText());
EXPECT_EQ("2001:db8:1::baca", check[1].toText());
- EXPECT_NO_THROW(
- delete opt1;
- delete opt2;
- );
+ EXPECT_NO_THROW(opt1.reset());
+ EXPECT_NO_THROW(opt2.reset());
}
TEST_F(Option6AddrLstTest, setAddress) {
- Option6AddrLst* opt1 = 0;
+ scoped_ptr<Option6AddrLst> opt1;
EXPECT_NO_THROW(
- opt1 = new Option6AddrLst(1234, IOAddress("::1"));
+ opt1.reset(new Option6AddrLst(1234, IOAddress("::1")));
);
opt1->setAddress(IOAddress("2001:db8:1::2"));
/// TODO It used to be ::2 address, but io_address represents
@@ -227,12 +228,10 @@ TEST_F(Option6AddrLstTest, setAddress) {
/// a test for IOAddress)
Option6AddrLst::AddressContainer addrs = opt1->getAddresses();
- ASSERT_EQ(1, addrs.size() );
+ ASSERT_EQ(1, addrs.size());
EXPECT_EQ("2001:db8:1::2", addrs[0].toText());
- EXPECT_NO_THROW(
- delete opt1;
- );
+ EXPECT_NO_THROW(opt1.reset());
}
} // namespace
diff --git a/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc b/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc
new file mode 100644
index 0000000..d644a2e
--- /dev/null
+++ b/src/lib/dhcp/tests/option6_client_fqdn_unittest.cc
@@ -0,0 +1,809 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dns/name.h>
+#include <util/buffer.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+namespace {
+
+using namespace isc;
+using namespace isc::dhcp;
+
+// This test verifies that constructor accepts empty partial domain-name but
+// does not accept empty fully qualified domain name.
+TEST(Option6ClientFqdnTest, constructEmptyName) {
+ // Create an instance of the source option.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ // Constructor should not accept empty fully qualified domain name.
+ EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, "",
+ Option6ClientFqdn::FULL),
+ InvalidOption6FqdnDomainName);
+ // This check is similar to previous one, but using domain-name comprising
+ // a single space character. This should be treated as empty domain-name.
+ EXPECT_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S, " ",
+ Option6ClientFqdn::FULL),
+ InvalidOption6FqdnDomainName);
+
+ // Try different constructor.
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option->getDomainName().empty());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that copy constructor makes a copy of the option and
+// the source option instance can be deleted (both instances don't share
+// any resources).
+TEST(Option6ClientFqdnTest, copyConstruct) {
+ // Create an instance of the source option.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+
+ // Use copy constructor to create a second instance of the option.
+ boost::scoped_ptr<Option6ClientFqdn> option_copy;
+ ASSERT_NO_THROW(
+ option_copy.reset(new Option6ClientFqdn(*option))
+ );
+ ASSERT_TRUE(option_copy);
+
+ // Copy construction should result in no shared resources between
+ // two objects. In particular, pointer to implementation should not
+ // be shared. Thus, we can release the source object now.
+ option.reset();
+
+ // Verify that all parameters have been copied to the target object.
+ EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example.com.", option_copy->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, option_copy->getDomainNameType());
+
+ // Do another test with different parameters to verify that parameters
+ // change when copied object is changed.
+
+ // Create an option with different parameters.
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+ "example",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+
+ // Call copy-constructor to copy the option.
+ ASSERT_NO_THROW(
+ option_copy.reset(new Option6ClientFqdn(*option))
+ );
+ ASSERT_TRUE(option_copy);
+
+ option.reset();
+
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("example", option_copy->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option_copy->getDomainNameType());
+}
+
+// This test verifies that copy constructor makes a copy of the option, when
+// domain-name is empty.
+TEST(Option6ClientFqdnTest, copyConstructEmptyDomainName) {
+ // Create an instance of the source option.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S));
+ );
+ ASSERT_TRUE(option);
+
+ // Use copy constructor to create a second instance of the option.
+ boost::scoped_ptr<Option6ClientFqdn> option_copy;
+ ASSERT_NO_THROW(
+ option_copy.reset(new Option6ClientFqdn(*option))
+ );
+ ASSERT_TRUE(option_copy);
+
+ // Copy construction should result in no shared resources between
+ // two objects. In particular, pointer to implementation should not
+ // be shared. Thus, we can release the source object now.
+ option.reset();
+
+ // Verify that all parameters have been copied to the target object.
+ EXPECT_TRUE(option_copy->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option_copy->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("", option_copy->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option_copy->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format is parsed correctly.
+TEST(Option6ClientFqdnTest, constructFromWire) {
+ const uint8_t in_data[] = {
+ Option6ClientFqdn::FLAG_S, // flags
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// Verify that exception is thrown if the domain-name label is
+// longer than 63.
+TEST(Option6ClientFqdnTest, constructFromWireTooLongLabel) {
+ OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
+ in_buf.push_back(70);
+ in_buf.insert(in_buf.end(), 70, 109);
+ in_buf.push_back(0);
+
+ EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+ InvalidOption6FqdnDomainName);
+}
+
+// Verify that exception is thrown if the overall length of the domain-name
+// is over 255.
+TEST(Option6ClientFqdnTest, constructFromWireTooLongDomainName) {
+ OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
+ for (int i = 0; i < 26; ++i) {
+ in_buf.push_back(10);
+ in_buf.insert(in_buf.end(), 10, 109);
+ }
+ in_buf.push_back(0);
+
+ try {
+ Option6ClientFqdn(in_buf.begin(), in_buf.end());
+ } catch (const Exception& ex) {
+ std::cout << ex.what() << std::endl;
+ }
+ EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+ InvalidOption6FqdnDomainName);
+}
+
+// This test verifies that truncated option is rejected.
+TEST(Option6ClientFqdnTest, constructFromWireTruncated) {
+ // Empty buffer is invalid. It should be at least one octet long.
+ OptionBuffer in_buf;
+ ASSERT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+ OutOfRange);
+}
+
+// This test verifies that the option in the on-wire format with partial
+// domain-name is parsed correctly.
+TEST(Option6ClientFqdnTest, constructFromWirePartial) {
+ const uint8_t in_data[] = {
+ Option6ClientFqdn::FLAG_N, // flags
+ 6, 109, 121, 104, 111, 115, 116 // myhost
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the option in the on-wire format with empty
+// domain-name is parsed correctly.
+TEST(Option6ClientFqdnTest, constructFromWireEmpty) {
+ OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(in_buf.begin(), in_buf.end()))
+ );
+ ASSERT_TRUE(option);
+
+ // domain-name field should be empty because on-wire data comprised
+ // flags field only.
+ EXPECT_TRUE(option->getDomainName().empty());
+}
+
+// This test verifies that assignment operator can be used to assign one
+// instance of the option to another.
+TEST(Option6ClientFqdnTest, assignment) {
+ // Usually the smart pointer is used to declare options and call
+ // constructor within assert. Thanks to this approach, the option instance
+ // is in the function scope and only initialization is done within assert.
+ // In this particular test we can't use smart pointers because we are
+ // testing assignment operator like this:
+ //
+ // option2 = option;
+ //
+ // The two asserts below do not create the instances that we will used to
+ // test assignment. They just attempt to create instances of the options
+ // with the same parameters as those that will be created for the actual
+ // assignment test. If these asserts do not fail, we can create options
+ // for the assignment test, do not surround them with asserts and be sure
+ // they will not throw.
+ ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL));
+
+ ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_N,
+ "myhost",
+ Option6ClientFqdn::PARTIAL));
+
+ // Create options with the same parameters as tested above.
+
+ // Create first option.
+ Option6ClientFqdn option(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL);
+
+ // Verify that the values have been set correctly.
+ ASSERT_TRUE(option.getFlag(Option6ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_O));
+ ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_N));
+ ASSERT_EQ("myhost.example.com.", option.getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::FULL, option.getDomainNameType());
+
+ // Create a second option.
+ Option6ClientFqdn option2(Option6ClientFqdn::FLAG_N,
+ "myhost",
+ Option6ClientFqdn::PARTIAL);
+
+ // Verify tha the values have been set correctly.
+ ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+ ASSERT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+ ASSERT_EQ("myhost", option2.getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::PARTIAL, option2.getDomainNameType());
+
+
+ // Make the assignment.
+ ASSERT_NO_THROW(option2 = option);
+
+ // Both options should now have the same values.
+ EXPECT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ(option.getDomainName(), option2.getDomainName());
+ EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
+}
+
+// This test verifies that assignment operator can be used to assign one
+// instance of the option to another, when the domain-name is empty.
+TEST(Option6ClientFqdnTest, assignmentEmptyDomainName) {
+ ASSERT_NO_THROW(
+ Option6ClientFqdn(static_cast<uint8_t>(Option6ClientFqdn::FLAG_S))
+ );
+
+ ASSERT_NO_THROW(Option6ClientFqdn(Option6ClientFqdn::FLAG_N,
+ "myhost",
+ Option6ClientFqdn::PARTIAL));
+
+ // Create options with the same parameters as tested above.
+
+ // Create first option.
+ Option6ClientFqdn option(Option6ClientFqdn::FLAG_S);
+
+ // Verify that the values have been set correctly.
+ ASSERT_TRUE(option.getFlag(Option6ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_O));
+ ASSERT_FALSE(option.getFlag(Option6ClientFqdn::FLAG_N));
+ ASSERT_EQ("", option.getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::PARTIAL, option.getDomainNameType());
+
+ // Create a second option.
+ Option6ClientFqdn option2(Option6ClientFqdn::FLAG_N,
+ "myhost",
+ Option6ClientFqdn::PARTIAL);
+
+ // Verify that the values have been set correctly.
+ ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+ ASSERT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+ ASSERT_EQ("myhost", option2.getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::PARTIAL, option2.getDomainNameType());
+
+
+ // Make the assignment.
+ ASSERT_NO_THROW(option2 = option);
+
+ // Both options should now have the same values.
+ EXPECT_TRUE(option2.getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option2.getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_EQ("", option2.getDomainName());
+ EXPECT_EQ(option.getDomainNameType(), option2.getDomainNameType());
+}
+
+
+// This test verifies that constructor will throw an exception if invalid
+// DHCPv6 Client FQDN Option flags are specified.
+TEST(Option6ClientFqdnTest, constructInvalidFlags) {
+ // First, check that constructor does not throw an exception when
+ // valid flags values are provided. That way we eliminate the issue
+ // that constructor always throws exception.
+ uint8_t flags = 0;
+ ASSERT_NO_THROW(Option6ClientFqdn(flags, "myhost.example.com"));
+
+ // Invalid flags: The maximal value is 0x7 when all flag bits are set
+ // (00000111b). The flag value of 0x14 sets the bit from the Must Be
+ // Zero (MBZ) bitset (00001100b).
+ flags = 0x14;
+ EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"),
+ InvalidOption6FqdnFlags);
+
+ // According to RFC 4704, section 4.1. if the N bit is set the S bit MUST
+ // be zero. If both are set, constructor is expected to throw.
+ flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S;
+ EXPECT_THROW(Option6ClientFqdn(flags, "myhost.example.com"),
+ InvalidOption6FqdnFlags);
+}
+
+// This test verifies that constructor which parses option from on-wire format
+// will throw exception if parsed flags field is invalid.
+TEST(Option6ClientFqdnTest, constructFromWireInvalidFlags) {
+ // Create a buffer which holds flags field only. Set valid flag field at
+ // at first to make sure that constructor doesn't always throw an exception.
+ OptionBuffer in_buf(Option6ClientFqdn::FLAG_N);
+ ASSERT_NO_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()));
+
+ // Replace the flags with invalid value and verify that constructor throws
+ // appropriate exception.
+ in_buf[0] = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_S;
+ EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
+ InvalidOption6FqdnFlags);
+}
+
+// This test verifies that if invalid domain name is used the constructor
+// will throw appropriate exception.
+TEST(Option6ClientFqdnTest, constructInvalidName) {
+ // First, check that constructor does not throw when valid domain name
+ // is specified. That way we eliminate the possibility that constructor
+ // always throws exception.
+ ASSERT_NO_THROW(Option6ClientFqdn(0, "myhost.example.com"));
+
+ // Specify invalid domain name and expect that exception is thrown.
+ EXPECT_THROW(Option6ClientFqdn(0, "my...host.example.com"),
+ InvalidOption6FqdnDomainName);
+}
+
+// This test verifies that getFlag throws an exception if flag value other
+// than FLAG_N, FLAG_S, FLAG_O is specified.
+TEST(Option6ClientFqdnTest, getFlag) {
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // The 0x3 (binary 011) specifies two distinct bits in the flags field.
+ // This value is ambiguous for getFlag function and this function doesn't
+ // know which flag the caller is attempting to check.
+ EXPECT_THROW(option->getFlag(0x3), InvalidOption6FqdnFlags);
+}
+
+// This test verifies that flags can be modified and that incorrect flags
+// are rejected.
+TEST(Option6ClientFqdnTest, setFlag) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // All flags should be set to 0 initially.
+ ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+
+ // Set N = 1
+ ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, true));
+ ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_N));
+
+ // Set O = 1
+ ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_O, true));
+ ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+
+ // Set S = 1, this should throw exception because S and N must not
+ // be set in the same time.
+ ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_S, true),
+ InvalidOption6FqdnFlags);
+
+ // Set N = 0
+ ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, false));
+ ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+
+ // Set S = 1, this should not result in exception because N has been
+ // cleared.
+ ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_S, true));
+ ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+
+ // Set N = 1, this should result in exception because S = 1
+ ASSERT_THROW(option->setFlag(Option6ClientFqdn::FLAG_N, true),
+ InvalidOption6FqdnFlags);
+
+ // Set O = 0
+ ASSERT_NO_THROW(option->setFlag(Option6ClientFqdn::FLAG_O, false));
+ ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+
+ // Try out of bounds settings.
+ uint8_t flags = 0;
+ ASSERT_THROW(option->setFlag(flags, true), InvalidOption6FqdnFlags);
+
+ flags = 0x14;
+ ASSERT_THROW(option->setFlag(flags, true), InvalidOption6FqdnFlags);
+}
+
+// This test verifies that flags field of the option is set to 0 when resetFlags
+// function is called.
+TEST(Option6ClientFqdnTest, resetFlags) {
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S |
+ Option6ClientFqdn::FLAG_O,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+
+ // Check that flags we set in the constructor are set.
+ ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ ASSERT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ ASSERT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+
+ option->resetFlags();
+
+ // After reset, all flags should be 0.
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+}
+
+// This test verifies that current domain-name can be replaced with a new
+// domain-name.
+TEST(Option6ClientFqdnTest, setDomainName) {
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+ ASSERT_EQ("myhost.example.com.", option->getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+ // Partial domain-name.
+ ASSERT_NO_THROW(option->setDomainName("myhost",
+ Option6ClientFqdn::PARTIAL));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ // Fully qualified domain-name.
+ ASSERT_NO_THROW(option->setDomainName("example.com",
+ Option6ClientFqdn::FULL));
+ EXPECT_EQ("example.com.", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+ // Empty domain name (partial). This should be successful.
+ ASSERT_NO_THROW(option->setDomainName("", Option6ClientFqdn::PARTIAL));
+ EXPECT_TRUE(option->getDomainName().empty());
+
+ // Fully qualified domain-names must not be empty.
+ EXPECT_THROW(option->setDomainName("", Option6ClientFqdn::FULL),
+ InvalidOption6FqdnDomainName);
+ EXPECT_THROW(option->setDomainName(" ", Option6ClientFqdn::FULL),
+ InvalidOption6FqdnDomainName);
+}
+
+// This test verifies that current domain-name can be reset to empty one.
+TEST(Option6ClientFqdnTest, resetDomainName) {
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+ "myhost.example.com",
+ Option6ClientFqdn::FULL))
+ );
+ ASSERT_TRUE(option);
+ ASSERT_EQ("myhost.example.com.", option->getDomainName());
+ ASSERT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+ // Set the domain-name to empty one.
+ ASSERT_NO_THROW(option->resetDomainName());
+ EXPECT_TRUE(option->getDomainName().empty());
+}
+
+// This test verifies on-wire format of the option is correctly created.
+TEST(Option6ClientFqdnTest, pack) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option6ClientFqdn::FLAG_S;
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(flags, "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 0, 39, 0, 21, // header
+ Option6ClientFqdn::FLAG_S, // flags
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies on-wire format of the option with partial domain name
+// is correctly created.
+TEST(Option6ClientFqdnTest, packPartial) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option6ClientFqdn::FLAG_S;
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(flags, "myhost",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(10);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 0, 39, 0, 8, // header
+ Option6ClientFqdn::FLAG_S, // flags
+ 6, 109, 121, 104, 111, 115, 116 // myhost
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies that it is possible to encode the option which carries
+// empty domain-name in the wire format.
+TEST(Option6ClientFqdnTest, packEmpty) {
+ // Create option instance. Check that constructor doesn't throw.
+ const uint8_t flags = Option6ClientFqdn::FLAG_S;
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(flags))
+ );
+ ASSERT_TRUE(option);
+
+ // Prepare on-wire format of the option.
+ isc::util::OutputBuffer buf(5);
+ ASSERT_NO_THROW(option->pack(buf));
+
+ // Prepare reference data.
+ const uint8_t ref_data[] = {
+ 0, 39, 0, 1, // header
+ Option6ClientFqdn::FLAG_S // flags
+ };
+ size_t ref_data_size = sizeof(ref_data) / sizeof(ref_data[0]);
+
+ // Check if the buffer has the same length as the reference data,
+ // so as they can be compared directly.
+ ASSERT_EQ(ref_data_size, buf.getLength());
+ EXPECT_EQ(0, memcmp(ref_data, buf.getData(), buf.getLength()));
+}
+
+// This test verifies that on-wire option data holding fully qualified domain
+// name is parsed correctly.
+TEST(Option6ClientFqdnTest, unpack) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+ "myhost",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ // Make sure that the parameters have been set correctly. Later in this
+ // test we will check that they will be replaced with new values when
+ // unpack is called.
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+
+ const uint8_t in_data[] = {
+ Option6ClientFqdn::FLAG_S, // flags
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Initialize new values from the on-wire format.
+ ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+ // Check that new values are correct.
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+}
+
+// This test verifies that on-wire option data holding partial domain name
+// is parsed correctly.
+TEST(Option6ClientFqdnTest, unpackPartial) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+ // Make sure that the parameters have been set correctly. Later in this
+ // test we will check that they will be replaced with new values when
+ // unpack is called.
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost.example.com.", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::FULL, option->getDomainNameType());
+
+ const uint8_t in_data[] = {
+ Option6ClientFqdn::FLAG_S, // flags
+ 6, 109, 121, 104, 111, 115, 116 // myhost
+ };
+ size_t in_data_size = sizeof(in_data) / sizeof(in_data[0]);
+ OptionBuffer in_buf(in_data, in_data + in_data_size);
+
+ // Initialize new values from the on-wire format.
+ ASSERT_NO_THROW(option->unpack(in_buf.begin(), in_buf.end()));
+
+ // Check that new values are correct.
+ EXPECT_TRUE(option->getFlag(Option6ClientFqdn::FLAG_S));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_N));
+ EXPECT_FALSE(option->getFlag(Option6ClientFqdn::FLAG_O));
+ EXPECT_EQ("myhost", option->getDomainName());
+ EXPECT_EQ(Option6ClientFqdn::PARTIAL, option->getDomainNameType());
+}
+
+// This test verifies that the empty buffer is rejected when decoding an option
+// from on-wire format.
+TEST(Option6ClientFqdnTest, unpackTruncated) {
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O))
+ );
+ ASSERT_TRUE(option);
+
+ // Empty buffer is invalid. It should be at least 1 octet long.
+ OptionBuffer in_buf;
+ EXPECT_THROW(option->unpack(in_buf.begin(), in_buf.end()), OutOfRange);
+}
+
+// This test verifies that string representation of the option returned by
+// toText method is correctly formatted.
+TEST(Option6ClientFqdnTest, toText) {
+ // Create option instance. Check that constructor doesn't throw.
+ uint8_t flags = Option6ClientFqdn::FLAG_N | Option6ClientFqdn::FLAG_O;
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(flags,
+ "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+
+ // The base indentation of the option will be set to 2. It should appear
+ // as follows.
+ std::string ref_string =
+ " type=39(CLIENT_FQDN), flags: (N=1, O=1, S=0), "
+ "domain-name='myhost.example.com.' (full)";
+ const int indent = 2;
+ EXPECT_EQ(ref_string, option->toText(indent));
+
+ // Create another option with different parameters:
+ // - flags set to 0
+ // - domain-name is now partial, not fully qualified
+ // Also, remove base indentation.
+ flags = 0;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(flags, "myhost",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ref_string =
+ "type=39(CLIENT_FQDN), flags: (N=0, O=0, S=0), "
+ "domain-name='myhost' (partial)";
+ EXPECT_EQ(ref_string, option->toText());
+}
+
+// This test verifies that the correct length of the option in on-wire
+// format is returned.
+TEST(Option6ClientFqdnTest, len) {
+ // Create option instance. Check that constructor doesn't throw.
+ boost::scoped_ptr<Option6ClientFqdn> option;
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(0, "myhost.example.com"))
+ );
+ ASSERT_TRUE(option);
+ // This option comprises a header (4 octets), flag field (1 octet),
+ // and wire representation of the domain name (length equal to the
+ // length of the string representation of the domain name + 1).
+ EXPECT_EQ(25, option->len());
+
+ // Let's check that the size will change when domain name of a different
+ // size is used.
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(0, "example.com"))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_EQ(18, option->len());
+
+ ASSERT_NO_THROW(
+ option.reset(new Option6ClientFqdn(0, "myhost",
+ Option6ClientFqdn::PARTIAL))
+ );
+ ASSERT_TRUE(option);
+ EXPECT_EQ(12, option->len());
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option6_ia_unittest.cc b/src/lib/dhcp/tests/option6_ia_unittest.cc
index 24f101d..071edb9 100644
--- a/src/lib/dhcp/tests/option6_ia_unittest.cc
+++ b/src/lib/dhcp/tests/option6_ia_unittest.cc
@@ -20,6 +20,7 @@
#include <dhcp/option6_iaaddr.h>
#include <util/buffer.h>
+#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <iostream>
@@ -32,6 +33,7 @@ using namespace isc;
using namespace isc::dhcp;
using namespace isc::asiolink;
using namespace isc::util;
+using boost::scoped_ptr;
namespace {
class Option6IATest : public ::testing::Test {
@@ -61,11 +63,11 @@ TEST_F(Option6IATest, basic) {
buf_[10] = 0x02;
buf_[11] = 0x01;
- // create an option
+ // Create an option
// unpack() is called from constructor
- Option6IA* opt = new Option6IA(D6O_IA_NA,
- buf_.begin(),
- buf_.begin() + 12);
+ scoped_ptr<Option6IA> opt(new Option6IA(D6O_IA_NA,
+ buf_.begin(),
+ buf_.begin() + 12));
EXPECT_EQ(Option::V6, opt->getUniverse());
EXPECT_EQ(D6O_IA_NA, opt->getType());
@@ -73,10 +75,10 @@ TEST_F(Option6IATest, basic) {
EXPECT_EQ(0x81020304, opt->getT1());
EXPECT_EQ(0x84030201, opt->getT2());
- // pack this option again in the same buffer, but in
+ // Pack this option again in the same buffer, but in
// different place
- // test for pack()
+ // Test for pack()
opt->pack(outBuf_);
// 12 bytes header + 4 bytes content
@@ -85,31 +87,29 @@ TEST_F(Option6IATest, basic) {
EXPECT_EQ(16, outBuf_.getLength()); // lenght(IA_NA) = 16
- // check if pack worked properly:
+ // Check if pack worked properly:
InputBuffer out(outBuf_.getData(), outBuf_.getLength());
- // if option type is correct
+ // - if option type is correct
EXPECT_EQ(D6O_IA_NA, out.readUint16());
- // if option length is correct
+ // - if option length is correct
EXPECT_EQ(12, out.readUint16());
- // if iaid is correct
+ // - if iaid is correct
EXPECT_EQ(0xa1a2a3a4, out.readUint32() );
- // if T1 is correct
+ // - if T1 is correct
EXPECT_EQ(0x81020304, out.readUint32() );
- // if T1 is correct
+ // - if T1 is correct
EXPECT_EQ(0x84030201, out.readUint32() );
- EXPECT_NO_THROW(
- delete opt;
- );
+ EXPECT_NO_THROW(opt.reset());
}
TEST_F(Option6IATest, simple) {
- Option6IA* ia = new Option6IA(D6O_IA_NA, 1234);
+ scoped_ptr<Option6IA> ia(new Option6IA(D6O_IA_NA, 1234));
// Check that the values are really different than what we are about
// to set them to.
@@ -128,9 +128,7 @@ TEST_F(Option6IATest, simple) {
ia->setIAID(890);
EXPECT_EQ(890, ia->getIAID());
- EXPECT_NO_THROW(
- delete ia;
- );
+ EXPECT_NO_THROW(ia.reset());
}
@@ -140,7 +138,7 @@ TEST_F(Option6IATest, suboptions_pack) {
buf_[1] = 0xfe;
buf_[2] = 0xfc;
- Option6IA * ia = new Option6IA(D6O_IA_NA, 0x13579ace);
+ scoped_ptr<Option6IA> ia(new Option6IA(D6O_IA_NA, 0x13579ace));
ia->setT1(0x2345);
ia->setT2(0x3456);
@@ -181,9 +179,7 @@ TEST_F(Option6IATest, suboptions_pack) {
EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 48));
- EXPECT_NO_THROW(
- delete ia;
- );
+ EXPECT_NO_THROW(ia.reset());
}
@@ -213,10 +209,11 @@ TEST_F(Option6IATest, suboptions_unpack) {
memcpy(&buf_[0], expected, sizeof(expected));
- Option6IA* ia = 0;
- EXPECT_NO_THROW({
- ia = new Option6IA(D6O_IA_NA, buf_.begin() + 4, buf_.begin() + sizeof(expected));
- });
+ scoped_ptr<Option6IA> ia;
+ EXPECT_NO_THROW(
+ ia.reset(new Option6IA(D6O_IA_NA, buf_.begin() + 4,
+ buf_.begin() + sizeof(expected)));
+ );
ASSERT_TRUE(ia);
EXPECT_EQ(D6O_IA_NA, ia->getType());
@@ -227,7 +224,7 @@ TEST_F(Option6IATest, suboptions_unpack) {
OptionPtr subopt = ia->getOption(D6O_IAADDR);
ASSERT_NE(OptionPtr(), subopt); // non-NULL
- // checks for address option
+ // Checks for address option
Option6IAAddr * addr = dynamic_cast<Option6IAAddr*>(subopt.get());
ASSERT_TRUE(NULL != addr);
@@ -237,21 +234,19 @@ TEST_F(Option6IATest, suboptions_unpack) {
EXPECT_EQ(0x7000, addr->getValid());
EXPECT_EQ("2001:db8:1234:5678::abcd", addr->getAddress().toText());
- // checks for dummy option
+ // Checks for dummy option
subopt = ia->getOption(0xcafe);
ASSERT_TRUE(subopt); // should be non-NULL
EXPECT_EQ(0xcafe, subopt->getType());
EXPECT_EQ(4, subopt->len());
- // there should be no data at all
+ // There should be no data at all
EXPECT_EQ(0, subopt->getData().size());
subopt = ia->getOption(1); // get option 1
ASSERT_FALSE(subopt); // should be NULL
- EXPECT_NO_THROW(
- delete ia;
- );
+ EXPECT_NO_THROW(ia.reset());
}
}
diff --git a/src/lib/dhcp/tests/option6_iaaddr_unittest.cc b/src/lib/dhcp/tests/option6_iaaddr_unittest.cc
index 8c87d80..e28e2e0 100644
--- a/src/lib/dhcp/tests/option6_iaaddr_unittest.cc
+++ b/src/lib/dhcp/tests/option6_iaaddr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -19,6 +19,7 @@
#include <dhcp/option6_iaaddr.h>
#include <util/buffer.h>
+#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <iostream>
@@ -68,12 +69,12 @@ TEST_F(Option6IAAddrTest, basic) {
buf_[22] = 0x5e;
buf_[23] = 0x00; // 3,000,000,000
- // create an option (unpack content)
- Option6IAAddr* opt = new Option6IAAddr(D6O_IAADDR,
- buf_.begin(),
- buf_.begin() + 24);
+ // Create an option (unpack content)
+ boost::scoped_ptr<Option6IAAddr> opt(new Option6IAAddr(D6O_IAADDR,
+ buf_.begin(),
+ buf_.begin() + 24));
- // pack this option
+ // Pack this option
opt->pack(outBuf_);
EXPECT_EQ(28, outBuf_.getLength());
@@ -90,19 +91,19 @@ TEST_F(Option6IAAddrTest, basic) {
EXPECT_EQ(Option::OPTION6_HDR_LEN + Option6IAAddr::OPTION6_IAADDR_LEN,
opt->len());
- // check if pack worked properly:
+ // Check if pack worked properly:
const uint8_t* out = (const uint8_t*)outBuf_.getData();
- // if option type is correct
+ // - if option type is correct
EXPECT_EQ(D6O_IAADDR, out[0]*256 + out[1]);
- // if option length is correct
+ // - if option length is correct
EXPECT_EQ(24, out[2]*256 + out[3]);
- // if option content is correct
+ // - if option content is correct
EXPECT_EQ(0, memcmp(out + 4, &buf_[0], 24));
- EXPECT_NO_THROW( delete opt );
+ EXPECT_NO_THROW(opt.reset());
}
}
diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc
index a34852b..c04bbc2 100644
--- a/src/lib/dhcp/tests/option_custom_unittest.cc
+++ b/src/lib/dhcp/tests/option_custom_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -262,7 +262,7 @@ TEST_F(OptionCustomTest, int16Data) {
// We should have just one data field.
ASSERT_EQ(1, option->getDataFieldsNum());
- // Initialize value to 0 explicitely to make sure that is
+ // Initialize value to 0 explicitly to make sure that is
// modified by readInteger function to expected -234.
int16_t value = 0;
ASSERT_NO_THROW(value = option->readInteger<int16_t>(0));
@@ -295,7 +295,7 @@ TEST_F(OptionCustomTest, int32Data) {
// We should have just one data field.
ASSERT_EQ(1, option->getDataFieldsNum());
- // Initialize value to 0 explicitely to make sure that is
+ // Initialize value to 0 explicitly to make sure that is
// modified by readInteger function to expected -234.
int32_t value = 0;
ASSERT_NO_THROW(value = option->readInteger<int32_t>(0));
@@ -310,7 +310,7 @@ TEST_F(OptionCustomTest, int32Data) {
}
// The purpose of this test is to verify that the option definition comprising
-// single IPv4 addres can be used to create an instance of custom option.
+// single IPv4 address can be used to create an instance of custom option.
TEST_F(OptionCustomTest, ipv4AddressData) {
OptionDefinition opt_def("OPTION_FOO", 231, "ipv4-address");
@@ -343,7 +343,7 @@ TEST_F(OptionCustomTest, ipv4AddressData) {
}
// The purpose of this test is to verify that the option definition comprising
-// single IPv6 addres can be used to create an instance of custom option.
+// single IPv6 address can be used to create an instance of custom option.
TEST_F(OptionCustomTest, ipv6AddressData) {
OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address");
@@ -766,9 +766,15 @@ TEST_F(OptionCustomTest, recordDataTruncated) {
// 2 bytes of uint16_t value and IPv6 address. Option definitions specifies
// 3 data fields for this option but the length of the data is insufficient
// to initialize 3 data field.
- EXPECT_THROW(
- option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 18)),
- isc::OutOfRange
+
+ // @todo:
+ // Currently the code was modified to allow empty string or empty binary data
+ // Potentially change this back to EXPECT_THROW(..., OutOfRange) once we
+ // decide how to treat zero length strings and binary data (they are typically
+ // valid or invalid on a per option basis, so there likely won't be a single
+ // one answer to all)
+ EXPECT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 18))
);
// Try to further reduce the length of the buffer to make it insufficient
@@ -999,7 +1005,7 @@ TEST_F(OptionCustomTest, setBooleanDataArray) {
EXPECT_TRUE(value2);
}
-// The purpose of this test is to verify that am option carying
+// The purpose of this test is to verify that am option carrying
// an array of 16-bit signed integer values can be created with
// no values initially and that the values can be later added to it.
TEST_F(OptionCustomTest, setUint16DataArray) {
@@ -1318,7 +1324,7 @@ TEST_F(OptionCustomTest, unpack) {
// The purpose of this test is to verify that new data can be set for
// a custom option.
-TEST_F(OptionCustomTest, setData) {
+TEST_F(OptionCustomTest, initialize) {
OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address", true);
// Initialize reference data.
@@ -1364,7 +1370,7 @@ TEST_F(OptionCustomTest, setData) {
}
// Replace the option data.
- ASSERT_NO_THROW(option->setData(buf.begin(), buf.end()));
+ ASSERT_NO_THROW(option->initialize(buf.begin(), buf.end()));
// Now we should have only 2 data fields.
ASSERT_EQ(2, option->getDataFieldsNum());
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
index 174bafb..d3b766d 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -25,6 +25,7 @@
#include <dhcp/option_definition.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
+#include <dhcp/option_string.h>
#include <exceptions/exceptions.h>
#include <boost/pointer_cast.hpp>
@@ -927,7 +928,7 @@ TEST_F(OptionDefinitionTest, utf8StringTokenized) {
// Let's create some dummy option.
const uint16_t opt_code = 80;
OptionDefinition opt_def("OPTION_WITH_STRING", opt_code, "string");
-
+
std::vector<std::string> values;
values.push_back("Hello World");
values.push_back("this string should not be included in the option");
@@ -936,11 +937,10 @@ TEST_F(OptionDefinitionTest, utf8StringTokenized) {
option_v6 = opt_def.optionFactory(Option::V6, opt_code, values);
);
ASSERT_TRUE(option_v6);
- ASSERT_TRUE(typeid(*option_v6) == typeid(OptionCustom));
- std::vector<uint8_t> data = option_v6->getData();
- std::vector<uint8_t> ref_data(values[0].c_str(), values[0].c_str()
- + values[0].length());
- EXPECT_TRUE(std::equal(ref_data.begin(), ref_data.end(), data.begin()));
+ ASSERT_TRUE(typeid(*option_v6) == typeid(OptionString));
+ OptionStringPtr option_v6_string =
+ boost::static_pointer_cast<OptionString>(option_v6);
+ EXPECT_TRUE(values[0] == option_v6_string->getValue());
}
// The purpose of this test is to check that non-integer data type can't
@@ -960,7 +960,7 @@ TEST_F(OptionDefinitionTest, integerInvalidType) {
// The purpose of this test is to verify that helper methods
// haveIA6Format and haveIAAddr6Format can be used to determine
// IA_NA and IAADDR option formats.
-TEST_F(OptionDefinitionTest, recognizeFormat) {
+TEST_F(OptionDefinitionTest, haveIAFormat) {
// IA_NA option format.
OptionDefinition opt_def1("OPTION_IA_NA", D6O_IA_NA, "record");
for (int i = 0; i < 3; ++i) {
@@ -984,4 +984,19 @@ TEST_F(OptionDefinitionTest, recognizeFormat) {
EXPECT_FALSE(opt_def4.haveIAAddr6Format());
}
+// This test verifies that haveClientFqdnFormat function recognizes that option
+// definition describes the format of DHCPv6 Client Fqdn Option Format.
+TEST_F(OptionDefinitionTest, haveClientFqdnFormat) {
+ OptionDefinition opt_def("OPTION_CLIENT_FQDN", D6O_CLIENT_FQDN, "record");
+ opt_def.addRecordField("uint8");
+ opt_def.addRecordField("fqdn");
+ EXPECT_TRUE(opt_def.haveClientFqdnFormat());
+
+ // Create option format which is not matching the Client FQDN option format
+ // to verify that tested function does dont always return true.
+ OptionDefinition opt_def_invalid("OPTION_CLIENT_FQDN", D6O_CLIENT_FQDN,
+ "uint8");
+ EXPECT_FALSE(opt_def_invalid.haveClientFqdnFormat());
+}
+
} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_int_unittest.cc b/src/lib/dhcp/tests/option_int_unittest.cc
index 81ebcf0..96212c2 100644
--- a/src/lib/dhcp/tests/option_int_unittest.cc
+++ b/src/lib/dhcp/tests/option_int_unittest.cc
@@ -296,7 +296,7 @@ TEST_F(OptionIntTest, basicInt32V6) {
TEST_F(OptionIntTest, setValueUint8) {
boost::shared_ptr<OptionInt<uint8_t> > opt(new OptionInt<uint8_t>(Option::V6,
D6O_PREFERENCE, 123));
- // Check if constructor intitialized the option value correctly.
+ // Check if constructor initialized the option value correctly.
EXPECT_EQ(123, opt->getValue());
// Override the value.
opt->setValue(111);
@@ -310,7 +310,7 @@ TEST_F(OptionIntTest, setValueUint8) {
TEST_F(OptionIntTest, setValueInt8) {
boost::shared_ptr<OptionInt<int8_t> > opt(new OptionInt<int8_t>(Option::V6,
D6O_PREFERENCE, -123));
- // Check if constructor intitialized the option value correctly.
+ // Check if constructor initialized the option value correctly.
EXPECT_EQ(-123, opt->getValue());
// Override the value.
opt->setValue(-111);
@@ -325,7 +325,7 @@ TEST_F(OptionIntTest, setValueInt8) {
TEST_F(OptionIntTest, setValueUint16) {
boost::shared_ptr<OptionInt<uint16_t> > opt(new OptionInt<uint16_t>(Option::V6,
D6O_ELAPSED_TIME, 123));
- // Check if constructor intitialized the option value correctly.
+ // Check if constructor initialized the option value correctly.
EXPECT_EQ(123, opt->getValue());
// Override the value.
opt->setValue(0x0102);
@@ -339,7 +339,7 @@ TEST_F(OptionIntTest, setValueUint16) {
TEST_F(OptionIntTest, setValueInt16) {
boost::shared_ptr<OptionInt<int16_t> > opt(new OptionInt<int16_t>(Option::V6,
D6O_ELAPSED_TIME, -16500));
- // Check if constructor intitialized the option value correctly.
+ // Check if constructor initialized the option value correctly.
EXPECT_EQ(-16500, opt->getValue());
// Override the value.
opt->setValue(-20100);
@@ -353,7 +353,7 @@ TEST_F(OptionIntTest, setValueInt16) {
TEST_F(OptionIntTest, setValueUint32) {
boost::shared_ptr<OptionInt<uint32_t> > opt(new OptionInt<uint32_t>(Option::V6,
D6O_CLT_TIME, 123));
- // Check if constructor intitialized the option value correctly.
+ // Check if constructor initialized the option value correctly.
EXPECT_EQ(123, opt->getValue());
// Override the value.
opt->setValue(0x01020304);
@@ -367,7 +367,7 @@ TEST_F(OptionIntTest, setValueUint32) {
TEST_F(OptionIntTest, setValueInt32) {
boost::shared_ptr<OptionInt<int32_t> > opt(new OptionInt<int32_t>(Option::V6,
D6O_CLT_TIME, -120100));
- // Check if constructor intitialized the option value correctly.
+ // Check if constructor initialized the option value correctly.
EXPECT_EQ(-120100, opt->getValue());
// Override the value.
opt->setValue(-125000);
diff --git a/src/lib/dhcp/tests/option_string_unittest.cc b/src/lib/dhcp/tests/option_string_unittest.cc
new file mode 100644
index 0000000..0be3c3d
--- /dev/null
+++ b/src/lib/dhcp/tests/option_string_unittest.cc
@@ -0,0 +1,160 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/option_string.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+/// @brief OptionString test class.
+class OptionStringTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ ///
+ /// Initializes the test buffer with some data.
+ OptionStringTest() {
+ std::string test_string("This is a test string");
+ buf_.assign(test_string.begin(), test_string.end());
+ }
+
+ OptionBuffer buf_;
+
+};
+
+// This test verifies that the constructor which creates an option instance
+// from a string value will create it properly.
+TEST_F(OptionStringTest, constructorFromString) {
+ const std::string optv4_value = "some option";
+ OptionString optv4(Option::V4, 123, optv4_value);
+ EXPECT_EQ(Option::V4, optv4.getUniverse());
+ EXPECT_EQ(123, optv4.getType());
+ EXPECT_EQ(optv4_value, optv4.getValue());
+ EXPECT_EQ(Option::OPTION4_HDR_LEN + optv4_value.size(), optv4.len());
+
+ // Do another test with the same constructor to make sure that
+ // different set of parameters would initialize the class members
+ // to different values.
+ const std::string optv6_value = "other option";
+ OptionString optv6(Option::V6, 234, optv6_value);
+ EXPECT_EQ(Option::V6, optv6.getUniverse());
+ EXPECT_EQ(234, optv6.getType());
+ EXPECT_EQ("other option", optv6.getValue());
+ EXPECT_EQ(Option::OPTION6_HDR_LEN + optv6_value.size(), optv6.len());
+
+ // Check that an attempt to use empty string in the constructor
+ // will result in an exception.
+ EXPECT_THROW(OptionString(Option::V6, 123, ""), isc::OutOfRange);
+}
+
+// This test verifies that the constructor which creates an option instance
+// from a buffer, holding option payload, will create it properly.
+// This function calls unpack() internally thus test test is considered
+// to cover testing of unpack() functionality.
+TEST_F(OptionStringTest, constructorFromBuffer) {
+ // Attempt to create an option using empty buffer should result in
+ // an exception.
+ EXPECT_THROW(
+ OptionString(Option::V4, 234, buf_.begin(), buf_.begin()),
+ isc::OutOfRange
+ );
+
+ // Declare option as a scoped pointer here so as its scope is
+ // function wide. The initialization (constructor invocation)
+ // is pushed to the ASSERT_NO_THROW macro below, as it may
+ // throw exception if buffer is truncated.
+ boost::scoped_ptr<OptionString> optv4;
+ ASSERT_NO_THROW(
+ optv4.reset(new OptionString(Option::V4, 234, buf_.begin(), buf_.end()));
+ );
+ // Make sure that it has been initialized to non-NULL value.
+ ASSERT_TRUE(optv4);
+
+ // Test the instance of the created option.
+ const std::string optv4_value = "This is a test string";
+ EXPECT_EQ(Option::V4, optv4->getUniverse());
+ EXPECT_EQ(234, optv4->getType());
+ EXPECT_EQ(Option::OPTION4_HDR_LEN + buf_.size(), optv4->len());
+ EXPECT_EQ(optv4_value, optv4->getValue());
+
+ // Do the same test for V6 option.
+ boost::scoped_ptr<OptionString> optv6;
+ ASSERT_NO_THROW(
+ // Let's reduce the size of the buffer by one byte and see if our option
+ // will absorb this little change.
+ optv6.reset(new OptionString(Option::V6, 123, buf_.begin(), buf_.end() - 1));
+ );
+ // Make sure that it has been initialized to non-NULL value.
+ ASSERT_TRUE(optv6);
+
+ // Test the instance of the created option.
+ const std::string optv6_value = "This is a test strin";
+ EXPECT_EQ(Option::V6, optv6->getUniverse());
+ EXPECT_EQ(123, optv6->getType());
+ EXPECT_EQ(Option::OPTION6_HDR_LEN + buf_.size() - 1, optv6->len());
+ EXPECT_EQ(optv6_value, optv6->getValue());
+}
+
+// This test verifies that the current option value can be overriden
+// with new value, using setValue method.
+TEST_F(OptionStringTest, setValue) {
+ // Create an instance of the option and set some initial value.
+ OptionString optv4(Option::V4, 123, "some option");
+ EXPECT_EQ("some option", optv4.getValue());
+ // Replace the value with the new one, and make sure it has
+ // been successful.
+ EXPECT_NO_THROW(optv4.setValue("new option value"));
+ EXPECT_EQ("new option value", optv4.getValue());
+ // Try to set to an empty string. It should throw exception.
+ EXPECT_THROW(optv4.setValue(""), isc::OutOfRange);
+}
+
+// This test verifies that the pack function encodes the option in
+// a on-wire format properly.
+TEST_F(OptionStringTest, pack) {
+ // Create an instance of the option.
+ std::string option_value("sample option value");
+ OptionString optv4(Option::V4, 123, option_value);
+ // Encode the option in on-wire format.
+ OutputBuffer buf(Option::OPTION4_HDR_LEN);
+ EXPECT_NO_THROW(optv4.pack(buf));
+
+ // Sanity check the length of the buffer.
+ ASSERT_EQ(Option::OPTION4_HDR_LEN + option_value.length(),
+ buf.getLength());
+ // Copy the contents of the OutputBuffer to InputBuffer because
+ // the latter has API to read data from it.
+ InputBuffer test_buf(buf.getData(), buf.getLength());
+ // First byte holds option code.
+ EXPECT_EQ(123, test_buf.readUint8());
+ // Second byte holds option length.
+ EXPECT_EQ(option_value.size(), test_buf.readUint8());
+ // Read the option data.
+ std::vector<uint8_t> data;
+ test_buf.readVector(data, test_buf.getLength() - test_buf.getPosition());
+ // And create a string from it.
+ std::string test_string(data.begin(), data.end());
+ // This string should be equal to the string used to create
+ // option's instance.
+ EXPECT_TRUE(option_value == test_string);
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_unittest.cc b/src/lib/dhcp/tests/option_unittest.cc
index 1fc49f1..237e73b 100644
--- a/src/lib/dhcp/tests/option_unittest.cc
+++ b/src/lib/dhcp/tests/option_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// 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,6 +20,7 @@
#include <util/buffer.h>
#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <iostream>
@@ -31,6 +32,7 @@ using namespace std;
using namespace isc;
using namespace isc::dhcp;
using namespace isc::util;
+using boost::scoped_ptr;
namespace {
class OptionTest : public ::testing::Test {
@@ -44,50 +46,27 @@ public:
OutputBuffer outBuf_;
};
-// v4 is not really implemented yet. A simple test will do for now
+// Basic tests for V4 functionality
TEST_F(OptionTest, v4_basic) {
- Option* opt = 0;
- EXPECT_NO_THROW(
- opt = new Option(Option::V4, 17);
- );
+ scoped_ptr<Option> opt;
+ EXPECT_NO_THROW(opt.reset(new Option(Option::V4, 17)));
EXPECT_EQ(Option::V4, opt->getUniverse());
EXPECT_EQ(17, opt->getType());
EXPECT_EQ(0, opt->getData().size());
EXPECT_EQ(2, opt->len()); // just v4 header
- EXPECT_NO_THROW(
- delete opt;
- );
- opt = 0;
+ EXPECT_NO_THROW(opt.reset());
// V4 options have type 0...255
- EXPECT_THROW(
- opt = new Option(Option::V4, 256),
- BadValue
- );
-
- delete opt;
- opt = 0;
+ EXPECT_THROW(opt.reset(new Option(Option::V4, 256)), BadValue);
// 0 is a special PAD option
- EXPECT_THROW(
- opt = new Option(Option::V4, 0),
- BadValue
- );
-
- delete opt;
- opt = 0;
+ EXPECT_THROW(opt.reset(new Option(Option::V4, 0)), BadValue);
// 255 is a special END option
- EXPECT_THROW(
- opt = new Option(Option::V4, 255),
- BadValue
- );
-
- delete opt;
- opt = 0;
+ EXPECT_THROW(opt.reset(new Option(Option::V4, 255)), BadValue);
}
const uint8_t dummyPayload[] =
@@ -97,15 +76,12 @@ TEST_F(OptionTest, v4_data1) {
vector<uint8_t> data(dummyPayload, dummyPayload + sizeof(dummyPayload));
- Option* opt = 0;
+ scoped_ptr<Option> opt;
- // create DHCPv4 option of type 123
- // that contains 4 bytes of data
- ASSERT_NO_THROW(
- opt= new Option(Option::V4, 123, data);
- );
+ // Create DHCPv4 option of type 123 that contains 4 bytes of data.
+ ASSERT_NO_THROW(opt.reset(new Option(Option::V4, 123, data)));
- // check that content is reported properly
+ // Check that content is reported properly
EXPECT_EQ(123, opt->getType());
vector<uint8_t> optData = opt->getData();
ASSERT_EQ(optData.size(), data.size());
@@ -113,31 +89,26 @@ TEST_F(OptionTest, v4_data1) {
EXPECT_EQ(2, opt->getHeaderLen());
EXPECT_EQ(6, opt->len());
- // now store that option into a buffer
+ // Now store that option into a buffer
OutputBuffer buf(100);
- EXPECT_NO_THROW(
- opt->pack(buf);
- );
-
- // check content of that buffer
+ EXPECT_NO_THROW(opt->pack(buf));
+ // Check content of that buffer:
// 2 byte header + 4 bytes data
ASSERT_EQ(6, buf.getLength());
- // that's how this option is supposed to look like
+ // That's how this option is supposed to look like
uint8_t exp[] = { 123, 4, 1, 2, 3, 4 };
/// TODO: use vector<uint8_t> getData() when it will be implemented
EXPECT_EQ(0, memcmp(exp, buf.getData(), 6));
- // check that we can destroy that option
- EXPECT_NO_THROW(
- delete opt;
- );
+ // Check that we can destroy that option
+ EXPECT_NO_THROW(opt.reset());
}
-// this is almost the same test as v4_data1, but it uses
-// different constructor
+// This is almost the same test as v4_data1, but it uses a different
+// constructor
TEST_F(OptionTest, v4_data2) {
vector<uint8_t> data(dummyPayload, dummyPayload + sizeof(dummyPayload));
@@ -153,16 +124,16 @@ TEST_F(OptionTest, v4_data2) {
// ignored, as we pass interators to proper data. Only subset (limited by
// iterators) of the vector should be used.
// expData contains expected content (just valid data, without garbage).
-
- Option* opt = 0;
+ scoped_ptr<Option> opt;
// Create DHCPv4 option of type 123 that contains
// 4 bytes (sizeof(dummyPayload).
ASSERT_NO_THROW(
- opt= new Option(Option::V4, 123, data.begin() + 1, data.end() - 1);
+ opt.reset(new Option(Option::V4, 123, data.begin() + 1,
+ data.end() - 1));
);
- // check that content is reported properly
+ // Check that content is reported properly
EXPECT_EQ(123, opt->getType());
vector<uint8_t> optData = opt->getData();
ASSERT_EQ(optData.size(), expData.size());
@@ -170,27 +141,23 @@ TEST_F(OptionTest, v4_data2) {
EXPECT_EQ(2, opt->getHeaderLen());
EXPECT_EQ(6, opt->len());
- // now store that option into a buffer
+ // Now store that option into a buffer
OutputBuffer buf(100);
- EXPECT_NO_THROW(
- opt->pack(buf);
- );
+ EXPECT_NO_THROW(opt->pack(buf));
- // check content of that buffer
+ // Check content of that buffer
// 2 byte header + 4 bytes data
ASSERT_EQ(6, buf.getLength());
- // that's how this option is supposed to look like
+ // That's how this option is supposed to look like
uint8_t exp[] = { 123, 4, 1, 2, 3, 4 };
/// TODO: use vector<uint8_t> getData() when it will be implemented
EXPECT_EQ(0, memcmp(exp, buf.getData(), 6));
- // check that we can destroy that option
- EXPECT_NO_THROW(
- delete opt;
- );
+ // Check that we can destroy that option
+ EXPECT_NO_THROW(opt.reset());
}
TEST_F(OptionTest, v4_toText) {
@@ -205,29 +172,29 @@ TEST_F(OptionTest, v4_toText) {
EXPECT_EQ("type=253, len=3: 00:0f:ff", opt.toText());
}
-// tests simple constructor
+// Tests simple constructor
TEST_F(OptionTest, v6_basic) {
- Option* opt = new Option(Option::V6, 1);
+ scoped_ptr<Option> opt(new Option(Option::V6, 1));
EXPECT_EQ(Option::V6, opt->getUniverse());
EXPECT_EQ(1, opt->getType());
EXPECT_EQ(0, opt->getData().size());
- EXPECT_EQ(4, opt->len()); // just v6 header
+ EXPECT_EQ(4, opt->len()); // Just v6 header
- EXPECT_NO_THROW(
- delete opt;
- );
+ EXPECT_NO_THROW(opt.reset());
}
-// tests contructor used in pkt reception
-// option contains actual data
+// Tests constructor used in packet reception. Option contains actual data
TEST_F(OptionTest, v6_data1) {
- for (int i = 0; i < 32; i++)
- buf_[i] = 100+i;
- Option* opt = new Option(Option::V6, 333, //type
- buf_.begin() + 3, // begin offset
- buf_.begin() + 10); // end offset (7 bytes of data)
+ for (int i = 0; i < 32; i++) {
+ buf_[i] = 100 + i;
+ }
+
+ // Create option with seven bytes of data.
+ scoped_ptr<Option> opt(new Option(Option::V6, 333, // Type
+ buf_.begin() + 3, // Begin offset
+ buf_.begin() + 10)); // End offset
EXPECT_EQ(333, opt->getType());
ASSERT_EQ(11, opt->len());
@@ -237,23 +204,20 @@ TEST_F(OptionTest, v6_data1) {
opt->pack(outBuf_);
EXPECT_EQ(11, outBuf_.getLength());
- const uint8_t* out = (const uint8_t*)outBuf_.getData();
- EXPECT_EQ(out[0], 333/256); // type
- EXPECT_EQ(out[1], 333%256);
+ const uint8_t* out = static_cast<const uint8_t*>(outBuf_.getData());
+ EXPECT_EQ(out[0], 333 / 256); // Type
+ EXPECT_EQ(out[1], 333 % 256);
- EXPECT_EQ(out[2], 0); // len
+ EXPECT_EQ(out[2], 0); // Length
EXPECT_EQ(out[3], 7);
- // payload
- EXPECT_EQ(0, memcmp(&buf_[3], out+4, 7) );
+ // Payload
+ EXPECT_EQ(0, memcmp(&buf_[3], out + 4, 7));
- EXPECT_NO_THROW(
- delete opt;
- );
+ EXPECT_NO_THROW(opt.reset());
}
-// another text that tests the same thing, just
-// with different input parameters
+// Another test that tests the same thing, just with different input parameters.
TEST_F(OptionTest, v6_data2) {
buf_[0] = 0xa1;
@@ -261,13 +225,11 @@ TEST_F(OptionTest, v6_data2) {
buf_[2] = 0xa3;
buf_[3] = 0xa4;
- // create an option (unpack content)
- Option* opt = new Option(Option::V6,
- D6O_CLIENTID,
- buf_.begin(),
- buf_.begin() + 4);
+ // Create an option (unpack content)
+ scoped_ptr<Option> opt(new Option(Option::V6, D6O_CLIENTID,
+ buf_.begin(), buf_.begin() + 4));
- // pack this option
+ // Pack this option
opt->pack(outBuf_);
// 4 bytes header + 4 bytes content
@@ -276,35 +238,35 @@ TEST_F(OptionTest, v6_data2) {
EXPECT_EQ(8, outBuf_.getLength());
- // check if pack worked properly:
- // if option type is correct
- const uint8_t* out = (const uint8_t*)outBuf_.getData();
+ // Check if pack worked properly:
+ // If option type is correct
+ const uint8_t* out = static_cast<const uint8_t*>(outBuf_.getData());
- EXPECT_EQ(D6O_CLIENTID, out[0]*256 + out[1]);
+ EXPECT_EQ(D6O_CLIENTID, out[0] * 256 + out[1]);
- // if option length is correct
- EXPECT_EQ(4, out[2]*256 + out[3]);
+ // If option length is correct
+ EXPECT_EQ(4, out[2] * 256 + out[3]);
- // if option content is correct
+ // If option content is correct
EXPECT_EQ(0, memcmp(&buf_[0], out + 4, 4));
- EXPECT_NO_THROW(
- delete opt;
- );
+ EXPECT_NO_THROW(opt.reset());
}
-// check that an option can contain 2 suboptions:
+// Check that an option can contain 2 suboptions:
// opt1
// +----opt2
// |
// +----opt3
//
TEST_F(OptionTest, v6_suboptions1) {
- for (int i=0; i<128; i++)
- buf_[i] = 100+i;
- Option* opt1 = new Option(Option::V6, 65535, //type
- buf_.begin(), // 3 bytes of data
- buf_.begin() + 3);
+ for (int i = 0; i < 128; i++) {
+ buf_[i] = 100 + i;
+ }
+
+ scoped_ptr<Option> opt1(new Option(Option::V6, 65535, // Type
+ buf_.begin(), // 3 bytes of data
+ buf_.begin() + 3));
OptionPtr opt2(new Option(Option::V6, 13));
OptionPtr opt3(new Option(Option::V6, 7,
buf_.begin() + 3,
@@ -328,30 +290,29 @@ TEST_F(OptionTest, v6_suboptions1) {
opt1->pack(outBuf_);
EXPECT_EQ(20, outBuf_.getLength());
- // payload
+ // Payload
EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 20) );
- EXPECT_NO_THROW(
- delete opt1;
- );
+ EXPECT_NO_THROW(opt1.reset());
}
-// check that an option can contain nested suboptions:
+// Check that an option can contain nested suboptions:
// opt1
// +----opt2
// |
// +----opt3
//
TEST_F(OptionTest, v6_suboptions2) {
- for (int i=0; i<128; i++)
- buf_[i] = 100+i;
- Option* opt1 = new Option(Option::V6, 65535, //type
- buf_.begin(),
- buf_.begin() + 3);
+ for (int i = 0; i < 128; i++) {
+ buf_[i] = 100 + i;
+ }
+
+ scoped_ptr<Option> opt1(new Option(Option::V6, 65535, // Type
+ buf_.begin(), buf_.begin() + 3));
OptionPtr opt2(new Option(Option::V6, 13));
OptionPtr opt3(new Option(Option::V6, 7,
- buf_.begin() + 3,
- buf_.begin() + 8));
+ buf_.begin() + 3,
+ buf_.begin() + 8));
opt1->addOption(opt2);
opt2->addOption(opt3);
// opt3 len = 9 4(header)+5(data)
@@ -367,18 +328,18 @@ TEST_F(OptionTest, v6_suboptions2) {
opt1->pack(outBuf_);
EXPECT_EQ(20, outBuf_.getLength());
- // payload
+ // Payload
EXPECT_EQ(0, memcmp(outBuf_.getData(), expected, 20) );
- EXPECT_NO_THROW(
- delete opt1;
- );
+ EXPECT_NO_THROW(opt1.reset());
}
TEST_F(OptionTest, v6_addgetdel) {
- for (int i=0; i<128; i++)
- buf_[i] = 100+i;
- Option* parent = new Option(Option::V6, 65535); //type
+ for (int i = 0; i < 128; i++) {
+ buf_[i] = 100 + i;
+ }
+
+ scoped_ptr<Option> parent(new Option(Option::V6, 65535)); // Type
OptionPtr opt1(new Option(Option::V6, 1));
OptionPtr opt2(new Option(Option::V6, 2));
OptionPtr opt3(new Option(Option::V6, 2));
@@ -390,28 +351,26 @@ TEST_F(OptionTest, v6_addgetdel) {
EXPECT_EQ(opt1, parent->getOption(1));
EXPECT_EQ(opt2, parent->getOption(2));
- // expect NULL
+ // Expect NULL
EXPECT_EQ(OptionPtr(), parent->getOption(4));
- // now there are 2 options of type 2
+ // Now there are 2 options of type 2
parent->addOption(opt3);
- // let's delete one of them
+ // Let's delete one of them
EXPECT_EQ(true, parent->delOption(2));
- // there still should be the other option 2
+ // There still should be the other option 2
EXPECT_NE(OptionPtr(), parent->getOption(2));
- // let's delete the other option 2
+ // Let's delete the other option 2
EXPECT_EQ(true, parent->delOption(2));
- // no more options with type=2
+ // No more options with type=2
EXPECT_EQ(OptionPtr(), parent->getOption(2));
- // let's try to delete - should fail
+ // Let's try to delete - should fail
EXPECT_TRUE(false == parent->delOption(2));
-
- delete parent;
}
TEST_F(OptionTest, v6_toText) {
@@ -419,8 +378,7 @@ TEST_F(OptionTest, v6_toText) {
buf_[1] = 0xf;
buf_[2] = 0xff;
- OptionPtr opt(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3 ));
-
+ OptionPtr opt(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3 ));
EXPECT_EQ("type=258, len=3: 00:0f:ff", opt->toText());
}
@@ -433,7 +391,7 @@ TEST_F(OptionTest, getUintX) {
buf_[3] = 0x2;
buf_[4] = 0x1;
- // five options with varying lengths
+ // Five options with varying lengths
OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1));
OptionPtr opt2(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
OptionPtr opt3(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3));
@@ -456,7 +414,7 @@ TEST_F(OptionTest, getUintX) {
EXPECT_EQ(0x0504, opt4->getUint16());
EXPECT_EQ(0x05040302, opt4->getUint32());
- // the same as for 4-byte long, just get first 1,2 or 4 bytes
+ // The same as for 4-byte long, just get first 1,2 or 4 bytes
EXPECT_EQ(5, opt5->getUint8());
EXPECT_EQ(0x0504, opt5->getUint16());
EXPECT_EQ(0x05040302, opt5->getUint32());
@@ -468,7 +426,7 @@ TEST_F(OptionTest, setUintX) {
OptionPtr opt2(new Option(Option::V4, 125));
OptionPtr opt4(new Option(Option::V4, 125));
- // verify setUint8
+ // Verify setUint8
opt1->setUint8(255);
EXPECT_EQ(255, opt1->getUint8());
opt1->pack(outBuf_);
@@ -477,7 +435,7 @@ TEST_F(OptionTest, setUintX) {
uint8_t exp1[] = {125, 1, 255};
EXPECT_TRUE(0 == memcmp(exp1, outBuf_.getData(), 3));
- // verify getUint16
+ // Verify getUint16
outBuf_.clear();
opt2->setUint16(12345);
opt2->pack(outBuf_);
@@ -487,7 +445,7 @@ TEST_F(OptionTest, setUintX) {
uint8_t exp2[] = {125, 2, 12345/256, 12345%256};
EXPECT_TRUE(0 == memcmp(exp2, outBuf_.getData(), 4));
- // verify getUint32
+ // Verify getUint32
outBuf_.clear();
opt4->setUint32(0x12345678);
opt4->pack(outBuf_);
@@ -499,8 +457,8 @@ TEST_F(OptionTest, setUintX) {
}
TEST_F(OptionTest, setData) {
- // verify data override with new buffer larger than
- // initial option buffer size
+ // Verify data override with new buffer larger than initial option buffer
+ // size.
OptionPtr opt1(new Option(Option::V4, 125,
buf_.begin(), buf_.begin() + 10));
buf_.resize(20, 1);
@@ -511,8 +469,8 @@ TEST_F(OptionTest, setData) {
EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(),
buf_.size()));
- // verify data override with new buffer shorter than
- // initial option buffer size
+ // Verify data override with new buffer shorter than initial option buffer
+ // size.
OptionPtr opt2(new Option(Option::V4, 125,
buf_.begin(), buf_.begin() + 10));
outBuf_.clear();
@@ -528,15 +486,15 @@ TEST_F(OptionTest, setData) {
// This test verifies that options can be compared using equal() method.
TEST_F(OptionTest, equal) {
- // five options with varying lengths
+ // Five options with varying lengths
OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1));
OptionPtr opt2(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
OptionPtr opt3(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3));
- // the same content as opt2, but different type
+ // The same content as opt2, but different type
OptionPtr opt4(new Option(Option::V6, 1, buf_.begin(), buf_.begin() + 2));
- // another instance with the same type and content as opt2
+ // Another instance with the same type and content as opt2
OptionPtr opt5(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
EXPECT_TRUE(opt1->equal(opt1));
diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc
index 49588e1..a108930 100644
--- a/src/lib/dhcp/tests/pkt4_unittest.cc
+++ b/src/lib/dhcp/tests/pkt4_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -16,6 +16,7 @@
#include <asiolink/io_address.h>
#include <dhcp/dhcp4.h>
+#include <dhcp/option_string.h>
#include <dhcp/pkt4.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
@@ -35,7 +36,7 @@ using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace isc::util;
-// don't import the entire boost namespace. It will unexpectedly hide uint8_t
+// Don't import the entire boost namespace. It will unexpectedly hide uint8_t
// for some systems.
using boost::scoped_ptr;
@@ -44,55 +45,39 @@ namespace {
TEST(Pkt4Test, constructor) {
ASSERT_EQ(236U, static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) );
- Pkt4* pkt = 0;
+ scoped_ptr<Pkt4> pkt;
// Just some dummy payload.
uint8_t testData[250];
for (int i = 0; i < 250; i++) {
- testData[i]=i;
+ testData[i] = i;
}
// Positive case1. Normal received packet.
- EXPECT_NO_THROW(
- pkt = new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN);
- );
+ EXPECT_NO_THROW(pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN)));
EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
- EXPECT_NO_THROW(
- delete pkt;
- pkt = 0;
- );
+ EXPECT_NO_THROW(pkt.reset());
// Positive case2. Normal outgoing packet.
- EXPECT_NO_THROW(
- pkt = new Pkt4(DHCPDISCOVER, 0xffffffff);
- );
+ EXPECT_NO_THROW(pkt.reset(new Pkt4(DHCPDISCOVER, 0xffffffff)));
// DHCPv4 packet must be at least 236 bytes long, with Message Type
// Option taking extra 3 bytes it is 239
EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
EXPECT_EQ(DHCPDISCOVER, pkt->getType());
EXPECT_EQ(0xffffffff, pkt->getTransid());
- EXPECT_NO_THROW(
- delete pkt;
- pkt = 0;
- );
+ EXPECT_NO_THROW(pkt.reset());
// Negative case. Should drop truncated messages.
EXPECT_THROW(
- pkt = new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN-1),
+ pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN - 1)),
OutOfRange
);
- if (pkt) {
- // Test failed. Exception should have been thrown, but
- // object was created instead. Let's clean this up.
- delete pkt;
- pkt = 0;
- }
}
-// a sample data
+// Sample data
const uint8_t dummyOp = BOOTREQUEST;
const uint8_t dummyHtype = 6;
const uint8_t dummyHlen = 6;
@@ -109,16 +94,16 @@ const IOAddress dummyGiaddr("255.255.255.255");
// a dummy MAC address
const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5};
-// a dummy MAC address, padded with 0s
+// A dummy MAC address, padded with 0s
const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 };
-// let's use some creative test content here (128 chars + \0)
+// Let's use some creative test content here (128 chars + \0)
const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur "
"adipiscing elit. Proin mollis placerat metus, at "
"lacinia orci ornare vitae. Mauris amet.";
-// yet another type of test content (64 chars + \0)
+// Yet another type of test content (64 chars + \0)
const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
"adipiscing elit posuere.";
@@ -127,12 +112,11 @@ BOOST_STATIC_ASSERT(sizeof(dummySname) == Pkt4::MAX_SNAME_LEN + 1);
/// @brief Generates test packet.
///
-/// Allocates and generates test packet, with all fixed
-/// fields set to non-zero values. Content is not always
-/// reasonable.
+/// Allocates and generates test packet, with all fixed fields set to non-zero
+/// values. Content is not always reasonable.
///
-/// See generateTestPacket2() function that returns
-/// exactly the same packet in on-wire format.
+/// See generateTestPacket2() function that returns exactly the same packet in
+/// on-wire format.
///
/// @return pointer to allocated Pkt4 object.
boost::shared_ptr<Pkt4>
@@ -162,12 +146,11 @@ generateTestPacket1() {
/// @brief Generates test packet.
///
-/// Allocates and generates on-wire buffer that represents
-/// test packet, with all fixed fields set to non-zero values.
-/// Content is not always reasonable.
+/// Allocates and generates on-wire buffer that represents test packet, with all
+/// fixed fields set to non-zero values. Content is not always reasonable.
///
-/// See generateTestPacket1() function that returns
-/// exactly the same packet as Pkt4 object.
+/// See generateTestPacket1() function that returns exactly the same packet as
+/// Pkt4 object.
///
/// @return pointer to allocated Pkt4 object
// Returns a vector containing a DHCPv4 packet header.
@@ -206,7 +189,7 @@ TEST(Pkt4Test, fixedFields) {
boost::shared_ptr<Pkt4> pkt = generateTestPacket1();
- // ok, let's check packet values
+ // OK, let's check packet values
EXPECT_EQ(dummyOp, pkt->getOp());
EXPECT_EQ(dummyHtype, pkt->getHtype());
EXPECT_EQ(dummyHlen, pkt->getHlen());
@@ -244,7 +227,7 @@ TEST(Pkt4Test, fixedFieldsPack) {
// DHCP Message Type Option
ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
- // redundant but MUCH easier for debug in gdb
+ // Redundant but MUCH easier for debug in gdb
const uint8_t* exp = &expectedFormat[0];
const uint8_t* got = static_cast<const uint8_t*>(pkt->getBuffer().getData());
@@ -272,7 +255,7 @@ TEST(Pkt4Test, fixedFieldsUnpack) {
pkt->unpack()
);
- // ok, let's check packet values
+ // OK, let's check packet values
EXPECT_EQ(dummyOp, pkt->getOp());
EXPECT_EQ(dummyHtype, pkt->getHtype());
EXPECT_EQ(dummyHlen, pkt->getHlen());
@@ -298,7 +281,7 @@ TEST(Pkt4Test, fixedFieldsUnpack) {
EXPECT_EQ(DHCPDISCOVER, pkt->getType());
}
-// this test is for hardware addresses (htype, hlen and chaddr fields)
+// This test is for hardware addresses (htype, hlen and chaddr fields)
TEST(Pkt4Test, hwAddr) {
vector<uint8_t> mac;
@@ -309,7 +292,7 @@ TEST(Pkt4Test, hwAddr) {
// (growing length back to MAX_CHADDR_LEN).
mac.resize(Pkt4::MAX_CHADDR_LEN);
- Pkt4* pkt = 0;
+ scoped_ptr<Pkt4> pkt;
// let's test each hlen, from 0 till 16
for (int macLen = 0; macLen < Pkt4::MAX_CHADDR_LEN; macLen++) {
for (int i = 0; i < Pkt4::MAX_CHADDR_LEN; i++) {
@@ -322,8 +305,8 @@ TEST(Pkt4Test, hwAddr) {
}
// type and transaction doesn't matter in this test
- pkt = new Pkt4(DHCPOFFER, 1234);
- pkt->setHWAddr(255-macLen*10, // just weird htype
+ pkt.reset(new Pkt4(DHCPOFFER, 1234));
+ pkt->setHWAddr(255 - macLen * 10, // just weird htype
macLen,
mac);
EXPECT_EQ(0, memcmp(expectedChaddr, &pkt->getHWAddr()->hwaddr_[0],
@@ -335,11 +318,11 @@ TEST(Pkt4Test, hwAddr) {
// CHADDR starts at offset 28 in DHCP packet
const uint8_t* ptr =
- static_cast<const uint8_t*>(pkt->getBuffer().getData())+28;
+ static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 28;
EXPECT_EQ(0, memcmp(ptr, expectedChaddr, Pkt4::MAX_CHADDR_LEN));
- delete pkt;
+ pkt.reset();
}
/// TODO: extend this test once options support is implemented. HW address
@@ -368,35 +351,28 @@ TEST(Pkt4Test, msgTypes) {
{DHCPLEASEACTIVE, BOOTREPLY}
};
- Pkt4* pkt = 0;
+ scoped_ptr<Pkt4> pkt;
for (int i = 0; i < sizeof(types) / sizeof(msgType); i++) {
-
- pkt = new Pkt4(types[i].dhcp, 0);
+ pkt.reset(new Pkt4(types[i].dhcp, 0));
EXPECT_EQ(types[i].dhcp, pkt->getType());
-
EXPECT_EQ(types[i].bootp, pkt->getOp());
-
- delete pkt;
- pkt = 0;
+ pkt.reset();
}
EXPECT_THROW(
- pkt = new Pkt4(100, 0), // there's no message type 100
+ pkt.reset(new Pkt4(100, 0)), // There's no message type 100
OutOfRange
);
- if (pkt) {
- delete pkt;
- }
}
-// this test verifies handling of sname field
+// This test verifies handling of sname field
TEST(Pkt4Test, sname) {
uint8_t sname[Pkt4::MAX_SNAME_LEN];
- Pkt4* pkt = 0;
- // let's test each sname length, from 0 till 64
- for (int snameLen=0; snameLen < Pkt4::MAX_SNAME_LEN; snameLen++) {
+ scoped_ptr<Pkt4> pkt;
+ // Let's test each sname length, from 0 till 64
+ for (int snameLen = 0; snameLen < Pkt4::MAX_SNAME_LEN; snameLen++) {
for (int i = 0; i < Pkt4::MAX_SNAME_LEN; i++) {
sname[i] = 0;
}
@@ -404,8 +380,8 @@ TEST(Pkt4Test, sname) {
sname[i] = i;
}
- // type and transaction doesn't matter in this test
- pkt = new Pkt4(DHCPOFFER, 1234);
+ // Type and transaction doesn't matter in this test
+ pkt.reset(new Pkt4(DHCPOFFER, 1234));
pkt->setSname(sname, snameLen);
EXPECT_EQ(0, memcmp(sname, &pkt->getSname()[0], Pkt4::MAX_SNAME_LEN));
@@ -416,10 +392,10 @@ TEST(Pkt4Test, sname) {
// SNAME starts at offset 44 in DHCP packet
const uint8_t* ptr =
- static_cast<const uint8_t*>(pkt->getBuffer().getData())+44;
+ static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 44;
EXPECT_EQ(0, memcmp(ptr, sname, Pkt4::MAX_SNAME_LEN));
- delete pkt;
+ pkt.reset();
}
// Check that a null argument generates an exception.
@@ -432,7 +408,7 @@ TEST(Pkt4Test, file) {
uint8_t file[Pkt4::MAX_FILE_LEN];
- Pkt4* pkt = 0;
+ scoped_ptr<Pkt4> pkt;
// Let's test each file length, from 0 till 128.
for (int fileLen = 0; fileLen < Pkt4::MAX_FILE_LEN; fileLen++) {
for (int i = 0; i < Pkt4::MAX_FILE_LEN; i++) {
@@ -443,22 +419,21 @@ TEST(Pkt4Test, file) {
}
// Type and transaction doesn't matter in this test.
- pkt = new Pkt4(DHCPOFFER, 1234);
+ pkt.reset(new Pkt4(DHCPOFFER, 1234));
pkt->setFile(file, fileLen);
EXPECT_EQ(0, memcmp(file, &pkt->getFile()[0], Pkt4::MAX_FILE_LEN));
- //
EXPECT_NO_THROW(
pkt->pack();
);
// FILE starts at offset 108 in DHCP packet.
const uint8_t* ptr =
- static_cast<const uint8_t*>(pkt->getBuffer().getData())+108;
+ static_cast<const uint8_t*>(pkt->getBuffer().getData()) + 108;
EXPECT_EQ(0, memcmp(ptr, file, Pkt4::MAX_FILE_LEN));
- delete pkt;
+ pkt.reset();
}
// Check that a null argument generates an exception.
@@ -481,13 +456,13 @@ static uint8_t v4Opts[] = {
};
TEST(Pkt4Test, options) {
- Pkt4* pkt = new Pkt4(DHCPOFFER, 0);
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 0));
vector<uint8_t> payload[5];
for (int i = 0; i < 5; i++) {
- payload[i].push_back(i*10);
- payload[i].push_back(i*10+1);
- payload[i].push_back(i*10+2);
+ payload[i].push_back(i * 10);
+ payload[i].push_back(i * 10 + 1);
+ payload[i].push_back(i * 10 + 2);
}
boost::shared_ptr<Option> opt1(new Option(Option::V4, 12, payload[0]));
@@ -509,7 +484,7 @@ TEST(Pkt4Test, options) {
EXPECT_TRUE(pkt->getOption(254));
EXPECT_FALSE(pkt->getOption(127)); // no such option
- // options are unique in DHCPv4. It should not be possible
+ // Options are unique in DHCPv4. It should not be possible
// to add more than one option of the same type.
EXPECT_THROW(
pkt->addOption(opt1),
@@ -521,26 +496,28 @@ TEST(Pkt4Test, options) {
);
const OutputBuffer& buf = pkt->getBuffer();
- // check that all options are stored, they should take sizeof(v4Opts),
+ // Check that all options are stored, they should take sizeof(v4Opts),
// DHCP magic cookie (4 bytes), and OPTION_END added (just one byte)
- ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + sizeof(DHCP_OPTIONS_COOKIE)
- + sizeof(v4Opts) + 1, buf.getLength());
+ ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) +
+ sizeof(DHCP_OPTIONS_COOKIE) + sizeof(v4Opts) + 1,
+ buf.getLength());
- // that that this extra data actually contain our options
+ // That that this extra data actually contain our options
const uint8_t* ptr = static_cast<const uint8_t*>(buf.getData());
- ptr += Pkt4::DHCPV4_PKT_HDR_LEN + sizeof(DHCP_OPTIONS_COOKIE); // rewind to end of fixed part
+
+ // Rewind to end of fixed part.
+ ptr += Pkt4::DHCPV4_PKT_HDR_LEN + sizeof(DHCP_OPTIONS_COOKIE);
+
EXPECT_EQ(0, memcmp(ptr, v4Opts, sizeof(v4Opts)));
EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4Opts))));
// delOption() checks
- EXPECT_TRUE(pkt->getOption(12)); // Sanity check: option 12 is still there
- EXPECT_TRUE(pkt->delOption(12)); // We should be able to remove it
+ EXPECT_TRUE(pkt->getOption(12)); // Sanity check: option 12 is still there
+ EXPECT_TRUE(pkt->delOption(12)); // We should be able to remove it
EXPECT_FALSE(pkt->getOption(12)); // It should not be there anymore
EXPECT_FALSE(pkt->delOption(12)); // And removal should fail
- EXPECT_NO_THROW(
- delete pkt;
- );
+ EXPECT_NO_THROW(pkt.reset());
}
TEST(Pkt4Test, unpackOptions) {
@@ -573,45 +550,53 @@ TEST(Pkt4Test, unpackOptions) {
boost::shared_ptr<Option> x = pkt->getOption(12);
ASSERT_TRUE(x); // option 1 should exist
- EXPECT_EQ(12, x->getType()); // this should be option 12
- ASSERT_EQ(3, x->getData().size()); // it should be of length 3
- EXPECT_EQ(5, x->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+2, 3)); // data len=3
+ // Option 12 is represented by the OptionString class so let's do
+ // the appropriate conversion.
+ OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x);
+ ASSERT_TRUE(option12);
+ EXPECT_EQ(12, option12->getType()); // this should be option 12
+ ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3
+ EXPECT_EQ(5, option12->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&option12->getValue()[0], v4Opts + 2, 3)); // data len=3
x = pkt->getOption(14);
- ASSERT_TRUE(x); // option 13 should exist
- EXPECT_EQ(14, x->getType()); // this should be option 13
- ASSERT_EQ(3, x->getData().size()); // it should be of length 3
- EXPECT_EQ(5, x->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+7, 3)); // data len=3
+ ASSERT_TRUE(x); // option 14 should exist
+ // Option 14 is represented by the OptionString class so let's do
+ // the appropriate conversion.
+ OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x);
+ ASSERT_TRUE(option14);
+ EXPECT_EQ(14, option14->getType()); // this should be option 14
+ ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3
+ EXPECT_EQ(5, option14->len()); // total option length 5
+ EXPECT_EQ(0, memcmp(&option14->getValue()[0], v4Opts + 7, 3)); // data len=3
x = pkt->getOption(60);
ASSERT_TRUE(x); // option 60 should exist
EXPECT_EQ(60, x->getType()); // this should be option 60
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+15, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 15, 3)); // data len=3
x = pkt->getOption(128);
ASSERT_TRUE(x); // option 3 should exist
EXPECT_EQ(128, x->getType()); // this should be option 254
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+20, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 20, 3)); // data len=3
x = pkt->getOption(254);
ASSERT_TRUE(x); // option 3 should exist
EXPECT_EQ(254, x->getType()); // this should be option 254
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+25, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 25, 3)); // data len=3
}
// This test verifies methods that are used for manipulating meta fields
// i.e. fields that are not part of DHCPv4 (e.g. interface name).
TEST(Pkt4Test, metaFields) {
- Pkt4* pkt = new Pkt4(DHCPOFFER, 1234);
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
pkt->setIface("loooopback");
pkt->setIndex(42);
pkt->setRemoteAddr(IOAddress("1.2.3.4"));
@@ -621,8 +606,6 @@ TEST(Pkt4Test, metaFields) {
EXPECT_EQ(42, pkt->getIndex());
EXPECT_EQ("1.2.3.4", pkt->getRemoteAddr().toText());
EXPECT_EQ("4.3.2.1", pkt->getLocalAddr().toText());
-
- delete pkt;
}
TEST(Pkt4Test, Timestamp) {
@@ -670,4 +653,49 @@ TEST(Pkt4Test, hwaddr) {
EXPECT_TRUE(hwaddr == pkt->getHWAddr());
}
+// This test verifies that the packet remte and local HW address can
+// be set and returned.
+TEST(Pkt4Test, hwaddrSrcRemote) {
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+ const uint8_t src_hw[] = { 1, 2, 3, 4, 5, 6 };
+ const uint8_t dst_hw[] = { 7, 8, 9, 10, 11, 12 };
+ const uint8_t hw_type = 123;
+
+ HWAddrPtr dst_hwaddr(new HWAddr(dst_hw, sizeof(src_hw), hw_type));
+ HWAddrPtr src_hwaddr(new HWAddr(src_hw, sizeof(src_hw), hw_type));
+
+ // Check that we can set the local address.
+ EXPECT_NO_THROW(pkt->setLocalHWAddr(dst_hwaddr));
+ EXPECT_TRUE(dst_hwaddr == pkt->getLocalHWAddr());
+
+ // Check that we can set the remote address.
+ EXPECT_NO_THROW(pkt->setRemoteHWAddr(src_hwaddr));
+ EXPECT_TRUE(src_hwaddr == pkt->getRemoteHWAddr());
+
+ // Can't set the NULL addres.
+ EXPECT_THROW(pkt->setRemoteHWAddr(HWAddrPtr()), BadValue);
+ EXPECT_THROW(pkt->setLocalHWAddr(HWAddrPtr()), BadValue);
+
+ // Test alternative way to set local address.
+ const uint8_t dst_hw2[] = { 19, 20, 21, 22, 23, 24 };
+ std::vector<uint8_t> dst_hw_vec(dst_hw2, dst_hw2 + sizeof(dst_hw2));
+ const uint8_t hw_type2 = 234;
+ EXPECT_NO_THROW(pkt->setLocalHWAddr(hw_type2, sizeof(dst_hw2), dst_hw_vec));
+ HWAddrPtr local_addr = pkt->getLocalHWAddr();
+ ASSERT_TRUE(local_addr);
+ EXPECT_EQ(hw_type2, local_addr->htype_);
+ EXPECT_TRUE(std::equal(dst_hw_vec.begin(), dst_hw_vec.end(),
+ local_addr->hwaddr_.begin()));
+
+ // Set remote address.
+ const uint8_t src_hw2[] = { 25, 26, 27, 28, 29, 30 };
+ std::vector<uint8_t> src_hw_vec(src_hw2, src_hw2 + sizeof(src_hw2));
+ EXPECT_NO_THROW(pkt->setRemoteHWAddr(hw_type2, sizeof(src_hw2), src_hw_vec));
+ HWAddrPtr remote_addr = pkt->getRemoteHWAddr();
+ ASSERT_TRUE(remote_addr);
+ EXPECT_EQ(hw_type2, remote_addr->htype_);
+ EXPECT_TRUE(std::equal(src_hw_vec.begin(), src_hw_vec.end(),
+ remote_addr->hwaddr_.begin()));
+}
+
} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc
index cdaad3b..138148a 100644
--- a/src/lib/dhcp/tests/pkt6_unittest.cc
+++ b/src/lib/dhcp/tests/pkt6_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -17,9 +17,16 @@
#include <asiolink/io_address.h>
#include <dhcp/dhcp6.h>
#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
#include <dhcp/pkt6.h>
+#include <util/range_utilities.h>
#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <util/encode/hex.h>
#include <gtest/gtest.h>
#include <iostream>
@@ -31,6 +38,7 @@ using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
+using boost::scoped_ptr;
namespace {
// empty class for now, but may be extended once Addr6 becomes bigger
@@ -38,16 +46,26 @@ class Pkt6Test : public ::testing::Test {
public:
Pkt6Test() {
}
+
+ /// @brief generates an option with given code (and length) and random content
+ ///
+ /// @param code option code
+ /// @param len data length (data will be randomized)
+ ///
+ /// @return pointer to the new option
+ OptionPtr generateRandomOption(uint16_t code, size_t len = 10) {
+ OptionBuffer data(len);
+ util::fillRandom(data.begin(), data.end());
+ return OptionPtr(new Option(Option::V6, code, data));
+ }
};
TEST_F(Pkt6Test, constructor) {
uint8_t data[] = { 0, 1, 2, 3, 4, 5 };
- Pkt6 * pkt1 = new Pkt6(data, sizeof(data) );
-
- EXPECT_EQ(6, pkt1->getData().size());
- EXPECT_EQ(0, memcmp( &pkt1->getData()[0], data, sizeof(data)));
+ scoped_ptr<Pkt6> pkt1(new Pkt6(data, sizeof(data)));
- delete pkt1;
+ EXPECT_EQ(6, pkt1->data_.size());
+ EXPECT_EQ(0, memcmp( &pkt1->data_[0], data, sizeof(data)));
}
/// @brief returns captured actual SOLICIT packet
@@ -99,41 +117,99 @@ Pkt6* capture1() {
return (pkt);
}
+/// @brief creates doubly relayed solicit message
+///
+/// This is a traffic capture exported from wireshark. It includes a SOLICIT
+/// message that passed through two relays. Each relay include interface-id,
+/// remote-id and relay-forw encapsulation. It is especially interesting,
+/// because of the following properties:
+/// - double encapsulation
+/// - first relay inserts relay-msg before extra options
+/// - second relay inserts relay-msg after extra options
+/// - both relays are from different vendors
+/// - interface-id are different for each relay
+/// - first relay inserts valid remote-id
+/// - second relay inserts remote-id with empty vendor data
+/// - the solicit message requests for custom options in ORO
+/// - there are option types in RELAY-FORW that do not appear in SOLICIT
+/// - there are option types in SOLICT that do not appear in RELAY-FORW
+///
+/// RELAY-FORW
+/// - relay message option
+/// - RELAY-FORW
+/// - interface-id option
+/// - remote-id option
+/// - RELAY-FORW
+/// SOLICIT
+/// - client-id option
+/// - ia_na option
+/// - elapsed time
+/// - ORO
+/// - interface-id option
+/// - remote-id option
+///
+/// The original capture was posted to dibbler users mailing list.
+///
+/// @return created double relayed SOLICIT message
+Pkt6* capture2() {
+
+ // string exported from Wireshark
+ string hex_string =
+ "0c01200108880db800010000000000000000fe80000000000000020021fffe5c18a900"
+ "09007d0c0000000000000000000000000000000000fe80000000000000020021fffe5c"
+ "18a9001200154953414d3134342065746820312f312f30352f30310025000400000de9"
+ "00090036016b4fe20001000e0001000118b033410000215c18a90003000c00000001ff"
+ "ffffffffffffff00080002000000060006001700f200f30012001c4953414d3134347c"
+ "3239397c697076367c6e743a76703a313a313130002500120000197f0001000118b033"
+ "410000215c18a9";
+
+ std::vector<uint8_t> bin;
+
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(hex_string, bin);
+
+ Pkt6* pkt = new Pkt6(&bin[0], bin.size());
+ pkt->setRemotePort(547);
+ pkt->setRemoteAddr(IOAddress("fe80::1234"));
+ pkt->setLocalPort(547);
+ pkt->setLocalAddr(IOAddress("ff05::1:3"));
+ pkt->setIndex(2);
+ pkt->setIface("eth0");
+ return (pkt);
+}
TEST_F(Pkt6Test, unpack_solicit1) {
- Pkt6* sol = capture1();
+ scoped_ptr<Pkt6> sol(capture1());
ASSERT_EQ(true, sol->unpack());
- // check for length
+ // Check for length
EXPECT_EQ(98, sol->len() );
- // check for type
+ // Check for type
EXPECT_EQ(DHCPV6_SOLICIT, sol->getType() );
- // check that all present options are returned
+ // Check that all present options are returned
EXPECT_TRUE(sol->getOption(D6O_CLIENTID)); // client-id is present
EXPECT_TRUE(sol->getOption(D6O_IA_NA)); // IA_NA is present
EXPECT_TRUE(sol->getOption(D6O_ELAPSED_TIME)); // elapsed is present
EXPECT_TRUE(sol->getOption(D6O_NAME_SERVERS));
EXPECT_TRUE(sol->getOption(D6O_ORO));
- // let's check that non-present options are not returned
+ // Let's check that non-present options are not returned
EXPECT_FALSE(sol->getOption(D6O_SERVERID)); // server-id is missing
EXPECT_FALSE(sol->getOption(D6O_IA_TA));
EXPECT_FALSE(sol->getOption(D6O_IAADDR));
-
- delete sol;
}
TEST_F(Pkt6Test, packUnpack) {
-
- Pkt6* parent = new Pkt6(DHCPV6_SOLICIT, 0x020304);
+ scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_SOLICIT, 0x020304));
OptionPtr opt1(new Option(Option::V6, 1));
OptionPtr opt2(new Option(Option::V6, 2));
OptionPtr opt3(new Option(Option::V6, 100));
- // let's not use zero-length option type 3 as it is IA_NA
+ // Let's not use zero-length option type 3 as it is IA_NA
parent->addOption(opt1);
parent->addOption(opt2);
@@ -141,39 +217,37 @@ TEST_F(Pkt6Test, packUnpack) {
EXPECT_EQ(DHCPV6_SOLICIT, parent->getType());
- // calculated length should be 16
+ // Calculated length should be 16
EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
parent->len());
- EXPECT_TRUE(parent->pack());
+ EXPECT_NO_THROW(parent->pack());
EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
parent->len());
- // create second packet,based on assembled data from the first one
- Pkt6* clone = new Pkt6(static_cast<const uint8_t*>(parent->getBuffer().getData()),
- parent->getBuffer().getLength());
+ // Create second packet,based on assembled data from the first one
+ scoped_ptr<Pkt6> clone(new Pkt6(
+ static_cast<const uint8_t*>(parent->getBuffer().getData()),
+ parent->getBuffer().getLength()));
- // now recreate options list
+ // Now recreate options list
EXPECT_TRUE( clone->unpack() );
// transid, message-type should be the same as before
EXPECT_EQ(parent->getTransid(), parent->getTransid());
EXPECT_EQ(DHCPV6_SOLICIT, clone->getType());
- EXPECT_TRUE( clone->getOption(1));
- EXPECT_TRUE( clone->getOption(2));
- EXPECT_TRUE( clone->getOption(100));
- EXPECT_FALSE( clone->getOption(4));
-
- delete parent;
- delete clone;
+ EXPECT_TRUE(clone->getOption(1));
+ EXPECT_TRUE(clone->getOption(2));
+ EXPECT_TRUE(clone->getOption(100));
+ EXPECT_FALSE(clone->getOption(4));
}
// This test verifies that options can be added (addOption()), retrieved
// (getOption(), getOptions()) and deleted (delOption()).
TEST_F(Pkt6Test, addGetDelOptions) {
- Pkt6* parent = new Pkt6(DHCPV6_SOLICIT, random() );
+ scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_SOLICIT, random()));
OptionPtr opt1(new Option(Option::V6, 1));
OptionPtr opt2(new Option(Option::V6, 2));
@@ -186,16 +260,16 @@ TEST_F(Pkt6Test, addGetDelOptions) {
EXPECT_EQ(opt1, parent->getOption(1));
EXPECT_EQ(opt2, parent->getOption(2));
- // expect NULL
+ // Expect NULL
EXPECT_EQ(OptionPtr(), parent->getOption(4));
- // now there are 2 options of type 2
+ // Now there are 2 options of type 2
parent->addOption(opt3);
Option::OptionCollection options = parent->getOptions(2);
EXPECT_EQ(2, options.size()); // there should be 2 instances
- // both options must be of type 2 and there must not be
+ // Both options must be of type 2 and there must not be
// any other type returned
for (Option::OptionCollection::const_iterator x= options.begin();
x != options.end(); ++x) {
@@ -211,26 +285,24 @@ TEST_F(Pkt6Test, addGetDelOptions) {
EXPECT_EQ(1, (*options.begin()).second->getType());
EXPECT_EQ(opt1, options.begin()->second);
- // let's delete one of them
+ // Let's delete one of them
EXPECT_EQ(true, parent->delOption(2));
- // there still should be the other option 2
+ // There still should be the other option 2
EXPECT_NE(OptionPtr(), parent->getOption(2));
- // let's delete the other option 2
+ // Let's delete the other option 2
EXPECT_EQ(true, parent->delOption(2));
- // no more options with type=2
+ // No more options with type=2
EXPECT_EQ(OptionPtr(), parent->getOption(2));
- // let's try to delete - should fail
+ // Let's try to delete - should fail
EXPECT_TRUE(false == parent->delOption(2));
// Finally try to get a non-existent option
options = parent->getOptions(1234);
EXPECT_EQ(0, options.size());
-
- delete parent;
}
TEST_F(Pkt6Test, Timestamp) {
@@ -306,5 +378,292 @@ TEST_F(Pkt6Test, getName) {
}
}
+// This test verifies that a fancy solicit that passed through two
+// relays can be parsed properly. See capture2() method description
+// for details regarding the packet.
+TEST_F(Pkt6Test, relayUnpack) {
+ boost::scoped_ptr<Pkt6> msg(capture2());
+
+ EXPECT_NO_THROW(msg->unpack());
+
+ EXPECT_EQ(DHCPV6_SOLICIT, msg->getType());
+ EXPECT_EQ(217, msg->len());
+
+ ASSERT_EQ(2, msg->relay_info_.size());
+
+ OptionPtr opt;
+
+ // Part 1: Check options inserted by the first relay
+
+ // There should be 2 options in first relay
+ EXPECT_EQ(2, msg->relay_info_[0].options_.size());
+
+ // There should be interface-id option
+ ASSERT_TRUE(opt = msg->getRelayOption(D6O_INTERFACE_ID, 0));
+ OptionBuffer data = opt->getData();
+ EXPECT_EQ(32, opt->len()); // 28 bytes of data + 4 bytes header
+ EXPECT_EQ(data.size(), 28);
+ // That's a strange interface-id, but this is a real life example
+ EXPECT_TRUE(0 == memcmp("ISAM144|299|ipv6|nt:vp:1:110", &data[0], 28));
+
+ // Get the remote-id option
+ ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 0));
+ EXPECT_EQ(22, opt->len()); // 18 bytes of data + 4 bytes header
+ boost::shared_ptr<OptionCustom> custom = boost::dynamic_pointer_cast<OptionCustom>(opt);
+
+ uint32_t vendor_id = custom->readInteger<uint32_t>(0);
+ EXPECT_EQ(6527, vendor_id); // 6527 = Panthera Networks
+
+ uint8_t expected_remote_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0, 0x33, 0x41, 0x00,
+ 0x00, 0x21, 0x5c, 0x18, 0xa9 };
+ OptionBuffer remote_id = custom->readBinary(1);
+ ASSERT_EQ(sizeof(expected_remote_id), remote_id.size());
+ ASSERT_EQ(0, memcmp(expected_remote_id, &remote_id[0], remote_id.size()));
+
+ // Part 2: Check options inserted by the second relay
+
+ // Get the interface-id from the second relay
+ ASSERT_TRUE(opt = msg->getRelayOption(D6O_INTERFACE_ID, 1));
+ data = opt->getData();
+ EXPECT_EQ(25, opt->len()); // 21 bytes + 4 bytes header
+ EXPECT_EQ(data.size(), 21);
+ EXPECT_TRUE(0 == memcmp("ISAM144 eth 1/1/05/01", &data[0], 21));
+
+ // Get the remote-id option
+ ASSERT_TRUE(opt = msg->getRelayOption(D6O_REMOTE_ID, 1));
+ EXPECT_EQ(8, opt->len());
+ custom = boost::dynamic_pointer_cast<OptionCustom>(opt);
+
+ vendor_id = custom->readInteger<uint32_t>(0);
+ EXPECT_EQ(3561, vendor_id); // 3561 = Broadband Forum
+ // @todo: See if we can validate empty remote-id field
+
+ // Let's check if there is no leak between options stored in
+ // the SOLICIT message and the relay.
+ EXPECT_FALSE(opt = msg->getRelayOption(D6O_IA_NA, 1));
+
+
+ // Part 3: Let's check options in the message itself
+ // This is not redundant compared to other direct messages tests,
+ // as we parsed it differently
+ EXPECT_EQ(DHCPV6_SOLICIT, msg->getType());
+ EXPECT_EQ(0x6b4fe2, msg->getTransid());
+
+ ASSERT_TRUE(opt = msg->getOption(D6O_CLIENTID));
+ EXPECT_EQ(18, opt->len()); // 14 bytes of data + 4 bytes of header
+ uint8_t expected_client_id[] = { 0x00, 0x01, 0x00, 0x01, 0x18, 0xb0, 0x33, 0x41, 0x00,
+ 0x00, 0x21, 0x5c, 0x18, 0xa9 };
+ data = opt->getData();
+ ASSERT_EQ(data.size(), sizeof(expected_client_id));
+ ASSERT_EQ(0, memcmp(&data[0], expected_client_id, data.size()));
+
+ ASSERT_TRUE(opt = msg->getOption(D6O_IA_NA));
+ boost::shared_ptr<Option6IA> ia =
+ boost::dynamic_pointer_cast<Option6IA>(opt);
+ ASSERT_TRUE(ia);
+ EXPECT_EQ(1, ia->getIAID());
+ EXPECT_EQ(0xffffffff, ia->getT1());
+ EXPECT_EQ(0xffffffff, ia->getT2());
+
+ ASSERT_TRUE(opt = msg->getOption(D6O_ELAPSED_TIME));
+ EXPECT_EQ(6, opt->len()); // 2 bytes of data + 4 bytes of header
+ boost::shared_ptr<OptionInt<uint16_t> > elapsed =
+ boost::dynamic_pointer_cast<OptionInt<uint16_t> > (opt);
+ ASSERT_TRUE(elapsed);
+ EXPECT_EQ(0, elapsed->getValue());
+
+ ASSERT_TRUE(opt = msg->getOption(D6O_ORO));
+ boost::shared_ptr<OptionIntArray<uint16_t> > oro =
+ boost::dynamic_pointer_cast<OptionIntArray<uint16_t> > (opt);
+ const std::vector<uint16_t> oro_list = oro->getValues();
+ EXPECT_EQ(3, oro_list.size());
+ EXPECT_EQ(23, oro_list[0]);
+ EXPECT_EQ(242, oro_list[1]);
+ EXPECT_EQ(243, oro_list[2]);
+}
+
+// This test verified that message with relay information can be
+// packed and then unpacked.
+TEST_F(Pkt6Test, relayPack) {
+
+ scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_ADVERTISE, 0x020304));
+
+ Pkt6::RelayInfo relay1;
+ relay1.msg_type_ = DHCPV6_RELAY_REPL;
+ relay1.hop_count_ = 17; // not very miningful, but useful for testing
+ relay1.linkaddr_ = IOAddress("2001:db8::1");
+ relay1.peeraddr_ = IOAddress("fe80::abcd");
+
+ uint8_t relay_opt_data[] = { 1, 2, 3, 4, 5, 6, 7, 8};
+ vector<uint8_t> relay_data(relay_opt_data,
+ relay_opt_data + sizeof(relay_opt_data));
+
+ OptionPtr optRelay1(new Option(Option::V6, 200, relay_data));
+
+ relay1.options_.insert(make_pair(optRelay1->getType(), optRelay1));
+
+ OptionPtr opt1(new Option(Option::V6, 100));
+ OptionPtr opt2(new Option(Option::V6, 101));
+ OptionPtr opt3(new Option(Option::V6, 102));
+ // Let's not use zero-length option type 3 as it is IA_NA
+
+ parent->addRelayInfo(relay1);
+
+ parent->addOption(opt1);
+ parent->addOption(opt2);
+ parent->addOption(opt3);
+
+ EXPECT_EQ(DHCPV6_ADVERTISE, parent->getType());
+
+ EXPECT_NO_THROW(parent->pack());
+
+ EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN // ADVERTISE
+ + Pkt6::DHCPV6_RELAY_HDR_LEN // Relay header
+ + Option::OPTION6_HDR_LEN // Relay-msg
+ + optRelay1->len(),
+ parent->len());
+
+ // Create second packet,based on assembled data from the first one
+ scoped_ptr<Pkt6> clone(new Pkt6(static_cast<const uint8_t*>(
+ parent->getBuffer().getData()),
+ parent->getBuffer().getLength()));
+
+ // Now recreate options list
+ EXPECT_TRUE( clone->unpack() );
+
+ // transid, message-type should be the same as before
+ EXPECT_EQ(parent->getTransid(), parent->getTransid());
+ EXPECT_EQ(DHCPV6_ADVERTISE, clone->getType());
+
+ EXPECT_TRUE( clone->getOption(100));
+ EXPECT_TRUE( clone->getOption(101));
+ EXPECT_TRUE( clone->getOption(102));
+ EXPECT_FALSE(clone->getOption(103));
+
+ // Now check relay info
+ ASSERT_EQ(1, clone->relay_info_.size());
+ EXPECT_EQ(DHCPV6_RELAY_REPL, clone->relay_info_[0].msg_type_);
+ EXPECT_EQ(17, clone->relay_info_[0].hop_count_);
+ EXPECT_EQ("2001:db8::1", clone->relay_info_[0].linkaddr_.toText());
+ EXPECT_EQ("fe80::abcd", clone->relay_info_[0].peeraddr_.toText());
+
+ // There should be exactly one option
+ EXPECT_EQ(1, clone->relay_info_[0].options_.size());
+ OptionPtr opt = clone->getRelayOption(200, 0);
+ EXPECT_TRUE(opt);
+ EXPECT_EQ(opt->getType() , optRelay1->getType());
+ EXPECT_EQ(opt->len(), optRelay1->len());
+ OptionBuffer data = opt->getData();
+ ASSERT_EQ(data.size(), sizeof(relay_opt_data));
+ EXPECT_EQ(0, memcmp(relay_opt_data, relay_opt_data, sizeof(relay_opt_data)));
+}
+
+
+// This test verified that options added by relays to the message can be
+// accessed and retrieved properly
+TEST_F(Pkt6Test, getAnyRelayOption) {
+
+ boost::scoped_ptr<Pkt6> msg(new Pkt6(DHCPV6_ADVERTISE, 0x020304));
+ msg->addOption(generateRandomOption(300));
+
+ // generate options for relay1
+ Pkt6::RelayInfo relay1;
+
+ // generate 3 options with code 200,201,202 and random content
+ OptionPtr relay1_opt1(generateRandomOption(200));
+ OptionPtr relay1_opt2(generateRandomOption(201));
+ OptionPtr relay1_opt3(generateRandomOption(202));
+
+ relay1.options_.insert(make_pair(200, relay1_opt1));
+ relay1.options_.insert(make_pair(201, relay1_opt2));
+ relay1.options_.insert(make_pair(202, relay1_opt3));
+ msg->addRelayInfo(relay1);
+
+ // generate options for relay2
+ Pkt6::RelayInfo relay2;
+ OptionPtr relay2_opt1(new Option(Option::V6, 100));
+ OptionPtr relay2_opt2(new Option(Option::V6, 101));
+ OptionPtr relay2_opt3(new Option(Option::V6, 102));
+ OptionPtr relay2_opt4(new Option(Option::V6, 200)); // the same code as relay1_opt3
+ relay2.options_.insert(make_pair(100, relay2_opt1));
+ relay2.options_.insert(make_pair(101, relay2_opt2));
+ relay2.options_.insert(make_pair(102, relay2_opt3));
+ relay2.options_.insert(make_pair(200, relay2_opt4));
+ msg->addRelayInfo(relay2);
+
+ // generate options for relay3
+ Pkt6::RelayInfo relay3;
+ OptionPtr relay3_opt1(generateRandomOption(200, 7));
+ relay3.options_.insert(make_pair(200, relay3_opt1));
+ msg->addRelayInfo(relay3);
+
+ // Ok, so we now have a packet that traversed the following network:
+ // client---relay3---relay2---relay1---server
+
+ // First check that the getAnyRelayOption does not confuse client options
+ // and relay options
+ // 300 is a client option, present in the message itself.
+ OptionPtr opt = msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ EXPECT_FALSE(opt);
+ opt = msg->getAnyRelayOption(300, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ EXPECT_FALSE(opt);
+ opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_FIRST);
+ EXPECT_FALSE(opt);
+ opt = msg->getAnyRelayOption(300, Pkt6::RELAY_GET_LAST);
+ EXPECT_FALSE(opt);
+
+ // Option 200 is added in every relay.
+
+ // We want to get that one inserted by relay3 (first match, starting from
+ // closest to the client.
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equal(relay3_opt1));
+
+ // We want to ge that one inserted by relay1 (first match, starting from
+ // closest to the server.
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equal(relay1_opt1));
+
+ // We just want option from the first relay (closest to the client)
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_FIRST);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equal(relay3_opt1));
+
+ // We just want option from the last relay (closest to the server)
+ opt = msg->getAnyRelayOption(200, Pkt6::RELAY_GET_LAST);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equal(relay1_opt1));
+
+ // Let's try to ask for something that is inserted by the middle relay
+ // only.
+ opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equal(relay2_opt1));
+
+ opt = msg->getAnyRelayOption(100, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ ASSERT_TRUE(opt);
+ EXPECT_TRUE(opt->equal(relay2_opt1));
+
+ opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_FIRST);
+ EXPECT_FALSE(opt);
+
+ opt = msg->getAnyRelayOption(100, Pkt6::RELAY_GET_LAST);
+ EXPECT_FALSE(opt);
+
+ // Finally, try to get an option that does not exist
+ opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_FIRST);
+ EXPECT_FALSE(opt);
+
+ opt = msg->getAnyRelayOption(500, Pkt6::RELAY_GET_LAST);
+ EXPECT_FALSE(opt);
+
+ opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_SERVER);
+ EXPECT_FALSE(opt);
+
+ opt = msg->getAnyRelayOption(500, Pkt6::RELAY_SEARCH_FROM_CLIENT);
+ EXPECT_FALSE(opt);
+}
}
diff --git a/src/lib/dhcp/tests/pkt_filter_inet_unittest.cc b/src/lib/dhcp/tests/pkt_filter_inet_unittest.cc
new file mode 100644
index 0000000..eaf2e62
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter_inet_unittest.cc
@@ -0,0 +1,283 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_inet.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/socket.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+/// Port number used by tests.
+const uint16_t PORT = 10067;
+/// Size of the buffer holding received packets.
+const size_t RECV_BUF_SIZE = 2048;
+
+/// This class handles has simple algorithm checking
+/// presence of loopback interface and initializing
+/// its index.
+class PktFilterInetTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ ///
+ /// This constructor initializes socket_ member to a negative value.
+ /// Explcit initialization is performed here because some of the
+ /// tests do not initialize this value. In such cases, destructor
+ /// could invoke close() on uninitialized socket descriptor which
+ /// would result in errors being reported by Valgrind.
+ PktFilterInetTest()
+ : socket_(-1) {
+ // Initialize ifname_ and ifindex_.
+ loInit();
+ }
+
+ /// @brief Destructor
+ ///
+ /// Closes open socket (if any).
+ ~PktFilterInetTest() {
+ // Cleanup after each test. This guarantees
+ // that the socket does not hang after a test.
+ if (socket_ >= 0) {
+ close(socket_);
+ }
+ }
+
+ /// @brief Detect loopback interface.
+ ///
+ /// @todo this function will be removed once cross-OS interface
+ /// detection is implemented
+ void loInit() {
+ if (if_nametoindex("lo") > 0) {
+ ifname_ = "lo";
+ ifindex_ = if_nametoindex("lo");
+
+ } else if (if_nametoindex("lo0") > 0) {
+ ifname_ = "lo0";
+ ifindex_ = if_nametoindex("lo0");
+
+ } else {
+ std::cout << "Failed to detect loopback interface. Neither "
+ << "lo nor lo0 worked. Giving up." << std::endl;
+ FAIL();
+
+
+
+ }
+ }
+
+ std::string ifname_; ///< Loopback interface name
+ uint16_t ifindex_; ///< Loopback interface index.
+ int socket_; ///< Socket descriptor.
+
+};
+
+// This test verifies that the PktFilterInet class reports its lack
+// of capability to send packets to the host having no IP address
+// assigned.
+TEST_F(PktFilterInetTest, isDirectResponseSupported) {
+ // Create object under test.
+ PktFilterInet pkt_filter;
+ // This Packet Filter class does not support direct responses
+ // under any conditions.
+ EXPECT_FALSE(pkt_filter.isDirectResponseSupported());
+}
+
+// This test verifies that the INET datagram socket is correctly opened and
+// bound to the appropriate address and port.
+TEST_F(PktFilterInetTest, openSocket) {
+ // Create object representing loopback interface.
+ Iface iface(ifname_, ifindex_);
+ // Set loopback address.
+ IOAddress addr("127.0.0.1");
+
+ // Try to open socket.
+ PktFilterInet pkt_filter;
+ socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ // Check that socket has been opened.
+ ASSERT_GE(socket_, 0);
+
+ // Verify that the socket belongs to AF_INET family.
+ sockaddr_in sock_address;
+ socklen_t sock_address_len = sizeof(sock_address);
+ ASSERT_EQ(0, getsockname(socket_, reinterpret_cast<sockaddr*>(&sock_address),
+ &sock_address_len));
+ EXPECT_EQ(AF_INET, sock_address.sin_family);
+
+ // Verify that the socket is bound the appropriate address.
+ const std::string bind_addr(inet_ntoa(sock_address.sin_addr));
+ EXPECT_EQ("127.0.0.1", bind_addr);
+
+ // Verify that the socket is bound to appropriate port.
+ EXPECT_EQ(PORT, ntohs(sock_address.sin_port));
+
+ // Verify that the socket has SOCK_DGRAM type.
+ int sock_type;
+ socklen_t sock_type_len = sizeof(sock_type);
+ ASSERT_EQ(0, getsockopt(socket_, SOL_SOCKET, SO_TYPE, &sock_type, &sock_type_len));
+ EXPECT_EQ(SOCK_DGRAM, sock_type);
+}
+
+// This test verifies that the packet is correctly sent over the INET
+// datagram socket.
+TEST_F(PktFilterInetTest, send) {
+ // Let's create a DHCPv4 packet.
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+ ASSERT_TRUE(pkt);
+
+ // Set required fields.
+ pkt->setLocalAddr(IOAddress("127.0.0.1"));
+ pkt->setRemoteAddr(IOAddress("127.0.0.1"));
+ pkt->setRemotePort(PORT);
+ pkt->setLocalPort(PORT + 1);
+ pkt->setIndex(ifindex_);
+ pkt->setIface(ifname_);
+ pkt->setHops(6);
+ pkt->setSecs(42);
+ pkt->setCiaddr(IOAddress("192.0.2.1"));
+ pkt->setSiaddr(IOAddress("192.0.2.2"));
+ pkt->setYiaddr(IOAddress("192.0.2.3"));
+ pkt->setGiaddr(IOAddress("192.0.2.4"));
+
+ // Create the on-wire data.
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Packet will be sent over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterInet pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ ASSERT_GE(socket_, 0);
+
+ // Send the packet over the socket.
+ ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+
+ // Read the data from socket.
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(socket_, &readfds);
+
+ struct timeval timeout;
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+ int result = select(socket_ + 1, &readfds, NULL, NULL, &timeout);
+ // We should receive some data from loopback interface.
+ ASSERT_GT(result, 0);
+
+ // Get the actual data.
+ uint8_t rcv_buf[RECV_BUF_SIZE];
+ result = recv(socket_, rcv_buf, RECV_BUF_SIZE, 0);
+ ASSERT_GT(result, 0);
+
+ // Create the DHCPv4 packet from the received data.
+ Pkt4Ptr rcvd_pkt(new Pkt4(rcv_buf, result));
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Verify that the received packet matches sent packet.
+ EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+ EXPECT_EQ(pkt->getOp(), rcvd_pkt->getOp());
+ EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
+ EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
+ EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
+ EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
+ EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
+ EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
+ EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
+ EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
+ EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
+ EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
+ EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
+}
+
+// This test verifies that the DHCPv4 packet is correctly received via
+// INET datagram socket and that it matches sent packet.
+TEST_F(PktFilterInetTest, receive) {
+ // Let's create a DHCPv4 packet.
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+ ASSERT_TRUE(pkt);
+
+ // Set required fields.
+ pkt->setLocalAddr(IOAddress("127.0.0.1"));
+ pkt->setRemoteAddr(IOAddress("127.0.0.1"));
+ pkt->setRemotePort(PORT);
+ pkt->setLocalPort(PORT + 1);
+ pkt->setIndex(ifindex_);
+ pkt->setIface(ifname_);
+ pkt->setHops(6);
+ pkt->setSecs(42);
+ pkt->setCiaddr(IOAddress("192.0.2.1"));
+ pkt->setSiaddr(IOAddress("192.0.2.2"));
+ pkt->setYiaddr(IOAddress("192.0.2.3"));
+ pkt->setGiaddr(IOAddress("192.0.2.4"));
+
+ // Create the on-wire data.
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Packet will be sent over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterInet pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ ASSERT_GE(socket_, 0);
+
+ // Send the packet over the socket.
+ ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+
+ // Receive the packet.
+ SocketInfo socket_info(socket_, IOAddress("127.0.0.1"), PORT);
+ Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, socket_info);
+ // Check that the packet has been correctly received.
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Verify that the received packet matches sent packet.
+ EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+ EXPECT_EQ(pkt->getOp(), rcvd_pkt->getOp());
+ EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
+ EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
+ EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
+ EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
+ EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
+ EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
+ EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
+ EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
+ EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
+ EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
+ EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc b/src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc
new file mode 100644
index 0000000..742a7c9
--- /dev/null
+++ b/src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc
@@ -0,0 +1,307 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt_filter_lpf.h>
+#include <dhcp/protocol_util.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+
+#include <linux/if_packet.h>
+#include <sys/socket.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+/// Port number used by tests.
+const uint16_t PORT = 10067;
+/// Size of the buffer holding received packets.
+const size_t RECV_BUF_SIZE = 2048;
+
+/// This class handles has simple algorithm checking
+/// presence of loopback interface and initializing
+/// its index.
+class PktFilterLPFTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ ///
+ /// This constructor initializes socket_ member to a negative value.
+ /// Explcit initialization is performed here because some of the
+ /// tests do not initialize this value. In such cases, destructor
+ /// could invoke close() on uninitialized socket descriptor which
+ /// would result in errors being reported by Valgrind.
+ PktFilterLPFTest()
+ : socket_(-1) {
+ // Initialize ifname_ and ifindex_.
+ loInit();
+ }
+
+ /// @brief Destructor
+ ///
+ /// Closes open socket (if any).
+ ~PktFilterLPFTest() {
+ // Cleanup after each test. This guarantees
+ // that the socket does not hang after a test.
+ if (socket_ >= 0) {
+ close(socket_);
+ }
+ }
+
+ /// @brief Detect loopback interface.
+ ///
+ /// @todo this function will be removed once cross-OS interface
+ /// detection is implemented
+ void loInit() {
+ if (if_nametoindex("lo") > 0) {
+ ifname_ = "lo";
+ ifindex_ = if_nametoindex("lo");
+
+ } else if (if_nametoindex("lo0") > 0) {
+ ifname_ = "lo0";
+ ifindex_ = if_nametoindex("lo0");
+
+ } else {
+ std::cout << "Failed to detect loopback interface. Neither "
+ << "lo nor lo0 worked. Giving up." << std::endl;
+ FAIL();
+
+
+
+ }
+ }
+
+ std::string ifname_; ///< Loopback interface name
+ uint16_t ifindex_; ///< Loopback interface index.
+ int socket_; ///< Socket descriptor.
+
+};
+
+// This test verifies that the PktFilterLPF class reports its capability
+// to send packets to the host having no IP address assigned.
+TEST_F(PktFilterLPFTest, isDirectResponseSupported) {
+ // Create object under test.
+ PktFilterLPF pkt_filter;
+ // Must support direct responses.
+ EXPECT_TRUE(pkt_filter.isDirectResponseSupported());
+}
+
+// All tests below require root privileges to execute successfully. If
+// they are run as non-root user they will fail due to insufficient privileges
+// to open raw network sockets. Therefore, they should remain disabled by default
+// and "DISABLED_" tags should not be removed. If one is willing to run these
+// tests please run "make check" as root and enable execution of disabled tests
+// by setting GTEST_ALSO_RUN_DISABLED_TESTS to a value other than 0. In order
+// to run tests from this particular file, set the GTEST_FILTER environmental
+// variable to "PktFilterLPFTest.*" apart from GTEST_ALSO_RUN_DISABLED_TESTS
+// setting.
+
+// This test verifies that the raw AF_PACKET family socket can
+// be opened and bound to the specific interface.
+TEST_F(PktFilterLPFTest, DISABLED_openSocket) {
+ // Create object representing loopback interface.
+ Iface iface(ifname_, ifindex_);
+ // Set loopback address.
+ IOAddress addr("127.0.0.1");
+
+ // Try to open socket.
+ PktFilterLPF pkt_filter;
+ socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ // Check that socket has been opened.
+ ASSERT_GE(socket_, 0);
+
+ // Verify that the socket belongs to AF_PACKET family.
+ sockaddr_ll sock_address;
+ socklen_t sock_address_len = sizeof(sock_address);
+ ASSERT_EQ(0, getsockname(socket_, reinterpret_cast<sockaddr*>(&sock_address),
+ &sock_address_len));
+ EXPECT_EQ(AF_PACKET, sock_address.sll_family);
+
+ // Verify that the socket is bound to appropriate interface.
+ EXPECT_EQ(ifindex_, sock_address.sll_ifindex);
+
+ // Verify that the socket has SOCK_RAW type.
+ int sock_type;
+ socklen_t sock_type_len = sizeof(sock_type);
+ ASSERT_EQ(0, getsockopt(socket_, SOL_SOCKET, SO_TYPE, &sock_type, &sock_type_len));
+ EXPECT_EQ(SOCK_RAW, sock_type);
+}
+
+// This test verifies correctness of sending DHCP packet through the raw
+// socket, whereby all IP stack headers are hand-crafted.
+TEST_F(PktFilterLPFTest, DISABLED_send) {
+ // Let's create a DHCPv4 packet.
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+ ASSERT_TRUE(pkt);
+
+ // Set required fields.
+ // By setting the local address to broadcast we simulate the
+ // typical scenario when client's request was send to broadcast
+ // address and server by default used it as a source address
+ // in its response. The send() function should be able to detect
+ // it and correct the source address.
+ pkt->setLocalAddr(IOAddress("255.255.255.255"));
+ pkt->setRemoteAddr(IOAddress("127.0.0.1"));
+ pkt->setRemotePort(PORT);
+ pkt->setLocalPort(PORT + 1);
+ pkt->setIndex(ifindex_);
+ pkt->setIface(ifname_);
+ pkt->setHops(6);
+ pkt->setSecs(42);
+ pkt->setCiaddr(IOAddress("192.0.2.1"));
+ pkt->setSiaddr(IOAddress("192.0.2.2"));
+ pkt->setYiaddr(IOAddress("192.0.2.3"));
+ pkt->setGiaddr(IOAddress("192.0.2.4"));
+
+ // Create the on-wire data.
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Packet will be sent over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterLPF pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ ASSERT_GE(socket_, 0);
+
+ // Send the packet over the socket.
+ ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+
+ // Read the data from socket.
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(socket_, &readfds);
+
+ struct timeval timeout;
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+ int result = select(socket_ + 1, &readfds, NULL, NULL, &timeout);
+ // We should receive some data from loopback interface.
+ ASSERT_GT(result, 0);
+
+ // Get the actual data.
+ uint8_t rcv_buf[RECV_BUF_SIZE];
+ result = recv(socket_, rcv_buf, RECV_BUF_SIZE, 0);
+ ASSERT_GT(result, 0);
+
+ Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0));
+
+ InputBuffer buf(rcv_buf, result);
+
+ // Decode ethernet, ip and udp headers.
+ decodeEthernetHeader(buf, dummy_pkt);
+ decodeIpUdpHeader(buf, dummy_pkt);
+
+ // Create the DHCPv4 packet from the received data.
+ std::vector<uint8_t> dhcp_buf;
+ buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition());
+ Pkt4Ptr rcvd_pkt(new Pkt4(&dhcp_buf[0], dhcp_buf.size()));
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Verify that the received packet matches sent packet.
+ EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+ EXPECT_EQ(pkt->getOp(), rcvd_pkt->getOp());
+ EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
+ EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
+ EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
+ EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
+ EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
+ EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
+ EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
+ EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
+ EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
+ EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
+ EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
+}
+
+// This test verifies correctness of reception of the DHCP packet over
+// raw socket, whereby all IP stack headers are hand-crafted.
+TEST_F(PktFilterLPFTest, DISABLED_receive) {
+
+ // Let's create a DHCPv4 packet.
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+ ASSERT_TRUE(pkt);
+
+ // Set required fields.
+ pkt->setLocalAddr(IOAddress("127.0.0.1"));
+ pkt->setRemoteAddr(IOAddress("127.0.0.1"));
+ pkt->setRemotePort(PORT);
+ pkt->setLocalPort(PORT + 1);
+ pkt->setIndex(ifindex_);
+ pkt->setIface(ifname_);
+ pkt->setHops(6);
+ pkt->setSecs(42);
+ pkt->setCiaddr(IOAddress("192.0.2.1"));
+ pkt->setSiaddr(IOAddress("192.0.2.2"));
+ pkt->setYiaddr(IOAddress("192.0.2.3"));
+ pkt->setGiaddr(IOAddress("192.0.2.4"));
+
+ // Create the on-wire data.
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Packet will be sent over loopback interface.
+ Iface iface(ifname_, ifindex_);
+ IOAddress addr("127.0.0.1");
+
+ // Create an instance of the class which we are testing.
+ PktFilterLPF pkt_filter;
+ // Open socket. We don't check that the socket has appropriate
+ // options and family set because we have checked that in the
+ // openSocket test already.
+ socket_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+ ASSERT_GE(socket_, 0);
+
+ // Send the packet over the socket.
+ ASSERT_NO_THROW(pkt_filter.send(iface, socket_, pkt));
+
+ // Receive the packet.
+ SocketInfo socket_info(socket_, IOAddress("127.0.0.1"), PORT);
+ Pkt4Ptr rcvd_pkt = pkt_filter.receive(iface, socket_info);
+ // Check that the packet has been correctly received.
+ ASSERT_TRUE(rcvd_pkt);
+
+ // Parse the packet.
+ ASSERT_NO_THROW(rcvd_pkt->unpack());
+
+ // Verify that the received packet matches sent packet.
+ EXPECT_EQ(pkt->getHops(), rcvd_pkt->getHops());
+ EXPECT_EQ(pkt->getOp(), rcvd_pkt->getOp());
+ EXPECT_EQ(pkt->getSecs(), rcvd_pkt->getSecs());
+ EXPECT_EQ(pkt->getFlags(), rcvd_pkt->getFlags());
+ EXPECT_EQ(pkt->getCiaddr(), rcvd_pkt->getCiaddr());
+ EXPECT_EQ(pkt->getSiaddr(), rcvd_pkt->getSiaddr());
+ EXPECT_EQ(pkt->getYiaddr(), rcvd_pkt->getYiaddr());
+ EXPECT_EQ(pkt->getGiaddr(), rcvd_pkt->getGiaddr());
+ EXPECT_EQ(pkt->getTransid(), rcvd_pkt->getTransid());
+ EXPECT_TRUE(pkt->getSname() == rcvd_pkt->getSname());
+ EXPECT_TRUE(pkt->getFile() == rcvd_pkt->getFile());
+ EXPECT_EQ(pkt->getHtype(), rcvd_pkt->getHtype());
+ EXPECT_EQ(pkt->getHlen(), rcvd_pkt->getHlen());
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/protocol_util_unittest.cc b/src/lib/dhcp/tests/protocol_util_unittest.cc
new file mode 100644
index 0000000..644dbf7
--- /dev/null
+++ b/src/lib/dhcp/tests/protocol_util_unittest.cc
@@ -0,0 +1,392 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/protocol_util.h>
+#include <util/buffer.h>
+#include <gtest/gtest.h>
+// in_systm.h is required on some some BSD systems
+// complaining that n_time is undefined but used
+// in ip.h.
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+ /*/// @brief OptionCustomTest test class.
+class OptionCustomTest : public ::testing::Test {
+public:
+};*/
+
+/// The purpose of this test is to verify that the IP header checksum
+/// is calculated correctly.
+TEST(ProtocolUtilTest, checksum) {
+ // IPv4 header to be used to calculate checksum.
+ const uint8_t hdr[] = {
+ 0x45, // IP version and header length
+ 0x00, // TOS
+ 0x00, 0x3c, // Total length of the IP packet.
+ 0x1c, 0x46, // Identification field.
+ 0x40, 0x00, // Fragmentation.
+ 0x40, // TTL
+ 0x06, // Protocol
+ 0x00, 0x00, // Checksum (reset to 0x00).
+ 0xac, 0x10, 0x0a, 0x63, // Source IP address.
+ 0xac, 0x10, 0x0a, 0x0c // Destination IP address.
+ };
+ // Calculate size of the header array.
+ const uint32_t hdr_size = sizeof(hdr) / sizeof(hdr[0]);
+ // Get the actual checksum.
+ uint16_t chksum = ~calcChecksum(hdr, hdr_size);
+ // The 0xb1e6 value has been calculated by other means.
+ EXPECT_EQ(0xb1e6, chksum);
+ // Tested function may also take the initial value of the sum.
+ // Let's set it to 2 and see whether it is included in the
+ // calculation.
+ chksum = ~calcChecksum(hdr, hdr_size, 2);
+ // The checkum value should change.
+ EXPECT_EQ(0xb1e4, chksum);
+}
+
+// The purpose of this test is to verify that the Ethernet frame header
+// can be decoded correctly. In particular it verifies that the source
+// HW address can be extracted from it.
+TEST(ProtocolUtilTest, decodeEthernetHeader) {
+ // Source HW address, 6 bytes.
+ const uint8_t src_hw_addr[6] = {
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+ };
+ // Destination HW address, 6 bytes.
+ const uint8_t dest_hw_addr[6] = {
+ 0x20, 0x31, 0x42, 0x53, 0x64, 0x75
+ };
+
+ // Prepare a buffer holding Ethernet frame header and 4 bytes of
+ // dummy data.
+ OutputBuffer buf(1);
+ buf.writeData(dest_hw_addr, sizeof(dest_hw_addr));
+ buf.writeData(src_hw_addr, sizeof(src_hw_addr));
+ buf.writeUint16(ETHERNET_TYPE_IP);
+ // Append dummy data. We will later check that this data is not
+ // removed or corrupted when reading the ethernet header.
+ buf.writeUint32(0x01020304);
+
+ // Create a buffer with truncated ethernet frame header..
+ InputBuffer in_buf_truncated(buf.getData(), buf.getLength() - 6);
+ // But provide valid packet object to make sure that the function
+ // under test does not throw due to NULL pointer packet.
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+ // Function should throw because header data is truncated.
+ EXPECT_THROW(decodeEthernetHeader(in_buf_truncated, pkt),
+ InvalidPacketHeader);
+
+ // Get not truncated buffer.
+ InputBuffer in_buf(buf.getData(), buf.getLength());
+ // But provide NULL packet object instead.
+ pkt.reset();
+ // It should throw again but a different exception.
+ EXPECT_THROW(decodeEthernetHeader(in_buf, pkt),
+ BadValue);
+ // Now provide, correct data.
+ pkt.reset(new Pkt4(DHCPDISCOVER, 0));
+ // It should not throw now.
+ ASSERT_NO_THROW(decodeEthernetHeader(in_buf, pkt));
+ // Verify that the destination HW address has been initialized...
+ HWAddrPtr checked_dest_hwaddr = pkt->getLocalHWAddr();
+ ASSERT_TRUE(checked_dest_hwaddr);
+ // and is correct.
+ EXPECT_EQ(HWTYPE_ETHERNET, checked_dest_hwaddr->htype_);
+ ASSERT_EQ(sizeof(dest_hw_addr), checked_dest_hwaddr->hwaddr_.size());
+ EXPECT_TRUE(std::equal(dest_hw_addr, dest_hw_addr + sizeof(dest_hw_addr),
+ checked_dest_hwaddr->hwaddr_.begin()));
+
+ // Verify that the HW address of the source has been initialized.
+ HWAddrPtr checked_src_hwaddr = pkt->getRemoteHWAddr();
+ ASSERT_TRUE(checked_src_hwaddr);
+ // And that it is correct.
+ EXPECT_EQ(HWTYPE_ETHERNET, checked_src_hwaddr->htype_);
+ ASSERT_EQ(sizeof(src_hw_addr), checked_src_hwaddr->hwaddr_.size());
+ EXPECT_TRUE(std::equal(src_hw_addr, src_hw_addr + sizeof(src_hw_addr),
+ checked_src_hwaddr->hwaddr_.begin()));
+
+ // The entire ethernet packet header should have been read. This means
+ // that the internal buffer pointer should now point to its tail.
+ ASSERT_EQ(ETHERNET_HEADER_LEN, in_buf.getPosition());
+ // And the dummy data should be still readable and correct.
+ uint32_t dummy_data = in_buf.readUint32();
+ EXPECT_EQ(0x01020304, dummy_data);
+}
+
+/// The purpose of this test is to verify that the IP and UDP header
+/// is decoded correctly and appropriate values of IP addresses and
+/// ports are assigned to a Pkt4 object.
+TEST(ProtocolUtilTest, decodeIpUdpHeader) {
+ // IPv4 header to be parsed.
+ const uint8_t hdr[] = {
+ 0x45, // IP version and header length
+ 0x00, // TOS
+ 0x00, 0x3c, // Total length of the IP packet.
+ 0x1c, 0x46, // Identification field.
+ 0x40, 0x00, // Fragmentation.
+ 0x40, // TTL
+ IPPROTO_UDP, // Protocol
+ 0x00, 0x00, // Checksum (reset to 0x00).
+ 0xc0, 0x00, 0x02, 0x63, // Source IP address.
+ 0xc0, 0x00, 0x02, 0x0c, // Destination IP address.
+ 0x27, 0x54, // Source port
+ 0x27, 0x53, // Destination port
+ 0x00, 0x08, // UDP length
+ 0x00, 0x00 // Checksum
+ };
+
+ // Write header data to the buffer.
+ OutputBuffer buf(1);
+ buf.writeData(hdr, sizeof(hdr));
+ // Append some dummy data.
+ buf.writeUint32(0x01020304);
+
+ // Create an input buffer holding truncated headers.
+ InputBuffer in_buf_truncated(buf.getData(), buf.getLength() - 10);
+ // Create non NULL Pkt4 object to make sure that the function under
+ // test does not throw due to invalid Pkt4 object.
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+ // Function should throw because buffer holds truncated data.
+ EXPECT_THROW(decodeIpUdpHeader(in_buf_truncated, pkt), InvalidPacketHeader);
+
+ // Create a valid input buffer (not truncated).
+ InputBuffer in_buf(buf.getData(), buf.getLength());
+ // Set NULL Pkt4 object to verify that function under test will
+ // return exception as expected.
+ pkt.reset();
+ // And check whether it throws exception.
+ EXPECT_THROW(decodeIpUdpHeader(in_buf, pkt), BadValue);
+
+ // Now, let's provide valid arguments and make sure it doesn't throw.
+ pkt.reset(new Pkt4(DHCPDISCOVER, 0));
+ ASSERT_TRUE(pkt);
+ EXPECT_NO_THROW(decodeIpUdpHeader(in_buf, pkt));
+
+ // Verify the source address and port.
+ EXPECT_EQ("192.0.2.99", pkt->getRemoteAddr().toText());
+ EXPECT_EQ(10068, pkt->getRemotePort());
+
+ // Verify the destination address and port.
+ EXPECT_EQ("192.0.2.12", pkt->getLocalAddr().toText());
+ EXPECT_EQ(10067, pkt->getLocalPort());
+
+ // Verify that the dummy data has not been corrupted and that the
+ // internal read pointer has been moved to the tail of the UDP
+ // header.
+ ASSERT_EQ(MIN_IP_HEADER_LEN + UDP_HEADER_LEN, in_buf.getPosition());
+ EXPECT_EQ(0x01020304, in_buf.readUint32());
+}
+
+/// The purpose of this test is to verify that the ethernet
+/// header is correctly constructed from the destination and
+/// hardware addresses.
+TEST(ProtocolUtilTest, writeEthernetHeader) {
+ // Source HW address, 6 bytes.
+ const uint8_t src_hw_addr[6] = {
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+ };
+ // Destination HW address, 6 bytes.
+ const uint8_t dest_hw_addr[6] = {
+ 0x20, 0x31, 0x42, 0x53, 0x64, 0x75
+ };
+
+ // Create output buffer.
+ OutputBuffer buf(1);
+ Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0));
+
+ HWAddrPtr local_hw_addr(new HWAddr(src_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setLocalHWAddr(local_hw_addr));
+
+ // Set invalid length (7) of the hw address. Fill it with
+ // values of 1.
+ std::vector<uint8_t> invalid_length_addr(7, 1);
+ HWAddrPtr remote_hw_addr(new HWAddr(invalid_length_addr, 1));
+ ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr));
+ // HW address is too long, so it should fail.
+ EXPECT_THROW(writeEthernetHeader(pkt, buf), BadValue);
+
+ // Finally, set a valid HW address.
+ remote_hw_addr.reset(new HWAddr(dest_hw_addr, 6, 1));
+ ASSERT_NO_THROW(pkt->setRemoteHWAddr(remote_hw_addr));
+
+ // Construct the ethernet header using HW addresses stored
+ // in the pkt object.
+ writeEthernetHeader(pkt, buf);
+
+ // The resulting ethernet header consists of destination
+ // and src HW address (each 6 bytes long) and two bytes
+ // of encapsulated protocol type.
+ ASSERT_EQ(14, buf.getLength());
+
+ // Verify that first 6 bytes comprise valid destination
+ // HW address. Instead of using memory comparison functions
+ // we check bytes one-by-one. In case of mismatch we will
+ // get exact values that are mismatched. If memcmp was used
+ // the error message would not indicate the values of
+ // mismatched bytes.
+ for (int i = 0; i < 6; ++i) {
+ EXPECT_EQ(dest_hw_addr[i], buf[i]);
+ }
+ // Verify that following 6 bytes comprise the valid source
+ // HW address.
+ for (int i = 0; i < 6; ++i) {
+ EXPECT_EQ(src_hw_addr[i], buf[i + 6]);
+ }
+
+ // The last two bytes comprise the encapsulated protocol type.
+ // We expect IPv4 protocol type which is specified by 0x0800.
+ EXPECT_EQ(0x08, buf[12]);
+ EXPECT_EQ(0x0, buf[13]);
+}
+
+TEST(ProtocolUtilTest, writeIpUdpHeader) {
+ // Create DHCPv4 packet. Some values held by this object are
+ // used by the utility function under test to figure out the
+ // contents of the IP and UDP headers, e.g. source and
+ // destination IP address or port number.
+ Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+ ASSERT_TRUE(pkt);
+
+ // Set local and remote address and port.
+ pkt->setLocalAddr(IOAddress("192.0.2.1"));
+ pkt->setRemoteAddr(IOAddress("192.0.2.111"));
+ pkt->setLocalPort(DHCP4_SERVER_PORT);
+ pkt->setRemotePort(DHCP4_CLIENT_PORT);
+
+ // Pack the contents of the packet.
+ ASSERT_NO_THROW(pkt->pack());
+
+ // Create output buffer. The headers will be written to it.
+ OutputBuffer buf(1);
+ // Write some dummy data to the buffer. We will check that the
+ // function under test appends to this data, not overrides it.
+ buf.writeUint16(0x0102);
+
+ // Write both IP and UDP headers.
+ writeIpUdpHeader(pkt, buf);
+
+ // The resulting size of the buffer must be 30. The 28 bytes are
+ // consumed by the IP and UDP headers. The other 2 bytes are dummy
+ // data at the beginning of the buffer.
+ ASSERT_EQ(30, buf.getLength());
+
+ // Make sure that the existing data in the buffer was not corrupted
+ // by the function under test.
+ EXPECT_EQ(0x01, buf[0]);
+ EXPECT_EQ(0x02, buf[1]);
+
+ // Copy the contents of the buffer to InputBuffer object. This object
+ // exposes convenient functions for reading.
+ InputBuffer in_buf(buf.getData(), buf.getLength());
+
+ // Check dummy data.
+ uint16_t dummy_data = in_buf.readUint16();
+ EXPECT_EQ(0x0102, dummy_data);
+
+ // The IP version and IHL are stored in the same octet (4 bits each).
+ uint8_t ver_len = in_buf.readUint8();
+ // The most significant bits define IP version.
+ uint8_t ip_ver = ver_len >> 4;
+ EXPECT_EQ(4, ip_ver);
+ // The least significant bits define header length (in 32-bits chunks).
+ uint8_t ip_len = ver_len & 0x0F;
+ EXPECT_EQ(5, ip_len);
+
+ // Get Differentiated Services Codepoint and Explicit Congestion
+ // Notification field.
+ uint8_t dscp_ecn = in_buf.readUint8();
+ EXPECT_EQ(IPTOS_LOWDELAY, dscp_ecn);
+
+ // Total length of IP packet. Includes UDP header and payload.
+ uint16_t total_len = in_buf.readUint16();
+ EXPECT_EQ(28 + pkt->getBuffer().getLength(), total_len);
+
+ // Identification field.
+ uint16_t ident = in_buf.readUint16();
+ EXPECT_EQ(0, ident);
+
+ // Fragmentation.
+ uint16_t fragment = in_buf.readUint16();
+ // Setting second most significant bit means no fragmentation.
+ EXPECT_EQ(0x4000, fragment);
+
+ // Get TTL
+ uint8_t ttl = in_buf.readUint8();
+ // Expect non-zero TTL.
+ EXPECT_GE(ttl, 1);
+
+ // Protocol type is UDP.
+ uint8_t proto = in_buf.readUint8();
+ EXPECT_EQ(IPPROTO_UDP, proto);
+
+ // Check that the checksum is correct. The reference checksum value
+ // has been calculated manually.
+ uint16_t ip_checksum = in_buf.readUint16();
+ EXPECT_EQ(0x755c, ip_checksum);
+
+ // Validate source address.
+ // Initializing it to IPv6 address guarantees that it is not initialized
+ // to the value that we expect to be read from a header since the value
+ // read from a header will be IPv4.
+ IOAddress src_addr("::1");
+ // Read src address as an array of bytes because it is easely convertible
+ // to IOAddress object.
+ uint8_t src_addr_data[4];
+ ASSERT_NO_THROW(
+ in_buf.readData(src_addr_data, 4);
+ src_addr = IOAddress::fromBytes(AF_INET, src_addr_data);
+ );
+ EXPECT_EQ(IOAddress("192.0.2.1").toText(), src_addr.toText());
+
+ // Validate destination address.
+ IOAddress dest_addr("::1");
+ uint8_t dest_addr_data[4];
+ ASSERT_NO_THROW(
+ in_buf.readData(dest_addr_data, 4);
+ dest_addr = IOAddress::fromBytes(AF_INET, dest_addr_data);
+ );
+ EXPECT_EQ(IOAddress("192.0.2.111").toText(), dest_addr.toText());
+
+ // UDP header starts here.
+
+ // Check source port.
+ uint16_t src_port = in_buf.readUint16();
+ EXPECT_EQ(pkt->getLocalPort(), src_port);
+
+ // Check destination port.
+ uint16_t dest_port = in_buf.readUint16();
+ EXPECT_EQ(pkt->getRemotePort(), dest_port);
+
+ // UDP header and data length.
+ uint16_t udp_len = in_buf.readUint16();
+ EXPECT_EQ(8 + pkt->getBuffer().getLength(), udp_len);
+
+ // Verify UDP checksum. The reference checksum has been calculated manually.
+ uint16_t udp_checksum = in_buf.readUint16();
+ EXPECT_EQ(0x8817, udp_checksum);
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp_ddns/.gitignore b/src/lib/dhcp_ddns/.gitignore
new file mode 100644
index 0000000..6388b8c
--- /dev/null
+++ b/src/lib/dhcp_ddns/.gitignore
@@ -0,0 +1,2 @@
+/dhcp_ddns_messages.cc
+/dhcp_ddns_messages.h
diff --git a/src/lib/dhcp_ddns/Makefile.am b/src/lib/dhcp_ddns/Makefile.am
new file mode 100644
index 0000000..c0d8a7d
--- /dev/null
+++ b/src/lib/dhcp_ddns/Makefile.am
@@ -0,0 +1,56 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(BOTAN_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+
+# Define rule to build logging source files from message file
+dhcp_ddns_messages.h dhcp_ddns_messages.cc: dhcp_ddns_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
+
+# Tell automake that the message files are built as part of the build process
+# (so that they are built before the main library is built).
+BUILT_SOURCES = dhcp_ddns_messages.h dhcp_ddns_messages.cc
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = dhcp_ddns_messages.mes libdhcp_ddns.dox
+
+# Get rid of generated message files on a clean
+CLEANFILES = *.gcno *.gcda dhcp_ddns_messages.h dhcp_ddns_messages.cc
+
+lib_LTLIBRARIES = libb10-dhcp_ddns.la
+libb10_dhcp_ddns_la_SOURCES =
+libb10_dhcp_ddns_la_SOURCES += dhcp_ddns_log.cc dhcp_ddns_log.h
+libb10_dhcp_ddns_la_SOURCES += ncr_io.cc ncr_io.h
+libb10_dhcp_ddns_la_SOURCES += ncr_msg.cc ncr_msg.h
+libb10_dhcp_ddns_la_SOURCES += ncr_udp.cc ncr_udp.h
+
+nodist_libb10_dhcp_ddns_la_SOURCES = dhcp_ddns_messages.cc dhcp_ddns_messages.h
+
+libb10_dhcp_ddns_la_CXXFLAGS = $(AM_CXXFLAGS)
+libb10_dhcp_ddns_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+libb10_dhcp_ddns_la_LDFLAGS = $(AM_LDFLAGS)
+libb10_dhcp_ddns_la_LDFLAGS += ${BOTAN_LDFLAGS}
+libb10_dhcp_ddns_la_LIBADD =
+libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libb10-cryptolink.la
+libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
+libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
+libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+libb10_dhcp_ddns_la_LIBADD += ${BOTAN_LIBS} ${BOTAN_RPATH}
+
+if USE_CLANGPP
+# Disable unused parameter warning caused by some of the
+# Boost headers when compiling with clang.
+libb10_dhcp_ddns_la_CXXFLAGS += -Wno-unused-parameter
+endif
+
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_log.cc b/src/lib/dhcp_ddns/dhcp_ddns_log.cc
new file mode 100644
index 0000000..4411b41
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_log.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 top-level component of b10-dhcp_ddns.
+
+#include <dhcp_ddns/dhcp_ddns_log.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Defines the logger used within lib dhcp_ddns.
+isc::log::Logger dhcp_ddns_logger("libdhcp-ddns");
+
+} // namespace dhcp_ddns
+} // namespace isc
+
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_log.h b/src/lib/dhcp_ddns/dhcp_ddns_log.h
new file mode 100644
index 0000000..951f61e
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_log.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 DHCP_DDNS_LOG_H
+#define DHCP_DDNS_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <dhcp_ddns/dhcp_ddns_messages.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// Define the logger for the "dhcp_ddns" logging.
+extern isc::log::Logger dhcp_ddns_logger;
+
+} // namespace dhcp_ddns
+} // namespace isc
+
+#endif // DHCP_DDNS_LOG_H
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_messages.mes b/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
new file mode 100644
index 0000000..51688ad
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
@@ -0,0 +1,76 @@
+# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::dhcp_ddns
+
+% DHCP_DDNS_INVALID_NCR application received an invalid DNS update request: %1
+This is an error message that indicates that an invalid request to update
+a DNS entry was received by the application. Either the format or the content
+of the request is incorrect. The request will be ignored.
+
+% DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR application encountered an error while closing the listener used to receive NameChangeRequests : %1
+This is an error message that indicates the application was unable to close the
+listener connection used to receive NameChangeRequests. Closure may occur
+during the course of error recovery or during normal shutdown procedure. In
+either case the error is unlikely to impair the application's ability to
+process requests but it should be reported for analysis.
+
+% DHCP_DDNS_NCR_RECV_NEXT_ERROR application could not initiate the next read following a request receive.
+This is a error message indicating that NameChangeRequest listener could not
+start another read after receiving a request. While possible, this is highly
+unlikely and is probably a programmatic error. The application should recover
+on its own.
+
+% DHCP_DDNS_NCR_SEND_CLOSE_ERROR DHCP-DDNS client encountered an error while closing the sender connection used to send NameChangeRequests : %1
+This is an error message that indicates the DHCP-DDNS client was unable to
+close the connection used to send NameChangeRequests. Closure may occur during
+the course of error recovery or during normal shutdown procedure. In either
+case the error is unlikely to impair the client's ability to send requests but
+it should be reported for analysis.
+
+% DHCP_DDNS_NCR_SEND_NEXT_ERROR DHCP-DDNS client could not initiate the next request send following send completion.
+This is a error message indicating that NameChangeRequest sender could not
+start another send after completing the send of the previous request. While
+possible, this is highly unlikely and is probably a programmatic error. The
+application should recover on its own.
+
+% DHCP_DDNS_NCR_UDP_RECV_CANCELED UDP socket receive was canceled while listening for DNS Update requests: %1
+This is an informational message indicating that the listening over a UDP socket for DNS update requests has been canceled. This is a normal part of suspending listening operations.
+
+% DHCP_DDNS_NCR_UDP_RECV_ERROR UDP socket receive error while listening for DNS Update requests: %1
+This is an error message indicating that an IO error occurred while listening
+over a UDP socket for DNS update requests. This could indicate a network
+connectivity or system resource issue.
+
+% DHCP_DDNS_NCR_UDP_SEND_CANCELED UDP socket send was canceled while sending a DNS Update request to DHCP_DDNS: %1
+This is an informational message indicating that sending requests via UDP
+socket to DHCP_DDNS has been interrupted. This is a normal part of suspending
+send operations.
+
+% DHCP_DDNS_NCR_UDP_SEND_ERROR UDP socket send error while sending a DNS Update request: %1
+This is an error message indicating that an IO error occurred while sending a
+DNS update request to DHCP_DDNS over a UDP socket. This could indicate a
+network connectivity or system resource issue.
+
+% DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR unexpected exception thrown from the application receive completion handler: %1
+This is an error message that indicates that an exception was thrown but not
+caught in the application's request receive completion handler. This is a
+programmatic error that needs to be reported. Dependent upon the nature of
+the error the application may or may not continue operating normally.
+
+% DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR unexpected exception thrown from the DHCP-DDNS client send completion handler: %1
+This is an error message that indicates that an exception was thrown but not
+caught in the application's send completion handler. This is a programmatic
+error that needs to be reported. Dependent upon the nature of the error the
+client may or may not continue operating normally.
diff --git a/src/lib/dhcp_ddns/libdhcp_ddns.dox b/src/lib/dhcp_ddns/libdhcp_ddns.dox
new file mode 100644
index 0000000..af7c943
--- /dev/null
+++ b/src/lib/dhcp_ddns/libdhcp_ddns.dox
@@ -0,0 +1,47 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/**
+ at page libdhcp_ddns DHCP_DDNS Request IO Library
+
+ at section libdhcp_ddnsIntro Libdhcp_ddns Library Introduction
+
+libdhcp_ddns is a library of classes for sending and receiving requests used
+by ISC's DHCP-DDNS service for carrying out DHCP-driven DDNS. These requests
+are implemented in this library by the class, NameChangeRequest.
+
+This class provides services for constructing the requests as well as
+marshalling them to and from various transport formats. Currently, the only
+format supported is JSON, however the design of the classes in this library is such that supporting additional formats will be easy to add.
+
+For sending and receiving NameChangeRequests, this library supplies an abstract
+pair of classes, NameChangeSender and NameChangeListener. NameChangeSender
+defines the public interface that DHCP_DDNS clients, such as DHCP servers use
+for sending requests to DHCP_DDNS. NameChangeListener is used by request
+consumers, primarily the DHCP_DDNS service, for receiving the requests.
+
+By providing abstract interfaces, the implementation isolates the senders and
+listeners from any underlying details of request transportation. This was done
+to allow support for a variety of transportation mechanisms. Currently, the
+only transport supported is via UDP Sockets, though support for TCP/IP sockets
+is forthcoming. There may eventually be an implementation which is driven
+through an RDBMS.
+
+The UDP implementation is provided by NameChangeUDPSender and NameChangeUPDListener.
+The implementation is strictly unidirectional. This means that there is no explicit
+acknowledgement of receipt of a request, and as it is UDP there is no guarantee of
+delivery. Once provided, transport via TCP/IP sockets will provide a more reliable
+mechanism.
+
+*/
diff --git a/src/lib/dhcp_ddns/ncr_io.cc b/src/lib/dhcp_ddns/ncr_io.cc
new file mode 100644
index 0000000..4f3f1d7
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_io.cc
@@ -0,0 +1,292 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <dhcp_ddns/dhcp_ddns_log.h>
+#include <dhcp_ddns/ncr_io.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+//************************** NameChangeListener ***************************
+
+NameChangeListener::NameChangeListener(RequestReceiveHandler&
+ recv_handler)
+ : listening_(false), io_pending_(false), recv_handler_(recv_handler) {
+};
+
+
+void
+NameChangeListener::startListening(isc::asiolink::IOService& io_service) {
+ if (amListening()) {
+ // This amounts to a programmatic error.
+ isc_throw(NcrListenerError, "NameChangeListener is already listening");
+ }
+
+ // Call implementation dependent open.
+ try {
+ open(io_service);
+ } catch (const isc::Exception& ex) {
+ stopListening();
+ isc_throw(NcrListenerOpenError, "Open failed:" << ex.what());
+ }
+
+ // Set our status to listening.
+ setListening(true);
+
+ // Start the first asynchronous receive.
+ try {
+ receiveNext();
+ } catch (const isc::Exception& ex) {
+ stopListening();
+ isc_throw(NcrListenerReceiveError, "doReceive failed:" << ex.what());
+ }
+}
+
+void
+NameChangeListener::receiveNext() {
+ io_pending_ = true;
+ doReceive();
+}
+
+void
+NameChangeListener::stopListening() {
+ try {
+ // Call implementation dependent close.
+ close();
+ } catch (const isc::Exception &ex) {
+ // Swallow exceptions. If we have some sort of error we'll log
+ // it but we won't propagate the throw.
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR)
+ .arg(ex.what());
+ }
+
+ // Set it false, no matter what. This allows us to at least try to
+ // re-open via startListening().
+ setListening(false);
+}
+
+void
+NameChangeListener::invokeRecvHandler(const Result result,
+ NameChangeRequestPtr& ncr) {
+ // Call the registered application layer handler.
+ // Surround the invocation with a try-catch. The invoked handler is
+ // not supposed to throw, but in the event it does we will at least
+ // report it.
+ try {
+ io_pending_ = false;
+ recv_handler_(result, ncr);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
+ .arg(ex.what());
+ }
+
+ // Start the next IO layer asynchronous receive.
+ // In the event the handler above intervened and decided to stop listening
+ // we need to check that first.
+ if (amListening()) {
+ try {
+ receiveNext();
+ } catch (const isc::Exception& ex) {
+ // It is possible though unlikely, for doReceive to fail without
+ // scheduling the read. While, unlikely, it does mean the callback
+ // will not get called with a failure. A throw here would surface
+ // at the IOService::run (or run variant) invocation. So we will
+ // close the window by invoking the application handler with
+ // a failed result, and let the application layer sort it out.
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_RECV_NEXT_ERROR)
+ .arg(ex.what());
+
+ // Call the registered application layer handler.
+ // Surround the invocation with a try-catch. The invoked handler is
+ // not supposed to throw, but in the event it does we will at least
+ // report it.
+ NameChangeRequestPtr empty;
+ try {
+ io_pending_ = false;
+ recv_handler_(ERROR, empty);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp_ddns_logger,
+ DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
+ .arg(ex.what());
+ }
+ }
+ }
+}
+
+//************************* NameChangeSender ******************************
+
+NameChangeSender::NameChangeSender(RequestSendHandler& send_handler,
+ size_t send_queue_max)
+ : sending_(false), send_handler_(send_handler),
+ send_queue_max_(send_queue_max) {
+
+ // Queue size must be big enough to hold at least 1 entry.
+ if (send_queue_max == 0) {
+ isc_throw(NcrSenderError, "NameChangeSender constructor"
+ " queue size must be greater than zero");
+ }
+}
+
+void
+NameChangeSender::startSending(isc::asiolink::IOService& io_service) {
+ if (amSending()) {
+ // This amounts to a programmatic error.
+ isc_throw(NcrSenderError, "NameChangeSender is already sending");
+ }
+
+ // Clear send marker.
+ ncr_to_send_.reset();
+
+ // Call implementation dependent open.
+ try {
+ open(io_service);
+ } catch (const isc::Exception& ex) {
+ stopSending();
+ isc_throw(NcrSenderOpenError, "Open failed: " << ex.what());
+ }
+
+ // Set our status to sending.
+ setSending(true);
+}
+
+void
+NameChangeSender::stopSending() {
+ try {
+ // Call implementation dependent close.
+ close();
+ } catch (const isc::Exception &ex) {
+ // Swallow exceptions. If we have some sort of error we'll log
+ // it but we won't propagate the throw.
+ LOG_ERROR(dhcp_ddns_logger,
+ DHCP_DDNS_NCR_SEND_CLOSE_ERROR).arg(ex.what());
+ }
+
+ // Set it false, no matter what. This allows us to at least try to
+ // re-open via startSending().
+ setSending(false);
+}
+
+void
+NameChangeSender::sendRequest(NameChangeRequestPtr& ncr) {
+ if (!amSending()) {
+ isc_throw(NcrSenderError, "sender is not ready to send");
+ }
+
+ if (!ncr) {
+ isc_throw(NcrSenderError, "request to send is empty");
+ }
+
+ if (send_queue_.size() >= send_queue_max_) {
+ isc_throw(NcrSenderQueueFull, "send queue has reached maximum capacity:"
+ << send_queue_max_ );
+ }
+
+ // Put it on the queue.
+ send_queue_.push_back(ncr);
+
+ // Call sendNext to schedule the next one to go.
+ sendNext();
+}
+
+void
+NameChangeSender::sendNext() {
+ if (ncr_to_send_) {
+ // @todo Not sure if there is any risk of getting stuck here but
+ // an interval timer to defend would be good.
+ // In reality, the derivation should ensure they timeout themselves
+ return;
+ }
+
+ // If queue isn't empty, then get one from the front. Note we leave
+ // it on the front of the queue until we successfully send it.
+ if (!send_queue_.empty()) {
+ ncr_to_send_ = send_queue_.front();
+
+ // @todo start defense timer
+ // If a send were to hang and we timed it out, then timeout
+ // handler need to cycle thru open/close ?
+
+ // Call implementation dependent send.
+ doSend(ncr_to_send_);
+ }
+}
+
+void
+NameChangeSender::invokeSendHandler(const NameChangeSender::Result result) {
+ // @todo reset defense timer
+ if (result == SUCCESS) {
+ // It shipped so pull it off the queue.
+ send_queue_.pop_front();
+ }
+
+ // Invoke the completion handler passing in the result and a pointer
+ // the request involved.
+ // Surround the invocation with a try-catch. The invoked handler is
+ // not supposed to throw, but in the event it does we will at least
+ // report it.
+ try {
+ send_handler_(result, ncr_to_send_);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR)
+ .arg(ex.what());
+ }
+
+ // Clear the pending ncr pointer.
+ ncr_to_send_.reset();
+
+ // Set up the next send
+ try {
+ sendNext();
+ } catch (const isc::Exception& ex) {
+ // It is possible though unlikely, for sendNext to fail without
+ // scheduling the send. While, unlikely, it does mean the callback
+ // will not get called with a failure. A throw here would surface
+ // at the IOService::run (or run variant) invocation. So we will
+ // close the window by invoking the application handler with
+ // a failed result, and let the application layer sort it out.
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_SEND_NEXT_ERROR)
+ .arg(ex.what());
+
+ // Invoke the completion handler passing in failed result.
+ // Surround the invocation with a try-catch. The invoked handler is
+ // not supposed to throw, but in the event it does we will at least
+ // report it.
+ try {
+ send_handler_(ERROR, ncr_to_send_);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(dhcp_ddns_logger,
+ DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR).arg(ex.what());
+ }
+ }
+}
+
+void
+NameChangeSender::skipNext() {
+ if (!send_queue_.empty()) {
+ // Discards the request at the front of the queue.
+ send_queue_.pop_front();
+ }
+}
+
+void
+NameChangeSender::clearSendQueue() {
+ if (amSending()) {
+ isc_throw(NcrSenderError, "Cannot clear queue while sending");
+ }
+
+ send_queue_.clear();
+}
+
+} // namespace isc::dhcp_ddns
+} // namespace isc
diff --git a/src/lib/dhcp_ddns/ncr_io.h b/src/lib/dhcp_ddns/ncr_io.h
new file mode 100644
index 0000000..94d08f7
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_io.h
@@ -0,0 +1,671 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 NCR_IO_H
+#define NCR_IO_H
+
+/// @file ncr_io.h
+/// @brief This file defines abstract classes for exchanging NameChangeRequests.
+///
+/// These classes are used for sending and receiving requests to update DNS
+/// information for FQDNs embodied as NameChangeRequests (aka NCRs). Ultimately,
+/// NCRs must move through the following three layers in order to implement
+/// DHCP-DDNS:
+///
+/// * Application layer - the business layer which needs to
+/// transport NameChangeRequests, and is unaware of the means by which
+/// they are transported.
+///
+/// * NameChangeRequest layer - This is the layer which acts as the
+/// intermediary between the Application layer and the IO layer. It must
+/// be able to move NameChangeRequests to the IO layer as raw data and move
+/// raw data from the IO layer in the Application layer as
+/// NameChangeRequests.
+///
+/// * IO layer - the low-level layer that is directly responsible for
+/// sending and receiving data asynchronously and is supplied through
+/// other libraries. This layer is largely unaware of the nature of the
+/// data being transmitted. In other words, it doesn't know beans about
+/// NCRs.
+///
+/// The abstract classes defined here implement the latter, middle layer,
+/// the NameChangeRequest layer. There are two types of participants in this
+/// middle ground:
+///
+/// * listeners - Receive NCRs from one or more sources. The DHCP-DDNS
+/// application, (aka D2), is a listener. Listeners are embodied by the
+/// class, NameChangeListener.
+///
+/// * senders - sends NCRs to a given target. DHCP servers are senders.
+/// Senders are embodied by the class, NameChangeListener.
+///
+/// These two classes present a public interface for asynchronous
+/// communications that is independent of the IO layer mechanisms. While the
+/// type and details of the IO mechanism are not relevant to either class, it
+/// is presumed to use isc::asiolink library for asynchronous event processing.
+///
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <exceptions/exceptions.h>
+
+#include <deque>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Exception thrown if an NcrListenerError encounters a general error.
+class NcrListenerError : public isc::Exception {
+public:
+ NcrListenerError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class NcrListenerOpenError : public isc::Exception {
+public:
+ NcrListenerOpenError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO receive.
+class NcrListenerReceiveError : public isc::Exception {
+public:
+ NcrListenerReceiveError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Abstract interface for receiving NameChangeRequests.
+///
+/// NameChangeListener provides the means to:
+/// - Supply a callback to invoke upon receipt of an NCR or a listening
+/// error
+/// - Start listening using a given IOService instance to process events
+/// - Stop listening
+///
+/// It implements the high level logic flow to listen until a request arrives,
+/// invoke the implementation's handler and return to listening for the next
+/// request.
+///
+/// It provides virtual methods that allow derivations supply implementations
+/// to open the appropriate IO source, perform a listen, and close the IO
+/// source.
+///
+/// The overall design is based on a callback chain. The listener's caller (the
+/// application) supplies an "application" layer callback through which it will
+/// receive inbound NameChangeRequests. The listener derivation will supply
+/// its own callback to the IO layer to process receive events from its IO
+/// source. This is referred to as the NameChangeRequest completion handler.
+/// It is through this handler that the NameChangeRequest layer gains access
+/// to the low level IO read service results. It is expected to assemble
+/// NameChangeRequests from the inbound data and forward them to the
+/// application layer by invoking the application layer callback registered
+/// with the listener.
+///
+/// The application layer callback is structured around a nested class,
+/// RequestReceiveHandler. It consists of single, abstract operator() which
+/// accepts a result code and a pointer to a NameChangeRequest as parameters.
+/// In order to receive inbound NCRs, a caller implements a derivation of the
+/// RequestReceiveHandler and supplies an instance of this derivation to the
+/// NameChangeListener constructor. This "registers" the handler with the
+/// listener.
+///
+/// To begin listening, the caller invokes the listener's startListener()
+/// method, passing in an IOService instance. This in turn will pass the
+/// IOService into the virtual method, open(). The open method is where the
+/// listener derivation performs the steps necessary to prepare its IO source
+/// for reception (e.g. opening a socket, connecting to a database).
+///
+/// Assuming the open is successful, startListener will call receiveNext, to
+/// initiate an asynchronous receive. This method calls the virtual method,
+/// doReceive(). The listener derivation uses doReceive to instigate an IO
+/// layer asynchronous receieve passing in its IO layer callback to
+/// handle receive events from the IO source.
+///
+/// As stated earlier, the derivation's NameChangeRequest completion handler
+/// MUST invoke the application layer handler registered with the listener.
+/// This is done by passing in either a success status and a populated
+/// NameChangeRequest or an error status and an empty request into the
+/// listener's invokeRecvHandler method. This is the mechanism by which the
+/// listener's caller is handed inbound NCRs.
+class NameChangeListener {
+public:
+
+ /// @brief Defines the outcome of an asynchronous NCR receive
+ enum Result {
+ SUCCESS,
+ TIME_OUT,
+ STOPPED,
+ ERROR
+ };
+
+ /// @brief Abstract class for defining application layer receive callbacks.
+ ///
+ /// Applications which will receive NameChangeRequests must provide a
+ /// derivation of this class to the listener constructor in order to
+ /// receive NameChangeRequests.
+ class RequestReceiveHandler {
+ public:
+ /// @brief Function operator implementing a NCR receive callback.
+ ///
+ /// This method allows the application to receive the inbound
+ /// NameChangeRequests. It is intended to function as a hand off of
+ /// information and should probably not be time-consuming.
+ ///
+ /// @param result contains that receive outcome status.
+ /// @param ncr is a pointer to the newly received NameChangeRequest if
+ /// result is NameChangeListener::SUCCESS. It is indeterminate other
+ /// wise.
+ /// @throw This method MUST NOT throw.
+ virtual void operator ()(const Result result,
+ NameChangeRequestPtr& ncr) = 0;
+
+ virtual ~RequestReceiveHandler() {
+ }
+ };
+
+ /// @brief Constructor
+ ///
+ /// @param recv_handler is a pointer the application layer handler to be
+ /// invoked each time a NCR is received or a receive error occurs.
+ NameChangeListener(RequestReceiveHandler& recv_handler);
+
+ /// @brief Destructor
+ virtual ~NameChangeListener() {
+ };
+
+ /// @brief Prepares the IO for reception and initiates the first receive.
+ ///
+ /// Calls the derivation's open implementation to initialize the IO layer
+ /// source for receiving inbound requests. If successful, it starts the
+ /// first asynchronous read by receiveNext.
+ ///
+ /// @param io_service is the IOService that will handle IO event processing.
+ ///
+ /// @throw NcrListenError if the listener is already "listening" or
+ /// in the event the open or doReceive methods fail.
+ void startListening(isc::asiolink::IOService& io_service);
+
+ /// @brief Closes the IO source and stops listen logic.
+ ///
+ /// Calls the derivation's implementation of close and marks the state
+ /// as not listening.
+ void stopListening();
+
+protected:
+ /// @brief Initiates an asynchronous receive
+ ///
+ /// Sets context information to indicate that IO is in progress and invokes
+ /// the derivation's asynchronous receive method, doReceive. Note doReceive
+ /// should not be called outside this method to ensure context information
+ /// integrity.
+ ///
+ /// @throw Derivation's doReceive method may throw isc::Exception upon
+ /// error.
+ void receiveNext();
+
+ /// @brief Calls the NCR receive handler registered with the listener.
+ ///
+ /// This is the hook by which the listener's caller's NCR receive handler
+ /// is called. This method MUST be invoked by the derivation's
+ /// implementation of doReceive.
+ ///
+ /// NOTE:
+ /// The handler invoked by this method MUST NOT THROW. The handler is
+ /// at application level and should trap and handle any errors at
+ /// that level, rather than throw exceptions. If an error has occurred
+ /// prior to invoking the handler, it will be expressed in terms a failed
+ /// result being passed to the handler, not a throw. Therefore any
+ /// exceptions at the handler level are application issues and should be
+ /// dealt with at that level.
+ ///
+ /// This method does wrap the handler invocation within a try-catch
+ /// block as a fail-safe. The exception will be logged but the
+ /// receive logic will continue. What this implies is that continued
+ /// operation may or may not succeed as the application has violated
+ /// the interface contract.
+ ///
+ /// @param result contains that receive outcome status.
+ /// @param ncr is a pointer to the newly received NameChangeRequest if
+ /// result is NameChangeListener::SUCCESS. It is indeterminate other
+ /// wise.
+ void invokeRecvHandler(const Result result, NameChangeRequestPtr& ncr);
+
+ /// @brief Abstract method which opens the IO source for reception.
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// prepare the IO source to receive requests.
+ ///
+ /// @param io_service is the IOService that process IO events.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void open(isc::asiolink::IOService& io_service) = 0;
+
+ /// @brief Abstract method which closes the IO source.
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// "close" the IO source.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void close() = 0;
+
+ /// @brief Initiates an IO layer asynchronous read.
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// initiate an asynchronous read of the IO source with the
+ /// derivation's IO layer handler as the IO completion callback.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void doReceive() = 0;
+
+public:
+ /// @brief Returns true if the listener is listening, false otherwise.
+ ///
+ /// A true value indicates that the IO source has been opened successfully,
+ /// and that receive loop logic is active. This implies that closing the
+ /// IO source will interrupt that operation, resulting in a callback
+ /// invocation.
+ bool amListening() const {
+ return (listening_);
+ }
+
+ /// @brief Returns true if the listener has an IO call in progress.
+ ///
+ /// A true value indicates that the listener has an asynchronous IO in
+ /// progress which will complete at some point in the future. Completion
+ /// of the call will invoke the registered callback. It is important to
+ /// understand that the listener and its related objects should not be
+ /// deleted while there is an IO call pending. This can result in the
+ /// IO service attempting to invoke methods on objects that are no longer
+ /// valid.
+ bool isIoPending() const {
+ return (io_pending_);
+ }
+
+private:
+ /// @brief Sets the listening indicator to the given value.
+ ///
+ /// Note, this method is private as it is used the base class is solely
+ /// responsible for managing the state.
+ ///
+ /// @param value is the new value to assign to the indicator.
+ void setListening(bool value) {
+ listening_ = value;
+ }
+
+ /// @brief Indicates if the listener is in listening mode.
+ bool listening_;
+
+ /// @brief Indicates that listener has an async IO pending completion.
+ bool io_pending_;
+
+ /// @brief Application level NCR receive completion handler.
+ RequestReceiveHandler& recv_handler_;
+};
+
+/// @brief Defines a smart pointer to an instance of a listener.
+typedef boost::shared_ptr<NameChangeListener> NameChangeListenerPtr;
+
+
+/// @brief Thrown when a NameChangeSender encounters an error.
+class NcrSenderError : public isc::Exception {
+public:
+ NcrSenderError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class NcrSenderOpenError : public isc::Exception {
+public:
+ NcrSenderOpenError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO send.
+class NcrSenderQueueFull : public isc::Exception {
+public:
+ NcrSenderQueueFull(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO send.
+class NcrSenderSendError : public isc::Exception {
+public:
+ NcrSenderSendError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Abstract interface for sending NameChangeRequests.
+///
+/// NameChangeSender provides the means to:
+/// - Supply a callback to invoke upon completing the delivery of an NCR or a
+/// send error
+/// - Start sending using a given IOService instance to process events
+/// - Queue NCRs for delivery
+/// - Stop sending
+///
+/// It implements the high level logic flow to queue requests for delivery,
+/// and ship them one at a time, waiting for the send to complete prior to
+/// sending the next request in the queue. If a send fails, the request
+/// will remain at the front of queue and will be the send will be retried
+/// endlessly unless the caller dequeues the request. Note, it is presumed that
+/// a send failure is some form of IO error such as loss of connectivity and
+/// not a message content error. It should not be possible to queue an invalid
+/// message.
+///
+/// It should be noted that once a request is placed onto the send queue it
+/// will remain there until one of three things occur:
+/// * It is successfully delivered
+/// * @c NameChangeSender::skipNext() is called
+/// * @c NameChangeSender::clearSendQueue() is called
+///
+/// The queue contents are preserved across start and stop listening
+/// transitions. This is to provide for error recovery without losing
+/// undelivered requests.
+
+/// It provides virtual methods so derivations may supply implementations to
+/// open the appropriate IO sink, perform a send, and close the IO sink.
+///
+/// The overall design is based on a callback chain. The sender's caller (the
+/// application) supplies an "application" layer callback through which it will
+/// be given send completion notifications. The sender derivation will employ
+/// its own callback at the IO layer to process send events from its IO sink.
+/// This callback is expected to forward the outcome of each asynchronous send
+/// to the application layer by invoking the application layer callback
+/// registered with the sender.
+///
+/// The application layer callback is structured around a nested class,
+/// RequestSendHandler. It consists of single, abstract operator() which
+/// accepts a result code and a pointer to a NameChangeRequest as parameters.
+/// In order to receive send completion notifications, a caller implements a
+/// derivation of the RequestSendHandler and supplies an instance of this
+/// derivation to the NameChangeSender constructor. This "registers" the
+/// handler with the sender.
+///
+/// To begin sending, the caller invokes the listener's startSending()
+/// method, passing in an IOService instance. This in turn will pass the
+/// IOService into the virtual method, open(). The open method is where the
+/// sender derivation performs the steps necessary to prepare its IO sink for
+/// output (e.g. opening a socket, connecting to a database). At this point,
+/// the sender is ready to send messages.
+///
+/// In order to send a request, the application layer invokes the sender
+/// method, sendRequest(), passing in the NameChangeRequest to send. This
+/// method places the request onto the back of the send queue, and then invokes
+/// the sender method, sendNext().
+///
+/// If there is already a send in progress when sendNext() is called, the method
+/// will return immediately rather than initiate the next send. This is to
+/// ensure that sends are processed sequentially.
+///
+/// If there is not a send in progress and the send queue is not empty,
+/// the sendNext method will pass the NCR at the front of the send queue into
+/// the virtual doSend() method.
+///
+/// The sender derivation uses this doSend() method to instigate an IO layer
+/// asynchronous send with its IO layer callback to handle send events from its
+/// IO sink.
+///
+/// As stated earlier, the derivation's IO layer callback MUST invoke the
+/// application layer handler registered with the sender. This is done by
+/// passing in a status indicating the outcome of the send into the sender's
+/// invokeSendHandler method. This is the mechanism by which the sender's
+/// caller is handed outbound notifications.
+
+/// After invoking the application layer handler, the invokeSendHandler method
+/// will call the sendNext() method to initiate the next send. This ensures
+/// that requests continue to dequeue and ship.
+///
+class NameChangeSender {
+public:
+
+ /// @brief Defines the type used for the request send queue.
+ typedef std::deque<NameChangeRequestPtr> SendQueue;
+
+ /// @brief Defines a default maximum number of entries in the send queue.
+ static const size_t MAX_QUEUE_DEFAULT = 1024;
+
+ /// @brief Defines the outcome of an asynchronous NCR send.
+ enum Result {
+ SUCCESS,
+ TIME_OUT,
+ STOPPED,
+ ERROR
+ };
+
+ /// @brief Abstract class for defining application layer send callbacks.
+ ///
+ /// Applications which will send NameChangeRequests must provide a
+ /// derivation of this class to the sender constructor in order to
+ /// receive send outcome notifications.
+ class RequestSendHandler {
+ public:
+ /// @brief Function operator implementing a NCR send callback.
+ ///
+ /// This method allows the application to receive the outcome of
+ /// each send. It is intended to function as a hand off of information
+ /// and should probably not be time-consuming.
+ ///
+ /// @param result contains that send outcome status.
+ /// @param ncr is a pointer to the NameChangeRequest that was
+ /// delivered (or attempted).
+ ///
+ /// @throw This method MUST NOT throw.
+ virtual void operator ()(const Result result,
+ NameChangeRequestPtr& ncr) = 0;
+
+ virtual ~RequestSendHandler() {
+ }
+ };
+
+ /// @brief Constructor
+ ///
+ /// @param send_handler is a pointer the application layer handler to be
+ /// invoked each time a NCR send attempt completes.
+ /// @param send_queue_max is the maximum number of entries allowed in the
+ /// send queue. Once the maximum number is reached, all calls to
+ /// sendRequest will fail with an exception.
+ NameChangeSender(RequestSendHandler& send_handler,
+ size_t send_queue_max = MAX_QUEUE_DEFAULT);
+
+ /// @brief Destructor
+ virtual ~NameChangeSender() {
+ }
+
+ /// @brief Prepares the IO for transmission.
+ ///
+ /// Calls the derivation's open implementation to initialize the IO layer
+ /// sink for sending outbound requests.
+ ///
+ /// @param io_service is the IOService that will handle IO event processing.
+ ///
+ /// @throw NcrSenderError if the sender is already "sending" or
+ /// NcrSenderOpenError if the open fails.
+ void startSending(isc::asiolink::IOService & io_service);
+
+ /// @brief Closes the IO sink and stops send logic.
+ ///
+ /// Calls the derivation's implementation of close and marks the state
+ /// as not sending.
+ void stopSending();
+
+ /// @brief Queues the given request to be sent.
+ ///
+ /// The given request is placed at the back of the send queue and then
+ /// sendNext is invoked.
+ ///
+ /// @param ncr is the NameChangeRequest to send.
+ ///
+ /// @throw NcrSenderError if the sender is not in sending state or
+ /// the request is empty; NcrSenderQueueFull if the send queue has reached
+ /// capacity.
+ void sendRequest(NameChangeRequestPtr& ncr);
+
+protected:
+ /// @brief Dequeues and sends the next request on the send queue.
+ ///
+ /// If there is already a send in progress just return. If there is not
+ /// a send in progress and the send queue is not empty the grab the next
+ /// message on the front of the queue and call doSend().
+ ///
+ void sendNext();
+
+ /// @brief Calls the NCR send completion handler registered with the
+ /// sender.
+ ///
+ /// This is the hook by which the sender's caller's NCR send completion
+ /// handler is called. This method MUST be invoked by the derivation's
+ /// implementation of doSend. Note that if the send was a success,
+ /// the entry at the front of the queue is removed from the queue.
+ /// If not we leave it there so we can retry it. After we invoke the
+ /// handler we clear the pending ncr value and queue up the next send.
+ ///
+ /// NOTE:
+ /// The handler invoked by this method MUST NOT THROW. The handler is
+ /// application level logic and should trap and handle any errors at
+ /// that level, rather than throw exceptions. If IO errors have occurred
+ /// prior to invoking the handler, they are expressed in terms a failed
+ /// result being passed to the handler. Therefore any exceptions at the
+ /// handler level are application issues and should be dealt with at that
+ /// level.
+ ///
+ /// This method does wrap the handler invocation within a try-catch
+ /// block as a fail-safe. The exception will be logged but the
+ /// send logic will continue. What this implies is that continued
+ /// operation may or may not succeed as the application has violated
+ /// the interface contract.
+ ///
+ /// @param result contains that send outcome status.
+ void invokeSendHandler(const NameChangeSender::Result result);
+
+ /// @brief Abstract method which opens the IO sink for transmission.
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// prepare the IO sink to send requests.
+ ///
+ /// @param io_service is the IOService that process IO events.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void open(isc::asiolink::IOService& io_service) = 0;
+
+ /// @brief Abstract method which closes the IO sink.
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// "close" the IO sink.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void close() = 0;
+
+ /// @brief Initiates an IO layer asynchronous send
+ ///
+ /// The derivation uses this method to perform the steps needed to
+ /// initiate an asynchronous send through the IO sink of the given NCR.
+ ///
+ /// @param ncr is a pointer to the NameChangeRequest to send.
+ /// derivation's IO layer handler as the IO completion callback.
+ ///
+ /// @throw If the implementation encounters an error it MUST
+ /// throw it as an isc::Exception or derivative.
+ virtual void doSend(NameChangeRequestPtr& ncr) = 0;
+
+public:
+ /// @brief Removes the request at the front of the send queue
+ ///
+ /// This method can be used to avoid further retries of a failed
+ /// send. It is provided primarily as a just-in-case measure. Since
+ /// a failed send results in the same request being retried continuously
+ /// this method makes it possible to remove that entry, causing the
+ /// subsequent entry in the queue to be attempted on the next send.
+ /// It is presumed that sends will only fail due to some sort of
+ /// communications issue. In the unlikely event that a request is
+ /// somehow tainted and causes an send failure based on its content,
+ /// this method provides a means to remove th message.
+ void skipNext();
+
+ /// @brief Flushes all entries in the send queue
+ ///
+ /// This method can be used to discard all of the NCRs currently in the
+ /// the send queue. Note it may not be called while the sender is in
+ /// the sending state.
+ /// @throw NcrSenderError if called and sender is in sending state.
+ void clearSendQueue();
+
+ /// @brief Returns true if the sender is in send mode, false otherwise.
+ ///
+ /// A true value indicates that the IO sink has been opened successfully,
+ /// and that send loop logic is active.
+ bool amSending() const {
+ return (sending_);
+ }
+
+ /// @brief Returns true when a send is in progress.
+ ///
+ /// A true value indicates that a request is actively in the process of
+ /// being delivered.
+ bool isSendInProgress() const {
+ return ((ncr_to_send_) ? true : false);
+ }
+
+ /// @brief Returns the maximum number of entries allowed in the send queue.
+ size_t getQueueMaxSize() const {
+ return (send_queue_max_);
+ }
+
+ /// @brief Returns the number of entries currently in the send queue.
+ size_t getQueueSize() const {
+ return (send_queue_.size());
+ }
+
+private:
+ /// @brief Sets the sending indicator to the given value.
+ ///
+ /// Note, this method is private as it is used the base class is solely
+ /// responsible for managing the state.
+ ///
+ /// @param value is the new value to assign to the indicator.
+ void setSending(bool value) {
+ sending_ = value;
+ }
+
+ /// @brief Boolean indicator which tracks sending status.
+ bool sending_;
+
+ /// @brief A pointer to regisetered send completion handler.
+ RequestSendHandler& send_handler_;
+
+ /// @brief Maximum number of entries permitted in the send queue.
+ size_t send_queue_max_;
+
+ /// @brief Queue of the requests waiting to be sent.
+ SendQueue send_queue_;
+
+ /// @brief Pointer to the request which is in the process of being sent.
+ NameChangeRequestPtr ncr_to_send_;
+};
+
+/// @brief Defines a smart pointer to an instance of a sender.
+typedef boost::shared_ptr<NameChangeSender> NameChangeSenderPtr;
+
+} // namespace isc::dhcp_ddns
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcp_ddns/ncr_msg.cc b/src/lib/dhcp_ddns/ncr_msg.cc
new file mode 100644
index 0000000..3c7a18e
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_msg.cc
@@ -0,0 +1,540 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <dhcp_ddns/ncr_msg.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+#include <cryptolink/cryptolink.h>
+
+#include <botan/sha2_32.h>
+
+#include <sstream>
+#include <limits>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/********************************* D2Dhcid ************************************/
+
+D2Dhcid::D2Dhcid() {
+}
+
+D2Dhcid::D2Dhcid(const std::string& data) {
+ fromStr(data);
+}
+
+D2Dhcid::D2Dhcid(const isc::dhcp::DUID& duid,
+ const std::vector<uint8_t>& wire_fqdn) {
+ fromDUID(duid, wire_fqdn);
+}
+
+
+void
+D2Dhcid::fromStr(const std::string& data) {
+ bytes_.clear();
+ try {
+ isc::util::encode::decodeHex(data, bytes_);
+ } catch (const isc::Exception& ex) {
+ isc_throw(NcrMessageError, "Invalid data in Dhcid:" << ex.what());
+ }
+}
+
+std::string
+D2Dhcid::toStr() const {
+ return (isc::util::encode::encodeHex(bytes_));
+}
+
+void
+D2Dhcid::fromDUID(const isc::dhcp::DUID& duid,
+ const std::vector<uint8_t>& wire_fqdn) {
+ // DHCID created from DUID starts with two bytes representing
+ // a type of the identifier. The value of 0x0002 indicates that
+ // DHCID has been created from DUID. The 3rd byte is equal to 1
+ // which indicates that the SHA-256 algorithm is used to create
+ // a DHCID digest. This value is called digest-type.
+ static uint8_t dhcid_header[] = { 0x00, 0x02, 0x01 };
+
+ // We get FQDN in the wire format, so we don't know if it is
+ // valid. It is caller's responsibility to make sure it is in
+ // the valid format. Here we just make sure it is not empty.
+ if (wire_fqdn.empty()) {
+ isc_throw(isc::dhcp_ddns::NcrMessageError,
+ "empty FQDN used to create DHCID");
+ }
+
+ // Get the wire representation of the DUID.
+ std::vector<uint8_t> data = duid.getDuid();
+ // It should be DUID class responsibility to validate the DUID
+ // but let's be on the safe side here and make sure that empty
+ // DUID is not returned.
+ if (data.empty()) {
+ isc_throw(isc::dhcp_ddns::NcrMessageError,
+ "empty DUID used to create DHCID");
+ }
+
+ // Append FQDN in the wire format.
+ data.insert(data.end(), wire_fqdn.begin(), wire_fqdn.end());
+
+ // Use the DUID and FQDN to compute the digest (see RFC4701, section 3).
+
+ // The getCryptoLink is a common function to initialize the Botan library.
+ cryptolink::CryptoLink::getCryptoLink();
+ // @todo The code below, which calculates the SHA-256 may need to be moved
+ // to the cryptolink library.
+ Botan::SecureVector<Botan::byte> secure;
+ try {
+ Botan::SHA_256 sha;
+ // We have checked already that the DUID and FQDN aren't empty
+ // so it is safe to assume that the data buffer is not empty.
+ secure = sha.process(static_cast<const Botan::byte*>(&data[0]),
+ data.size());
+ } catch (const std::exception& ex) {
+ isc_throw(isc::dhcp_ddns::NcrMessageError,
+ "error while generating DHCID from DUID: "
+ << ex.what());
+ }
+
+ // The exception unsafe part is finished, so we can finally replace
+ // the contents of bytes_.
+ bytes_.assign(dhcid_header, dhcid_header + sizeof(dhcid_header));
+ bytes_.insert(bytes_.end(), secure.begin(), secure.end());
+}
+
+
+/**************************** NameChangeRequest ******************************/
+
+NameChangeRequest::NameChangeRequest()
+ : change_type_(CHG_ADD), forward_change_(false),
+ reverse_change_(false), fqdn_(""), ip_address_(""),
+ dhcid_(), lease_expires_on_(), lease_length_(0), status_(ST_NEW) {
+}
+
+NameChangeRequest::NameChangeRequest(const NameChangeType change_type,
+ const bool forward_change, const bool reverse_change,
+ const std::string& fqdn, const std::string& ip_address,
+ const D2Dhcid& dhcid,
+ const uint64_t lease_expires_on,
+ const uint32_t lease_length)
+ : change_type_(change_type), forward_change_(forward_change),
+ reverse_change_(reverse_change), fqdn_(fqdn), ip_address_(ip_address),
+ dhcid_(dhcid), lease_expires_on_(lease_expires_on),
+ lease_length_(lease_length), status_(ST_NEW) {
+
+ // Validate the contents. This will throw a NcrMessageError if anything
+ // is invalid.
+ validateContent();
+}
+
+NameChangeRequestPtr
+NameChangeRequest::fromFormat(const NameChangeFormat format,
+ isc::util::InputBuffer& buffer) {
+ // Based on the format requested, pull the marshalled request from
+ // InputBuffer and pass it into the appropriate format-specific factory.
+ NameChangeRequestPtr ncr;
+ switch (format) {
+ case FMT_JSON: {
+ try {
+ // Get the length of the JSON text.
+ size_t len = buffer.readUint16();
+
+ // Read the text from the buffer into a vector.
+ std::vector<uint8_t> vec;
+ buffer.readVector(vec, len);
+
+ // Turn the vector into a string.
+ std::string string_data(vec.begin(), vec.end());
+
+ // Pass the string of JSON text into JSON factory to create the
+ // NameChangeRequest instance. Note the factory may throw
+ // NcrMessageError.
+ ncr = NameChangeRequest::fromJSON(string_data);
+ } catch (isc::util::InvalidBufferPosition& ex) {
+ // Read error accessing data in InputBuffer.
+ isc_throw(NcrMessageError, "fromFormat: buffer read error: "
+ << ex.what());
+ }
+
+ break;
+ }
+ default:
+ // Programmatic error, shouldn't happen.
+ isc_throw(NcrMessageError, "fromFormat - invalid format");
+ break;
+ }
+
+ return (ncr);
+}
+
+void
+NameChangeRequest::toFormat(const NameChangeFormat format,
+ isc::util::OutputBuffer& buffer) const {
+ // Based on the format requested, invoke the appropriate format handler
+ // which will marshal this request's contents into the OutputBuffer.
+ switch (format) {
+ case FMT_JSON: {
+ // Invoke toJSON to create a JSON text of this request's contents.
+ std::string json = toJSON();
+ uint16_t length = json.size();
+
+ // Write the length of the JSON text to the OutputBuffer first, then
+ // write the JSON text itself.
+ buffer.writeUint16(length);
+ buffer.writeData(json.c_str(), length);
+ break;
+ }
+ default:
+ // Programmatic error, shouldn't happen.
+ isc_throw(NcrMessageError, "toFormat - invalid format");
+ break;
+ }
+}
+
+NameChangeRequestPtr
+NameChangeRequest::fromJSON(const std::string& json) {
+ // This method leverages the existing JSON parsing provided by isc::data
+ // library. Should this prove to be a performance issue, it may be that
+ // lighter weight solution would be appropriate.
+
+ // Turn the string of JSON text into an Element set.
+ isc::data::ElementPtr elements;
+ try {
+ elements = isc::data::Element::fromJSON(json);
+ } catch (isc::data::JSONError& ex) {
+ isc_throw(NcrMessageError,
+ "Malformed NameChangeRequest JSON: " << ex.what());
+ }
+
+ // Get a map of the Elements, keyed by element name.
+ ElementMap element_map = elements->mapValue();
+ isc::data::ConstElementPtr element;
+
+ // Use default constructor to create a "blank" NameChangeRequest.
+ NameChangeRequestPtr ncr(new NameChangeRequest());
+
+ // For each member of NameChangeRequest, find its element in the map and
+ // call the appropriate Element-based setter. These setters may throw
+ // NcrMessageError if the given Element is the wrong type or its data
+ // content is lexically invalid. If the element is NOT found in the
+ // map, getElement will throw NcrMessageError indicating the missing
+ // member. Currently there are no optional values.
+ element = ncr->getElement("change_type", element_map);
+ ncr->setChangeType(element);
+
+ element = ncr->getElement("forward_change", element_map);
+ ncr->setForwardChange(element);
+
+ element = ncr->getElement("reverse_change", element_map);
+ ncr->setReverseChange(element);
+
+ element = ncr->getElement("fqdn", element_map);
+ ncr->setFqdn(element);
+
+ element = ncr->getElement("ip_address", element_map);
+ ncr->setIpAddress(element);
+
+ element = ncr->getElement("dhcid", element_map);
+ ncr->setDhcid(element);
+
+ element = ncr->getElement("lease_expires_on", element_map);
+ ncr->setLeaseExpiresOn(element);
+
+ element = ncr->getElement("lease_length", element_map);
+ ncr->setLeaseLength(element);
+
+ // All members were in the Element set and were correct lexically. Now
+ // validate the overall content semantically. This will throw an
+ // NcrMessageError if anything is amiss.
+ ncr->validateContent();
+
+ // Everything is valid, return the new instance.
+ return (ncr);
+}
+
+std::string
+NameChangeRequest::toJSON() const {
+ // Create a JSON string of this request's contents. Note that this method
+ // does NOT use the isc::data library as generating the output is straight
+ // forward.
+ std::ostringstream stream;
+
+ stream << "{\"change_type\":" << getChangeType() << ","
+ << "\"forward_change\":"
+ << (isForwardChange() ? "true" : "false") << ","
+ << "\"reverse_change\":"
+ << (isReverseChange() ? "true" : "false") << ","
+ << "\"fqdn\":\"" << getFqdn() << "\","
+ << "\"ip_address\":\"" << getIpAddress() << "\","
+ << "\"dhcid\":\"" << getDhcid().toStr() << "\","
+ << "\"lease_expires_on\":\"" << getLeaseExpiresOnStr() << "\","
+ << "\"lease_length\":" << getLeaseLength() << "}";
+
+ return (stream.str());
+}
+
+
+void
+NameChangeRequest::validateContent() {
+ //@todo This is an initial implementation which provides a minimal amount
+ // of validation. FQDN, DHCID, and IP Address members are all currently
+ // strings, these may be replaced with richer classes.
+ if (fqdn_ == "") {
+ isc_throw(NcrMessageError, "FQDN cannot be blank");
+ }
+
+ // Validate IP Address.
+ try {
+ isc::asiolink::IOAddress io_addr(ip_address_);
+ } catch (const isc::asiolink::IOError& ex) {
+ isc_throw(NcrMessageError,
+ "Invalid ip address string for ip_address: " << ip_address_);
+ }
+
+ // Validate the DHCID.
+ if (dhcid_.getBytes().size() == 0) {
+ isc_throw(NcrMessageError, "DHCID cannot be blank");
+ }
+
+ // Ensure the request specifies at least one direction to update.
+ if (!forward_change_ && !reverse_change_) {
+ isc_throw(NcrMessageError,
+ "Invalid Request, forward and reverse flags are both false");
+ }
+}
+
+isc::data::ConstElementPtr
+NameChangeRequest::getElement(const std::string& name,
+ const ElementMap& element_map) const {
+ // Look for "name" in the element map.
+ ElementMap::const_iterator it = element_map.find(name);
+ if (it == element_map.end()) {
+ // Didn't find the element, so throw.
+ isc_throw(NcrMessageError,
+ "NameChangeRequest value missing for: " << name );
+ }
+
+ // Found the element, return it.
+ return (it->second);
+}
+
+void
+NameChangeRequest::setChangeType(const NameChangeType value) {
+ change_type_ = value;
+}
+
+
+void
+NameChangeRequest::setChangeType(isc::data::ConstElementPtr element) {
+ long raw_value = -1;
+ try {
+ // Get the element's integer value.
+ raw_value = element->intValue();
+ } catch (isc::data::TypeError& ex) {
+ // We expect a integer Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for change_type: " << ex.what());
+ }
+
+ if ((raw_value != CHG_ADD) && (raw_value != CHG_REMOVE)) {
+ // Value is not a valid change type.
+ isc_throw(NcrMessageError,
+ "Invalid data value for change_type: " << raw_value);
+ }
+
+ // Good to go, make the assignment.
+ setChangeType(static_cast<NameChangeType>(raw_value));
+}
+
+void
+NameChangeRequest::setForwardChange(const bool value) {
+ forward_change_ = value;
+}
+
+void
+NameChangeRequest::setForwardChange(isc::data::ConstElementPtr element) {
+ bool value;
+ try {
+ // Get the element's boolean value.
+ value = element->boolValue();
+ } catch (isc::data::TypeError& ex) {
+ // We expect a boolean Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for forward_change :" << ex.what());
+ }
+
+ // Good to go, make the assignment.
+ setForwardChange(value);
+}
+
+void
+NameChangeRequest::setReverseChange(const bool value) {
+ reverse_change_ = value;
+}
+
+void
+NameChangeRequest::setReverseChange(isc::data::ConstElementPtr element) {
+ bool value;
+ try {
+ // Get the element's boolean value.
+ value = element->boolValue();
+ } catch (isc::data::TypeError& ex) {
+ // We expect a boolean Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for reverse_change :" << ex.what());
+ }
+
+ // Good to go, make the assignment.
+ setReverseChange(value);
+}
+
+
+void
+NameChangeRequest::setFqdn(isc::data::ConstElementPtr element) {
+ setFqdn(element->stringValue());
+}
+
+void
+NameChangeRequest::setFqdn(const std::string& value) {
+ fqdn_ = value;
+}
+
+void
+NameChangeRequest::setIpAddress(const std::string& value) {
+ ip_address_ = value;
+}
+
+
+void
+NameChangeRequest::setIpAddress(isc::data::ConstElementPtr element) {
+ setIpAddress(element->stringValue());
+}
+
+
+void
+NameChangeRequest::setDhcid(const std::string& value) {
+ dhcid_.fromStr(value);
+}
+
+void
+NameChangeRequest::setDhcid(isc::data::ConstElementPtr element) {
+ setDhcid(element->stringValue());
+}
+
+std::string
+NameChangeRequest::getLeaseExpiresOnStr() const {
+ return (isc::util::timeToText64(lease_expires_on_));
+}
+
+void
+NameChangeRequest::setLeaseExpiresOn(const std::string& value) {
+ try {
+ lease_expires_on_ = isc::util::timeFromText64(value);
+ } catch(...) {
+ // We were given an invalid string, so throw.
+ isc_throw(NcrMessageError,
+ "Invalid date-time string: [" << value << "]");
+ }
+
+}
+
+void NameChangeRequest::setLeaseExpiresOn(isc::data::ConstElementPtr element) {
+ // Pull out the string value and pass it into the string setter.
+ setLeaseExpiresOn(element->stringValue());
+}
+
+void
+NameChangeRequest::setLeaseLength(const uint32_t value) {
+ lease_length_ = value;
+}
+
+void
+NameChangeRequest::setLeaseLength(isc::data::ConstElementPtr element) {
+ long value = -1;
+ try {
+ // Get the element's integer value.
+ value = element->intValue();
+ } catch (isc::data::TypeError& ex) {
+ // We expect a integer Element type, don't have one.
+ isc_throw(NcrMessageError,
+ "Wrong data type for lease_length: " << ex.what());
+ }
+
+ // Make sure we the range is correct and value is positive.
+ if (value > std::numeric_limits<uint32_t>::max()) {
+ isc_throw(NcrMessageError, "lease_length value " << value <<
+ "is too large for unsigned 32-bit integer.");
+ }
+ if (value < 0) {
+ isc_throw(NcrMessageError, "lease_length value " << value <<
+ "is negative. It must greater than or equal to zero ");
+ }
+
+ // Good to go, make the assignment.
+ setLeaseLength(static_cast<uint32_t>(value));
+}
+
+void
+NameChangeRequest::setStatus(const NameChangeStatus value) {
+ status_ = value;
+}
+
+std::string
+NameChangeRequest::toText() const {
+ std::ostringstream stream;
+
+ stream << "Type: " << static_cast<int>(change_type_) << " (";
+ switch (change_type_) {
+ case CHG_ADD:
+ stream << "CHG_ADD)\n";
+ break;
+ case CHG_REMOVE:
+ stream << "CHG_REMOVE)\n";
+ break;
+ default:
+ // Shouldn't be possible.
+ stream << "Invalid Value\n";
+ }
+
+ stream << "Forward Change: " << (forward_change_ ? "yes" : "no")
+ << std::endl
+ << "Reverse Change: " << (reverse_change_ ? "yes" : "no")
+ << std::endl
+ << "FQDN: [" << fqdn_ << "]" << std::endl
+ << "IP Address: [" << ip_address_ << "]" << std::endl
+ << "DHCID: [" << dhcid_.toStr() << "]" << std::endl
+ << "Lease Expires On: " << getLeaseExpiresOnStr() << std::endl
+ << "Lease Length: " << lease_length_ << std::endl;
+
+ return (stream.str());
+}
+
+bool
+NameChangeRequest::operator == (const NameChangeRequest& other) {
+ return ((change_type_ == other.change_type_) &&
+ (forward_change_ == other.forward_change_) &&
+ (reverse_change_ == other.reverse_change_) &&
+ (fqdn_ == other.fqdn_) &&
+ (ip_address_ == other.ip_address_) &&
+ (dhcid_ == other.dhcid_) &&
+ (lease_expires_on_ == other.lease_expires_on_) &&
+ (lease_length_ == other.lease_length_));
+}
+
+bool
+NameChangeRequest::operator != (const NameChangeRequest& other) {
+ return (!(*this == other));
+}
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/dhcp_ddns/ncr_msg.h b/src/lib/dhcp_ddns/ncr_msg.h
new file mode 100644
index 0000000..ceb8715
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_msg.h
@@ -0,0 +1,525 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 NCR_MSG_H
+#define NCR_MSG_H
+
+/// @file ncr_msg.h
+/// @brief This file provides the classes needed to embody, compose, and
+/// decompose DNS update requests that are sent by DHCP-DDNS clients to
+/// DHCP-DDNS. These requests are referred to as NameChangeRequests.
+
+#include <cc/data.h>
+#include <dhcp/duid.h>
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+#include <util/time_utilities.h>
+
+#include <time.h>
+#include <string>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Exception thrown when NameChangeRequest marshalling error occurs.
+class NcrMessageError : public isc::Exception {
+public:
+ NcrMessageError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Defines the types of DNS updates that can be requested.
+enum NameChangeType {
+ CHG_ADD,
+ CHG_REMOVE
+};
+
+/// @brief Defines the runtime processing status values for requests.
+enum NameChangeStatus {
+ ST_NEW,
+ ST_PENDING,
+ ST_COMPLETED,
+ ST_FAILED
+};
+
+/// @brief Defines the list of data wire formats supported.
+enum NameChangeFormat {
+ FMT_JSON
+};
+
+/// @brief Container class for handling the DHCID value within a
+/// NameChangeRequest. It provides conversion to and from string for JSON
+/// formatting, but stores the data internally as unsigned bytes.
+class D2Dhcid {
+public:
+ /// @brief Default constructor
+ D2Dhcid();
+
+ /// @brief Constructor - Creates a new instance, populated by converting
+ /// a given string of digits into an array of unsigned bytes.
+ ///
+ /// @param data is a string of hexadecimal digits. The format is simply
+ /// a contiguous stream of digits, with no delimiters. For example a string
+ /// containing "14A3" converts to a byte array containing: 0x14, 0xA3.
+ ///
+ /// @throw NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ D2Dhcid(const std::string& data);
+
+ /// @brief Constructor, creates an instance of the @c D2Dhcid from the
+ /// @c isc::dhcp::DUID.
+ ///
+ /// @param duid An object representing DUID.
+ /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+ D2Dhcid(const isc::dhcp::DUID& duid,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Returns the DHCID value as a string of hexadecimal digits.
+ ///
+ /// @return a string containing a contiguous stream of digits.
+ std::string toStr() const;
+
+ /// @brief Sets the DHCID value based on the given string.
+ ///
+ /// @param data is a string of hexadecimal digits. The format is simply
+ /// a contiguous stream of digits, with no delimiters. For example a string
+ /// containing "14A3" converts to a byte array containing: 0x14, 0xA3.
+ ///
+ /// @throw NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ void fromStr(const std::string& data);
+
+ /// @brief Sets the DHCID value based on the DUID and FQDN.
+ ///
+ /// This function requires that the FQDN conforms to the section 3.5
+ /// of the RFC4701, which says that the FQDN must be in lowercase.
+ /// This function doesn't validate if it really converted.
+ ///
+ /// @param duid A @c isc::dhcp::DUID object encapsulating DUID.
+ /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+ void fromDUID(const isc::dhcp::DUID& duid,
+ const std::vector<uint8_t>& wire_fqdn);
+
+ /// @brief Returns a reference to the DHCID byte vector.
+ ///
+ /// @return a reference to the vector.
+ const std::vector<uint8_t>& getBytes() {
+ return (bytes_);
+ }
+
+ /// @brief Compares two D2Dhcids for equality
+ bool operator==(const D2Dhcid& other) const {
+ return (this->bytes_ == other.bytes_);
+ }
+
+ /// @brief Compares two D2Dhcids for inequality
+ bool operator!=(const D2Dhcid& other) const {
+ return (this->bytes_ != other.bytes_);
+ }
+
+ /// @brief Compares two D2Dhcids lexcially
+ bool operator<(const D2Dhcid& other) const {
+ return (this->bytes_ < other.bytes_);
+ }
+
+private:
+ /// @brief Storage for the DHCID value in unsigned bytes.
+ std::vector<uint8_t> bytes_;
+};
+
+class NameChangeRequest;
+/// @brief Defines a pointer to a NameChangeRequest.
+typedef boost::shared_ptr<NameChangeRequest> NameChangeRequestPtr;
+
+/// @brief Defines a map of Elements, keyed by their string name.
+typedef std::map<std::string, isc::data::ConstElementPtr> ElementMap;
+
+/// @brief Represents a DHCP-DDNS client request.
+/// This class is used by DHCP-DDNS clients (e.g. DHCP4, DHCP6) to
+/// request DNS updates. Each message contains a single DNS change (either an
+/// add/update or a remove) for a single FQDN. It provides marshalling services
+/// for moving instances to and from the wire. Currently, the only format
+/// supported is JSON, however the class provides an interface such that other
+/// formats can be readily supported.
+class NameChangeRequest {
+public:
+ /// @brief Default Constructor.
+ NameChangeRequest();
+
+ /// @brief Constructor. Full constructor, which provides parameters for
+ /// all of the class members, except status.
+ ///
+ /// @param change_type the type of change (Add or Update)
+ /// @param forward_change indicates if this change should be sent to forward
+ /// DNS servers.
+ /// @param reverse_change indicates if this change should be sent to reverse
+ /// DNS servers.
+ /// @param fqdn the domain name whose pointer record(s) should be
+ /// updated.
+ /// @param ip_address the ip address leased to the given FQDN.
+ /// @param dhcid the lease client's unique DHCID.
+ /// @param lease_expires_on a timestamp containing the date/time the lease
+ /// expires.
+ /// @param lease_length the amount of time in seconds for which the
+ /// lease is valid (TTL).
+ NameChangeRequest(const NameChangeType change_type,
+ const bool forward_change, const bool reverse_change,
+ const std::string& fqdn, const std::string& ip_address,
+ const D2Dhcid& dhcid,
+ const uint64_t lease_expires_on,
+ const uint32_t lease_length);
+
+ /// @brief Static method for creating a NameChangeRequest from a
+ /// buffer containing a marshalled request in a given format.
+ ///
+ /// When the format is:
+ ///
+ /// JSON: The buffer is expected to contain a two byte unsigned integer
+ /// which specified the length of the JSON text; followed by the JSON
+ /// text itself. This method attempts to extract "length" characters
+ /// from the buffer. This data is used to create a character string that
+ /// is than treated as JSON which is then parsed into the data needed
+ /// to create a request instance.
+ ///
+ /// (NOTE currently only JSON is supported.)
+ ///
+ /// @param format indicates the data format to use
+ /// @param buffer is the input buffer containing the marshalled request
+ ///
+ /// @return a pointer to the new NameChangeRequest
+ ///
+ /// @throw NcrMessageError if an error occurs creating new
+ /// request.
+ static NameChangeRequestPtr fromFormat(const NameChangeFormat format,
+ isc::util::InputBuffer& buffer);
+
+ /// @brief Instance method for marshalling the contents of the request
+ /// into the given buffer in the given format.
+ ///
+ /// When the format is:
+ ///
+ /// JSON: Upon completion, the buffer will contain a two byte unsigned
+ /// integer which specifies the length of the JSON text; followed by the
+ /// JSON text itself. The JSON text contains the names and values for all
+ /// the request data needed to reassemble the request on the receiving
+ /// end. The JSON text in the buffer is NOT null-terminated.
+ ///
+ /// (NOTE currently only JSON is supported.)
+ ///
+ /// @param format indicates the data format to use
+ /// @param buffer is the output buffer to which the request should be
+ /// marshalled.
+ void toFormat(const NameChangeFormat format,
+ isc::util::OutputBuffer& buffer) const;
+
+ /// @brief Static method for creating a NameChangeRequest from a
+ /// string containing a JSON rendition of a request.
+ ///
+ /// @param json is a string containing the JSON text
+ ///
+ /// @return a pointer to the new NameChangeRequest
+ ///
+ /// @throw NcrMessageError if an error occurs creating new request.
+ static NameChangeRequestPtr fromJSON(const std::string& json);
+
+ /// @brief Instance method for marshalling the contents of the request
+ /// into a string of JSON text.
+ ///
+ /// @return a string containing the JSON rendition of the request
+ std::string toJSON() const;
+
+ /// @brief Validates the content of a populated request. This method is
+ /// used by both the full constructor and from-wire marshalling to ensure
+ /// that the request is content valid. Currently it enforces the
+ /// following rules:
+ ///
+ /// - FQDN must not be blank.
+ /// - The IP address must be a valid address.
+ /// - The DHCID must not be blank.
+ /// - The lease expiration date must be a valid date/time.
+ /// - That at least one of the two direction flags, forward change and
+ /// reverse change is true.
+ ///
+ /// @todo This is an initial implementation which provides a minimal amount
+ /// of validation. FQDN, DHCID, and IP Address members are all currently
+ /// strings, these may be replaced with richer classes.
+ ///
+ /// @throw NcrMessageError if the request content violates any
+ /// of the validation rules.
+ void validateContent();
+
+ /// @brief Fetches the request change type.
+ ///
+ /// @return the change type
+ NameChangeType getChangeType() const {
+ return (change_type_);
+ }
+
+ /// @brief Sets the change type to the given value.
+ ///
+ /// @param value is the NameChangeType value to assign to the request.
+ void setChangeType(const NameChangeType value);
+
+ /// @brief Sets the change type to the value of the given Element.
+ ///
+ /// @param element is an integer Element containing the change type value.
+ ///
+ /// @throw NcrMessageError if the element is not an integer
+ /// Element or contains an invalid value.
+ void setChangeType(isc::data::ConstElementPtr element);
+
+ /// @brief Checks forward change flag.
+ ///
+ /// @return a true if the forward change flag is true.
+ bool isForwardChange() const {
+ return (forward_change_);
+ }
+
+ /// @brief Sets the forward change flag to the given value.
+ ///
+ /// @param value contains the new value to assign to the forward change
+ /// flag
+ void setForwardChange(const bool value);
+
+ /// @brief Sets the forward change flag to the value of the given Element.
+ ///
+ /// @param element is a boolean Element containing the forward change flag
+ /// value.
+ ///
+ /// @throw NcrMessageError if the element is not a boolean
+ /// Element
+ void setForwardChange(isc::data::ConstElementPtr element);
+
+ /// @brief Checks reverse change flag.
+ ///
+ /// @return a true if the reverse change flag is true.
+ bool isReverseChange() const {
+ return (reverse_change_);
+ }
+
+ /// @brief Sets the reverse change flag to the given value.
+ ///
+ /// @param value contains the new value to assign to the reverse change
+ /// flag
+ void setReverseChange(const bool value);
+
+ /// @brief Sets the reverse change flag to the value of the given Element.
+ ///
+ /// @param element is a boolean Element containing the reverse change flag
+ /// value.
+ ///
+ /// @throw NcrMessageError if the element is not a boolean
+ /// Element
+ void setReverseChange(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request FQDN
+ ///
+ /// @return a string containing the FQDN
+ const std::string getFqdn() const {
+ return (fqdn_);
+ }
+
+ /// @brief Sets the FQDN to the given value.
+ ///
+ /// @param value contains the new value to assign to the FQDN
+ void setFqdn(const std::string& value);
+
+ /// @brief Sets the FQDN to the value of the given Element.
+ ///
+ /// @param element is a string Element containing the FQDN
+ ///
+ /// @throw NcrMessageError if the element is not a string
+ /// Element
+ void setFqdn(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request IP address.
+ ///
+ /// @return a string containing the IP address
+ const std::string& getIpAddress() const {
+ return (ip_address_);
+ }
+
+ /// @brief Sets the IP address to the given value.
+ ///
+ /// @param value contains the new value to assign to the IP address
+ void setIpAddress(const std::string& value);
+
+ /// @brief Sets the IP address to the value of the given Element.
+ ///
+ /// @param element is a string Element containing the IP address
+ ///
+ /// @throw NcrMessageError if the element is not a string
+ /// Element
+ void setIpAddress(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request DHCID
+ ///
+ /// @return a reference to the request's D2Dhcid
+ const D2Dhcid& getDhcid() const {
+ return (dhcid_);
+ }
+
+ /// @brief Sets the DHCID based on the given string value.
+ ///
+ /// @param value is a string of hexadecimal digits. The format is simply
+ /// a contiguous stream of digits, with no delimiters. For example a string
+ /// containing "14A3" converts to a byte array containing: 0x14, 0xA3.
+ ///
+ /// @throw NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ void setDhcid(const std::string& value);
+
+ /// @brief Sets the DHCID based on the value of the given Element.
+ ///
+ /// @param element is a string Element containing the string of hexadecimal
+ /// digits. (See setDhcid(std::string&) above.)
+ ///
+ /// @throw NcrMessageError if the input data contains non-digits
+ /// or there is an odd number of digits.
+ void setDhcid(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request lease expiration
+ ///
+ /// @return the lease expiration as the number of seconds since
+ /// the (00:00:00 January 1, 1970)
+ uint64_t getLeaseExpiresOn() const {
+ return (lease_expires_on_);
+ }
+
+ /// @brief Fetches the request lease expiration as string.
+ ///
+ /// The format of the string returned is:
+ ///
+ /// YYYYMMDDHHmmSS
+ ///
+ /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
+ /// NOTE This is always UTC time.
+ ///
+ /// @return a ISO date-time string of the lease expiration.
+ std::string getLeaseExpiresOnStr() const;
+
+ /// @brief Sets the lease expiration based on the given string.
+ ///
+ /// @param value is an date-time string from which to set the
+ /// lease expiration. The format of the input is:
+ ///
+ /// YYYYMMDDHHmmSS
+ ///
+ /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
+ /// NOTE This is always UTC time.
+ ///
+ /// @throw NcrMessageError if the ISO string is invalid.
+ void setLeaseExpiresOn(const std::string& value);
+
+ /// @brief Sets the lease expiration based on the given Element.
+ ///
+ /// @param element is string Element containing a date-time string.
+ ///
+ /// @throw NcrMessageError if the element is not a string
+ /// Element, or if the element value is an invalid date-time string.
+ void setLeaseExpiresOn(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request lease length.
+ ///
+ /// @return an integer containing the lease length
+ uint32_t getLeaseLength() const {
+ return (lease_length_);
+ }
+
+ /// @brief Sets the lease length to the given value.
+ ///
+ /// @param value contains the new value to assign to the lease length
+ void setLeaseLength(const uint32_t value);
+
+ /// @brief Sets the lease length to the value of the given Element.
+ ///
+ /// @param element is a integer Element containing the lease length
+ ///
+ /// @throw NcrMessageError if the element is not a string
+ /// Element
+ void setLeaseLength(isc::data::ConstElementPtr element);
+
+ /// @brief Fetches the request status.
+ ///
+ /// @return the request status as a NameChangeStatus
+ NameChangeStatus getStatus() const {
+ return (status_);
+ }
+
+ /// @brief Sets the request status to the given value.
+ ///
+ /// @param value contains the new value to assign to request status
+ void setStatus(const NameChangeStatus value);
+
+ /// @brief Given a name, finds and returns an element from a map of
+ /// elements.
+ ///
+ /// @param name is the name of the desired element
+ /// @param element_map is the map of elements to search
+ ///
+ /// @return a pointer to the element if located
+ /// @throw NcrMessageError if the element cannot be found within
+ /// the map
+ isc::data::ConstElementPtr getElement(const std::string& name,
+ const ElementMap& element_map) const;
+
+ /// @brief Returns a text rendition of the contents of the request.
+ /// This method is primarily for logging purposes.
+ ///
+ /// @return a string containing the text.
+ std::string toText() const;
+
+ bool operator == (const NameChangeRequest& b);
+ bool operator != (const NameChangeRequest& b);
+
+private:
+ /// @brief Denotes the type of this change as either an Add or a Remove.
+ NameChangeType change_type_;
+
+ /// @brief Indicates if this change should sent to forward DNS servers.
+ bool forward_change_;
+
+ /// @brief Indicates if this change should sent to reverse DNS servers.
+ bool reverse_change_;
+
+ /// @brief The domain name whose DNS entry(ies) are to be updated.
+ /// @todo Currently, this is a std::string but may be replaced with
+ /// dns::Name which provides additional validation and domain name
+ /// manipulation.
+ std::string fqdn_;
+
+ /// @brief The ip address leased to the FQDN.
+ std::string ip_address_;
+
+ /// @brief The lease client's unique DHCID.
+ /// @todo Currently, this is uses D2Dhcid it but may be replaced with
+ /// dns::DHCID which provides additional validation.
+ D2Dhcid dhcid_;
+
+ /// @brief The date-time the lease expires.
+ uint64_t lease_expires_on_;
+
+ /// @brief The amount of time in seconds for which the lease is valid (TTL).
+ uint32_t lease_length_;
+
+ /// @brief The processing status of the request. Used internally.
+ NameChangeStatus status_;
+};
+
+
+}; // end of isc::dhcp_ddns namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/lib/dhcp_ddns/ncr_udp.cc b/src/lib/dhcp_ddns/ncr_udp.cc
new file mode 100644
index 0000000..9e83f7c
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_udp.cc
@@ -0,0 +1,328 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <dhcp_ddns/dhcp_ddns_log.h>
+#include <dhcp_ddns/ncr_udp.h>
+
+#include <asio/ip/udp.hpp>
+#include <asio/error_code.hpp>
+#include <boost/bind.hpp>
+
+namespace isc {
+namespace dhcp_ddns {
+
+//*************************** UDPCallback ***********************
+UDPCallback::UDPCallback (RawBufferPtr& buffer, const size_t buf_size,
+ UDPEndpointPtr& data_source,
+ const UDPCompletionHandler& handler)
+ : handler_(handler), data_(new Data(buffer, buf_size, data_source)) {
+ if (handler.empty()) {
+ isc_throw(NcrUDPError, "UDPCallback - handler can't be null");
+ }
+
+ if (!buffer) {
+ isc_throw(NcrUDPError, "UDPCallback - buffer can't be null");
+ }
+}
+
+void
+UDPCallback::operator ()(const asio::error_code error_code,
+ const size_t bytes_transferred) {
+
+ // Save the result state and number of bytes transferred.
+ setErrorCode(error_code);
+ setBytesTransferred(bytes_transferred);
+
+ // Invoke the NameChangeRequest layer completion handler.
+ // First argument is a boolean indicating success or failure.
+ // The second is a pointer to "this" callback object. By passing
+ // ourself in, we make all of the service related data available
+ // to the completion handler.
+ handler_(!error_code, this);
+}
+
+void
+UDPCallback::putData(const uint8_t* src, size_t len) {
+ if (!src) {
+ isc_throw(NcrUDPError, "UDPCallback putData, data source is NULL");
+ }
+
+ if (len > data_->buf_size_) {
+ isc_throw(NcrUDPError, "UDPCallback putData, data length too large");
+ }
+
+ memcpy (data_->buffer_.get(), src, len);
+ data_->put_len_ = len;
+}
+
+
+//*************************** NameChangeUDPListener ***********************
+NameChangeUDPListener::
+NameChangeUDPListener(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port, const NameChangeFormat format,
+ RequestReceiveHandler& ncr_recv_handler,
+ const bool reuse_address)
+ : NameChangeListener(ncr_recv_handler), ip_address_(ip_address),
+ port_(port), format_(format), reuse_address_(reuse_address) {
+ // Instantiate the receive callback. This gets passed into each receive.
+ // Note that the callback constructor is passed an instance method
+ // pointer to our completion handler method, receiveCompletionHandler.
+ RawBufferPtr buffer(new uint8_t[RECV_BUF_MAX]);
+ UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
+ recv_callback_.reset(new
+ UDPCallback(buffer, RECV_BUF_MAX, data_source,
+ boost::bind(&NameChangeUDPListener::
+ receiveCompletionHandler, this, _1, _2)));
+}
+
+NameChangeUDPListener::~NameChangeUDPListener() {
+ // Clean up.
+ stopListening();
+}
+
+void
+NameChangeUDPListener::open(isc::asiolink::IOService& io_service) {
+ // create our endpoint and bind the the low level socket to it.
+ isc::asiolink::UDPEndpoint endpoint(ip_address_.getAddress(), port_);
+
+ // Create the low level socket.
+ try {
+ asio_socket_.reset(new asio::ip::udp::
+ socket(io_service.get_io_service(),
+ (ip_address_.isV4() ? asio::ip::udp::v4() :
+ asio::ip::udp::v6())));
+
+ // Set the socket option to reuse addresses if it is enabled.
+ if (reuse_address_) {
+ asio_socket_->set_option(asio::socket_base::reuse_address(true));
+ }
+
+ // Bind the low level socket to our endpoint.
+ asio_socket_->bind(endpoint.getASIOEndpoint());
+ } catch (asio::system_error& ex) {
+ asio_socket_.reset();
+ isc_throw (NcrUDPError, ex.code().message());
+ }
+
+ // Create the asiolink socket from the low level socket.
+ socket_.reset(new NameChangeUDPSocket(*asio_socket_));
+}
+
+
+void
+NameChangeUDPListener::doReceive() {
+ // Call the socket's asychronous receiving, passing ourself in as callback.
+ RawBufferPtr recv_buffer = recv_callback_->getBuffer();
+ socket_->asyncReceive(recv_buffer.get(), recv_callback_->getBufferSize(),
+ 0, recv_callback_->getDataSource().get(),
+ *recv_callback_);
+}
+
+void
+NameChangeUDPListener::close() {
+ // Whether we think we are listening or not, make sure we aren't.
+ // Since we are managing our own socket, we need to close it ourselves.
+ // NOTE that if there is a pending receive, it will be canceled, which
+ // WILL generate an invocation of the callback with error code of
+ // "operation aborted".
+ if (asio_socket_) {
+ if (asio_socket_->is_open()) {
+ try {
+ asio_socket_->close();
+ } catch (asio::system_error& ex) {
+ // It is really unlikely that this will occur.
+ // If we do reopen later it will be with a new socket
+ // instance. Repackage exception as one that is conformant
+ // with the interface.
+ isc_throw (NcrUDPError, ex.code().message());
+ }
+ }
+
+ asio_socket_.reset();
+ }
+
+ socket_.reset();
+}
+
+void
+NameChangeUDPListener::receiveCompletionHandler(const bool successful,
+ const UDPCallback *callback) {
+ NameChangeRequestPtr ncr;
+ Result result = SUCCESS;
+
+ if (successful) {
+ // Make an InputBuffer from our internal array
+ isc::util::InputBuffer input_buffer(callback->getData(),
+ callback->getBytesTransferred());
+
+ try {
+ ncr = NameChangeRequest::fromFormat(format_, input_buffer);
+ } catch (const NcrMessageError& ex) {
+ // log it and go back to listening
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_INVALID_NCR).arg(ex.what());
+
+ // Queue up the next recieve.
+ // NOTE: We must call the base class, NEVER doReceive
+ receiveNext();
+ return;
+ }
+ } else {
+ asio::error_code error_code = callback->getErrorCode();
+ if (error_code.value() == asio::error::operation_aborted) {
+ LOG_INFO(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_RECV_CANCELED)
+ .arg(error_code.message());
+ result = STOPPED;
+ } else {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_RECV_ERROR)
+ .arg(error_code.message());
+ result = ERROR;
+ }
+ }
+
+ // Call the application's registered request receive handler.
+ invokeRecvHandler(result, ncr);
+}
+
+
+//*************************** NameChangeUDPSender ***********************
+
+NameChangeUDPSender::
+NameChangeUDPSender(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port,
+ const isc::asiolink::IOAddress& server_address,
+ const uint32_t server_port, const NameChangeFormat format,
+ RequestSendHandler& ncr_send_handler,
+ const size_t send_que_max, const bool reuse_address)
+ : NameChangeSender(ncr_send_handler, send_que_max),
+ ip_address_(ip_address), port_(port), server_address_(server_address),
+ server_port_(server_port), format_(format),
+ reuse_address_(reuse_address) {
+ // Instantiate the send callback. This gets passed into each send.
+ // Note that the callback constructor is passed the an instance method
+ // pointer to our completion handler, sendCompletionHandler.
+ RawBufferPtr buffer(new uint8_t[SEND_BUF_MAX]);
+ UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
+ send_callback_.reset(new UDPCallback(buffer, SEND_BUF_MAX, data_source,
+ boost::bind(&NameChangeUDPSender::
+ sendCompletionHandler, this,
+ _1, _2)));
+}
+
+NameChangeUDPSender::~NameChangeUDPSender() {
+ // Clean up.
+ stopSending();
+}
+
+void
+NameChangeUDPSender::open(isc::asiolink::IOService& io_service) {
+ // create our endpoint and bind the the low level socket to it.
+ isc::asiolink::UDPEndpoint endpoint(ip_address_.getAddress(), port_);
+
+ // Create the low level socket.
+ try {
+ asio_socket_.reset(new asio::ip::udp::
+ socket(io_service.get_io_service(),
+ (ip_address_.isV4() ? asio::ip::udp::v4() :
+ asio::ip::udp::v6())));
+
+ // Set the socket option to reuse addresses if it is enabled.
+ if (reuse_address_) {
+ asio_socket_->set_option(asio::socket_base::reuse_address(true));
+ }
+
+ // Bind the low leve socket to our endpoint.
+ asio_socket_->bind(endpoint.getASIOEndpoint());
+ } catch (asio::system_error& ex) {
+ isc_throw (NcrUDPError, ex.code().message());
+ }
+
+ // Create the asiolink socket from the low level socket.
+ socket_.reset(new NameChangeUDPSocket(*asio_socket_));
+
+ // Create the server endpoint
+ server_endpoint_.reset(new isc::asiolink::
+ UDPEndpoint(server_address_.getAddress(),
+ server_port_));
+
+ send_callback_->setDataSource(server_endpoint_);
+}
+
+void
+NameChangeUDPSender::close() {
+ // Whether we think we are sending or not, make sure we aren't.
+ // Since we are managing our own socket, we need to close it ourselves.
+ // NOTE that if there is a pending send, it will be canceled, which
+ // WILL generate an invocation of the callback with error code of
+ // "operation aborted".
+ if (asio_socket_) {
+ if (asio_socket_->is_open()) {
+ try {
+ asio_socket_->close();
+ } catch (asio::system_error& ex) {
+ // It is really unlikely that this will occur.
+ // If we do reopen later it will be with a new socket
+ // instance. Repackage exception as one that is conformant
+ // with the interface.
+ isc_throw (NcrUDPError, ex.code().message());
+ }
+ }
+
+ asio_socket_.reset();
+ }
+
+ socket_.reset();
+}
+
+void
+NameChangeUDPSender::doSend(NameChangeRequestPtr& ncr) {
+ // Now use the NCR to write JSON to an output buffer.
+ isc::util::OutputBuffer ncr_buffer(SEND_BUF_MAX);
+ ncr->toFormat(format_, ncr_buffer);
+
+ // Copy the wire-ized request to callback. This way we know after
+ // send completes what we sent (or attempted to send).
+ send_callback_->putData(static_cast<const uint8_t*>(ncr_buffer.getData()),
+ ncr_buffer.getLength());
+
+ // Call the socket's asychronous send, passing our callback
+ socket_->asyncSend(send_callback_->getData(), send_callback_->getPutLen(),
+ send_callback_->getDataSource().get(), *send_callback_);
+}
+
+void
+NameChangeUDPSender::sendCompletionHandler(const bool successful,
+ const UDPCallback *send_callback) {
+ Result result;
+ if (successful) {
+ result = SUCCESS;
+ }
+ else {
+ // On a failure, log the error and set the result to ERROR.
+ asio::error_code error_code = send_callback->getErrorCode();
+ if (error_code.value() == asio::error::operation_aborted) {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_SEND_CANCELED)
+ .arg(error_code.message());
+ result = STOPPED;
+ } else {
+ LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_SEND_ERROR)
+ .arg(error_code.message());
+ result = ERROR;
+ }
+ }
+
+ // Call the application's registered request send handler.
+ invokeSendHandler(result);
+}
+}; // end of isc::dhcp_ddns namespace
+}; // end of isc namespace
diff --git a/src/lib/dhcp_ddns/ncr_udp.h b/src/lib/dhcp_ddns/ncr_udp.h
new file mode 100644
index 0000000..7648a61
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_udp.h
@@ -0,0 +1,562 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 NCR_UDP_LISTENER_H
+#define NCR_UDP_LISTENER_H
+
+/// @file ncr_udp.h
+/// @brief This file provides UDP socket based implementation for sending and
+/// receiving NameChangeRequests
+///
+/// These classes are derived from the abstract classes, NameChangeListener
+/// and NameChangeSender (see ncr_io.h).
+///
+/// The following discussion will refer to three layers of communications:
+///
+/// * Application layer - This is the business layer which needs to
+/// transport NameChangeRequests, and is unaware of the means by which
+/// they are transported.
+///
+/// * IO layer - This is the low-level layer that is directly responsible
+/// for sending and receiving data asynchronously and is supplied through
+/// other libraries. This layer is largely unaware of the nature of the
+/// data being transmitted. In other words, it doesn't know beans about
+/// NCRs.
+///
+/// * NameChangeRequest layer - This is the layer which acts as the
+/// intermediary between the Application layer and the IO layer. It must
+/// be able to move NameChangeRequests to the IO layer as raw data and move
+/// raw data from the IO layer in the Application layer as
+/// NameChangeRequests.
+///
+/// This file defines NameChangeUDPListener class for receiving NCRs, and
+/// NameChangeUDPSender for sending NCRs.
+///
+/// Both the listener and sender implementations utilize the same underlying
+/// construct to move NCRs to and from a UDP socket. This construct consists
+/// of a set of classes centered around isc::asiolink::UDPSocket. UDPSocket
+/// is a templated class that supports asio asynchronous event processing; and
+/// which accepts as its parameter, the name of a callback class.
+///
+/// The asynchronous services provided by UDPSocket typically accept a buffer
+/// for transferring data (either in or out depending on the service direction)
+/// and an object which supplies a callback to invoke upon completion of the
+/// service.
+///
+/// The callback class must provide an operator() with the following signature:
+/// @code
+/// void operator ()(const asio::error_code error_code,
+/// const size_t bytes_transferred);
+/// @endcode
+///
+/// Upon completion of the service, the callback instance's operator() is
+/// invoked by the asio layer. It is given both a outcome result and the
+/// number of bytes either read or written, to or from the buffer supplied
+/// to the service.
+///
+/// Typically, an asiolink based implementation would simply implement the
+/// callback operator directly. However, the nature of the asiolink library
+/// is such that the callback object may be copied several times during course
+/// of a service invocation. This implies that any class being used as a
+/// callback class must be copyable. This is not always desirable. In order
+/// to separate the callback class from the NameChangeRequest, the construct
+/// defines the UDPCallback class for use as a copyable, callback object.
+///
+/// The UDPCallback class provides the asiolink layer callback operator(),
+/// which is invoked by the asiolink layer upon service completion. It
+/// contains:
+/// * a pointer to the transfer buffer
+/// * the capacity of the transfer buffer
+/// * a IO layer outcome result
+/// * the number of bytes transferred
+/// * a method pointer to a NameChangeRequest layer completion handler
+///
+/// This last item, is critical. It points to an instance method that
+/// will be invoked by the UDPCallback operator. This provides access to
+/// the outcome of the service call to the NameChangeRequest layer without
+/// that layer being used as the actual callback object.
+///
+/// The completion handler method signature is codified in the typedef,
+/// UDPCompletionHandler, and must be as follows:
+///
+/// @code
+/// void(const bool, const UDPCallback*)
+/// @endcode
+///
+/// Note that is accepts two parameters. The first is a boolean indicator
+/// which indicates if the service call completed successfully or not. The
+/// second is a pointer to the callback object invoked by the IOService upon
+/// completion of the service. The callback instance will contain all of the
+/// pertinent information about the invocation and outcome of the service.
+///
+/// Using the contents of the callback, it is the responsibility of the
+/// UDPCompletionHandler to interpret the results of the service invocation and
+/// pass the interpretation to the application layer via either
+/// NameChangeListener::invokeRecvHandler in the case of the UDP listener, or
+/// NameChangeSender::invokeSendHandler in the case of UDP sender.
+///
+#include <asio.hpp>
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <util/buffer.h>
+
+#include <boost/shared_array.hpp>
+
+/// responsibility of the completion handler to perform the steps necessary
+/// to interpret the raw data provided by the service outcome. The
+/// UDPCallback operator implementation is mostly a pass through.
+///
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Thrown when a UDP level exception occurs.
+class NcrUDPError : public isc::Exception {
+public:
+ NcrUDPError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+class UDPCallback;
+/// @brief Defines a function pointer for NameChangeRequest completion handlers.
+typedef boost::function<void(const bool, const UDPCallback*)>
+ UDPCompletionHandler;
+
+/// @brief Defines a dynamically allocated shared array.
+typedef boost::shared_array<uint8_t> RawBufferPtr;
+
+typedef boost::shared_ptr<asiolink::UDPEndpoint> UDPEndpointPtr;
+
+/// @brief Implements the callback class passed into UDPSocket calls.
+///
+/// It serves as the link between the asiolink::UDPSocket asynchronous services
+/// and the NameChangeRequest layer. The class provides the asiolink layer
+/// callback operator(), which is invoked by the asiolink layer upon service
+/// completion. It contains all of the data pertinent to both the invocation
+/// and completion of a service, as well as a pointer to NameChangeRequest
+/// layer completion handler to invoke.
+///
+class UDPCallback {
+
+public:
+ /// @brief Container class which stores service invocation related data.
+ ///
+ /// Because the callback object may be copied numerous times during the
+ /// course of service invocation, it does not directly contain data values.
+ /// Rather it will retain a shared pointer to an instance of this structure
+ /// thus ensuring that all copies of the callback object, ultimately refer
+ /// to the same data values.
+ struct Data {
+
+ /// @brief Constructor
+ ///
+ /// @param buffer is a pointer to the data transfer buffer. This is
+ /// the buffer data will be written to on a read, or read from on a
+ /// send.
+ /// @param buf_size is the capacity of the buffer
+ /// @param data_source storage for UDP endpoint which supplied the data
+ Data(RawBufferPtr& buffer, const size_t buf_size,
+ UDPEndpointPtr& data_source)
+ : buffer_(buffer), buf_size_(buf_size), data_source_(data_source),
+ put_len_(0), error_code_(), bytes_transferred_(0) {
+ };
+
+ /// @brief A pointer to the data transfer buffer.
+ RawBufferPtr buffer_;
+
+ /// @brief Storage capacity of the buffer.
+ size_t buf_size_;
+
+ /// @brief The UDP endpoint that is the origin of the data transferred.
+ UDPEndpointPtr data_source_;
+
+ /// @brief Stores this size of the data within the buffer when written
+ /// there manually. (See UDPCallback::putData()) .
+ size_t put_len_;
+
+ /// @brief Stores the IO layer result code of the completed IO service.
+ asio::error_code error_code_;
+
+ /// @brief Stores the number of bytes transferred by completed IO
+ /// service.
+ /// For a read it is the number of bytes written into the
+ /// buffer. For a write it is the number of bytes read from the
+ /// buffer.
+ size_t bytes_transferred_;
+
+ };
+
+ /// @brief Used as the callback object for UDPSocket services.
+ ///
+ /// @param buffer is a pointer to the data transfer buffer. This is
+ /// the buffer data will be written to on a read, or read from on a
+ /// send.
+ /// @param buf_size is the capacity of the buffer
+ /// @param data_source storage for UDP endpoint which supplied the data
+ /// @param handler is a method pointer to the completion handler that
+ /// is to be called by the operator() implementation.
+ ///
+ /// @throw NcrUDPError if either the handler or buffer pointers
+ /// are invalid.
+ UDPCallback (RawBufferPtr& buffer, const size_t buf_size,
+ UDPEndpointPtr& data_source,
+ const UDPCompletionHandler& handler);
+
+ /// @brief Operator that will be invoked by the asiolink layer.
+ ///
+ /// @param error_code is the IO layer result code of the
+ /// completed IO service.
+ /// @param bytes_transferred is the number of bytes transferred by
+ /// completed IO.
+ /// For a read it is the number of bytes written into the
+ /// buffer. For a write it is the number of bytes read from the
+ /// buffer.
+ void operator ()(const asio::error_code error_code,
+ const size_t bytes_transferred);
+
+ /// @brief Returns the number of bytes transferred by the completed IO
+ /// service.
+ ///
+ /// For a read it is the number of bytes written into the
+ /// buffer. For a write it is the number of bytes read from the
+ /// buffer.
+ size_t getBytesTransferred() const {
+ return (data_->bytes_transferred_);
+ }
+
+ /// @brief Sets the number of bytes transferred.
+ ///
+ /// @param value is the new value to assign to bytes transferred.
+ void setBytesTransferred(const size_t value) {
+ data_->bytes_transferred_ = value;
+ }
+
+ /// @brief Returns the completed IO layer service outcome status.
+ asio::error_code getErrorCode() const {
+ return (data_->error_code_);
+ }
+
+ /// @brief Sets the completed IO layer service outcome status.
+ ///
+ /// @param value is the new value to assign to outcome status.
+ void setErrorCode(const asio::error_code value) {
+ data_->error_code_ = value;
+ }
+
+ /// @brief Returns the data transfer buffer.
+ RawBufferPtr getBuffer() const {
+ return (data_->buffer_);
+ }
+
+ /// @brief Returns the data transfer buffer capacity.
+ size_t getBufferSize() const {
+ return (data_->buf_size_);
+ }
+
+ /// @brief Returns a pointer the data transfer buffer content.
+ const uint8_t* getData() const {
+ return (data_->buffer_.get());
+ }
+
+ /// @brief Copies data into the data transfer buffer.
+ ///
+ /// Copies the given number of bytes from the given source buffer
+ /// into the data transfer buffer, and updates the value of put length.
+ /// This method may be used when performing sends to make a copy of
+ /// the "raw data" that was shipped (or attempted) accessible to the
+ /// upstream callback.
+ ///
+ /// @param src is a pointer to the data source from which to copy
+ /// @param len is the number of bytes to copy
+ ///
+ /// @throw NcrUDPError if the number of bytes to copy exceeds
+ /// the buffer capacity or if the source pointer is invalid.
+ void putData(const uint8_t* src, size_t len);
+
+ /// @brief Returns the number of bytes manually written into the
+ /// transfer buffer.
+ size_t getPutLen() const {
+ return (data_->put_len_);
+ }
+
+ /// @brief Sets the data source to the given endpoint.
+ ///
+ /// @param endpoint is the new value to assign to data source.
+ void setDataSource(UDPEndpointPtr& endpoint) {
+ data_->data_source_ = endpoint;
+ }
+
+ /// @brief Returns the UDP endpoint that provided the transferred data.
+ const UDPEndpointPtr& getDataSource() {
+ return (data_->data_source_);
+ }
+
+ private:
+ /// @brief NameChangeRequest layer completion handler to invoke.
+ UDPCompletionHandler handler_;
+
+ /// @brief Shared pointer to the service data container.
+ boost::shared_ptr<Data> data_;
+};
+
+/// @brief Convenience type for UDP socket based listener
+typedef isc::asiolink::UDPSocket<UDPCallback> NameChangeUDPSocket;
+
+/// @brief Provides the ability to receive NameChangeRequests via UDP socket
+///
+/// This class is a derivation of the NameChangeListener which is capable of
+/// receiving NameChangeRequests through a UDP socket. The caller need only
+/// supply network addressing and a RequestReceiveHandler instance to receive
+/// NameChangeRequests asynchronously.
+class NameChangeUDPListener : public NameChangeListener {
+public:
+ /// @brief Defines the maximum size packet that can be received.
+ static const size_t RECV_BUF_MAX = isc::asiolink::
+ UDPSocket<UDPCallback>::MIN_SIZE;
+
+ /// @brief Constructor
+ ///
+ /// @param ip_address is the network address on which to listen
+ /// @param port is the UDP port on which to listen
+ /// @param format is the wire format of the inbound requests. Currently
+ /// only JSON is supported
+ /// @param ncr_recv_handler the receive handler object to notify when
+ /// a receive completes.
+ /// @param reuse_address enables IP address sharing when true
+ /// It defaults to false.
+ ///
+ /// @throw base class throws NcrListenerError if handler is invalid.
+ NameChangeUDPListener(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port,
+ const NameChangeFormat format,
+ RequestReceiveHandler& ncr_recv_handler,
+ const bool reuse_address = false);
+
+ /// @brief Destructor.
+ virtual ~NameChangeUDPListener();
+
+ /// @brief Opens a UDP socket using the given IOService.
+ ///
+ /// Creates a NameChangeUDPSocket bound to the listener's ip address
+ /// and port, that is monitored by the given IOService instance.
+ ///
+ /// @param io_service the IOService which will monitor the socket.
+ ///
+ /// @throw NcrUDPError if the open fails.
+ virtual void open(isc::asiolink::IOService& io_service);
+
+ /// @brief Closes the UDPSocket.
+ ///
+ /// It first invokes the socket's cancel method which should stop any
+ /// pending read and remove the socket callback from the IOService. It
+ /// then calls the socket's close method to actually close the socket.
+ ///
+ /// @throw NcrUDPError if the open fails.
+ virtual void close();
+
+ /// @brief Initiates an asynchronous read on the socket.
+ ///
+ /// Invokes the asyncReceive() method on the socket passing in the
+ /// recv_callback_ member's transfer buffer as the receive buffer, and
+ /// recv_callback_ itself as the callback object.
+ ///
+ /// @throw NcrUDPError if the open fails.
+ void doReceive();
+
+ /// @brief Implements the NameChangeRequest level receive completion
+ /// handler.
+ ///
+ /// This method is invoked by the UPDCallback operator() implementation,
+ /// passing in the boolean success indicator and pointer to itself.
+ ///
+ /// If the indicator denotes success, then the method will attempt to
+ /// to construct a NameChangeRequest from the received data. If the
+ /// construction was successful, it will send the new NCR to the
+ /// application layer by calling invokeRecvHandler() with a success
+ /// status and a pointer to the new NCR.
+ ///
+ /// If the buffer contains invalid data such that construction fails,
+ /// the method will log the failure and then call doReceive() to start a
+ /// initiate the next receive.
+ ///
+ /// If the indicator denotes failure the method will log the failure and
+ /// notify the application layer by calling invokeRecvHandler() with
+ /// an error status and an empty pointer.
+ ///
+ /// @param successful boolean indicator that should be true if the
+ /// socket receive completed without error, false otherwise.
+ /// @param recv_callback pointer to the callback instance which handled
+ /// the socket receive completion.
+ void receiveCompletionHandler(const bool successful,
+ const UDPCallback* recv_callback);
+private:
+ /// @brief IP address on which to listen for requests.
+ isc::asiolink::IOAddress ip_address_;
+
+ /// @brief Port number on which to listen for requests.
+ uint32_t port_;
+
+ /// @brief Wire format of the inbound requests.
+ NameChangeFormat format_;
+
+ /// @brief Low level socket underneath the listening socket
+ boost::shared_ptr<asio::ip::udp::socket> asio_socket_;
+
+ /// @brief NameChangeUDPSocket listening socket
+ boost::shared_ptr<NameChangeUDPSocket> socket_;
+
+ /// @brief Pointer to the receive callback
+ boost::shared_ptr<UDPCallback> recv_callback_;
+
+ /// @brief Flag which enables the reuse address socket option if true.
+ bool reuse_address_;
+
+ ///
+ /// @name Copy and constructor assignment operator
+ ///
+ /// The copy constructor and assignment operator are private to avoid
+ /// potential issues with multiple listeners attempting to share sockets
+ /// and callbacks.
+private:
+ NameChangeUDPListener(const NameChangeUDPListener& source);
+ NameChangeUDPListener& operator=(const NameChangeUDPListener& source);
+ //@}
+};
+
+
+/// @brief Provides the ability to send NameChangeRequests via UDP socket
+///
+/// This class is a derivation of the NameChangeSender which is capable of
+/// sending NameChangeRequests through a UDP socket. The caller need only
+/// supply network addressing and a RequestSendHandler instance to send
+/// NameChangeRequests asynchronously.
+class NameChangeUDPSender : public NameChangeSender {
+public:
+
+ /// @brief Defines the maximum size packet that can be sent.
+ static const size_t SEND_BUF_MAX = NameChangeUDPListener::RECV_BUF_MAX;
+
+ /// @brief Constructor
+ ///
+ /// @param ip_address the IP address from which to send
+ /// @param port the port from which to send
+ /// @param server_address the IP address of the target listener
+ /// @param server_port is the IP port of the target listener
+ /// @param format is the wire format of the outbound requests.
+ /// @param ncr_send_handler the send handler object to notify when
+ /// when a send completes.
+ /// @param send_que_max sets the maximum number of entries allowed in
+ /// the send queue.
+ /// It defaults to NameChangeSender::MAX_QUEUE_DEFAULT
+ /// @param reuse_address enables IP address sharing when true
+ /// It defaults to false.
+ ///
+ NameChangeUDPSender(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port, const isc::asiolink::IOAddress& server_address,
+ const uint32_t server_port, const NameChangeFormat format,
+ RequestSendHandler& ncr_send_handler,
+ const size_t send_que_max = NameChangeSender::MAX_QUEUE_DEFAULT,
+ const bool reuse_address = false);
+
+ /// @brief Destructor
+ virtual ~NameChangeUDPSender();
+
+
+ /// @brief Opens a UDP socket using the given IOService.
+ ///
+ /// Creates a NameChangeUDPSocket bound to the sender's IP address
+ /// and port, that is monitored by the given IOService instance.
+ ///
+ /// @param io_service the IOService which will monitor the socket.
+ ///
+ /// @throw NcrUDPError if the open fails.
+ virtual void open(isc::asiolink::IOService& io_service);
+
+
+ /// @brief Closes the UDPSocket.
+ ///
+ /// It first invokes the socket's cancel method which should stop any
+ /// pending send and remove the socket callback from the IOService. It
+ /// then calls the socket's close method to actually close the socket.
+ ///
+ /// @throw NcrUDPError if the open fails.
+ virtual void close();
+
+ /// @brief Sends a given request asynchronously over the socket
+ ///
+ /// The given NameChangeRequest is converted to wire format and copied
+ /// into the send callback's transfer buffer. Then the socket's
+ /// asyncSend() method is called, passing in send_callback_ member's
+ /// transfer buffer as the send buffer and the send_callback_ itself
+ /// as the callback object.
+ virtual void doSend(NameChangeRequestPtr& ncr);
+
+ /// @brief Implements the NameChangeRequest level send completion handler.
+ ///
+ /// This method is invoked by the UDPCallback operator() implementation,
+ /// passing in the boolean success indicator and pointer to itself.
+ ///
+ /// If the indicator denotes success, then the method will notify the
+ /// application layer by calling invokeSendHandler() with a success
+ /// status.
+ ///
+ /// If the indicator denotes failure the method will log the failure and
+ /// notify the application layer by calling invokeRecvHandler() with
+ /// an error status.
+ ///
+ /// @param successful boolean indicator that should be true if the
+ /// socket send completed without error, false otherwise.
+ /// @param send_callback pointer to the callback instance which handled
+ /// the socket receive completion.
+ void sendCompletionHandler(const bool successful,
+ const UDPCallback* send_callback);
+
+private:
+ /// @brief IP address from which to send.
+ isc::asiolink::IOAddress ip_address_;
+
+ /// @brief Port from which to send.
+ uint32_t port_;
+
+ /// @brief IP address of the target listener.
+ isc::asiolink::IOAddress server_address_;
+
+ /// @brief Port of the target listener.
+ uint32_t server_port_;
+
+ /// @brief Wire format of the outbound requests.
+ NameChangeFormat format_;
+
+ /// @brief Low level socket underneath the sending socket.
+ boost::shared_ptr<asio::ip::udp::socket> asio_socket_;
+
+ /// @brief NameChangeUDPSocket sending socket.
+ boost::shared_ptr<NameChangeUDPSocket> socket_;
+
+ /// @brief Endpoint of the target listener.
+ boost::shared_ptr<isc::asiolink::UDPEndpoint> server_endpoint_;
+
+ /// @brief Pointer to the send callback
+ boost::shared_ptr<UDPCallback> send_callback_;
+
+ /// @brief Flag which enables the reuse address socket option if true.
+ bool reuse_address_;
+};
+
+} // namespace isc::dhcp_ddns
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcp_ddns/tests/.gitignore b/src/lib/dhcp_ddns/tests/.gitignore
new file mode 100644
index 0000000..f022996
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/.gitignore
@@ -0,0 +1 @@
+/libdhcp_ddns_unittests
diff --git a/src/lib/dhcp_ddns/tests/Makefile.am b/src/lib/dhcp_ddns/tests/Makefile.am
new file mode 100644
index 0000000..78effc7
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/Makefile.am
@@ -0,0 +1,58 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(BOTAN_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcp_ddns/tests\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += libdhcp_ddns_unittests
+
+libdhcp_ddns_unittests_SOURCES = run_unittests.cc
+libdhcp_ddns_unittests_SOURCES += ncr_unittests.cc
+libdhcp_ddns_unittests_SOURCES += ncr_udp_unittests.cc
+
+libdhcp_ddns_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
+
+libdhcp_ddns_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+libdhcp_ddns_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_CLANGPP
+# This is to workaround unused variables tcout and tcerr in
+# log4cplus's streams.h and unused parameters from some of the
+# Boost headers.
+libdhcp_ddns_unittests_CXXFLAGS += -Wno-unused-parameter
+endif
+
+libdhcp_ddns_unittests_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libb10-cryptolink.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+libdhcp_ddns_unittests_LDADD += ${BOTAN_LIBS} ${BOTAN_RPATH}
+libdhcp_ddns_unittests_LDADD += $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
new file mode 100644
index 0000000..997ad4f
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
@@ -0,0 +1,518 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <asiolink/interval_timer.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <dhcp_ddns/ncr_udp.h>
+#include <util/time_utilities.h>
+
+#include <asio/ip/udp.hpp>
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <gtest/gtest.h>
+#include <algorithm>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest test messages.
+const char *valid_msgs[] =
+{
+ // Valid Add.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Valid Remove.
+ "{"
+ " \"change_type\" : 1 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Valid Add with IPv6 address
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}"
+};
+
+const char* TEST_ADDRESS = "127.0.0.1";
+const uint32_t LISTENER_PORT = 5301;
+const uint32_t SENDER_PORT = LISTENER_PORT+1;
+const long TEST_TIMEOUT = 5 * 1000;
+
+/// @brief A NOP derivation for constructor test purposes.
+class SimpleListenHandler : public NameChangeListener::RequestReceiveHandler {
+public:
+ virtual void operator ()(const NameChangeListener::Result,
+ NameChangeRequestPtr&) {
+ }
+};
+
+/// @brief Tests the NameChangeUDPListener constructors.
+/// This test verifies that:
+/// 1. Given valid parameters, the listener constructor works
+TEST(NameChangeUDPListenerBasicTest, constructionTests) {
+ // Verify the default constructor works.
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = LISTENER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleListenHandler ncr_handler;
+ // Verify that valid constructor works.
+ EXPECT_NO_THROW(NameChangeUDPListener(ip_address, port, FMT_JSON,
+ ncr_handler));
+}
+
+/// @brief Tests NameChangeUDPListener starting and stopping listening .
+/// This test verifies that the listener will:
+/// 1. Enter listening state
+/// 2. If in the listening state, does not allow calls to start listening
+/// 3. Exist the listening state
+/// 4. Return to the listening state after stopping
+TEST(NameChangeUDPListenerBasicTest, basicListenTests) {
+ // Verify the default constructor works.
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = LISTENER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleListenHandler ncr_handler;
+
+ NameChangeListenerPtr listener;
+ ASSERT_NO_THROW(listener.reset(
+ new NameChangeUDPListener(ip_address, port, FMT_JSON, ncr_handler)));
+
+ // Verify that we can start listening.
+ EXPECT_NO_THROW(listener->startListening(io_service));
+ // Verify that we are in listening mode.
+ EXPECT_TRUE(listener->amListening());
+ // Verify that a read is in progress.
+ EXPECT_TRUE(listener->isIoPending());
+
+ // Verify that attempting to listen when we already are is an error.
+ EXPECT_THROW(listener->startListening(io_service), NcrListenerError);
+
+ // Verify that we can stop listening.
+ EXPECT_NO_THROW(listener->stopListening());
+ EXPECT_FALSE(listener->amListening());
+
+ // Verify that IO pending is still true, as IO cancel event has not yet
+ // occurred.
+ EXPECT_TRUE(listener->isIoPending());
+
+ // Verify that IO pending is false, after cancel event occurs.
+ EXPECT_NO_THROW(io_service.run_one());
+ EXPECT_FALSE(listener->isIoPending());
+
+ // Verify that attempting to stop listening when we are not is ok.
+ EXPECT_NO_THROW(listener->stopListening());
+
+ // Verify that we can re-enter listening.
+ EXPECT_NO_THROW(listener->startListening(io_service));
+ EXPECT_TRUE(listener->amListening());
+}
+
+/// @brief Compares two NameChangeRequests for equality.
+bool checkSendVsReceived(NameChangeRequestPtr sent_ncr,
+ NameChangeRequestPtr received_ncr) {
+ return ((sent_ncr && received_ncr) &&
+ (*sent_ncr == *received_ncr));
+}
+
+/// @brief Text fixture for testing NameChangeUDPListener
+class NameChangeUDPListenerTest : public virtual ::testing::Test,
+ NameChangeListener::RequestReceiveHandler {
+public:
+ isc::asiolink::IOService io_service_;
+ NameChangeListener::Result result_;
+ NameChangeRequestPtr sent_ncr_;
+ NameChangeRequestPtr received_ncr_;
+ NameChangeListenerPtr listener_;
+ isc::asiolink::IntervalTimer test_timer_;
+
+ /// @brief Constructor
+ //
+ // Instantiates the listener member and the test timer. The timer is used
+ // to ensure a test doesn't go awry and hang forever.
+ NameChangeUDPListenerTest()
+ : io_service_(), result_(NameChangeListener::SUCCESS),
+ test_timer_(io_service_) {
+ isc::asiolink::IOAddress addr(TEST_ADDRESS);
+ listener_.reset(new NameChangeUDPListener(addr, LISTENER_PORT,
+ FMT_JSON, *this, true));
+
+ // Set the test timeout to break any running tasks if they hang.
+ test_timer_.setup(boost::bind(&NameChangeUDPListenerTest::
+ testTimeoutHandler, this),
+ TEST_TIMEOUT);
+ }
+
+ virtual ~NameChangeUDPListenerTest(){
+ }
+
+
+ /// @brief Converts JSON string into an NCR and sends it to the listener.
+ ///
+ void sendNcr(const std::string& msg) {
+ // Build an NCR from json string. This verifies that the
+ // test string is valid.
+ ASSERT_NO_THROW(sent_ncr_ = NameChangeRequest::fromJSON(msg));
+
+ // Now use the NCR to write JSON to an output buffer.
+ isc::util::OutputBuffer ncr_buffer(1024);
+ ASSERT_NO_THROW(sent_ncr_->toFormat(FMT_JSON, ncr_buffer));
+
+ // Create a UDP socket through which our "sender" will send the NCR.
+ asio::ip::udp::socket
+ udp_socket(io_service_.get_io_service(), asio::ip::udp::v4());
+
+ // Create an endpoint pointed at the listener.
+ asio::ip::udp::endpoint
+ listener_endpoint(asio::ip::address::from_string(TEST_ADDRESS),
+ LISTENER_PORT);
+
+ // A response message is now ready to send. Send it!
+ // Note this uses a synchronous send so it ships immediately.
+ // If listener isn't in listening mode, it will get missed.
+ udp_socket.send_to(asio::buffer(ncr_buffer.getData(),
+ ncr_buffer.getLength()),
+ listener_endpoint);
+ }
+
+ /// @brief RequestReceiveHandler operator implementation for receiving NCRs.
+ ///
+ /// The fixture acts as the "application" layer. It derives from
+ /// RequestReceiveHandler and as such implements operator() in order to
+ /// receive NCRs.
+ virtual void operator ()(const NameChangeListener::Result result,
+ NameChangeRequestPtr& ncr) {
+ // save the result and the NCR we received
+ result_ = result;
+ received_ncr_ = ncr;
+ }
+ // @brief Handler invoked when test timeout is hit.
+ //
+ // This callback stops all running (hanging) tasks on IO service.
+ void testTimeoutHandler() {
+ io_service_.stop();
+ FAIL() << "Test timeout hit.";
+ }
+};
+
+/// @brief Tests NameChangeUDPListener ability to receive NCRs.
+/// This test verifies that a listener can enter listening mode and
+/// receive NCRs in wire format on its UDP socket; reconstruct the
+/// NCRs and delivery them to the "application" layer.
+TEST_F(NameChangeUDPListenerTest, basicReceivetest) {
+ // Verify we can enter listening mode.
+ ASSERT_FALSE(listener_->amListening());
+ ASSERT_NO_THROW(listener_->startListening(io_service_));
+ ASSERT_TRUE(listener_->amListening());
+ ASSERT_TRUE(listener_->isIoPending());
+
+ // Iterate over a series of requests, sending and receiving one
+ /// at time.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+ for (int i = 0; i < num_msgs; i++) {
+ // We are not verifying ability to send, so if we can't test is over.
+ ASSERT_NO_THROW(sendNcr(valid_msgs[i]));
+
+ // Execute no more then one event, which should be receive complete.
+ EXPECT_NO_THROW(io_service_.run_one());
+
+ // Verify the "application" status value for a successful complete.
+ EXPECT_EQ(NameChangeListener::SUCCESS, result_);
+
+ // Verify the received request matches the sent request.
+ EXPECT_TRUE(checkSendVsReceived(sent_ncr_, received_ncr_));
+ }
+
+ // Verify we can gracefully stop listening.
+ EXPECT_NO_THROW(listener_->stopListening());
+ EXPECT_FALSE(listener_->amListening());
+
+ // Verify that IO pending is false, after cancel event occurs.
+ EXPECT_NO_THROW(io_service_.run_one());
+ EXPECT_FALSE(listener_->isIoPending());
+}
+
+/// @brief A NOP derivation for constructor test purposes.
+class SimpleSendHandler : public NameChangeSender::RequestSendHandler {
+public:
+ virtual void operator ()(const NameChangeSender::Result,
+ NameChangeRequestPtr&) {
+ }
+};
+
+/// @brief Tests the NameChangeUDPSender constructors.
+/// This test verifies that:
+/// 1. Constructing with a max queue size of 0 is not allowed
+/// 2. Given valid parameters, the sender constructor works
+/// 3. Default construction provides default max queue size
+/// 4. Construction with a custom max queue size works
+TEST(NameChangeUDPSenderBasicTest, constructionTests) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = SENDER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Verify that constructing with an queue size of zero is not allowed.
+ EXPECT_THROW(NameChangeUDPSender(ip_address, port,
+ ip_address, port, FMT_JSON, ncr_handler, 0), NcrSenderError);
+
+ NameChangeSenderPtr sender;
+ // Verify that valid constructor works.
+ EXPECT_NO_THROW(sender.reset(
+ new NameChangeUDPSender(ip_address, port, ip_address, port,
+ FMT_JSON, ncr_handler)));
+
+ // Verify that send queue default max is correct.
+ size_t expected = NameChangeSender::MAX_QUEUE_DEFAULT;
+ EXPECT_EQ(expected, sender->getQueueMaxSize());
+
+ // Verify that constructor with a valid custom queue size works.
+ EXPECT_NO_THROW(sender.reset(
+ new NameChangeUDPSender(ip_address, port, ip_address, port,
+ FMT_JSON, ncr_handler, 100)));
+
+ EXPECT_EQ(100, sender->getQueueMaxSize());
+}
+
+/// @brief Tests NameChangeUDPSender basic send functionality
+/// This test verifies that:
+TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
+ isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+ uint32_t port = SENDER_PORT;
+ isc::asiolink::IOService io_service;
+ SimpleSendHandler ncr_handler;
+
+ // Tests are based on a list of messages, get the count now.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Create the sender, setting the queue max equal to the number of
+ // messages we will have in the list.
+ NameChangeUDPSender sender(ip_address, port, ip_address, port,
+ FMT_JSON, ncr_handler, num_msgs);
+
+ // Verify that we can start sending.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Verify that attempting to send when we already are is an error.
+ EXPECT_THROW(sender.startSending(io_service), NcrSenderError);
+
+ // Verify that we can stop sending.
+ EXPECT_NO_THROW(sender.stopSending());
+ EXPECT_FALSE(sender.amSending());
+
+ // Verify that attempting to stop sending when we are not is ok.
+ EXPECT_NO_THROW(sender.stopSending());
+
+ // Verify that we can re-enter sending after stopping.
+ EXPECT_NO_THROW(sender.startSending(io_service));
+ EXPECT_TRUE(sender.amSending());
+
+ // Iterate over a series of messages, sending each one. Since we
+ // do not invoke IOService::run, then the messages should accumulate
+ // in the queue.
+ NameChangeRequestPtr ncr;
+ for (int i = 0; i < num_msgs; i++) {
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ // Verify that the queue count increments in step with each send.
+ EXPECT_EQ(i+1, sender.getQueueSize());
+ }
+
+ // Verify that attempting to send an additional message results in a
+ // queue full exception.
+ EXPECT_THROW(sender.sendRequest(ncr), NcrSenderQueueFull);
+
+ // Loop for the number of valid messages and invoke IOService::run_one.
+ // This should send exactly one message and the queue count should
+ // decrement accordingly.
+ for (int i = num_msgs; i > 0; i--) {
+ io_service.run_one();
+ // Verify that the queue count decrements in step with each run.
+ EXPECT_EQ(i-1, sender.getQueueSize());
+ }
+
+ // Verify that the queue is empty.
+ EXPECT_EQ(0, sender.getQueueSize());
+
+ // Verify that we can add back to the queue
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ EXPECT_EQ(1, sender.getQueueSize());
+
+ // Verify that we can remove the current entry at the front of the queue.
+ EXPECT_NO_THROW(sender.skipNext());
+ EXPECT_EQ(0, sender.getQueueSize());
+
+ // Verify that flushing the queue is not allowed in sending state.
+ EXPECT_THROW(sender.clearSendQueue(), NcrSenderError);
+
+ // Put a message on the queue.
+ EXPECT_NO_THROW(sender.sendRequest(ncr));
+ EXPECT_EQ(1, sender.getQueueSize());
+
+ // Verify that we can gracefully stop sending.
+ EXPECT_NO_THROW(sender.stopSending());
+ EXPECT_FALSE(sender.amSending());
+
+ // Verify that the queue is preserved after leaving sending state.
+ EXPECT_EQ(1, sender.getQueueSize());
+
+ // Verify that flushing the queue works when not sending.
+ EXPECT_NO_THROW(sender.clearSendQueue());
+ EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Text fixture that allows testing a listener and sender together
+/// It derives from both the receive and send handler classes and contains
+/// and instance of UDP listener and UDP sender.
+class NameChangeUDPTest : public virtual ::testing::Test,
+ NameChangeListener::RequestReceiveHandler,
+ NameChangeSender::RequestSendHandler {
+public:
+ isc::asiolink::IOService io_service_;
+ NameChangeListener::Result recv_result_;
+ NameChangeSender::Result send_result_;
+ NameChangeListenerPtr listener_;
+ NameChangeSenderPtr sender_;
+ isc::asiolink::IntervalTimer test_timer_;
+
+ std::vector<NameChangeRequestPtr> sent_ncrs_;
+ std::vector<NameChangeRequestPtr> received_ncrs_;
+
+ NameChangeUDPTest()
+ : io_service_(), recv_result_(NameChangeListener::SUCCESS),
+ send_result_(NameChangeSender::SUCCESS), test_timer_(io_service_) {
+ isc::asiolink::IOAddress addr(TEST_ADDRESS);
+ // Create our listener instance. Note that reuse_address is true.
+ listener_.reset(
+ new NameChangeUDPListener(addr, LISTENER_PORT, FMT_JSON,
+ *this, true));
+
+ // Create our sender instance. Note that reuse_address is true.
+ sender_.reset(
+ new NameChangeUDPSender(addr, SENDER_PORT, addr, LISTENER_PORT,
+ FMT_JSON, *this, 100, true));
+
+ // Set the test timeout to break any running tasks if they hang.
+ test_timer_.setup(boost::bind(&NameChangeUDPTest::testTimeoutHandler,
+ this),
+ TEST_TIMEOUT);
+ }
+
+ void reset_results() {
+ sent_ncrs_.clear();
+ received_ncrs_.clear();
+ }
+
+ /// @brief Implements the receive completion handler.
+ virtual void operator ()(const NameChangeListener::Result result,
+ NameChangeRequestPtr& ncr) {
+ // save the result and the NCR received.
+ recv_result_ = result;
+ received_ncrs_.push_back(ncr);
+ }
+
+ /// @brief Implements the send completion handler.
+ virtual void operator ()(const NameChangeSender::Result result,
+ NameChangeRequestPtr& ncr) {
+ // save the result and the NCR sent.
+ send_result_ = result;
+ sent_ncrs_.push_back(ncr);
+ }
+
+ // @brief Handler invoked when test timeout is hit.
+ //
+ // This callback stops all running (hanging) tasks on IO service.
+ void testTimeoutHandler() {
+ io_service_.stop();
+ FAIL() << "Test timeout hit.";
+ }
+};
+
+/// @brief Uses a sender and listener to test UDP-based NCR delivery
+/// Conducts a "round-trip" test using a sender to transmit a set of valid
+/// NCRs to a listener. The test verifies that what was sent matches what
+/// was received both in quantity and in content.
+TEST_F (NameChangeUDPTest, roundTripTest) {
+ // Place the listener into listening state.
+ ASSERT_NO_THROW(listener_->startListening(io_service_));
+ EXPECT_TRUE(listener_->amListening());
+
+ // Get the number of messages in the list of test messages.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+ // Place the sender into sending state.
+ ASSERT_NO_THROW(sender_->startSending(io_service_));
+ EXPECT_TRUE(sender_->amSending());
+
+ for (int i = 0; i < num_msgs; i++) {
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ sender_->sendRequest(ncr);
+ EXPECT_EQ(i+1, sender_->getQueueSize());
+ }
+
+ // Execute callbacks until we have sent and received all of messages.
+ while (sender_->getQueueSize() > 0 || (received_ncrs_.size() < num_msgs)) {
+ EXPECT_NO_THROW(io_service_.run_one());
+ }
+
+ // Send queue should be empty.
+ EXPECT_EQ(0, sender_->getQueueSize());
+
+ // We should have the same number of sends and receives as we do messages.
+ ASSERT_EQ(num_msgs, sent_ncrs_.size());
+ ASSERT_EQ(num_msgs, received_ncrs_.size());
+
+ // Verify that what we sent matches what we received.
+ for (int i = 0; i < num_msgs; i++) {
+ EXPECT_TRUE (checkSendVsReceived(sent_ncrs_[i], received_ncrs_[i]));
+ }
+
+ // Verify that we can gracefully stop listening.
+ EXPECT_NO_THROW(listener_->stopListening());
+ EXPECT_FALSE(listener_->amListening());
+
+ // Verify that IO pending is false, after cancel event occurs.
+ EXPECT_NO_THROW(io_service_.run_one());
+ EXPECT_FALSE(listener_->isIoPending());
+
+ // Verify that we can gracefully stop sending.
+ EXPECT_NO_THROW(sender_->stopSending());
+ EXPECT_FALSE(sender_->amSending());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp_ddns/tests/ncr_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_unittests.cc
new file mode 100644
index 0000000..df1a50a
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/ncr_unittests.cc
@@ -0,0 +1,516 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <dhcp_ddns/ncr_msg.h>
+#include <dhcp/duid.h>
+#include <util/time_utilities.h>
+
+#include <gtest/gtest.h>
+#include <algorithm>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest renditions.
+/// They are used as input to test conversion from JSON.
+const char *valid_msgs[] =
+{
+ // Valid Add.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Valid Remove.
+ "{"
+ " \"change_type\" : 1 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Valid Add with IPv6 address
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}"
+};
+
+/// @brief Defines a list of invalid JSON NameChangeRequest renditions.
+/// They are used as input to test conversion from JSON.
+const char *invalid_msgs[] =
+{
+ // Invalid change type.
+ "{"
+ " \"change_type\" : 7 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Invalid forward change.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : \"bogus\" , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Invalid reverse change.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : 500 , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Forward and reverse change both false.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : false , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Blank FQDN
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Bad IP address
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"xxxxxx\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Blank DHCID
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Odd number of digits in DHCID
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Text in DHCID
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"THIS IS BOGUS!!!\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Invalid lease expiration string
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"Wed Jun 26 13:46:46 EDT 2013\" , "
+ " \"lease_length\" : 1300 "
+ "}",
+ // Non-integer for lease length.
+ "{"
+ " \"change_type\" : 0 , "
+ " \"forward_change\" : true , "
+ " \"reverse_change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip_address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease_expires_on\" : \"20130121132405\" , "
+ " \"lease_length\" : \"BOGUS\" "
+ "}"
+
+};
+
+/// @brief Tests the NameChangeRequest constructors.
+/// This test verifies that:
+/// 1. Default constructor works.
+/// 2. "Full" constructor, when given valid parameter values, works.
+/// 3. "Full" constructor, given a blank FQDN fails
+/// 4. "Full" constructor, given an invalid IP Address FQDN fails
+/// 5. "Full" constructor, given a blank DHCID fails
+/// 6. "Full" constructor, given false for both forward and reverse fails
+TEST(NameChangeRequestTest, constructionTests) {
+ // Verify the default constructor works.
+ NameChangeRequestPtr ncr;
+ EXPECT_NO_THROW(ncr.reset(new NameChangeRequest()));
+ EXPECT_TRUE(ncr);
+
+ // Verify that full constructor works.
+ uint64_t expiry = isc::util::detail::gettimeWrapper();
+ D2Dhcid dhcid("010203040A7F8E3D");
+
+ EXPECT_NO_THROW(ncr.reset(new NameChangeRequest(
+ CHG_ADD, true, true, "walah.walah.com",
+ "192.168.1.101", dhcid, expiry, 1300)));
+ EXPECT_TRUE(ncr);
+ ncr.reset();
+
+ // Verify blank FQDN is detected.
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "",
+ "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+ // Verify that an invalid IP address is detected.
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "valid.fqdn",
+ "xxx.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+ // Verify that a blank DHCID is detected.
+ D2Dhcid blank_dhcid;
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "walah.walah.com",
+ "192.168.1.101", blank_dhcid, expiry, 1300), NcrMessageError);
+
+ // Verify that one or both of direction flags must be true.
+ EXPECT_THROW(NameChangeRequest(CHG_ADD, false, false, "valid.fqdn",
+ "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+}
+
+/// @brief Tests the basic workings of D2Dhcid to and from string conversions.
+/// It verifies that:
+/// 1. DHCID input strings must contain an even number of characters
+/// 2. DHCID input strings must contain only hexadecimal character digits
+/// 3. A valid DHCID string converts correctly.
+/// 4. Converting a D2Dhcid to a string works correctly.
+/// 5. Equality, inequality, and less-than-equal operators work.
+TEST(NameChangeRequestTest, dhcidTest) {
+ D2Dhcid dhcid;
+
+ // Odd number of digits should be rejected.
+ std::string test_str = "010203040A7F8E3";
+ EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
+
+ // Non digit content should be rejected.
+ test_str = "0102BOGUSA7F8E3D";
+ EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
+
+ // Verify that valid input converts into a proper byte array.
+ test_str = "010203040A7F8E3D";
+ ASSERT_NO_THROW(dhcid.fromStr(test_str));
+
+ // Create a test vector of expected byte contents.
+ const uint8_t bytes[] = { 0x1, 0x2, 0x3, 0x4, 0xA, 0x7F, 0x8E, 0x3D };
+ std::vector<uint8_t> expected_bytes(bytes, bytes + sizeof(bytes));
+
+ // Fetch the byte vector from the dhcid and verify if equals the expected
+ // content.
+ const std::vector<uint8_t>& converted_bytes = dhcid.getBytes();
+ EXPECT_EQ(expected_bytes.size(), converted_bytes.size());
+ EXPECT_TRUE (std::equal(expected_bytes.begin(),
+ expected_bytes.begin()+expected_bytes.size(),
+ converted_bytes.begin()));
+
+ // Convert the new dhcid back to string and verify it matches the original
+ // DHCID input string.
+ std::string next_str = dhcid.toStr();
+ EXPECT_EQ(test_str, next_str);
+
+ // Test equality, inequality, and less-than-equal operators
+ test_str="AABBCCDD";
+ EXPECT_NO_THROW(dhcid.fromStr(test_str));
+
+ D2Dhcid other_dhcid;
+ EXPECT_NO_THROW(other_dhcid.fromStr(test_str));
+
+ EXPECT_TRUE(dhcid == other_dhcid);
+ EXPECT_FALSE(dhcid != other_dhcid);
+
+ EXPECT_NO_THROW(other_dhcid.fromStr("BBCCDDEE"));
+ EXPECT_TRUE(dhcid < other_dhcid);
+
+}
+
+/// Tests that DHCID is correctly created from a DUID and FQDN. The final format
+/// of the DHCID is as follows:
+/// <identifier-type> <digest-type-code> <digest>
+/// where:
+/// - identifier-type (2 octets) is 0x0002.
+/// - digest-type-code (1 octet) indicates SHA-256 hashing and is equal 0x1.
+/// - digest = SHA-256(<DUID> <FQDN>)
+/// Note: FQDN is given in the on-wire canonical format.
+TEST(NameChangeRequestTest, dhcidFromDUID) {
+ D2Dhcid dhcid;
+
+ // Create DUID.
+ uint8_t duid_data[] = { 0, 1, 2, 3, 4, 5, 6 };
+ DUID duid(duid_data, sizeof(duid_data));
+
+ // Create FQDN in on-wire format: myhost.example.com. It is encoded
+ // as a set of labels, each preceded by its length. The whole FQDN
+ // is zero-terminated.
+ const uint8_t fqdn_data[] = {
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ std::vector<uint8_t> wire_fqdn(fqdn_data, fqdn_data + sizeof(fqdn_data));
+
+ // Create DHCID.
+ ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn));
+
+ // The reference DHCID (represented as string of hexadecimal digits)
+ // has been calculated using one of the online calculators.
+ std::string dhcid_ref = "0002012191B7B21AF97E0E656DF887C5E2D"
+ "EF30E7758A207EDF4CCB2DE8CA37066021C";
+
+ // Make sure that the DHCID is valid.
+ EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+// Test that DHCID is correctly created when the DUID has minimal length (1).
+TEST(NameChangeRequestTest, dhcidFromMinDUID) {
+ D2Dhcid dhcid;
+
+ // Create DUID.
+ uint8_t duid_data[] = { 1 };
+ DUID duid(duid_data, sizeof(duid_data));
+
+ // Create FQDN in on-wire format: myhost.example.com. It is encoded
+ // as a set of labels, each preceded by its length. The whole FQDN
+ // is zero-terminated.
+ const uint8_t fqdn_data[] = {
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ std::vector<uint8_t> wire_fqdn(fqdn_data, fqdn_data + sizeof(fqdn_data));
+
+ // Create DHCID.
+ ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn));
+
+ // The reference DHCID (represented as string of hexadecimal digits)
+ // has been calculated using one of the online calculators.
+ std::string dhcid_ref = "000201F89004F73E60CAEDFF514E11CB91D"
+ "1F45C8F0A55D4BC4C688484A819F8EA4074";
+
+ // Make sure that the DHCID is valid.
+ EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+// Test that DHCID is correctly created when the DUID has maximal length (128).
+TEST(NameChangeRequestTest, dhcidFromMaxDUID) {
+ D2Dhcid dhcid;
+
+ // Create DUID.
+ std::vector<uint8_t> duid_data(128, 1);
+ DUID duid(&duid_data[0], duid_data.size());
+
+ // Create FQDN in on-wire format: myhost.example.com. It is encoded
+ // as a set of labels, each preceded by its length. The whole FQDN
+ // is zero-terminated.
+ const uint8_t fqdn_data[] = {
+ 6, 109, 121, 104, 111, 115, 116, // myhost.
+ 7, 101, 120, 97, 109, 112, 108, 101, // example.
+ 3, 99, 111, 109, 0 // com.
+ };
+ std::vector<uint8_t> wire_fqdn(fqdn_data, fqdn_data + sizeof(fqdn_data));
+
+ // Create DHCID.
+ ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn));
+
+ // The reference DHCID (represented as string of hexadecimal digits)
+ // has been calculated using one of the online calculators.
+ std::string dhcid_ref = "00020137D8FBDC0585B44DFA03FAD2E36C6"
+ "159737D545A12EFB40B0D88D110A5748234";
+
+ // Make sure that the DHCID is valid.
+ EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+
+/// @brief Verifies the fundamentals of converting from and to JSON.
+/// It verifies that:
+/// 1. A NameChangeRequest can be created from a valid JSON string.
+/// 2. A valid JSON string can be created from a NameChangeRequest
+TEST(NameChangeRequestTest, basicJsonTest) {
+ // Define valid JSON rendition of a request.
+ std::string msg_str = "{"
+ "\"change_type\":1,"
+ "\"forward_change\":true,"
+ "\"reverse_change\":false,"
+ "\"fqdn\":\"walah.walah.com\","
+ "\"ip_address\":\"192.168.2.1\","
+ "\"dhcid\":\"010203040A7F8E3D\","
+ "\"lease_expires_on\":\"20130121132405\","
+ "\"lease_length\":1300"
+ "}";
+
+ // Verify that a NameChangeRequests can be instantiated from the
+ // a valid JSON rendition.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str));
+ ASSERT_TRUE(ncr);
+
+ // Verify that the JSON string created by the new request equals the
+ // original input string.
+ std::string json_str = ncr->toJSON();
+ EXPECT_EQ(msg_str, json_str);
+}
+
+/// @brief Tests a variety of invalid JSON message strings.
+/// This test iterates over a list of JSON messages, each containing a single
+/// content error. The list of messages is defined by the global array,
+/// invalid_messages. Currently that list contains the following invalid
+/// conditions:
+/// 1. Invalid change type
+/// 2. Invalid forward change
+/// 3. Invalid reverse change
+/// 4. Forward and reverse change both false
+/// 5. Invalid forward change
+/// 6. Blank FQDN
+/// 7. Bad IP address
+/// 8. Blank DHCID
+/// 9. Odd number of digits in DHCID
+/// 10. Text in DHCID
+/// 11. Invalid lease expiration string
+/// 12. Non-integer for lease length.
+/// If more permutations arise they can easily be added to the list.
+TEST(NameChangeRequestTest, invalidMsgChecks) {
+ // Iterate over the list of JSON strings, attempting to create a
+ // NameChangeRequest. The attempt should throw a NcrMessageError.
+ int num_msgs = sizeof(invalid_msgs)/sizeof(char*);
+ for (int i = 0; i < num_msgs; i++) {
+ EXPECT_THROW(NameChangeRequest::fromJSON(invalid_msgs[i]),
+ NcrMessageError) << "Invalid message not caught idx: "
+ << i << std::endl << " text:[" << invalid_msgs[i] << "]"
+ << std::endl;
+ }
+}
+
+/// @brief Tests a variety of valid JSON message strings.
+/// This test iterates over a list of JSON messages, each containing a single
+/// valid request rendition. The list of messages is defined by the global
+/// array, valid_messages. Currently that list contains the following valid
+/// messages:
+/// 1. Valid, IPv4 Add
+/// 2. Valid, IPv4 Remove
+/// 3. Valid, IPv6 Add
+/// If more permutations arise they can easily be added to the list.
+TEST(NameChangeRequestTest, validMsgChecks) {
+ // Iterate over the list of JSON strings, attempting to create a
+ // NameChangeRequest. The attempt should succeed.
+ int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+ for (int i = 0; i < num_msgs; i++) {
+ EXPECT_NO_THROW(NameChangeRequest::fromJSON(valid_msgs[i]))
+ << "Valid message failed, message idx: " << i
+ << std::endl << " text:[" << valid_msgs[i] << "]"
+ << std::endl;
+ }
+}
+
+/// @brief Tests converting to and from JSON via isc::util buffer classes.
+/// This test verifies that:
+/// 1. A NameChangeRequest can be rendered in JSON written to an OutputBuffer
+/// 2. A InputBuffer containing a valid JSON request rendition can be used
+/// to create a NameChangeRequest.
+TEST(NameChangeRequestTest, toFromBufferTest) {
+ // Define a string containing a valid JSON NameChangeRequest rendition.
+ std::string msg_str = "{"
+ "\"change_type\":1,"
+ "\"forward_change\":true,"
+ "\"reverse_change\":false,"
+ "\"fqdn\":\"walah.walah.com\","
+ "\"ip_address\":\"192.168.2.1\","
+ "\"dhcid\":\"010203040A7F8E3D\","
+ "\"lease_expires_on\":\"20130121132405\","
+ "\"lease_length\":1300"
+ "}";
+
+ // Create a request from JSON directly.
+ NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str));
+
+ // Verify that we output the request as JSON text to a buffer
+ // without error.
+ isc::util::OutputBuffer output_buffer(1024);
+ ASSERT_NO_THROW(ncr->toFormat(FMT_JSON, output_buffer));
+
+ // Make an InputBuffer from the OutputBuffer.
+ isc::util::InputBuffer input_buffer(output_buffer.getData(),
+ output_buffer.getLength());
+
+ // Verify that we can create a new request from the InputBuffer.
+ NameChangeRequestPtr ncr2;
+ ASSERT_NO_THROW(ncr2 =
+ NameChangeRequest::fromFormat(FMT_JSON, input_buffer));
+
+ // Convert the new request to JSON directly.
+ std::string final_str = ncr2->toJSON();
+
+ // Verify that the final string matches the original.
+ ASSERT_EQ(final_str, msg_str);
+}
+
+
+} // end of anonymous namespace
+
diff --git a/src/lib/dhcp_ddns/tests/run_unittests.cc b/src/lib/dhcp_ddns/tests/run_unittests.cc
new file mode 100644
index 0000000..bd32c4d
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/run_unittests.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log/logger_support.h>
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ isc::log::initLogger();
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index e721267..5390a01 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -1,6 +1,8 @@
SUBDIRS = . tests
-AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -DDHCP_DATA_DIR="\"$(localstatedir)\""
+dhcp_data_dir = @localstatedir@/@PACKAGE@
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -DDHCP_DATA_DIR="\"$(dhcp_data_dir)\""
AM_CPPFLAGS += $(BOOST_INCLUDES)
if HAVE_MYSQL
AM_CPPFLAGS += $(MYSQL_CPPFLAGS)
@@ -33,10 +35,13 @@ lib_LTLIBRARIES = libb10-dhcpsrv.la
libb10_dhcpsrv_la_SOURCES =
libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
+libb10_dhcpsrv_la_SOURCES += callout_handle_store.h
libb10_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h
libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
libb10_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
libb10_dhcpsrv_la_SOURCES += dhcp_config_parser.h
+libb10_dhcpsrv_la_SOURCES += dhcp_parsers.cc dhcp_parsers.h
+libb10_dhcpsrv_la_SOURCES += key_from_key.h
libb10_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
libb10_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
libb10_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
@@ -55,7 +60,12 @@ libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
libb10_dhcpsrv_la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
+libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
+libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
+
libb10_dhcpsrv_la_LDFLAGS = -no-undefined -version-info 3:0:0
if HAVE_MYSQL
libb10_dhcpsrv_la_LDFLAGS += $(MYSQL_LIBS)
@@ -73,3 +83,8 @@ EXTRA_DIST = dhcpsrv_messages.mes
# Distribute MySQL schema creation script and backend documentation
EXTRA_DIST += dhcpdb_create.mysql database_backends.dox libdhcpsrv.dox
dist_pkgdata_DATA = dhcpdb_create.mysql
+
+install-data-local:
+ $(mkinstalldirs) $(DESTDIR)$(dhcp_data_dir)
+
+
diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc
index bcb7851..2bef8f8 100644
--- a/src/lib/dhcpsrv/alloc_engine.cc
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -16,11 +16,39 @@
#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/lease_mgr_factory.h>
+#include <hooks/server_hooks.h>
+#include <hooks/hooks_manager.h>
+
#include <cstring>
#include <vector>
#include <string.h>
using namespace isc::asiolink;
+using namespace isc::hooks;
+
+namespace {
+
+/// Structure that holds registered hook indexes
+struct AllocEngineHooks {
+ int hook_index_lease4_select_; ///< index for "lease4_receive" hook point
+ int hook_index_lease4_renew_; ///< index for "lease4_renew" hook point
+ int hook_index_lease6_select_; ///< index for "lease6_receive" hook point
+
+ /// Constructor that registers hook points for AllocationEngine
+ AllocEngineHooks() {
+ hook_index_lease4_select_ = HooksManager::registerHook("lease4_select");
+ hook_index_lease4_renew_ = HooksManager::registerHook("lease4_renew");
+ hook_index_lease6_select_ = HooksManager::registerHook("lease6_select");
+ }
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+AllocEngineHooks Hooks;
+
+}; // anonymous namespace
namespace isc {
namespace dhcp {
@@ -65,7 +93,7 @@ AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet,
// Let's get the last allocated address. It is usually set correctly,
// but there are times when it won't be (like after removing a pool or
- // perhaps restaring the server).
+ // perhaps restarting the server).
IOAddress last = subnet->getLastAllocated();
const PoolCollection& pools = subnet->getPools();
@@ -112,7 +140,7 @@ AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet,
return (next);
}
- // there is a next pool, let's try first adddress from it
+ // there is a next pool, let's try first address from it
next = (*it)->getFirstAddress();
subnet->setLastAllocated(next);
return (next);
@@ -161,6 +189,10 @@ AllocEngine::AllocEngine(AllocType engine_type, unsigned int attempts)
default:
isc_throw(BadValue, "Invalid/unsupported allocation algorithm");
}
+
+ // Register hook points
+ hook_index_lease4_select_ = Hooks.hook_index_lease4_select_;
+ hook_index_lease6_select_ = Hooks.hook_index_lease6_select_;
}
Lease6Ptr
@@ -168,7 +200,11 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
const DuidPtr& duid,
uint32_t iaid,
const IOAddress& hint,
- bool fake_allocation /* = false */ ) {
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
+ bool fake_allocation,
+ const isc::hooks::CalloutHandlePtr& callout_handle) {
try {
// That check is not necessary. We create allocator in AllocEngine
@@ -177,6 +213,14 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
isc_throw(InvalidOperation, "No allocator selected");
}
+ if (!subnet) {
+ isc_throw(InvalidOperation, "Subnet is required for allocation");
+ }
+
+ if (!duid) {
+ isc_throw(InvalidOperation, "DUID is mandatory for allocation");
+ }
+
// check if there's existing lease for that subnet/duid/iaid combination.
Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(*duid, iaid, subnet->getID());
if (existing) {
@@ -193,7 +237,12 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
/// implemented
// the hint is valid and not currently used, let's create a lease for it
- Lease6Ptr lease = createLease6(subnet, duid, iaid, hint, fake_allocation);
+ Lease6Ptr lease = createLease6(subnet, duid, iaid,
+ hint,
+ fwd_dns_update,
+ rev_dns_update, hostname,
+ callout_handle,
+ fake_allocation);
// It can happen that the lease allocation failed (we could have lost
// the race condition. That means that the hint is lo longer usable and
@@ -204,6 +253,8 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
} else {
if (existing->expired()) {
return (reuseExpiredLease(existing, subnet, duid, iaid,
+ fwd_dns_update, rev_dns_update,
+ hostname, callout_handle,
fake_allocation));
}
@@ -238,7 +289,9 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
// there's no existing lease for selected candidate, so it is
// free. Let's allocate it.
Lease6Ptr lease = createLease6(subnet, duid, iaid, candidate,
- fake_allocation);
+ fwd_dns_update, rev_dns_update,
+ hostname,
+ callout_handle, fake_allocation);
if (lease) {
return (lease);
}
@@ -249,6 +302,8 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
} else {
if (existing->expired()) {
return (reuseExpiredLease(existing, subnet, duid, iaid,
+ fwd_dns_update, rev_dns_update,
+ hostname, callout_handle,
fake_allocation));
}
}
@@ -275,7 +330,17 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
const ClientIdPtr& clientid,
const HWAddrPtr& hwaddr,
const IOAddress& hint,
- bool fake_allocation /* = false */ ) {
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
+ bool fake_allocation,
+ const isc::hooks::CalloutHandlePtr& callout_handle,
+ Lease4Ptr& old_lease) {
+
+ // The NULL pointer indicates that the old lease didn't exist. It may
+ // be later set to non NULL value if existing lease is found in the
+ // database.
+ old_lease.reset();
try {
// Allocator is always created in AllocEngine constructor and there is
@@ -284,12 +349,24 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
isc_throw(InvalidOperation, "No allocator selected");
}
+ if (!subnet) {
+ isc_throw(InvalidOperation, "Can't allocate IPv4 address without subnet");
+ }
+
+ if (!hwaddr) {
+ isc_throw(InvalidOperation, "HWAddr must be defined");
+ }
+
// Check if there's existing lease for that subnet/clientid/hwaddr combination.
- Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(hwaddr->hwaddr_, subnet->getID());
+ Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(*hwaddr, subnet->getID());
if (existing) {
+ // Save the old lease, before renewal.
+ old_lease.reset(new Lease4(*existing));
// We have a lease already. This is a returning client, probably after
// its reboot.
- existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
+ existing = renewLease4(subnet, clientid, hwaddr,
+ fwd_dns_update, rev_dns_update, hostname,
+ existing, callout_handle, fake_allocation);
if (existing) {
return (existing);
}
@@ -301,9 +378,14 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
if (clientid) {
existing = LeaseMgrFactory::instance().getLease4(*clientid, subnet->getID());
if (existing) {
+ // Save the old lease before renewal.
+ old_lease.reset(new Lease4(*existing));
// we have a lease already. This is a returning client, probably after
// its reboot.
- existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
+ existing = renewLease4(subnet, clientid, hwaddr,
+ fwd_dns_update, rev_dns_update,
+ hostname, existing, callout_handle,
+ fake_allocation);
// @todo: produce a warning. We haven't found him using MAC address, but
// we found him using client-id
if (existing) {
@@ -320,7 +402,10 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
/// implemented
// The hint is valid and not currently used, let's create a lease for it
- Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, hint, fake_allocation);
+ Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, hint,
+ fwd_dns_update, rev_dns_update,
+ hostname, callout_handle,
+ fake_allocation);
// It can happen that the lease allocation failed (we could have lost
// the race condition. That means that the hint is lo longer usable and
@@ -330,7 +415,11 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
}
} else {
if (existing->expired()) {
+ // Save the old lease, before reusing it.
+ old_lease.reset(new Lease4(*existing));
return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
+ fwd_dns_update, rev_dns_update,
+ hostname, callout_handle,
fake_allocation));
}
@@ -364,8 +453,10 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
if (!existing) {
// there's no existing lease for selected candidate, so it is
// free. Let's allocate it.
- Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, candidate,
- fake_allocation);
+ Lease4Ptr lease = createLease4(subnet, clientid, hwaddr,
+ candidate, fwd_dns_update,
+ rev_dns_update, hostname,
+ callout_handle, fake_allocation);
if (lease) {
return (lease);
}
@@ -375,7 +466,11 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
// allocation attempts.
} else {
if (existing->expired()) {
+ // Save old lease before reusing it.
+ old_lease.reset(new Lease4(*existing));
return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
+ fwd_dns_update, rev_dns_update,
+ hostname, callout_handle,
fake_allocation));
}
}
@@ -399,9 +494,23 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
Lease4Ptr AllocEngine::renewLease4(const SubnetPtr& subnet,
const ClientIdPtr& clientid,
const HWAddrPtr& hwaddr,
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
const Lease4Ptr& lease,
+ const isc::hooks::CalloutHandlePtr& callout_handle,
bool fake_allocation /* = false */) {
+ if (!lease) {
+ isc_throw(InvalidOperation, "Lease4 must be specified");
+ }
+
+ // Let's keep the old data. This is essential if we are using memfile
+ // (the lease returned points directly to the lease4 object in the database)
+ // We'll need it if we want to skip update (i.e. roll back renewal)
+ /// @todo: remove this once #3083 is implemented
+ Lease4 old_values = *lease;
+
lease->subnet_id_ = subnet->getID();
lease->hwaddr_ = hwaddr->hwaddr_;
lease->client_id_ = clientid;
@@ -409,11 +518,52 @@ Lease4Ptr AllocEngine::renewLease4(const SubnetPtr& subnet,
lease->t1_ = subnet->getT1();
lease->t2_ = subnet->getT2();
lease->valid_lft_ = subnet->getValid();
+ lease->fqdn_fwd_ = fwd_dns_update;
+ lease->fqdn_rev_ = rev_dns_update;
+ lease->hostname_ = hostname;
+
+ bool skip = false;
+ // Execute all callouts registered for packet6_send
+ if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_lease4_renew_)) {
+
+ // Delete all previous arguments
+ callout_handle->deleteAllArguments();
+
+ // Subnet from which we do the allocation. Convert the general subnet
+ // pointer to a pointer to a Subnet4. Note that because we are using
+ // boost smart pointers here, we need to do the cast using the boost
+ // version of dynamic_pointer_cast.
+ Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(subnet);
+
+ // Pass the parameters
+ callout_handle->setArgument("subnet4", subnet4);
+ callout_handle->setArgument("clientid", clientid);
+ callout_handle->setArgument("hwaddr", hwaddr);
+
+ // Pass the lease to be updated
+ callout_handle->setArgument("lease4", lease);
+
+ // Call all installed callouts
+ HooksManager::callCallouts(Hooks.hook_index_lease4_renew_, *callout_handle);
+
+ // Callouts decided to skip the next processing step. The next
+ // processing step would to actually renew the lease, so skip at this
+ // stage means "keep the old lease as it is".
+ if (callout_handle->getSkip()) {
+ skip = true;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_RENEW_SKIP);
+ }
+ }
- if (!fake_allocation) {
+ if (!fake_allocation && !skip) {
// for REQUEST we do update the lease
LeaseMgrFactory::instance().updateLease4(lease);
}
+ if (skip) {
+ // Rollback changes (really useful only for memfile)
+ /// @todo: remove this once #3083 is implemented
+ *lease = old_values;
+ }
return (lease);
}
@@ -422,6 +572,10 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
const Subnet6Ptr& subnet,
const DuidPtr& duid,
uint32_t iaid,
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
+ const isc::hooks::CalloutHandlePtr& callout_handle,
bool fake_allocation /*= false */ ) {
if (!expired->expired()) {
@@ -438,13 +592,46 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
expired->cltt_ = time(NULL);
expired->subnet_id_ = subnet->getID();
expired->fixed_ = false;
- expired->hostname_ = std::string("");
- expired->fqdn_fwd_ = false;
- expired->fqdn_rev_ = false;
+ expired->hostname_ = hostname;
+ expired->fqdn_fwd_ = fwd_dns_update;
+ expired->fqdn_rev_ = rev_dns_update;
/// @todo: log here that the lease was reused (there's ticket #2524 for
/// logging in libdhcpsrv)
+ // Let's execute all callouts registered for lease6_select
+ if (callout_handle &&
+ HooksManager::getHooksManager().calloutsPresent(hook_index_lease6_select_)) {
+
+ // Delete all previous arguments
+ callout_handle->deleteAllArguments();
+
+ // Pass necessary arguments
+ // Subnet from which we do the allocation
+ callout_handle->setArgument("subnet6", subnet);
+
+ // Is this solicit (fake = true) or request (fake = false)
+ callout_handle->setArgument("fake_allocation", fake_allocation);
+
+ // The lease that will be assigned to a client
+ callout_handle->setArgument("lease6", expired);
+
+ // Call the callouts
+ HooksManager::callCallouts(hook_index_lease6_select_, *callout_handle);
+
+ // Callouts decided to skip the action. This means that the lease is not
+ // assigned, so the client will get NoAddrAvail as a result. The lease
+ // won't be inserted into the database.
+ if (callout_handle->getSkip()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP);
+ return (Lease6Ptr());
+ }
+
+ // Let's use whatever callout returned. Hopefully it is the same lease
+ // we handled to it.
+ callout_handle->getArgument("lease6", expired);
+ }
+
if (!fake_allocation) {
// for REQUEST we do update the lease
LeaseMgrFactory::instance().updateLease6(expired);
@@ -462,6 +649,10 @@ Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
const SubnetPtr& subnet,
const ClientIdPtr& clientid,
const HWAddrPtr& hwaddr,
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
+ const isc::hooks::CalloutHandlePtr& callout_handle,
bool fake_allocation /*= false */ ) {
if (!expired->expired()) {
@@ -477,13 +668,51 @@ Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
expired->cltt_ = time(NULL);
expired->subnet_id_ = subnet->getID();
expired->fixed_ = false;
- expired->hostname_ = std::string("");
- expired->fqdn_fwd_ = false;
- expired->fqdn_rev_ = false;
+ expired->hostname_ = hostname;
+ expired->fqdn_fwd_ = fwd_dns_update;
+ expired->fqdn_rev_ = rev_dns_update;
/// @todo: log here that the lease was reused (there's ticket #2524 for
/// logging in libdhcpsrv)
+ // Let's execute all callouts registered for lease4_select
+ if (callout_handle &&
+ HooksManager::getHooksManager().calloutsPresent(hook_index_lease4_select_)) {
+
+ // Delete all previous arguments
+ callout_handle->deleteAllArguments();
+
+ // Pass necessary arguments
+
+ // Subnet from which we do the allocation. Convert the general subnet
+ // pointer to a pointer to a Subnet4. Note that because we are using
+ // boost smart pointers here, we need to do the cast using the boost
+ // version of dynamic_pointer_cast.
+ Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(subnet);
+ callout_handle->setArgument("subnet4", subnet4);
+
+ // Is this solicit (fake = true) or request (fake = false)
+ callout_handle->setArgument("fake_allocation", fake_allocation);
+
+ // The lease that will be assigned to a client
+ callout_handle->setArgument("lease4", expired);
+
+ // Call the callouts
+ HooksManager::callCallouts(hook_index_lease6_select_, *callout_handle);
+
+ // Callouts decided to skip the action. This means that the lease is not
+ // assigned, so the client will get NoAddrAvail as a result. The lease
+ // won't be inserted into the database.
+ if (callout_handle->getSkip()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
+ return (Lease4Ptr());
+ }
+
+ // Let's use whatever callout returned. Hopefully it is the same lease
+ // we handled to it.
+ callout_handle->getArgument("lease4", expired);
+ }
+
if (!fake_allocation) {
// for REQUEST we do update the lease
LeaseMgrFactory::instance().updateLease4(expired);
@@ -501,12 +730,52 @@ Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
const DuidPtr& duid,
uint32_t iaid,
const IOAddress& addr,
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
+ const isc::hooks::CalloutHandlePtr& callout_handle,
bool fake_allocation /*= false */ ) {
Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid, iaid,
subnet->getPreferred(), subnet->getValid(),
subnet->getT1(), subnet->getT2(), subnet->getID()));
+ lease->fqdn_fwd_ = fwd_dns_update;
+ lease->fqdn_rev_ = rev_dns_update;
+ lease->hostname_ = hostname;
+
+ // Let's execute all callouts registered for lease6_select
+ if (callout_handle &&
+ HooksManager::getHooksManager().calloutsPresent(hook_index_lease6_select_)) {
+
+ // Delete all previous arguments
+ callout_handle->deleteAllArguments();
+
+ // Pass necessary arguments
+
+ // Subnet from which we do the allocation
+ callout_handle->setArgument("subnet6", subnet);
+
+ // Is this solicit (fake = true) or request (fake = false)
+ callout_handle->setArgument("fake_allocation", fake_allocation);
+ callout_handle->setArgument("lease6", lease);
+
+ // This is the first callout, so no need to clear any arguments
+ HooksManager::callCallouts(hook_index_lease6_select_, *callout_handle);
+
+ // Callouts decided to skip the action. This means that the lease is not
+ // assigned, so the client will get NoAddrAvail as a result. The lease
+ // won't be inserted into the database.
+ if (callout_handle->getSkip()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP);
+ return (Lease6Ptr());
+ }
+
+ // Let's use whatever callout returned. Hopefully it is the same lease
+ // we handled to it.
+ callout_handle->getArgument("lease6", lease);
+ }
+
if (!fake_allocation) {
// That is a real (REQUEST) allocation
bool status = LeaseMgrFactory::instance().addLease(lease);
@@ -539,6 +808,10 @@ Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
const DuidPtr& clientid,
const HWAddrPtr& hwaddr,
const IOAddress& addr,
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
+ const isc::hooks::CalloutHandlePtr& callout_handle,
bool fake_allocation /*= false */ ) {
if (!hwaddr) {
isc_throw(BadValue, "Can't create a lease with NULL HW address");
@@ -556,6 +829,49 @@ Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
subnet->getT1(), subnet->getT2(), now,
subnet->getID()));
+ // Set FQDN specific lease parameters.
+ lease->fqdn_fwd_ = fwd_dns_update;
+ lease->fqdn_rev_ = rev_dns_update;
+ lease->hostname_ = hostname;
+
+ // Let's execute all callouts registered for lease4_select
+ if (callout_handle &&
+ HooksManager::getHooksManager().calloutsPresent(hook_index_lease4_select_)) {
+
+ // Delete all previous arguments
+ callout_handle->deleteAllArguments();
+
+ // Pass necessary arguments
+
+ // Subnet from which we do the allocation (That's as far as we can go
+ // with using SubnetPtr to point to Subnet4 object. Users should not
+ // be confused with dynamic_pointer_casts. They should get a concrete
+ // pointer (Subnet4Ptr) pointing to a Subnet4 object.
+ Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(subnet);
+ callout_handle->setArgument("subnet4", subnet4);
+
+ // Is this solicit (fake = true) or request (fake = false)
+ callout_handle->setArgument("fake_allocation", fake_allocation);
+
+ // Pass the intended lease as well
+ callout_handle->setArgument("lease4", lease);
+
+ // This is the first callout, so no need to clear any arguments
+ HooksManager::callCallouts(hook_index_lease4_select_, *callout_handle);
+
+ // Callouts decided to skip the action. This means that the lease is not
+ // assigned, so the client will get NoAddrAvail as a result. The lease
+ // won't be inserted into the database.
+ if (callout_handle->getSkip()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
+ return (Lease4Ptr());
+ }
+
+ // Let's use whatever callout returned. Hopefully it is the same lease
+ // we handled to it.
+ callout_handle->getArgument("lease4", lease);
+ }
+
if (!fake_allocation) {
// That is a real (REQUEST) allocation
bool status = LeaseMgrFactory::instance().addLease(lease);
diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h
index 7e3d136..ad18b83 100644
--- a/src/lib/dhcpsrv/alloc_engine.h
+++ b/src/lib/dhcpsrv/alloc_engine.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// 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,6 +20,7 @@
#include <dhcp/hwaddr.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/lease_mgr.h>
+#include <hooks/callout_handle.h>
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>
@@ -181,25 +182,63 @@ protected:
/// we give up (0 means unlimited)
AllocEngine(AllocType engine_type, unsigned int attempts);
- /// @brief Allocates an IPv4 lease
+ /// @brief Returns IPv4 lease.
///
- /// This method uses currently selected allocator to pick an address from
- /// specified subnet, creates a lease for that address and then inserts
- /// it into LeaseMgr (if this allocation is not fake).
+ /// This method finds the appropriate lease for the client using the
+ /// following algorithm:
+ /// - If lease exists for the combination of the HW address, client id and
+ /// subnet, try to renew a lease and return it.
+ /// - If lease exists for the combination of the client id and subnet, try
+ /// to renew the lease and return it.
+ /// - If client supplied an address hint and this address is available,
+ /// allocate the new lease with this address.
+ /// - If client supplied an address hint and the lease for this address
+ /// exists in the database, return this lease if it is expired.
+ /// - Pick new address from the pool and try to allocate it for the client,
+ /// if expired lease exists for the picked address, try to reuse this lease.
+ ///
+ /// When a server should do DNS updates, it is required that allocation
+ /// returns the information how the lease was obtained by the allocation
+ /// engine. In particular, the DHCP server should be able to check whether
+ /// existing lease was returned, or new lease was allocated. When existing
+ /// lease was returned, server should check whether the FQDN has changed
+ /// between the allocation of the old and new lease. If so, server should
+ /// perform appropriate DNS update. If not, server may choose to not
+ /// perform the update. The information about the old lease is returned via
+ /// @c old_lease parameter. If NULL value is returned, it is an indication
+ /// that new lease was allocated for the client. If non-NULL value is
+ /// returned, it is an indication that allocation engine reused/renewed an
+ /// existing lease.
///
/// @param subnet subnet the allocation should come from
/// @param clientid Client identifier
- /// @param hwaddr client's hardware address info
- /// @param hint a hint that the client provided
- /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// @param hwaddr Client's hardware address info
+ /// @param hint A hint that the client provided
+ /// @param fwd_dns_update Indicates whether forward DNS update will be
+ /// performed for the client (true) or not (false).
+ /// @param rev_dns_update Indicates whether reverse DNS update will be
+ /// performed for the client (true) or not (false).
+ /// @param hostname A string carrying hostname to be used for DNS updates.
+ /// @param fake_allocation Is this real i.e. REQUEST (false) or just picking
/// an address for DISCOVER that is not really allocated (true)
+ /// @param callout_handle A callout handle (used in hooks). A lease callouts
+ /// will be executed if this parameter is passed.
+ /// @param [out] old_lease Holds the pointer to a previous instance of a
+ /// lease. The NULL pointer indicates that lease didn't exist prior
+ /// to calling this function (e.g. new lease has been allocated).
+ ///
/// @return Allocated IPv4 lease (or NULL if allocation failed)
Lease4Ptr
allocateAddress4(const SubnetPtr& subnet,
const ClientIdPtr& clientid,
const HWAddrPtr& hwaddr,
const isc::asiolink::IOAddress& hint,
- bool fake_allocation);
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
+ bool fake_allocation,
+ const isc::hooks::CalloutHandlePtr& callout_handle,
+ Lease4Ptr& old_lease);
/// @brief Renews a IPv4 lease
///
@@ -210,17 +249,28 @@ protected:
/// to get a new lease. It thinks that it gets a new lease, but in fact
/// we are only renewing the still valid lease for that client.
///
- /// @param subnet subnet the client is attached to
- /// @param clientid client identifier
- /// @param hwaddr client's hardware address
- /// @param lease lease to be renewed
- /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// @param subnet A subnet the client is attached to
+ /// @param clientid Client identifier
+ /// @param hwaddr Client's hardware address
+ /// @param fwd_dns_update Indicates whether forward DNS update will be
+ /// performed for the client (true) or not (false).
+ /// @param rev_dns_update Indicates whether reverse DNS update will be
+ /// performed for the client (true) or not (false).
+ /// @param hostname A string carrying hostname to be used for DNS updates.
+ /// @param lease A lease to be renewed
+ /// @param callout_handle a callout handle (used in hooks). A lease callouts
+ /// will be executed if this parameter is passed.
+ /// @param fake_allocation Is this real i.e. REQUEST (false) or just picking
/// an address for DISCOVER that is not really allocated (true)
Lease4Ptr
renewLease4(const SubnetPtr& subnet,
const ClientIdPtr& clientid,
const HWAddrPtr& hwaddr,
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
const Lease4Ptr& lease,
+ const isc::hooks::CalloutHandlePtr& callout_handle,
bool fake_allocation /* = false */);
/// @brief Allocates an IPv6 lease
@@ -233,15 +283,29 @@ protected:
/// @param duid Client's DUID
/// @param iaid iaid field from the IA_NA container that client sent
/// @param hint a hint that the client provided
+ /// @param fwd_dns_update A boolean value which indicates that server takes
+ /// responsibility for the forward DNS Update for this lease
+ /// (if true).
+ /// @param rev_dns_update A boolean value which indicates that server takes
+ /// responsibility for the reverse DNS Update for this lease
+ /// (if true).
+ /// @param hostname A fully qualified domain-name of the client.
/// @param fake_allocation is this real i.e. REQUEST (false) or just picking
/// an address for SOLICIT that is not really allocated (true)
+ /// @param callout_handle a callout handle (used in hooks). A lease callouts
+ /// will be executed if this parameter is passed.
+ ///
/// @return Allocated IPv6 lease (or NULL if allocation failed)
Lease6Ptr
allocateAddress6(const Subnet6Ptr& subnet,
const DuidPtr& duid,
uint32_t iaid,
const isc::asiolink::IOAddress& hint,
- bool fake_allocation);
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
+ bool fake_allocation,
+ const isc::hooks::CalloutHandlePtr& callout_handle);
/// @brief Destructor. Used during DHCPv6 service shutdown.
virtual ~AllocEngine();
@@ -253,17 +317,29 @@ private:
/// into the database. That may fail in some cases, e.g. when there is another
/// allocation process and we lost a race to a specific lease.
///
- /// @param subnet subnet the lease is allocated from
- /// @param clientid client identifier
- /// @param hwaddr client's hardware address
- /// @param addr an address that was selected and is confirmed to be available
- /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// @param subnet Subnet the lease is allocated from
+ /// @param clientid Client identifier
+ /// @param hwaddr Client's hardware address
+ /// @param addr An address that was selected and is confirmed to be available
+ /// @param fwd_dns_update Indicates whether forward DNS update will be
+ /// performed for the client (true) or not (false).
+ /// @param rev_dns_update Indicates whether reverse DNS update will be
+ /// performed for the client (true) or not (false).
+ /// @param hostname A string carrying hostname to be used for DNS updates.
+ /// @param callout_handle a callout handle (used in hooks). A lease callouts
+ /// will be executed if this parameter is passed (and there are callouts
+ /// registered)
+ /// @param fake_allocation Is this real i.e. REQUEST (false) or just picking
/// an address for DISCOVER that is not really allocated (true)
/// @return allocated lease (or NULL in the unlikely case of the lease just
/// becomed unavailable)
Lease4Ptr createLease4(const SubnetPtr& subnet, const DuidPtr& clientid,
const HWAddrPtr& hwaddr,
const isc::asiolink::IOAddress& addr,
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
+ const isc::hooks::CalloutHandlePtr& callout_handle,
bool fake_allocation = false);
/// @brief creates a lease and inserts it in LeaseMgr if necessary
@@ -275,13 +351,27 @@ private:
/// @param subnet subnet the lease is allocated from
/// @param duid client's DUID
/// @param iaid IAID from the IA_NA container the client sent to us
- /// @param addr an address that was selected and is confirmed to be available
+ /// @param addr an address that was selected and is confirmed to be
+ /// available
+ /// @param fwd_dns_update A boolean value which indicates that server takes
+ /// responsibility for the forward DNS Update for this lease
+ /// (if true).
+ /// @param rev_dns_update A boolean value which indicates that server takes
+ /// responsibility for the reverse DNS Update for this lease
+ /// (if true).
+ /// @param hostname A fully qualified domain-name of the client.
+ /// @param callout_handle a callout handle (used in hooks). A lease callouts
+ /// will be executed if this parameter is passed (and there are callouts
+ /// registered)
/// @param fake_allocation is this real i.e. REQUEST (false) or just picking
/// an address for SOLICIT that is not really allocated (true)
/// @return allocated lease (or NULL in the unlikely case of the lease just
- /// becomed unavailable)
+ /// became unavailable)
Lease6Ptr createLease6(const Subnet6Ptr& subnet, const DuidPtr& duid,
uint32_t iaid, const isc::asiolink::IOAddress& addr,
+ const bool fwd_dns_update, const bool rev_dns_update,
+ const std::string& hostname,
+ const isc::hooks::CalloutHandlePtr& callout_handle,
bool fake_allocation = false);
/// @brief Reuses expired IPv4 lease
@@ -290,17 +380,29 @@ private:
/// is updated if this is real (i.e. REQUEST, fake_allocation = false), not
/// dummy allocation request (i.e. DISCOVER, fake_allocation = true).
///
- /// @param expired old, expired lease
- /// @param subnet subnet the lease is allocated from
- /// @param clientid client identifier
- /// @param hwaddr client's hardware address
- /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// @param expired Old, expired lease
+ /// @param subnet Subnet the lease is allocated from
+ /// @param clientid Client identifier
+ /// @param hwaddr Client's hardware address
+ /// @param fwd_dns_update Indicates whether forward DNS update will be
+ /// performed for the client (true) or not (false).
+ /// @param rev_dns_update Indicates whether reverse DNS update will be
+ /// performed for the client (true) or not (false).
+ /// @param hostname A string carrying hostname to be used for DNS updates.
+ /// @param callout_handle A callout handle (used in hooks). A lease callouts
+ /// will be executed if this parameter is passed.
+ /// @param fake_allocation Is this real i.e. REQUEST (false) or just picking
/// an address for DISCOVER that is not really allocated (true)
/// @return refreshed lease
/// @throw BadValue if trying to recycle lease that is still valid
- Lease4Ptr reuseExpiredLease(Lease4Ptr& expired, const SubnetPtr& subnet,
+ Lease4Ptr reuseExpiredLease(Lease4Ptr& expired,
+ const SubnetPtr& subnet,
const ClientIdPtr& clientid,
const HWAddrPtr& hwaddr,
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
+ const isc::hooks::CalloutHandlePtr& callout_handle,
bool fake_allocation = false);
/// @brief Reuses expired IPv6 lease
@@ -313,12 +415,25 @@ private:
/// @param subnet subnet the lease is allocated from
/// @param duid client's DUID
/// @param iaid IAID from the IA_NA container the client sent to us
+ /// @param fwd_dns_update A boolean value which indicates that server takes
+ /// responsibility for the forward DNS Update for this lease
+ /// (if true).
+ /// @param rev_dns_update A boolean value which indicates that server takes
+ /// responsibility for the reverse DNS Update for this lease
+ /// (if true).
+ /// @param hostname A fully qualified domain-name of the client.
+ /// @param callout_handle a callout handle (used in hooks). A lease callouts
+ /// will be executed if this parameter is passed.
/// @param fake_allocation is this real i.e. REQUEST (false) or just picking
/// an address for SOLICIT that is not really allocated (true)
/// @return refreshed lease
/// @throw BadValue if trying to recycle lease that is still valid
Lease6Ptr reuseExpiredLease(Lease6Ptr& expired, const Subnet6Ptr& subnet,
const DuidPtr& duid, uint32_t iaid,
+ const bool fwd_dns_update,
+ const bool rev_dns_update,
+ const std::string& hostname,
+ const isc::hooks::CalloutHandlePtr& callout_handle,
bool fake_allocation = false);
/// @brief a pointer to currently used allocator
@@ -326,6 +441,10 @@ private:
/// @brief number of attempts before we give up lease allocation (0=unlimited)
unsigned int attempts_;
+
+ // hook name indexes (used in hooks callouts)
+ int hook_index_lease4_select_; ///< index for lease4_select hook
+ int hook_index_lease6_select_; ///< index for lease6_select hook
};
}; // namespace isc::dhcp
diff --git a/src/lib/dhcpsrv/callout_handle_store.h b/src/lib/dhcpsrv/callout_handle_store.h
new file mode 100644
index 0000000..697ba3a
--- /dev/null
+++ b/src/lib/dhcpsrv/callout_handle_store.h
@@ -0,0 +1,91 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 CALLOUT_HANDLE_STORE_H
+#define CALLOUT_HANDLE_STORE_H
+
+#include <hooks/hooks_manager.h>
+#include <hooks/callout_handle.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief CalloutHandle Store
+///
+/// When using the Hooks Framework, there is a need to associate an
+/// isc::hooks::CalloutHandle object with each request passing through the
+/// server. For the DHCP servers, the association is provided by this function.
+///
+/// The DHCP servers process a single request at a time. At points where the
+/// CalloutHandle is required, the pointer to the current request (packet) is
+/// passed to this function. If the request is a new one, a pointer to
+/// the request is stored, a new CalloutHandle is allocated (and stored) and
+/// a pointer to the latter object returned to the caller. If the request
+/// matches the one stored, the pointer to the stored CalloutHandle is
+/// returned.
+///
+/// A special case is a null pointer being passed. This has the effect of
+/// clearing the stored pointers to the packet being processed and
+/// CalloutHandle. As the stored pointers are shared pointers, clearing them
+/// removes one reference that keeps the pointed-to objects in existence.
+///
+/// @note If the behaviour of the server changes so that multiple packets can
+/// be active at the same time, this simplistic approach will no longer
+/// be adequate and a more complicated structure (such as a map) will
+/// be needed.
+///
+/// @param pktptr Pointer to the packet being processed. This is typically a
+/// Pkt4Ptr or Pkt6Ptr object. An empty pointer is passed to clear
+/// the stored pointers.
+///
+/// @return Shared pointer to a CalloutHandle. This is the previously-stored
+/// CalloutHandle if pktptr points to a packet that has been seen
+/// before or a new CalloutHandle if it points to a new one. An empty
+/// pointer is returned if pktptr is itself an empty pointer.
+
+template <typename T>
+isc::hooks::CalloutHandlePtr getCalloutHandle(const T& pktptr) {
+
+ // Stored data is declared static, so is initialized when first accessed
+ static T stored_pointer; // Pointer to last packet seen
+ static isc::hooks::CalloutHandlePtr stored_handle;
+ // Pointer to stored handle
+
+ if (pktptr) {
+
+ // Pointer given, have we seen it before? (If we have, we don't need to
+ // do anything as we will automatically return the stored handle.)
+ if (pktptr != stored_pointer) {
+
+ // Not seen before, so store the pointer passed to us and get a new
+ // CalloutHandle. (The latter operation frees and probably deletes
+ // (depending on other pointers) the stored one.)
+ stored_pointer = pktptr;
+ stored_handle = isc::hooks::HooksManager::createCalloutHandle();
+ }
+
+ } else {
+
+ // Empty pointer passed, clear stored data
+ stored_pointer.reset();
+ stored_handle.reset();
+ }
+
+ return (stored_handle);
+}
+
+} // namespace shcp
+} // namespace isc
+
+#endif // CALLOUT_HANDLE_STORE_H
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index b5e83e3..56e4c8e 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -40,8 +40,7 @@ CfgMgr::addOptionSpace4(const OptionSpacePtr& space) {
isc_throw(InvalidOptionSpace, "option space " << space->getName()
<< " already added.");
}
- spaces4_.insert(std::pair<std::string,
- OptionSpacePtr>(space->getName(), space));
+ spaces4_.insert(make_pair(space->getName(), space));
}
void
@@ -55,8 +54,7 @@ CfgMgr::addOptionSpace6(const OptionSpacePtr& space) {
isc_throw(InvalidOptionSpace, "option space " << space->getName()
<< " already added.");
}
- spaces6_.insert(std::pair<std::string,
- OptionSpacePtr>(space->getName(), space));
+ spaces6_.insert(make_pair(space->getName(), space));
}
void
@@ -147,7 +145,7 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
// If there's only one subnet configured, let's just use it
// The idea is to keep small deployments easy. In a small network - one
- // router that also runs DHCPv6 server. Users specifies a single pool and
+ // router that also runs DHCPv6 server. User specifies a single pool and
// expects it to just work. Without this, the server would complain that it
// doesn't have IP address on its interfaces that matches that
// configuration. Such requirement makes sense in IPv4, but not in IPv6.
@@ -178,14 +176,30 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
return (Subnet6Ptr());
}
-Subnet6Ptr CfgMgr::getSubnet6(OptionPtr /*interfaceId*/) {
- /// @todo: Implement get subnet6 by interface-id (for relayed traffic)
- isc_throw(NotImplemented, "Relayed DHCPv6 traffic is not supported yet.");
+Subnet6Ptr CfgMgr::getSubnet6(OptionPtr iface_id_option) {
+ if (!iface_id_option) {
+ return (Subnet6Ptr());
+ }
+
+ // Let's iterate over all subnets and for those that have interface-id
+ // defined, check if the interface-id is equal to what we are looking for
+ for (Subnet6Collection::iterator subnet = subnets6_.begin();
+ subnet != subnets6_.end(); ++subnet) {
+ if ( (*subnet)->getInterfaceId() &&
+ ((*subnet)->getInterfaceId()->equal(iface_id_option))) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET6_IFACE_ID)
+ .arg((*subnet)->toText());
+ return (*subnet);
+ }
+ }
+ return (Subnet6Ptr());
}
void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
/// @todo: Check that this new subnet does not cross boundaries of any
/// other already defined subnet.
+ /// @todo: Check that there is no subnet with the same interface-id
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
.arg(subnet->toText());
subnets6_.push_back(subnet);
@@ -248,13 +262,66 @@ void CfgMgr::deleteSubnets6() {
subnets6_.clear();
}
+
std::string CfgMgr::getDataDir() {
return (datadir_);
}
+void
+CfgMgr::addActiveIface(const std::string& iface) {
+ if (isIfaceListedActive(iface)) {
+ isc_throw(DuplicateListeningIface,
+ "attempt to add duplicate interface '" << iface << "'"
+ " to the set of interfaces on which server listens");
+ }
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_IFACE)
+ .arg(iface);
+ active_ifaces_.push_back(iface);
+}
+
+void
+CfgMgr::activateAllIfaces() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE);
+ all_ifaces_active_ = true;
+}
+
+void
+CfgMgr::deleteActiveIfaces() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES);
+ active_ifaces_.clear();
+ all_ifaces_active_ = false;
+}
+
+bool
+CfgMgr::isActiveIface(const std::string& iface) const {
+
+ // @todo Verify that the interface with the specified name is
+ // present in the system.
+
+ // If all interfaces are marked active, there is no need to check that
+ // the name of this interface has been explicitly listed.
+ if (all_ifaces_active_) {
+ return (true);
+ }
+ return (isIfaceListedActive(iface));
+}
+
+bool
+CfgMgr::isIfaceListedActive(const std::string& iface) const {
+ for (ActiveIfacesCollection::const_iterator it = active_ifaces_.begin();
+ it != active_ifaces_.end(); ++it) {
+ if (iface == *it) {
+ return (true);
+ }
+ }
+ return (false);
+}
CfgMgr::CfgMgr()
- :datadir_(DHCP_DATA_DIR) {
+ : datadir_(DHCP_DATA_DIR),
+ all_ifaces_active_(false) {
// DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
// Note: the definition of DHCP_DATA_DIR needs to include quotation marks
// See AM_CPPFLAGS definition in Makefile.am
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index f4eecb5..0ec51d0 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -30,17 +30,28 @@
#include <map>
#include <string>
#include <vector>
+#include <list>
namespace isc {
namespace dhcp {
+/// @brief Exception thrown when the same interface has been specified twice.
+///
+/// In particular, this exception is thrown when adding interface to the set
+/// of interfaces on which server is supposed to listen.
+class DuplicateListeningIface : public Exception {
+public:
+ DuplicateListeningIface(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
/// @brief Configuration Manager
///
/// This singleton class holds the whole configuration for DHCPv4 and DHCPv6
/// servers. It currently holds information about zero or more subnets6.
/// Each subnet may contain zero or more pools. Pool4 and Pool6 is the most
-/// basic "chunk" of configuration. It contains a range of assigneable
+/// basic "chunk" of configuration. It contains a range of assignable
/// addresses.
///
/// Below is a sketch of configuration inheritance (not implemented yet).
@@ -174,7 +185,6 @@ public:
/// @param interface_id content of interface-id option returned by a relay
///
/// @return a subnet object
- /// @todo This method is not currently supported.
Subnet6Ptr getSubnet6(OptionPtr interface_id);
/// @brief adds an IPv6 subnet
@@ -202,6 +212,26 @@ public:
/// completely new?
void deleteSubnets6();
+ /// @brief returns const reference to all subnets6
+ ///
+ /// This is used in a hook (subnet4_select), where the hook is able
+ /// to choose a different subnet. Server code has to offer a list
+ /// of possible choices (i.e. all subnets).
+ /// @return a pointer to const Subnet6 collection
+ const Subnet4Collection* getSubnets4() {
+ return (&subnets4_);
+ }
+
+ /// @brief returns const reference to all subnets6
+ ///
+ /// This is used in a hook (subnet6_select), where the hook is able
+ /// to choose a different subnet. Server code has to offer a list
+ /// of possible choices (i.e. all subnets).
+ /// @return a pointer to const Subnet6 collection
+ const Subnet6Collection* getSubnets6() {
+ return (&subnets6_);
+ }
+
/// @brief get IPv4 subnet by address
///
/// Finds a matching subnet, based on an address. This can be used
@@ -238,6 +268,43 @@ public:
/// @return data directory
std::string getDataDir();
+ /// @brief Adds the name of the interface to the set of interfaces on which
+ /// server should listen.
+ ///
+ /// @param iface A name of the interface being added to the listening set.
+ void addActiveIface(const std::string& iface);
+
+ /// @brief Sets the flag which indicates that server is supposed to listen
+ /// on all available interfaces.
+ ///
+ /// This function does not close or open sockets. It simply marks that
+ /// server should start to listen on all interfaces the next time sockets
+ /// are reopened. Server should examine this flag when it gets reconfigured
+ /// and configuration changes the interfaces that server should listen on.
+ void activateAllIfaces();
+
+ /// @brief Clear the collection of the interfaces that server should listen
+ /// on.
+ ///
+ /// Apart from clearing the list of interfaces specified with
+ /// @c CfgMgr::addListeningInterface, it also disables listening on all
+ /// interfaces if it has been enabled using
+ /// @c CfgMgr::activateAllInterfaces.
+ /// Likewise @c CfgMgr::activateAllIfaces, this function does not close or
+ /// open sockets. It marks all interfaces inactive for DHCP traffic.
+ /// Server should examine this new setting when it attempts to
+ /// reopen sockets (as a result of reconfiguration).
+ void deleteActiveIfaces();
+
+ /// @brief Check if specified interface should be used to listen to DHCP
+ /// traffic.
+ ///
+ /// @param iface A name of the interface to be checked.
+ ///
+ /// @return true if the specified interface belongs to the set of the
+ /// interfaces on which server is configured to listen.
+ bool isActiveIface(const std::string& iface) const;
+
protected:
/// @brief Protected constructor.
@@ -248,7 +315,7 @@ protected:
/// purposes.
CfgMgr();
- /// @brief virtual desctructor
+ /// @brief virtual destructor
virtual ~CfgMgr();
/// @brief a container for IPv6 subnets.
@@ -269,6 +336,20 @@ protected:
private:
+ /// @brief Checks if the specified interface is listed as active.
+ ///
+ /// This function searches for the specified interface name on the list of
+ /// active interfaces: @c CfgMgr::active_ifaces_. It does not take into
+ /// account @c CfgMgr::all_ifaces_active_ flag. If this flag is set to true
+ /// but the specified interface does not belong to
+ /// @c CfgMgr::active_ifaces_, it will return false.
+ ///
+ /// @param iface interface name.
+ ///
+ /// @return true if specified interface belongs to
+ /// @c CfgMgr::active_ifaces_.
+ bool isIfaceListedActive(const std::string& iface) const;
+
/// @brief A collection of option definitions.
///
/// A collection of option definitions that can be accessed
@@ -284,6 +365,16 @@ private:
/// @brief directory where data files (e.g. server-id) are stored
std::string datadir_;
+
+ /// @name A collection of interface names on which server listens.
+ //@{
+ typedef std::list<std::string> ActiveIfacesCollection;
+ std::list<std::string> active_ifaces_;
+ //@}
+
+ /// A flag which indicates that server should listen on all available
+ /// interfaces.
+ bool all_ifaces_active_;
};
} // namespace isc::dhcp
diff --git a/src/lib/dhcpsrv/database_backends.dox b/src/lib/dhcpsrv/database_backends.dox
index 43594fd..174b5e2 100644
--- a/src/lib/dhcpsrv/database_backends.dox
+++ b/src/lib/dhcpsrv/database_backends.dox
@@ -26,7 +26,7 @@
- The MySQL lease manager uses the freely available MySQL as its backend
database. This is not included in BIND 10 DHCP by default:
- the --with-dhcp-mysql switch must be supplied to "configure" for support
+ the \--with-dhcp-mysql switch must be supplied to "configure" for support
to be compiled into the software.
- Memfile is an in-memory lease database, with (currently) nothing being
written to persistent storage. The long-term plans for the backend do
@@ -83,9 +83,42 @@
@subsection dhcp-mysql-unittest MySQL
- A database called <i>keatest</i> needs to be set up using the MySQL
- <b>CREATE DATABASE</b> command. A database user, also called <i>keatest</i>
- (with a password <i>keatest</i>) must be given full privileges in that
- database. The unit tests create the schema in the database before each test
- and delete it afterwards.
+ A database called <i>keatest</i> must be created. A database user, also called
+ <i>keatest</i> (and with a password <i>keatest</i>) must also be created and
+ be given full privileges in that database. The unit tests create the schema
+ in the database before each test and delete it afterwards.
+
+ In detail, the steps to create the database and user are:
+
+ -# Log into MySQL as root:
+ @verbatim
+ % mysql -u root -p
+ Enter password:
+ :
+ mysql>@endverbatim\n
+ -# Create the test database. This must be called "keatest":
+ @verbatim
+ mysql> CREATE DATABASE keatest;
+ mysql>@endverbatim\n
+ -# Create the user under which the test client will connect to the database
+ (the apostrophes around the words <i>keatest</i> and <i>localhost</i> are
+ required):
+ @verbatim
+ mysql> CREATE USER 'keatest'@'localhost' IDENTIFIED BY 'keatest';
+ mysql>@endverbatim\n
+ -# Grant the created user permissions to access the <i>keatest</i> database
+ (again, the apostrophes around the words <i>keatest</i> and <i>localhost</i>
+ are required):
+ @verbatim
+ mysql> GRANT ALL ON keatest.* TO 'keatest'@'localhost';
+ mysql>@endverbatim\n
+ -# Exit MySQL:
+ @verbatim
+ mysql> quit
+ Bye
+ %@endverbatim
+
+ The unit tests are run automatically when "make check" is executed (providing
+ that BIND 10 has been build with the \--with-dhcp-mysql switch (see the installation
+ section in the <a href="http://bind10.isc.org/docs/bind10-guide.html">BIND 10 Guide</a>).
*/
diff --git a/src/lib/dhcpsrv/dbaccess_parser.h b/src/lib/dhcpsrv/dbaccess_parser.h
index 140f11d..53e3f81 100644
--- a/src/lib/dhcpsrv/dbaccess_parser.h
+++ b/src/lib/dhcpsrv/dbaccess_parser.h
@@ -41,7 +41,7 @@ public:
/// strings.
///
/// Only the "type" sub-element is mandatory: the remaining sub-elements
-/// depend on the datbase chosen.
+/// depend on the database chosen.
class DbAccessParser: public DhcpConfigParser {
public:
/// @brief Keyword and associated value
@@ -115,7 +115,7 @@ protected:
return (values_);
}
- /// @brief Construct dbtabase access string
+ /// @brief Construct database access string
///
/// Constructs the database access string from the stored parameters.
///
diff --git a/src/lib/dhcpsrv/dhcp_config_parser.h b/src/lib/dhcpsrv/dhcp_config_parser.h
index 44cf519..77e46c8 100644
--- a/src/lib/dhcpsrv/dhcp_config_parser.h
+++ b/src/lib/dhcpsrv/dhcp_config_parser.h
@@ -15,6 +15,12 @@
#ifndef DHCP_CONFIG_PARSER_H
#define DHCP_CONFIG_PARSER_H
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <stdint.h>
+#include <string>
+#include <map>
+
namespace isc {
namespace dhcp {
@@ -65,7 +71,7 @@ class DhcpConfigParser {
//@{
private:
- // Private construtor and assignment operator assures that nobody
+ // Private constructor and assignment operator assures that nobody
// will be able to copy or assign a parser. There are no defined
// bodies for them.
DhcpConfigParser(const DhcpConfigParser& source);
@@ -122,38 +128,10 @@ public:
/// This method is expected to be called after @c build(), and only once.
/// The result is undefined otherwise.
virtual void commit() = 0;
-
-protected:
-
- /// @brief Return the parsed entry from the provided storage.
- ///
- /// This method returns the parsed entry from the provided
- /// storage. If the entry is not found, then exception is
- /// thrown.
- ///
- /// @param param_id name of the configuration entry.
- /// @param storage storage where the entry should be searched.
- /// @tparam ReturnType type of the returned value.
- /// @tparam StorageType type of the storage.
- ///
- /// @throw DhcpConfigError if the entry has not been found
- /// in the storage.
- template<typename ReturnType, typename StorageType>
- static ReturnType getParam(const std::string& param_id,
- const StorageType& storage) {
- typename StorageType::const_iterator param = storage.find(param_id);
- if (param == storage.end()) {
- isc_throw(DhcpConfigError, "missing parameter '"
- << param_id << "'");
- }
- ReturnType value = param->second;
- return (value);
- }
-
};
-
-} // end of isc::dhcp namespace
-} // end of isc namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
#endif // DHCP_CONFIG_PARSER_H
+
diff --git a/src/lib/dhcpsrv/dhcp_parsers.cc b/src/lib/dhcpsrv/dhcp_parsers.cc
new file mode 100644
index 0000000..e03d13f
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcp_parsers.cc
@@ -0,0 +1,1107 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <dhcp/iface_mgr.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <hooks/hooks_manager.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::hooks;
+
+namespace isc {
+namespace dhcp {
+
+namespace {
+const char* ALL_IFACES_KEYWORD = "*";
+}
+
+// *********************** ParserContext *************************
+
+ParserContext::ParserContext(Option::Universe universe):
+ boolean_values_(new BooleanStorage()),
+ uint32_values_(new Uint32Storage()),
+ string_values_(new StringStorage()),
+ options_(new OptionStorage()),
+ option_defs_(new OptionDefStorage()),
+ hooks_libraries_(),
+ universe_(universe)
+{
+}
+
+ParserContext::ParserContext(const ParserContext& rhs):
+ boolean_values_(),
+ uint32_values_(),
+ string_values_(),
+ options_(),
+ option_defs_(),
+ hooks_libraries_(),
+ universe_(rhs.universe_)
+{
+ copyContext(rhs);
+}
+
+ParserContext&
+// The cppcheck version 1.56 doesn't recognize that copyContext
+// copies all context fields.
+// cppcheck-suppress operatorEqVarError
+ParserContext::operator=(const ParserContext& rhs) {
+ if (this != &rhs) {
+ copyContext(rhs);
+ }
+
+ return (*this);
+}
+
+void
+ParserContext::copyContext(const ParserContext& ctx) {
+ copyContextPointer(ctx.boolean_values_, boolean_values_);
+ copyContextPointer(ctx.uint32_values_, uint32_values_);
+ copyContextPointer(ctx.string_values_, string_values_);
+ copyContextPointer(ctx.options_, options_);
+ copyContextPointer(ctx.option_defs_, option_defs_);
+ copyContextPointer(ctx.hooks_libraries_, hooks_libraries_);
+ // Copy universe.
+ universe_ = ctx.universe_;
+}
+
+template<typename T>
+void
+ParserContext::copyContextPointer(const boost::shared_ptr<T>& source_ptr,
+ boost::shared_ptr<T>& dest_ptr) {
+ if (source_ptr) {
+ dest_ptr.reset(new T(*source_ptr));
+ } else {
+ dest_ptr.reset();
+ }
+}
+
+
+// **************************** DebugParser *************************
+
+DebugParser::DebugParser(const std::string& param_name)
+ :param_name_(param_name) {
+}
+
+void
+DebugParser::build(ConstElementPtr new_config) {
+ value_ = new_config;
+ std::cout << "Build for token: [" << param_name_ << "] = ["
+ << value_->str() << "]" << std::endl;
+}
+
+void
+DebugParser::commit() {
+ // Debug message. The whole DebugParser class is used only for parser
+ // debugging, and is not used in production code. It is very convenient
+ // to keep it around. Please do not turn this cout into logger calls.
+ std::cout << "Commit for token: [" << param_name_ << "] = ["
+ << value_->str() << "]" << std::endl;
+}
+
+// **************************** BooleanParser *************************
+
+template<> void ValueParser<bool>::build(isc::data::ConstElementPtr value) {
+ // The Config Manager checks if user specified a
+ // valid value for a boolean parameter: True or False.
+ // We should have a boolean Element, use value directly
+ try {
+ value_ = value->boolValue();
+ } catch (const isc::data::TypeError &) {
+ isc_throw(BadValue, " Wrong value type for " << param_name_
+ << " : build called with a non-boolean element.");
+ }
+}
+
+// **************************** Uin32Parser *************************
+
+template<> void ValueParser<uint32_t>::build(ConstElementPtr value) {
+ int64_t check;
+ string x = value->str();
+ try {
+ check = boost::lexical_cast<int64_t>(x);
+ } catch (const boost::bad_lexical_cast &) {
+ isc_throw(BadValue, "Failed to parse value " << value->str()
+ << " as unsigned 32-bit integer.");
+ }
+ if (check > std::numeric_limits<uint32_t>::max()) {
+ isc_throw(BadValue, "Value " << value->str() << "is too large"
+ << " for unsigned 32-bit integer.");
+ }
+ if (check < 0) {
+ isc_throw(BadValue, "Value " << value->str() << "is negative."
+ << " Only 0 or larger are allowed for unsigned 32-bit integer.");
+ }
+
+ // value is small enough to fit
+ value_ = static_cast<uint32_t>(check);
+}
+
+// **************************** StringParser *************************
+
+template <> void ValueParser<std::string>::build(ConstElementPtr value) {
+ value_ = value->str();
+ boost::erase_all(value_, "\"");
+}
+
+// ******************** InterfaceListConfigParser *************************
+
+InterfaceListConfigParser::
+InterfaceListConfigParser(const std::string& param_name)
+ : activate_all_(false),
+ param_name_(param_name) {
+ if (param_name_ != "interfaces") {
+ isc_throw(BadValue, "Internal error. Interface configuration "
+ "parser called for the wrong parameter: " << param_name);
+ }
+}
+
+void
+InterfaceListConfigParser::build(ConstElementPtr value) {
+ // First, we iterate over all specified entries and add it to the
+ // local container so as we can do some basic validation, e.g. eliminate
+ // duplicates.
+ BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
+ std::string iface_name = iface->stringValue();
+ if (iface_name != ALL_IFACES_KEYWORD) {
+ // Let's eliminate duplicates. We could possibly allow duplicates,
+ // but if someone specified duplicated interface name it is likely
+ // that he mistyped the configuration. Failing here should draw his
+ // attention.
+ if (isIfaceAdded(iface_name)) {
+ isc_throw(isc::dhcp::DhcpConfigError, "duplicate interface"
+ << " name '" << iface_name << "' specified in '"
+ << param_name_ << "' configuration parameter");
+ }
+ // @todo check that this interface exists in the system!
+ // The IfaceMgr exposes mechanisms to check this.
+
+ // Add the interface name if ok.
+ interfaces_.push_back(iface_name);
+
+ } else {
+ activate_all_ = true;
+
+ }
+ }
+}
+
+void
+InterfaceListConfigParser::commit() {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Remove active interfaces and clear a flag which marks all interfaces
+ // active
+ cfg_mgr.deleteActiveIfaces();
+
+ if (activate_all_) {
+ // Activate all interfaces. There is not need to add their names
+ // explicitly.
+ cfg_mgr.activateAllIfaces();
+
+ } else {
+ // Explicitly add names of the interfaces which server should listen on.
+ BOOST_FOREACH(std::string iface, interfaces_) {
+ cfg_mgr.addActiveIface(iface);
+ }
+ }
+}
+
+bool
+InterfaceListConfigParser::isIfaceAdded(const std::string& iface) const {
+ for (IfaceListStorage::const_iterator it = interfaces_.begin();
+ it != interfaces_.end(); ++it) {
+ if (iface == *it) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+// ******************** HooksLibrariesParser *************************
+
+HooksLibrariesParser::HooksLibrariesParser(const std::string& param_name)
+ : libraries_(), changed_(false)
+{
+ // Sanity check on the name.
+ if (param_name != "hooks-libraries") {
+ isc_throw(BadValue, "Internal error. Hooks libraries "
+ "parser called for the wrong parameter: " << param_name);
+ }
+}
+
+void
+HooksLibrariesParser::build(ConstElementPtr value) {
+ // Initialize.
+ libraries_.clear();
+ changed_ = false;
+
+ // Extract the list of libraries.
+ BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
+ string libname = iface->str();
+ boost::erase_all(libname, "\"");
+ libraries_.push_back(libname);
+ }
+
+ // Check if the list of libraries has changed. If not, nothing is done
+ // - the command "DhcpN libreload" is required to reload the same
+ // libraries (this prevents needless reloads when anything else in the
+ // configuration is changed).
+ vector<string> current_libraries = HooksManager::getLibraryNames();
+ if (current_libraries == libraries_) {
+ return;
+ }
+
+ // Library list has changed, validate each of the libraries specified.
+ vector<string> error_libs = HooksManager::validateLibraries(libraries_);
+ if (!error_libs.empty()) {
+
+ // Construct the list of libraries in error for the message.
+ string error_list = error_libs[0];
+ for (int i = 1; i < error_libs.size(); ++i) {
+ error_list += (string(", ") + error_libs[i]);
+ }
+ isc_throw(DhcpConfigError, "hooks libraries failed to validate - "
+ "library or libraries in error are: " + error_list);
+ }
+
+ // The library list has changed and the libraries are valid, so flag for
+ // update when commit() is called.
+ changed_ = true;
+}
+
+void
+HooksLibrariesParser::commit() {
+ /// Commits the list of libraries to the configuration manager storage if
+ /// the list of libraries has changed.
+ if (changed_) {
+ // TODO Delete any stored CalloutHandles before reloading the
+ // libraries
+ HooksManager::loadLibraries(libraries_);
+ }
+}
+
+// Method for testing
+void
+HooksLibrariesParser::getLibraries(std::vector<std::string>& libraries,
+ bool& changed) {
+ libraries = libraries_;
+ changed = changed_;
+}
+
+// **************************** OptionDataParser *************************
+OptionDataParser::OptionDataParser(const std::string&, OptionStoragePtr options,
+ ParserContextPtr global_context)
+ : boolean_values_(new BooleanStorage()),
+ string_values_(new StringStorage()), uint32_values_(new Uint32Storage()),
+ options_(options), option_descriptor_(false),
+ global_context_(global_context) {
+ if (!options_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "options storage may not be NULL");
+ }
+
+ if (!global_context_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "context may may not be NULL");
+ }
+}
+
+void
+OptionDataParser::build(ConstElementPtr option_data_entries) {
+ BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
+ ParserPtr parser;
+ if (param.first == "name" || param.first == "data" ||
+ param.first == "space") {
+ StringParserPtr name_parser(new StringParser(param.first,
+ string_values_));
+ parser = name_parser;
+ } else if (param.first == "code") {
+ Uint32ParserPtr code_parser(new Uint32Parser(param.first,
+ uint32_values_));
+ parser = code_parser;
+ } else if (param.first == "csv-format") {
+ BooleanParserPtr value_parser(new BooleanParser(param.first,
+ boolean_values_));
+ parser = value_parser;
+ } else {
+ isc_throw(DhcpConfigError,
+ "Parser error: option-data parameter not supported: "
+ << param.first);
+ }
+
+ parser->build(param.second);
+ // Before we can create an option we need to get the data from
+ // the child parsers. The only way to do it is to invoke commit
+ // on them so as they store the values in appropriate storages
+ // that this class provided to them. Note that this will not
+ // modify values stored in the global storages so the configuration
+ // will remain consistent even parsing fails somewhere further on.
+ parser->commit();
+ }
+
+ // Try to create the option instance.
+ createOption();
+}
+
+void
+OptionDataParser::commit() {
+ if (!option_descriptor_.option) {
+ // Before we can commit the new option should be configured. If it is
+ // not than somebody must have called commit() before build().
+ isc_throw(isc::InvalidOperation,
+ "parser logic error: no option has been configured and"
+ " thus there is nothing to commit. Has build() been called?");
+ }
+
+ uint16_t opt_type = option_descriptor_.option->getType();
+ Subnet::OptionContainerPtr options = options_->getItems(option_space_);
+ // The getItems() should never return NULL pointer. If there are no
+ // options configured for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(options);
+ Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+ // Try to find options with the particular option code in the main
+ // storage. If found, remove these options because they will be
+ // replaced with new one.
+ Subnet::OptionContainerTypeRange range = idx.equal_range(opt_type);
+ if (std::distance(range.first, range.second) > 0) {
+ idx.erase(range.first, range.second);
+ }
+
+ // Append new option to the main storage.
+ options_->addItem(option_descriptor_, option_space_);
+}
+
+void
+OptionDataParser::createOption() {
+ // Option code is held in the uint32_t storage but is supposed to
+ // be uint16_t value. We need to check that value in the configuration
+ // does not exceed range of uint8_t and is not zero.
+ uint32_t option_code = uint32_values_->getParam("code");
+ if (option_code == 0) {
+ isc_throw(DhcpConfigError, "option code must not be zero."
+ << " Option code '0' is reserved in DHCPv4.");
+ } else if (option_code > std::numeric_limits<uint8_t>::max()) {
+ isc_throw(DhcpConfigError, "invalid option code '" << option_code
+ << "', it must not exceed '"
+ << std::numeric_limits<uint8_t>::max() << "'");
+ }
+
+ // Check that the option name has been specified, is non-empty and does not
+ // contain spaces
+ std::string option_name = string_values_->getParam("name");
+ if (option_name.empty()) {
+ isc_throw(DhcpConfigError, "name of the option with code '"
+ << option_code << "' is empty");
+ } else if (option_name.find(" ") != std::string::npos) {
+ isc_throw(DhcpConfigError, "invalid option name '" << option_name
+ << "', space character is not allowed");
+ }
+
+ std::string option_space = string_values_->getParam("space");
+ if (!OptionSpace::validateName(option_space)) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << option_space << "' specified for option '"
+ << option_name << "' (code '" << option_code
+ << "')");
+ }
+
+ // Find the Option Definition for the option by its option code.
+ // findOptionDefinition will throw if not found, no need to test.
+ OptionDefinitionPtr def;
+ if (!(def = findServerSpaceOptionDefinition(option_space, option_code))) {
+ // If we are not dealing with a standard option then we
+ // need to search for its definition among user-configured
+ // options. They are expected to be in the global storage
+ // already.
+ OptionDefContainerPtr defs =
+ global_context_->option_defs_->getItems(option_space);
+
+ // The getItems() should never return the NULL pointer. If there are
+ // no option definitions for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(defs);
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ OptionDefContainerTypeRange range = idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) > 0) {
+ def = *range.first;
+ }
+ if (!def) {
+ isc_throw(DhcpConfigError, "definition for the option '"
+ << option_space << "." << option_name
+ << "' having code '" << option_code
+ << "' does not exist");
+ }
+ }
+
+ // Get option data from the configuration database ('data' field).
+ const std::string option_data = string_values_->getParam("data");
+ const bool csv_format = boolean_values_->getParam("csv-format");
+
+ // Transform string of hexadecimal digits into binary format.
+ std::vector<uint8_t> binary;
+ std::vector<std::string> data_tokens;
+
+ if (csv_format) {
+ // If the option data is specified as a string of comma
+ // separated values then we need to split this string into
+ // individual values - each value will be used to initialize
+ // one data field of an option.
+ data_tokens = isc::util::str::tokens(option_data, ",");
+ } else {
+ // Otherwise, the option data is specified as a string of
+ // hexadecimal digits that we have to turn into binary format.
+ try {
+ util::encode::decodeHex(option_data, binary);
+ } catch (...) {
+ isc_throw(DhcpConfigError, "option data is not a valid"
+ << " string of hexadecimal digits: " << option_data);
+ }
+ }
+
+ OptionPtr option;
+ if (!def) {
+ if (csv_format) {
+ isc_throw(DhcpConfigError, "the CSV option data format can be"
+ " used to specify values for an option that has a"
+ " definition. The option with code " << option_code
+ << " does not have a definition.");
+ }
+
+ // @todo We have a limited set of option definitions intiialized at
+ // the moment. In the future we want to initialize option definitions
+ // for all options. Consequently an error will be issued if an option
+ // definition does not exist for a particular option code. For now it is
+ // ok to create generic option if definition does not exist.
+ OptionPtr option(new Option(global_context_->universe_,
+ static_cast<uint16_t>(option_code), binary));
+ // The created option is stored in option_descriptor_ class member
+ // until the commit stage when it is inserted into the main storage.
+ // If an option with the same code exists in main storage already the
+ // old option is replaced.
+ option_descriptor_.option = option;
+ option_descriptor_.persistent = false;
+ } else {
+
+ // Option name should match the definition. The option name
+ // may seem to be redundant but in the future we may want
+ // to reference options and definitions using their names
+ // and/or option codes so keeping the option name in the
+ // definition of option value makes sense.
+ if (def->getName() != option_name) {
+ isc_throw(DhcpConfigError, "specified option name '"
+ << option_name << "' does not match the "
+ << "option definition: '" << option_space
+ << "." << def->getName() << "'");
+ }
+
+ // Option definition has been found so let's use it to create
+ // an instance of our option.
+ try {
+ OptionPtr option = csv_format ?
+ def->optionFactory(global_context_->universe_,
+ option_code, data_tokens) :
+ def->optionFactory(global_context_->universe_,
+ option_code, binary);
+ Subnet::OptionDescriptor desc(option, false);
+ option_descriptor_.option = option;
+ option_descriptor_.persistent = false;
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "option data does not match"
+ << " option definition (space: " << option_space
+ << ", code: " << option_code << "): "
+ << ex.what());
+ }
+ }
+
+ // All went good, so we can set the option space name.
+ option_space_ = option_space;
+}
+
+// **************************** OptionDataListParser *************************
+OptionDataListParser::OptionDataListParser(const std::string&,
+ OptionStoragePtr options, ParserContextPtr global_context,
+ OptionDataParserFactory* optionDataParserFactory)
+ : options_(options), local_options_(new OptionStorage()),
+ global_context_(global_context),
+ optionDataParserFactory_(optionDataParserFactory) {
+ if (!options_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "options storage may not be NULL");
+ }
+
+ if (!options_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "context may not be NULL");
+ }
+
+ if (!optionDataParserFactory_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "option data parser factory may not be NULL");
+ }
+}
+
+void
+OptionDataListParser::build(ConstElementPtr option_data_list) {
+ BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
+ boost::shared_ptr<OptionDataParser>
+ parser((*optionDataParserFactory_)("option-data",
+ local_options_, global_context_));
+
+ // options_ member will hold instances of all options thus
+ // each OptionDataParser takes it as a storage.
+ // Build the instance of a single option.
+ parser->build(option_value);
+ // Store a parser as it will be used to commit.
+ parsers_.push_back(parser);
+ }
+}
+
+void
+OptionDataListParser::commit() {
+ BOOST_FOREACH(ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+
+ // Parsing was successful and we have all configured
+ // options in local storage. We can now replace old values
+ // with new values.
+ std::swap(*local_options_, *options_);
+}
+
+// ******************************** OptionDefParser ****************************
+OptionDefParser::OptionDefParser(const std::string&,
+ OptionDefStoragePtr storage)
+ : storage_(storage), boolean_values_(new BooleanStorage()),
+ string_values_(new StringStorage()), uint32_values_(new Uint32Storage()) {
+ if (!storage_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "options storage may not be NULL");
+ }
+}
+
+void
+OptionDefParser::build(ConstElementPtr option_def) {
+ // Parse the elements that make up the option definition.
+ BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
+ std::string entry(param.first);
+ ParserPtr parser;
+ if (entry == "name" || entry == "type" || entry == "record-types"
+ || entry == "space" || entry == "encapsulate") {
+ StringParserPtr str_parser(new StringParser(entry,
+ string_values_));
+ parser = str_parser;
+ } else if (entry == "code") {
+ Uint32ParserPtr code_parser(new Uint32Parser(entry,
+ uint32_values_));
+ parser = code_parser;
+ } else if (entry == "array") {
+ BooleanParserPtr array_parser(new BooleanParser(entry,
+ boolean_values_));
+ parser = array_parser;
+ } else {
+ isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+ }
+
+ parser->build(param.second);
+ parser->commit();
+ }
+
+ // Create an instance of option definition.
+ createOptionDef();
+
+ // Get all items we collected so far for the particular option space.
+ OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
+
+ // Check if there are any items with option code the same as the
+ // one specified for the definition we are now creating.
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range =
+ idx.equal_range(option_definition_->getCode());
+
+ // If there are any items with this option code already we need
+ // to issue an error because we don't allow duplicates for
+ // option definitions within an option space.
+ if (std::distance(range.first, range.second) > 0) {
+ isc_throw(DhcpConfigError, "duplicated option definition for"
+ << " code '" << option_definition_->getCode() << "'");
+ }
+}
+
+void
+OptionDefParser::commit() {
+ if (storage_ && option_definition_ &&
+ OptionSpace::validateName(option_space_name_)) {
+ storage_->addItem(option_definition_, option_space_name_);
+ }
+}
+
+void
+OptionDefParser::createOptionDef() {
+ // Get the option space name and validate it.
+ std::string space = string_values_->getParam("space");
+ if (!OptionSpace::validateName(space)) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << space << "'");
+ }
+
+ // Get other parameters that are needed to create the
+ // option definition.
+ std::string name = string_values_->getParam("name");
+ uint32_t code = uint32_values_->getParam("code");
+ std::string type = string_values_->getParam("type");
+ bool array_type = boolean_values_->getParam("array");
+ std::string encapsulates = string_values_->getParam("encapsulate");
+
+ // Create option definition.
+ OptionDefinitionPtr def;
+ // We need to check if user has set encapsulated option space
+ // name. If so, different constructor will be used.
+ if (!encapsulates.empty()) {
+ // Arrays can't be used together with sub-options.
+ if (array_type) {
+ isc_throw(DhcpConfigError, "option '" << space << "."
+ << "name" << "', comprising an array of data"
+ << " fields may not encapsulate any option space");
+
+ } else if (encapsulates == space) {
+ isc_throw(DhcpConfigError, "option must not encapsulate"
+ << " an option space it belongs to: '"
+ << space << "." << name << "' is set to"
+ << " encapsulate '" << space << "'");
+
+ } else {
+ def.reset(new OptionDefinition(name, code, type,
+ encapsulates.c_str()));
+ }
+
+ } else {
+ def.reset(new OptionDefinition(name, code, type, array_type));
+
+ }
+
+ // The record-types field may carry a list of comma separated names
+ // of data types that form a record.
+ std::string record_types = string_values_->getParam("record-types");
+
+ // Split the list of record types into tokens.
+ std::vector<std::string> record_tokens =
+ isc::util::str::tokens(record_types, ",");
+ // Iterate over each token and add a record type into
+ // option definition.
+ BOOST_FOREACH(std::string record_type, record_tokens) {
+ try {
+ boost::trim(record_type);
+ if (!record_type.empty()) {
+ def->addRecordField(record_type);
+ }
+ } catch (const Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid record type values"
+ << " specified for the option definition: "
+ << ex.what());
+ }
+ }
+
+ // Check the option definition parameters are valid.
+ try {
+ def->validate();
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid option definition"
+ << " parameters: " << ex.what());
+ }
+
+ // Option definition has been created successfully.
+ option_space_name_ = space;
+ option_definition_ = def;
+}
+
+// ******************************** OptionDefListParser ************************
+OptionDefListParser::OptionDefListParser(const std::string&,
+ OptionDefStoragePtr storage) :storage_(storage) {
+ if (!storage_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "storage may not be NULL");
+ }
+}
+
+void
+OptionDefListParser::build(ConstElementPtr option_def_list) {
+ // Clear existing items in the storage.
+ // We are going to replace all of them.
+ storage_->clearItems();
+
+ if (!option_def_list) {
+ isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+ << " option definitions is NULL");
+ }
+
+ BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
+ boost::shared_ptr<OptionDefParser>
+ parser(new OptionDefParser("single-option-def", storage_));
+ parser->build(option_def);
+ parser->commit();
+ }
+}
+
+void
+OptionDefListParser::commit() {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteOptionDefs();
+
+ // We need to move option definitions from the temporary
+ // storage to the storage.
+ std::list<std::string> space_names =
+ storage_->getOptionSpaceNames();
+
+ BOOST_FOREACH(std::string space_name, space_names) {
+ BOOST_FOREACH(OptionDefinitionPtr def,
+ *(storage_->getItems(space_name))) {
+ // All option definitions should be initialized to non-NULL
+ // values. The validation is expected to be made by the
+ // OptionDefParser when creating an option definition.
+ assert(def);
+ cfg_mgr.addOptionDef(def, space_name);
+ }
+ }
+}
+
+//****************************** PoolParser ********************************
+PoolParser::PoolParser(const std::string&, PoolStoragePtr pools)
+ :pools_(pools) {
+
+ if (!pools_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "storage may not be NULL");
+ }
+}
+
+void
+PoolParser::build(ConstElementPtr pools_list) {
+ BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) {
+ // That should be a single pool representation. It should contain
+ // text is form prefix/len or first - last. Note that spaces
+ // are allowed
+ string txt = text_pool->stringValue();
+
+ // first let's remove any whitespaces
+ boost::erase_all(txt, " "); // space
+ boost::erase_all(txt, "\t"); // tabulation
+
+ // Is this prefix/len notation?
+ size_t pos = txt.find("/");
+ if (pos != string::npos) {
+ isc::asiolink::IOAddress addr("::");
+ uint8_t len = 0;
+ try {
+ addr = isc::asiolink::IOAddress(txt.substr(0, pos));
+
+ // start with the first character after /
+ string prefix_len = txt.substr(pos + 1);
+
+ // It is lexical cast to int and then downcast to uint8_t.
+ // Direct cast to uint8_t (which is really an unsigned char)
+ // will result in interpreting the first digit as output
+ // value and throwing exception if length is written on two
+ // digits (because there are extra characters left over).
+
+ // No checks for values over 128. Range correctness will
+ // be checked in Pool4 constructor.
+ len = boost::lexical_cast<int>(prefix_len);
+ } catch (...) {
+ isc_throw(DhcpConfigError, "Failed to parse pool "
+ "definition: " << text_pool->stringValue());
+ }
+
+ PoolPtr pool(poolMaker(addr, len));
+ local_pools_.push_back(pool);
+ continue;
+ }
+
+ // Is this min-max notation?
+ pos = txt.find("-");
+ if (pos != string::npos) {
+ // using min-max notation
+ isc::asiolink::IOAddress min(txt.substr(0,pos));
+ isc::asiolink::IOAddress max(txt.substr(pos + 1));
+
+ PoolPtr pool(poolMaker(min, max));
+ local_pools_.push_back(pool);
+ continue;
+ }
+
+ isc_throw(DhcpConfigError, "Failed to parse pool definition:"
+ << text_pool->stringValue() <<
+ ". Does not contain - (for min-max) nor / (prefix/len)");
+ }
+}
+
+void
+PoolParser::commit() {
+ if (pools_) {
+ // local_pools_ holds the values produced by the build function.
+ // At this point parsing should have completed successfuly so
+ // we can append new data to the supplied storage.
+ pools_->insert(pools_->end(), local_pools_.begin(), local_pools_.end());
+ }
+}
+
+//****************************** SubnetConfigParser *************************
+
+SubnetConfigParser::SubnetConfigParser(const std::string&,
+ ParserContextPtr global_context)
+ : uint32_values_(new Uint32Storage()), string_values_(new StringStorage()),
+ pools_(new PoolStorage()), options_(new OptionStorage()),
+ global_context_(global_context) {
+ // The first parameter should always be "subnet", but we don't check
+ // against that here in case some wants to reuse this parser somewhere.
+ if (!global_context_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "context storage may not be NULL");
+ }
+}
+
+void
+SubnetConfigParser::build(ConstElementPtr subnet) {
+ BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
+ ParserPtr parser(createSubnetConfigParser(param.first));
+ parser->build(param.second);
+ parsers_.push_back(parser);
+ }
+
+ // In order to create new subnet we need to get the data out
+ // of the child parsers first. The only way to do it is to
+ // invoke commit on them because it will make them write
+ // parsed data into storages we have supplied.
+ // Note that triggering commits on child parsers does not
+ // affect global data because we supplied pointers to storages
+ // local to this object. Thus, even if this method fails
+ // later on, the configuration remains consistent.
+ BOOST_FOREACH(ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+
+ // Create a subnet.
+ createSubnet();
+}
+
+void
+SubnetConfigParser::appendSubOptions(const std::string& option_space,
+ OptionPtr& option) {
+ // Only non-NULL options are stored in option container.
+ // If this option pointer is NULL this is a serious error.
+ assert(option);
+
+ OptionDefinitionPtr def;
+ if (isServerStdOption(option_space, option->getType())) {
+ def = getServerStdOptionDefinition(option->getType());
+ // Definitions for some of the standard options hasn't been
+ // implemented so it is ok to leave here.
+ if (!def) {
+ return;
+ }
+ } else {
+ const OptionDefContainerPtr defs =
+ global_context_->option_defs_->getItems(option_space);
+
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range =
+ idx.equal_range(option->getType());
+ // There is no definition so we have to leave.
+ if (std::distance(range.first, range.second) == 0) {
+ return;
+ }
+
+ def = *range.first;
+
+ // If the definition exists, it must be non-NULL.
+ // Otherwise it is a programming error.
+ assert(def);
+ }
+
+ // We need to get option definition for the particular option space
+ // and code. This definition holds the information whether our
+ // option encapsulates any option space.
+ // Get the encapsulated option space name.
+ std::string encapsulated_space = def->getEncapsulatedSpace();
+ // If option space name is empty it means that our option does not
+ // encapsulate any option space (does not include sub-options).
+ if (!encapsulated_space.empty()) {
+ // Get the sub-options that belong to the encapsulated
+ // option space.
+ const Subnet::OptionContainerPtr sub_opts =
+ global_context_->options_->getItems(encapsulated_space);
+ // Append sub-options to the option.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc, *sub_opts) {
+ if (desc.option) {
+ option->addOption(desc.option);
+ }
+ }
+ }
+}
+
+void
+SubnetConfigParser::createSubnet() {
+ std::string subnet_txt;
+ try {
+ subnet_txt = string_values_->getParam("subnet");
+ } catch (const DhcpConfigError &) {
+ // rethrow with precise error
+ isc_throw(DhcpConfigError,
+ "Mandatory subnet definition in subnet missing");
+ }
+
+ // Remove any spaces or tabs.
+ boost::erase_all(subnet_txt, " ");
+ boost::erase_all(subnet_txt, "\t");
+
+ // The subnet format is prefix/len. We are going to extract
+ // the prefix portion of a subnet string to create IOAddress
+ // object from it. IOAddress will be passed to the Subnet's
+ // constructor later on. In order to extract the prefix we
+ // need to get all characters preceding "/".
+ size_t pos = subnet_txt.find("/");
+ if (pos == string::npos) {
+ isc_throw(DhcpConfigError,
+ "Invalid subnet syntax (prefix/len expected):" << subnet_txt);
+ }
+
+ // Try to create the address object. It also validates that
+ // the address syntax is ok.
+ isc::asiolink::IOAddress addr(subnet_txt.substr(0, pos));
+ uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+
+ // Call the subclass's method to instantiate the subnet
+ initSubnet(addr, len);
+
+ // Add pools to it.
+ for (PoolStorage::iterator it = pools_->begin(); it != pools_->end();
+ ++it) {
+ subnet_->addPool(*it);
+ }
+
+ // Configure interface, if defined
+
+ // Get interface name. If it is defined, then the subnet is available
+ // directly over specified network interface.
+ std::string iface;
+ try {
+ iface = string_values_->getParam("interface");
+ } catch (const DhcpConfigError &) {
+ // iface not mandatory so swallow the exception
+ }
+
+ if (!iface.empty()) {
+ if (!IfaceMgr::instance().getIface(iface)) {
+ isc_throw(DhcpConfigError, "Specified interface name " << iface
+ << " for subnet " << subnet_->toText()
+ << " is not present" << " in the system.");
+ }
+
+ subnet_->setIface(iface);
+ }
+
+ // We are going to move configured options to the Subnet object.
+ // Configured options reside in the container where options
+ // are grouped by space names. Thus we need to get all space names
+ // and iterate over all options that belong to them.
+ std::list<std::string> space_names = options_->getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all options within a particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *options_->getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // We want to check whether an option with the particular
+ // option code has been already added. If so, we want
+ // to issue a warning.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor("option_space",
+ desc.option->getType());
+ if (existing_desc.option) {
+ duplicate_option_warning(desc.option->getType(), addr);
+ }
+ // Add sub-options (if any).
+ appendSubOptions(option_space, desc.option);
+ // In any case, we add the option to the subnet.
+ subnet_->addOption(desc.option, false, option_space);
+ }
+ }
+
+ // Check all global options and add them to the subnet object if
+ // they have been configured in the global scope. If they have been
+ // configured in the subnet scope we don't add global option because
+ // the one configured in the subnet scope always takes precedence.
+ space_names = global_context_->options_->getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all global options for the particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *(global_context_->options_->getItems(option_space))) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // Check if the particular option has been already added.
+ // This would mean that it has been configured in the
+ // subnet scope. Since option values configured in the
+ // subnet scope take precedence over globally configured
+ // values we don't add option from the global storage
+ // if there is one already.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor(option_space,
+ desc.option->getType());
+ if (!existing_desc.option) {
+ // Add sub-options (if any).
+ appendSubOptions(option_space, desc.option);
+ subnet_->addOption(desc.option, false, option_space);
+ }
+ }
+ }
+}
+
+isc::dhcp::Triplet<uint32_t>
+SubnetConfigParser::getParam(const std::string& name) {
+ uint32_t value = 0;
+ try {
+ // look for local value
+ value = uint32_values_->getParam(name);
+ } catch (const DhcpConfigError &) {
+ try {
+ // no local, use global value
+ value = global_context_->uint32_values_->getParam(name);
+ } catch (const DhcpConfigError &) {
+ isc_throw(DhcpConfigError, "Mandatory parameter " << name
+ << " missing (no global default and no subnet-"
+ << "specific value)");
+ }
+ }
+
+ return (Triplet<uint32_t>(value));
+}
+
+}; // namespace dhcp
+}; // namespace isc
diff --git a/src/lib/dhcpsrv/dhcp_parsers.h b/src/lib/dhcpsrv/dhcp_parsers.h
new file mode 100644
index 0000000..0829b21
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcp_parsers.h
@@ -0,0 +1,879 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 DHCP_PARSERS_H
+#define DHCP_PARSERS_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/option_space_container.h>
+#include <dhcpsrv/subnet.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Storage for option definitions.
+typedef OptionSpaceContainer<OptionDefContainer,
+ OptionDefinitionPtr> OptionDefStorage;
+/// @brief Shared pointer to option definitions storage.
+typedef boost::shared_ptr<OptionDefStorage> OptionDefStoragePtr;
+
+/// Collection of containers holding option spaces. Each container within
+/// a particular option space holds so-called option descriptors.
+typedef OptionSpaceContainer<Subnet::OptionContainer,
+ Subnet::OptionDescriptor> OptionStorage;
+/// @brief Shared pointer to option storage.
+typedef boost::shared_ptr<OptionStorage> OptionStoragePtr;
+
+/// @brief Shared pointer to collection of hooks libraries.
+typedef boost::shared_ptr<std::vector<std::string> > HooksLibsStoragePtr;
+
+/// @brief A template class that stores named elements of a given data type.
+///
+/// This template class is provides data value storage for configuration parameters
+/// of a given data type. The values are stored by parameter name and as instances
+/// of type "ValueType".
+///
+/// @param ValueType is the data type of the elements to store.
+template<typename ValueType>
+class ValueStorage {
+ public:
+ /// @brief Stores the the parameter and its value in the store.
+ ///
+ /// If the parameter does not exist in the store, then it will be added,
+ /// otherwise its data value will be updated with the given value.
+ ///
+ /// @param name is the name of the paramater to store.
+ /// @param value is the data value to store.
+ void setParam(const std::string& name, const ValueType& value) {
+ values_[name] = value;
+ }
+
+ /// @brief Returns the data value for the given parameter.
+ ///
+ /// Finds and returns the data value for the given parameter.
+ /// @param name is the name of the parameter for which the data
+ /// value is desired.
+ ///
+ /// @return The paramater's data value of type @c ValueType.
+ /// @throw DhcpConfigError if the parameter is not found.
+ ValueType getParam(const std::string& name) const {
+ typename std::map<std::string, ValueType>::const_iterator param
+ = values_.find(name);
+
+ if (param == values_.end()) {
+ isc_throw(DhcpConfigError, "Missing parameter '"
+ << name << "'");
+ }
+
+ return (param->second);
+ }
+
+ /// @brief Remove the parameter from the store.
+ ///
+ /// Deletes the entry for the given parameter from the store if it
+ /// exists.
+ ///
+ /// @param name is the name of the paramater to delete.
+ void delParam(const std::string& name) {
+ values_.erase(name);
+ }
+
+ /// @brief Deletes all of the entries from the store.
+ ///
+ void clear() {
+ values_.clear();
+ }
+
+ private:
+ /// @brief An std::map of the data values, keyed by parameter names.
+ std::map<std::string, ValueType> values_;
+};
+
+
+/// @brief a collection of elements that store uint32 values
+typedef ValueStorage<uint32_t> Uint32Storage;
+typedef boost::shared_ptr<Uint32Storage> Uint32StoragePtr;
+
+/// @brief a collection of elements that store string values
+typedef ValueStorage<std::string> StringStorage;
+typedef boost::shared_ptr<StringStorage> StringStoragePtr;
+
+/// @brief Storage for parsed boolean values.
+typedef ValueStorage<bool> BooleanStorage;
+typedef boost::shared_ptr<BooleanStorage> BooleanStoragePtr;
+
+/// @brief Container for the current parsing context. It provides a
+/// single enclosure for the storage of configuration parameters,
+/// options, option definitions, and other context specific information
+/// that needs to be accessible throughout the parsing and parsing
+/// constructs.
+class ParserContext {
+public:
+ /// @brief Constructor
+ ///
+ /// @param universe is the Option::Universe value of this
+ /// context.
+ ParserContext(Option::Universe universe);
+
+ /// @brief Copy constructor
+ ParserContext(const ParserContext& rhs);
+
+ /// @brief Storage for boolean parameters.
+ BooleanStoragePtr boolean_values_;
+
+ /// @brief Storage for uint32 parameters.
+ Uint32StoragePtr uint32_values_;
+
+ /// @brief Storage for string parameters.
+ StringStoragePtr string_values_;
+
+ /// @brief Storage for options.
+ OptionStoragePtr options_;
+
+ /// @brief Storage for option definitions.
+ OptionDefStoragePtr option_defs_;
+
+ /// @brief Hooks libraries pointer.
+ ///
+ /// The hooks libraries information is a vector of strings, each containing
+ /// the name of a library. Hooks libraries should only be reloaded if the
+ /// list of names has changed, so the list of current DHCP parameters
+ /// (in isc::dhcp::CfgMgr) contains an indication as to whether the list has
+ /// altered. This indication is implemented by storing a pointer to the
+ /// list of library names which is cleared when the libraries are loaded.
+ /// So either the pointer is null (meaning don't reload the libraries and
+ /// the list of current names can be obtained from the HooksManager) or it
+ /// is non-null (this is the new list of names, reload the libraries when
+ /// possible).
+ HooksLibsStoragePtr hooks_libraries_;
+
+ /// @brief The parsing universe of this context.
+ Option::Universe universe_;
+
+ /// @brief Assignment operator
+ ParserContext& operator=(const ParserContext& rhs);
+
+ /// @brief Copy the context fields.
+ ///
+ /// This class method initializes the context data by copying the data
+ /// stored in the context instance provided as an argument. Note that
+ /// this function will also handle copying the NULL pointers.
+ ///
+ /// @param ctx context to be copied.
+ void copyContext(const ParserContext& ctx);
+
+ template<typename T>
+ void copyContextPointer(const boost::shared_ptr<T>& source_ptr,
+ boost::shared_ptr<T>& dest_ptr);
+};
+
+/// @brief Pointer to various parser context.
+typedef boost::shared_ptr<ParserContext> ParserContextPtr;
+
+/// @brief Simple data-type parser template class
+///
+/// This is the template class for simple data-type parsers. It supports
+/// parsing a configuration parameter with specific data-type for its
+/// possible values. It provides a common constructor, commit, and templated
+/// data storage. The "build" method implementation must be provided by a
+/// declaring type.
+/// @param ValueType is the data type of the configuration paramater value
+/// the parser should handle.
+template<typename ValueType>
+class ValueParser : public DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param param_name name of the parameter.
+ /// @param storage is a pointer to the storage container where the parsed
+ /// value be stored upon commit.
+ /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
+ /// name is empty.
+ /// @throw isc::dhcp::DhcpConfigError if storage is null.
+ ValueParser(const std::string& param_name,
+ boost::shared_ptr<ValueStorage<ValueType> > storage)
+ : storage_(storage), param_name_(param_name), value_() {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+
+ // NUll storage is invalid.
+ if (!storage_) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "storage may not be NULL");
+ }
+ }
+
+
+ /// @brief Parse a given element into a value of type @c ValueType
+ ///
+ /// @param value a value to be parsed.
+ ///
+ /// @throw isc::BadValue Typically the implementing type will throw
+ /// a BadValue exception when given an invalid Element to parse.
+ void build(isc::data::ConstElementPtr value);
+
+ /// @brief Put a parsed value to the storage.
+ void commit() {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ storage_->setParam(param_name_, value_);
+ }
+
+private:
+ /// Pointer to the storage where committed value is stored.
+ boost::shared_ptr<ValueStorage<ValueType> > storage_;
+
+ /// Name of the parameter which value is parsed with this parser.
+ std::string param_name_;
+
+ /// Parsed value.
+ ValueType value_;
+};
+
+/// @brief typedefs for simple data type parsers
+typedef ValueParser<bool> BooleanParser;
+typedef ValueParser<uint32_t> Uint32Parser;
+typedef ValueParser<std::string> StringParser;
+
+/// @brief a dummy configuration parser
+///
+/// It is a debugging parser. It does not configure anything,
+/// will accept any configuration and will just print it out
+/// on commit. Useful for debugging existing configurations and
+/// adding new ones.
+class DebugParser : public DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @param param_name name of the parsed parameter
+ DebugParser(const std::string& param_name);
+
+ /// @brief builds parameter value
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @param new_config pointer to the new configuration
+ virtual void build(isc::data::ConstElementPtr new_config);
+
+ /// @brief pretends to apply the configuration
+ ///
+ /// This is a method required by base class. It pretends to apply the
+ /// configuration, but in fact it only prints the parameter out.
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ virtual void commit();
+
+private:
+ /// name of the parsed parameter
+ std::string param_name_;
+
+ /// pointer to the actual value of the parameter
+ isc::data::ConstElementPtr value_;
+
+};
+
+/// @brief parser for interface list definition
+///
+/// This parser handles Dhcp4/interface entry.
+/// It contains a list of network interfaces that the server listens on.
+/// In particular, it can contain an entry called "all" or "any" that
+/// designates all interfaces.
+///
+/// It is useful for parsing Dhcp4/interface parameter.
+class InterfaceListConfigParser : public DhcpConfigParser {
+public:
+
+ /// @brief constructor
+ ///
+ /// As this is a dedicated parser, it must be used to parse
+ /// "interface" parameter only. All other types will throw exception.
+ ///
+ /// @param param_name name of the configuration parameter being parsed
+ /// @throw BadValue if supplied parameter name is not "interface"
+ InterfaceListConfigParser(const std::string& param_name);
+
+ /// @brief parses parameters value
+ ///
+ /// Parses configuration entry (list of parameters) and adds each element
+ /// to the interfaces list.
+ ///
+ /// @param value pointer to the content of parsed values
+ virtual void build(isc::data::ConstElementPtr value);
+
+ /// @brief commits interfaces list configuration
+ virtual void commit();
+
+private:
+ /// @brief Check that specified interface exists in
+ /// @c InterfaceListConfigParser::interfaces_.
+ ///
+ /// @param iface A name of the interface.
+ ///
+ /// @return true if specified interface name was found.
+ bool isIfaceAdded(const std::string& iface) const;
+
+ /// contains list of network interfaces
+ typedef std::list<std::string> IfaceListStorage;
+ IfaceListStorage interfaces_;
+
+ // Should server listen on all interfaces.
+ bool activate_all_;
+
+ // Parsed parameter name
+ std::string param_name_;
+};
+
+/// @brief Parser for hooks library list
+///
+/// This parser handles the list of hooks libraries. This is an optional list,
+/// which may be empty.
+///
+/// However, the parser does more than just check the list of library names.
+/// It does two other things:
+///
+/// -# The problem faced with the hooks libraries is that we wish to avoid
+/// reloading the libraries if they have not changed. (This would cause the
+/// "unload" and "load" methods to run. Although libraries should be written
+/// to cope with this, it is feasible that such an action may be constly in
+/// terms of time and resources, or may cause side effects such as clearning
+/// an internal cache.) To this end, the parser also checks the list against
+/// the list of libraries current loaded and notes if there are changes.
+/// -# If there are, the parser validates the libraries; it opens them and
+/// checks that the "version" function exists and returns the correct value.
+///
+/// Only if the library list has changed and the libraries are valid will the
+/// change be applied.
+class HooksLibrariesParser : public DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// As this is a dedicated parser, it must be used to parse
+ /// "hooks-libraries" parameter only. All other types will throw exception.
+ ///
+ /// @param param_name name of the configuration parameter being parsed.
+ ///
+ /// @throw BadValue if supplied parameter name is not "hooks-libraries"
+ HooksLibrariesParser(const std::string& param_name);
+
+ /// @brief Parses parameters value
+ ///
+ /// Parses configuration entry (list of parameters) and adds each element
+ /// to the hooks libraries list. The method also checks whether the
+ /// list of libraries is the same as that already loaded. If not, it
+ /// checks each of the libraries in the list for validity (they exist and
+ /// have a "version" function that returns the correct value).
+ ///
+ /// @param value pointer to the content of parsed values
+ virtual void build(isc::data::ConstElementPtr value);
+
+ /// @brief Commits hooks libraries data
+ ///
+ /// Providing that the specified libraries are valid and are different
+ /// to those already loaded, this method loads the new set of libraries
+ /// (and unloads the existing set).
+ virtual void commit();
+
+ /// @brief Returns list of parsed libraries
+ ///
+ /// Principally for testing, this returns the list of libraries as well as
+ /// an indication as to whether the list is different from the list of
+ /// libraries already loaded.
+ ///
+ /// @param [out] libraries List of libraries that were specified in the
+ /// new configuration.
+ /// @param [out] changed true if the list is different from that currently
+ /// loaded.
+ void getLibraries(std::vector<std::string>& libraries, bool& changed);
+
+private:
+ /// List of hooks libraries.
+ std::vector<std::string> libraries_;
+
+ /// Indicator flagging that the list of libraries has changed.
+ bool changed_;
+};
+
+/// @brief Parser for option data value.
+///
+/// This parser parses configuration entries that specify value of
+/// a single option. These entries include option name, option code
+/// and data carried by the option. The option data can be specified
+/// in one of the two available formats: binary value represented as
+/// a string of hexadecimal digits or a list of comma separated values.
+/// The format being used is controlled by csv-format configuration
+/// parameter. When setting this value to True, the latter format is
+/// used. The subsequent values in the CSV format apply to relevant
+/// option data fields in the configured option. For example the
+/// configuration: "data" : "192.168.2.0, 56, hello world" can be
+/// used to set values for the option comprising IPv4 address,
+/// integer and string data field. Note that order matters. If the
+/// order of values does not match the order of data fields within
+/// an option the configuration will not be accepted. If parsing
+/// is successful then an instance of an option is created and
+/// added to the storage provided by the calling class.
+class OptionDataParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param dummy first argument is ignored, all Parser constructors
+ /// accept string as first argument.
+ /// @param options is the option storage in which to store the parsed option
+ /// upon "commit".
+ /// @param global_context is a pointer to the global context which
+ /// stores global scope parameters, options, option defintions.
+ /// @throw isc::dhcp::DhcpConfigError if options or global_context are null.
+ OptionDataParser(const std::string& dummy, OptionStoragePtr options,
+ ParserContextPtr global_context);
+
+ /// @brief Parses the single option data.
+ ///
+ /// This method parses the data of a single option from the configuration.
+ /// The option data includes option name, option code and data being
+ /// carried by this option. Eventually it creates the instance of the
+ /// option.
+ ///
+ /// @param option_data_entries collection of entries that define value
+ /// for a particular option.
+ /// @throw DhcpConfigError if invalid parameter specified in
+ /// the configuration.
+ /// @throw isc::InvalidOperation if failed to set storage prior to
+ /// calling build.
+ virtual void build(isc::data::ConstElementPtr option_data_entries);
+
+ /// @brief Commits option value.
+ ///
+ /// This function adds a new option to the storage or replaces an existing
+ /// option with the same code.
+ ///
+ /// @throw isc::InvalidOperation if failed to set pointer to storage or
+ /// failed
+ /// to call build() prior to commit. If that happens data in the storage
+ /// remain un-modified.
+ virtual void commit();
+
+ /// @brief virtual destructor to ensure orderly destruction of derivations.
+ virtual ~OptionDataParser(){};
+
+protected:
+ /// @brief Finds an option definition within the server's option space
+ ///
+ /// Given an option space and an option code, find the correpsonding
+ /// option defintion within the server's option defintion storage. This
+ /// method is pure virtual requiring derivations to manage which option
+ /// space(s) is valid for search.
+ ///
+ /// @param option_space name of the parameter option space
+ /// @param option_code numeric value of the parameter to find
+ /// @return OptionDefintionPtr of the option defintion or an
+ /// empty OptionDefinitionPtr if not found.
+ /// @throw DhcpConfigError if the option space requested is not valid
+ /// for this server.
+ virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
+ std::string& option_space, uint32_t option_code) = 0;
+
+private:
+
+ /// @brief Create option instance.
+ ///
+ /// Creates an instance of an option and adds it to the provided
+ /// options storage. If the option data parsed by \ref build function
+ /// are invalid or insufficient this function emits an exception.
+ ///
+ /// @warning this function does not check if options_ storage pointer
+ /// is intitialized but this check is not needed here because it is done
+ /// in the \ref build function.
+ ///
+ /// @throw DhcpConfigError if parameters provided in the configuration
+ /// are invalid.
+ void createOption();
+
+ /// Storage for boolean values.
+ BooleanStoragePtr boolean_values_;
+
+ /// Storage for string values (e.g. option name or data).
+ StringStoragePtr string_values_;
+
+ /// Storage for uint32 values (e.g. option code).
+ Uint32StoragePtr uint32_values_;
+
+ /// Pointer to options storage. This storage is provided by
+ /// the calling class and is shared by all OptionDataParser objects.
+ OptionStoragePtr options_;
+
+ /// Option descriptor holds newly configured option.
+ Subnet::OptionDescriptor option_descriptor_;
+
+ /// Option space name where the option belongs to.
+ std::string option_space_;
+
+ /// Parsing context which contains global values, options and option
+ /// definitions.
+ ParserContextPtr global_context_;
+};
+
+///@brief Function pointer for OptionDataParser factory methods
+typedef OptionDataParser *OptionDataParserFactory(const std::string&,
+ OptionStoragePtr options, ParserContextPtr global_context);
+
+/// @brief Parser for option data values within a subnet.
+///
+/// This parser iterates over all entries that define options
+/// data for a particular subnet and creates a collection of options.
+/// If parsing is successful, all these options are added to the Subnet
+/// object.
+class OptionDataListParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param dummy nominally would be param name, this is always ignored.
+ /// @param options parsed option storage for options in this list
+ /// @param global_context is a pointer to the global context which
+ /// stores global scope parameters, options, option defintions.
+ /// @param optionDataParserFactory factory method for creating individual
+ /// option parsers
+ /// @throw isc::dhcp::DhcpConfigError if options or global_context are null.
+ OptionDataListParser(const std::string& dummy, OptionStoragePtr options,
+ ParserContextPtr global_context,
+ OptionDataParserFactory *optionDataParserFactory);
+
+ /// @brief Parses entries that define options' data for a subnet.
+ ///
+ /// This method iterates over all entries that define option data
+ /// for options within a single subnet and creates options' instances.
+ ///
+ /// @param option_data_list pointer to a list of options' data sets.
+ /// @throw DhcpConfigError if option parsing failed.
+ void build(isc::data::ConstElementPtr option_data_list);
+
+ /// @brief Commit all option values.
+ ///
+ /// This function invokes commit for all option values.
+ void commit();
+
+private:
+ /// Pointer to options instances storage.
+ OptionStoragePtr options_;
+
+ /// Intermediate option storage. This storage is used by
+ /// lower level parsers to add new options. Values held
+ /// in this storage are assigned to main storage (options_)
+ /// if overall parsing was successful.
+ OptionStoragePtr local_options_;
+
+ /// Collection of parsers;
+ ParserCollection parsers_;
+
+ /// Parsing context which contains global values, options and option
+ /// definitions.
+ ParserContextPtr global_context_;
+
+ /// Factory to create server-specific option data parsers
+ OptionDataParserFactory *optionDataParserFactory_;
+};
+
+
+/// @brief Parser for a single option definition.
+///
+/// This parser creates an instance of a single option definition.
+class OptionDefParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param dummy first argument is ignored, all Parser constructors
+ /// accept string as first argument.
+ /// @param storage is the definition storage in which to store the parsed
+ /// definition upon "commit".
+ /// @throw isc::dhcp::DhcpConfigError if storage is null.
+ OptionDefParser(const std::string& dummy, OptionDefStoragePtr storage);
+
+ /// @brief Parses an entry that describes single option definition.
+ ///
+ /// @param option_def a configuration entry to be parsed.
+ ///
+ /// @throw DhcpConfigError if parsing was unsuccessful.
+ void build(isc::data::ConstElementPtr option_def);
+
+ /// @brief Stores the parsed option definition in a storage.
+ void commit();
+
+private:
+
+ /// @brief Create option definition from the parsed parameters.
+ void createOptionDef();
+
+ /// Instance of option definition being created by this parser.
+ OptionDefinitionPtr option_definition_;
+ /// Name of the space the option definition belongs to.
+ std::string option_space_name_;
+
+ /// Pointer to a storage where the option definition will be
+ /// added when \ref commit is called.
+ OptionDefStoragePtr storage_;
+
+ /// Storage for boolean values.
+ BooleanStoragePtr boolean_values_;
+
+ /// Storage for string values.
+ StringStoragePtr string_values_;
+
+ /// Storage for uint32 values.
+ Uint32StoragePtr uint32_values_;
+};
+
+/// @brief Parser for a list of option definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// option definitions and creates instances of these definitions.
+/// If the parsing is successful, the collection of created definitions
+/// is put into the provided storage.
+class OptionDefListParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param dummy first argument is ignored, all Parser constructors
+ /// accept string as first argument.
+ /// @param storage is the definition storage in which to store the parsed
+ /// definitions in this list
+ /// @throw isc::dhcp::DhcpConfigError if storage is null.
+ OptionDefListParser(const std::string& dummy, OptionDefStoragePtr storage);
+
+ /// @brief Parse configuration entries.
+ ///
+ /// This function parses configuration entries and creates instances
+ /// of option definitions.
+ ///
+ /// @param option_def_list pointer to an element that holds entries
+ /// that define option definitions.
+ /// @throw DhcpConfigError if configuration parsing fails.
+ void build(isc::data::ConstElementPtr option_def_list);
+
+ /// @brief Stores option definitions in the CfgMgr.
+ void commit();
+
+private:
+ /// @brief storage for option definitions.
+ OptionDefStoragePtr storage_;
+};
+
+/// @brief a collection of pools
+///
+/// That type is used as intermediate storage, when pools are parsed, but there is
+/// no subnet object created yet to store them.
+typedef std::vector<PoolPtr> PoolStorage;
+typedef boost::shared_ptr<PoolStorage> PoolStoragePtr;
+
+/// @brief parser for pool definition
+///
+/// This abstract parser handles pool definitions, i.e. a list of entries of one
+/// of two syntaxes: min-max and prefix/len. Pool objects are created
+/// and stored in chosen PoolStorage container.
+///
+/// It is useful for parsing Dhcp<4/6>/subnet<4/6>[X]/pool parameters.
+class PoolParser : public DhcpConfigParser {
+public:
+
+ /// @brief constructor.
+ ///
+ /// @param dummy first argument is ignored, all Parser constructors
+ /// accept string as first argument.
+ /// @param pools is the storage in which to store the parsed pool
+ /// upon "commit".
+ /// @throw isc::dhcp::DhcpConfigError if storage is null.
+ PoolParser(const std::string& dummy, PoolStoragePtr pools);
+
+ /// @brief parses the actual list
+ ///
+ /// This method parses the actual list of interfaces.
+ /// No validation is done at this stage, everything is interpreted as
+ /// interface name.
+ /// @param pools_list list of pools defined for a subnet
+ /// @throw isc::dhcp::DhcpConfigError when pool parsing fails
+ virtual void build(isc::data::ConstElementPtr pools_list);
+
+ /// @brief Stores the parsed values in a storage provided
+ /// by an upper level parser.
+ virtual void commit();
+
+protected:
+ /// @brief Creates a Pool object given a IPv4 prefix and the prefix length.
+ ///
+ /// @param addr is the IP prefix of the pool.
+ /// @param len is the prefix length.
+ /// @param ptype is the type of pool to create.
+ /// @return returns a PoolPtr to the new Pool object.
+ virtual PoolPtr poolMaker(isc::asiolink::IOAddress &addr, uint32_t len,
+ int32_t ptype=0) = 0;
+
+ /// @brief Creates a Pool object given starting and ending IP addresses.
+ ///
+ /// @param min is the first IP address in the pool.
+ /// @param max is the last IP address in the pool.
+ /// @param ptype is the type of pool to create (not used by all derivations)
+ /// @return returns a PoolPtr to the new Pool object.
+ virtual PoolPtr poolMaker(isc::asiolink::IOAddress &min,
+ isc::asiolink::IOAddress &max, int32_t ptype=0) = 0;
+
+ /// @brief pointer to the actual Pools storage
+ ///
+ /// That is typically a storage somewhere in Subnet parser
+ /// (an upper level parser).
+ PoolStoragePtr pools_;
+
+ /// A temporary storage for pools configuration. It is a
+ /// storage where pools are stored by build function.
+ PoolStorage local_pools_;
+};
+
+/// @brief this class parses a single subnet
+///
+/// This class parses the whole subnet definition. It creates parsers
+/// for received configuration parameters as needed.
+class SubnetConfigParser : public DhcpConfigParser {
+public:
+
+ /// @brief constructor
+ SubnetConfigParser(const std::string&, ParserContextPtr global_context);
+
+ /// @brief parses parameter value
+ ///
+ /// @param subnet pointer to the content of subnet definition
+ ///
+ /// @throw isc::DhcpConfigError if subnet configuration parsing failed.
+ virtual void build(isc::data::ConstElementPtr subnet);
+
+ /// @brief Adds the created subnet to a server's configuration.
+ virtual void commit() = 0;
+
+protected:
+ /// @brief creates parsers for entries in subnet definition
+ ///
+ /// @param config_id name od the entry
+ ///
+ /// @return parser object for specified entry name
+ /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
+ /// for unknown config element
+ virtual DhcpConfigParser* createSubnetConfigParser(
+ const std::string& config_id) = 0;
+
+ /// @brief Determines if the given option space name and code describe
+ /// a standard option for the server.
+ ///
+ /// @param option_space is the name of the option space to consider
+ /// @param code is the numeric option code to consider
+ /// @return returns true if the space and code are part of the server's
+ /// standard options.
+ virtual bool isServerStdOption(std::string option_space, uint32_t code) = 0;
+
+ /// @brief Returns the option definition for a given option code from
+ /// the server's standard set of options.
+ /// @param code is the numeric option code of the desired option definition.
+ /// @return returns a pointer the option definition
+ virtual OptionDefinitionPtr getServerStdOptionDefinition (
+ uint32_t code) = 0;
+
+ /// @brief Issues a server specific warning regarding duplicate subnet
+ /// options.
+ ///
+ /// @param code is the numeric option code of the duplicate option
+ /// @param addr is the subnet address
+ /// @todo a means to know the correct logger and perhaps a common
+ /// message would allow this method to be emitted by the base class.
+ virtual void duplicate_option_warning(uint32_t code,
+ isc::asiolink::IOAddress& addr) = 0;
+
+ /// @brief Instantiates the subnet based on a given IP prefix and prefix
+ /// length.
+ ///
+ /// @param addr is the IP prefix of the subnet.
+ /// @param len is the prefix length
+ virtual void initSubnet(isc::asiolink::IOAddress addr, uint8_t len) = 0;
+
+ /// @brief Returns value for a given parameter (after using inheritance)
+ ///
+ /// This method implements inheritance. For a given parameter name, it first
+ /// checks if there is a global value for it and overwrites it with specific
+ /// value if such value was defined in subnet.
+ ///
+ /// @param name name of the parameter
+ /// @return triplet with the parameter name
+ /// @throw DhcpConfigError when requested parameter is not present
+ isc::dhcp::Triplet<uint32_t> getParam(const std::string& name);
+
+private:
+
+ /// @brief Append sub-options to an option.
+ ///
+ /// @param option_space a name of the encapsulated option space.
+ /// @param option option instance to append sub-options to.
+ void appendSubOptions(const std::string& option_space, OptionPtr& option);
+
+ /// @brief Create a new subnet using a data from child parsers.
+ ///
+ /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing
+ /// failed.
+ void createSubnet();
+
+protected:
+
+ /// Storage for subnet-specific integer values.
+ Uint32StoragePtr uint32_values_;
+
+ /// Storage for subnet-specific string values.
+ StringStoragePtr string_values_;
+
+ /// Storage for pools belonging to this subnet.
+ PoolStoragePtr pools_;
+
+ /// Storage for options belonging to this subnet.
+ OptionStoragePtr options_;
+
+ /// Parsers are stored here.
+ ParserCollection parsers_;
+
+ /// Pointer to the created subnet object.
+ isc::dhcp::SubnetPtr subnet_;
+
+ /// Parsing context which contains global values, options and option
+ /// definitions.
+ ParserContextPtr global_context_;
+};
+
+// Pointers to various parser objects.
+typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
+typedef boost::shared_ptr<StringParser> StringParserPtr;
+typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // DHCP_PARSERS_H
+
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.h b/src/lib/dhcpsrv/dhcpsrv_log.h
index 9b6350a..fe997ff 100644
--- a/src/lib/dhcpsrv/dhcpsrv_log.h
+++ b/src/lib/dhcpsrv/dhcpsrv_log.h
@@ -50,6 +50,9 @@ const int DHCPSRV_DBG_TRACE_DETAIL = DBGLVL_TRACE_DETAIL;
/// Record detailed (and verbose) data on the server.
const int DHCPSRV_DBG_TRACE_DETAIL_DATA = DBGLVL_TRACE_DETAIL_DATA;
+// Trace hook related operations
+const int DHCPSRV_DBG_HOOKS = DBGLVL_TRACE_BASIC;
+
///@}
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
index 21a7c79..a915b02 100644
--- a/src/lib/dhcpsrv/dhcpsrv_messages.mes
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -54,6 +54,10 @@ consider reducing the lease lifetime. In this way, addresses allocated
to clients that are no longer active on the network will become available
available sooner.
+% DHCPSRV_CFGMGR_ADD_IFACE adding listening interface %1
+A debug message issued when new interface is being added to the collection of
+interfaces on which server listens to DHCP messages.
+
% DHCPSRV_CFGMGR_ADD_SUBNET4 adding subnet %1
A debug message reported when the DHCP configuration manager is adding the
specified IPv4 subnet to its database.
@@ -62,6 +66,16 @@ specified IPv4 subnet to its database.
A debug message reported when the DHCP configuration manager is adding the
specified IPv6 subnet to its database.
+% DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE enabling listening on all interfaces
+A debug message issued when server is being configured to listen on all
+interfaces.
+
+% DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES stop listening on all interfaces
+A debug message issued when configuration manager clears the internal list
+of active interfaces. This doesn't prevent the server from listening to
+the DHCP traffic through open sockets, but will rather be used by Interface
+Manager to select active interfaces when sockets are re-opened.
+
% DHCPSRV_CFGMGR_DELETE_SUBNET4 deleting all IPv4 subnets
A debug message noting that the DHCP configuration manager has deleted all IPv4
subnets in its database.
@@ -105,7 +119,14 @@ This is a debug message reporting that the DHCP configuration manager
has returned the specified IPv6 subnet for a packet received over
given interface. This particular subnet was selected, because it
was specified as being directly reachable over given interface. (see
-'interface' parameter in subnet6 definition).
+'interface' parameter in the subnet6 definition).
+
+% DHCPSRV_CFGMGR_SUBNET6_IFACE_ID selected subnet %1 (interface-id match) for incoming packet
+This is a debug message reporting that the DHCP configuration manager
+has returned the specified IPv6 subnet for a received packet. This particular
+subnet was selected, because value of interface-id option matched what was
+configured in server's interface-id option for that selected subnet6.
+(see 'interface-id' parameter in the subnet6 definition).
% DHCPSRV_CLOSE_DB closing currently open %1 database
This is a debug message, issued when the DHCP server closes the currently
@@ -114,6 +135,24 @@ the database access parameters are changed: in the latter case, the
server closes the currently open database, and opens a database using
the new parameters.
+% DHCPSRV_HOOK_LEASE4_SELECT_SKIP Lease4 creation was skipped, because of callout skip flag.
+This debug message is printed when a callout installed on lease4_select
+hook point sets the skip flag. It means that the server was told that
+no lease4 should be assigned. The server will not put that lease in its
+database and the client will get a NAK packet.
+
+% DHCPSRV_HOOK_LEASE4_RENEW_SKIP DHCPv4 lease was not renewed because a callout set the skip flag.
+This debug message is printed when a callout installed on lease4_renew
+hook point set the skip flag. For this particular hook point, the setting
+of the flag by a callout instructs the server to not renew a lease. The
+server will use existing lease as it is, without extending its lifetime.
+
+% DHCPSRV_HOOK_LEASE6_SELECT_SKIP Lease6 (non-temporary) creation was skipped, because of callout skip flag.
+This debug message is printed when a callout installed on lease6_select
+hook point sets the skip flag. It means that the server was told that
+no lease6 should be assigned. The server will not put that lease in its
+database and the client will get a NoAddrsAvail for that IA_NA option.
+
% DHCPSRV_INVALID_ACCESS invalid database access string: %1
This is logged when an attempt has been made to parse a database access string
and the attempt ended in error. The access string in question - which
@@ -215,7 +254,7 @@ with the specified address to the MySQL backend database.
% DHCPSRV_MYSQL_COMMIT committing to MySQL database
The code has issued a commit call. All outstanding transactions will be
committed to the database. Note that depending on the MySQL settings,
-the commital may not include a write to disk.
+the committal may not include a write to disk.
% DHCPSRV_MYSQL_DB opening MySQL lease database: %1
This informational message is logged when a DHCP server (either V4 or
diff --git a/src/lib/dhcpsrv/key_from_key.h b/src/lib/dhcpsrv/key_from_key.h
new file mode 100644
index 0000000..4fb9ddb
--- /dev/null
+++ b/src/lib/dhcpsrv/key_from_key.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 KEY_FROM_KEY_H
+#define KEY_FROM_KEY_H
+
+#include <functional>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Utility class which cascades two key extractors.
+///
+/// The key extractor (a.k.a. key extraction class) is used by the
+/// key-based indices to obtain the indexing keys from the elements of
+/// a multi_index_container. The standard key extractors can be used
+/// to retrieve indexing key values by accessing members or methods
+/// exposed by the elements (objects or structures) stored in a
+/// multi_index_container. For example, if a container holds objects
+/// of type A, then the public members of object A or its accessors can
+/// be used by the standard extractor classes such as "member" or
+/// "const_mem_fun" respectively. Assume more complex scenario, where
+/// multi_index_container holds objects of a type A, object A exposes
+/// its public member B, which in turn exposes the accessor function
+/// returning object C. One may want to use the value C (e.g. integer)
+/// to index objects A in the container. This can't be solved by using
+/// standard key extractors because object C is nested in B and thus
+/// it is not directly accessible from A. However, it is possible
+/// to specify two distinct key extractors, one used to extract value
+/// C from B, another one to extract value B from A. These two extractors
+/// can be then wrapped by another key extractor which can be used
+/// to obtain index key C from object A. This key extractor is implemented
+/// as a functor class. The functor calls functors specified as
+/// template parameters to retrieve the index value from the cascaded
+/// structure.
+///
+/// @tparam KeyExtractor1 extractor used to extract the key value from
+/// the object containing it.
+/// @tparam KeyExtractor2 extractor used to extract the nested object
+/// containing a key.
+template<typename KeyExtractor1, typename KeyExtractor2>
+class KeyFromKeyExtractor {
+public:
+ typedef typename KeyExtractor1::result_type result_type;
+
+ /// @brief Constructor.
+ KeyFromKeyExtractor()
+ : key1_(KeyExtractor1()), key2_(KeyExtractor2()) { };
+
+ /// @brief Extract key value from the object hierarchy.
+ ///
+ /// @param arg the key value.
+ ///
+ /// @tparam key value type.
+ template<typename T>
+ result_type operator() (T& arg) const {
+ return (key1_(key2_(arg)));
+ }
+private:
+ /// Key Extractor used to extract the key value from the
+ /// object containing it.
+ KeyExtractor1 key1_;
+ /// Key Extractor used to extract the nested object
+ /// containing a key.
+ KeyExtractor2 key2_;
+};
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // KEY_FROM_KEY_H
diff --git a/src/lib/dhcpsrv/lease_mgr.cc b/src/lib/dhcpsrv/lease_mgr.cc
index 2310dd4..c4810fd 100644
--- a/src/lib/dhcpsrv/lease_mgr.cc
+++ b/src/lib/dhcpsrv/lease_mgr.cc
@@ -38,6 +38,52 @@ Lease::Lease(const isc::asiolink::IOAddress& addr, uint32_t t1, uint32_t t2,
subnet_id_(subnet_id), fixed_(false), fqdn_fwd_(false), fqdn_rev_(false) {
}
+Lease4::Lease4(const Lease4& other)
+ : Lease(other.addr_, other.t1_, other.t2_, other.valid_lft_,
+ other.subnet_id_, other.cltt_), ext_(other.ext_),
+ hwaddr_(other.hwaddr_) {
+
+ fixed_ = other.fixed_;
+ fqdn_fwd_ = other.fqdn_fwd_;
+ fqdn_rev_ = other.fqdn_rev_;
+ hostname_ = other.hostname_;
+ comments_ = other.comments_;
+
+ if (other.client_id_) {
+ client_id_.reset(new ClientId(other.client_id_->getClientId()));
+
+ } else {
+ client_id_.reset();
+
+ }
+}
+
+Lease4&
+Lease4::operator=(const Lease4& other) {
+ if (this != &other) {
+ addr_ = other.addr_;
+ t1_ = other.t1_;
+ t2_ = other.t2_;
+ valid_lft_ = other.valid_lft_;
+ cltt_ = other.cltt_;
+ subnet_id_ = other.subnet_id_;
+ fixed_ = other.fixed_;
+ hostname_ = other.hostname_;
+ fqdn_fwd_ = other.fqdn_fwd_;
+ fqdn_rev_ = other.fqdn_rev_;
+ comments_ = other.comments_;
+ ext_ = other.ext_;
+ hwaddr_ = other.hwaddr_;
+
+ if (other.client_id_) {
+ client_id_.reset(new ClientId(other.client_id_->getClientId()));
+ } else {
+ client_id_.reset();
+ }
+ }
+ return (*this);
+}
+
Lease6::Lease6(LeaseType type, const isc::asiolink::IOAddress& addr,
DuidPtr duid, uint32_t iaid, uint32_t preferred, uint32_t valid,
uint32_t t1, uint32_t t2, SubnetID subnet_id, uint8_t prefixlen)
diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h
index e6aaa51..3e0e092 100644
--- a/src/lib/dhcpsrv/lease_mgr.h
+++ b/src/lib/dhcpsrv/lease_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -44,7 +44,7 @@
/// There are no intermediate plans to implement DHCPv4 failover
/// (draft-ietf-dhc-failover-12.txt). Currently (Oct. 2012) the DHCPv6 failover
/// is being defined in DHC WG in IETF (draft-ietf-dhcpv6-failover-requirements,
-/// draft-ietf-dhcpv6-dailover-design), but the work is not advanced enough
+/// draft-ietf-dhcpv6-failover-design), but the work is not advanced enough
/// for implementation plans yet. v4 failover requires additional parameters
/// to be kept with a lease. It is likely that v6 failover will require similar
/// fields. Such implementation will require database schema extension.
@@ -211,8 +211,6 @@ struct Lease {
/// would be required. As this is a critical part of the code that will be used
/// extensively, direct access is warranted.
struct Lease4 : public Lease {
- /// @brief Maximum size of a hardware address
- static const size_t HWADDR_MAX = 20;
/// @brief Address extension
///
@@ -249,8 +247,10 @@ struct Lease4 : public Lease {
const uint8_t* clientid, size_t clientid_len, uint32_t valid_lft,
uint32_t t1, uint32_t t2, time_t cltt, uint32_t subnet_id)
: Lease(addr, t1, t2, valid_lft, subnet_id, cltt),
- ext_(0), hwaddr_(hwaddr, hwaddr + hwaddr_len),
- client_id_(new ClientId(clientid, clientid_len)) {
+ ext_(0), hwaddr_(hwaddr, hwaddr + hwaddr_len) {
+ if (clientid_len) {
+ client_id_.reset(new ClientId(clientid, clientid_len));
+ }
}
/// @brief Default constructor
@@ -259,6 +259,16 @@ struct Lease4 : public Lease {
Lease4() : Lease(0, 0, 0, 0, 0, 0) {
}
+ /// @brief Copy constructor
+ ///
+ /// @param other the @c Lease4 object to be copied.
+ Lease4(const Lease4& other);
+
+ /// @brief Assignment operator.
+ ///
+ /// @param other the @c Lease4 object to be assigned.
+ Lease4& operator=(const Lease4& other);
+
/// @brief Compare two leases for equality
///
/// @param other lease6 object with which to compare
@@ -477,7 +487,7 @@ public:
/// @brief Returns existing IPv6 lease for a given IPv6 address.
///
/// For a given address, we assume that there will be only one lease.
- /// The assumtion here is that there will not be site or link-local
+ /// The assumption here is that there will not be site or link-local
/// addresses used, so there is no way of having address duplication.
///
/// @param addr address of the searched lease
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc
index e5846eb..804eef8 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.cc
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.cc
@@ -56,11 +56,13 @@ Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) cons
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_MEMFILE_GET_ADDR4).arg(addr.toText());
- Lease4Storage::iterator l = storage4_.find(addr);
+ typedef Lease4Storage::nth_index<0>::type SearchIndex;
+ const SearchIndex& idx = storage4_.get<0>();
+ Lease4Storage::iterator l = idx.find(addr);
if (l == storage4_.end()) {
return (Lease4Ptr());
} else {
- return (*l);
+ return (Lease4Ptr(new Lease4(**l)));
}
}
@@ -77,16 +79,22 @@ Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr,
DHCPSRV_MEMFILE_GET_SUBID_HWADDR).arg(subnet_id)
.arg(hwaddr.toText());
- Lease4Storage::iterator l;
- for (l = storage4_.begin(); l != storage4_.end(); ++l) {
- if ( ((*l)->hwaddr_ == hwaddr.hwaddr_) &&
- ((*l)->subnet_id_ == subnet_id)) {
- return (*l);
- }
+ // We are going to use index #1 of the multi index container.
+ // We define SearchIndex locally in this function because
+ // currently only this function uses this index.
+ typedef Lease4Storage::nth_index<1>::type SearchIndex;
+ // Get the index.
+ const SearchIndex& idx = storage4_.get<1>();
+ // Try to find the lease using HWAddr and subnet id.
+ SearchIndex::const_iterator lease =
+ idx.find(boost::make_tuple(hwaddr.hwaddr_, subnet_id));
+ // Lease was not found. Return empty pointer to the caller.
+ if (lease == idx.end()) {
+ return Lease4Ptr();
}
- // not found
- return (Lease4Ptr());
+ // Lease was found. Return it to the caller.
+ return (Lease4Ptr(new Lease4(**lease)));
}
Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& clientid) const {
@@ -100,20 +108,26 @@ Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId& client_id,
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_MEMFILE_GET_SUBID_CLIENTID).arg(subnet_id)
.arg(client_id.toText());
- Lease4Storage::iterator l;
- for (l = storage4_.begin(); l != storage4_.end(); ++l) {
- if ( (*(*l)->client_id_ == client_id) &&
- ((*l)->subnet_id_ == subnet_id)) {
- return (*l);
- }
- }
- // not found
- return (Lease4Ptr());
+ // We are going to use index #2 of the multi index container.
+ // We define SearchIndex locally in this function because
+ // currently only this function uses this index.
+ typedef Lease4Storage::nth_index<2>::type SearchIndex;
+ // Get the index.
+ const SearchIndex& idx = storage4_.get<2>();
+ // Try to get the lease using client id and subnet id.
+ SearchIndex::const_iterator lease =
+ idx.find(boost::make_tuple(client_id.getClientId(), subnet_id));
+ // Lease was not found. Return empty pointer to the caller.
+ if (lease == idx.end()) {
+ return Lease4Ptr();
+ }
+ // Lease was found. Return it to the caller.
+ return (Lease4Ptr(new Lease4(**lease)));
}
-Lease6Ptr Memfile_LeaseMgr::getLease6(
- const isc::asiolink::IOAddress& addr) const {
+Lease6Ptr
+Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_MEMFILE_GET_ADDR6).arg(addr.toText());
@@ -121,7 +135,7 @@ Lease6Ptr Memfile_LeaseMgr::getLease6(
if (l == storage6_.end()) {
return (Lease6Ptr());
} else {
- return (*l);
+ return (Lease6Ptr(new Lease6(**l)));
}
}
@@ -139,28 +153,45 @@ Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID)
.arg(iaid).arg(subnet_id).arg(duid.toText());
- /// @todo: Slow, naive implementation. Write it using additional indexes
- for (Lease6Storage::iterator l = storage6_.begin(); l != storage6_.end(); ++l) {
- if ( (*((*l)->duid_) == duid) &&
- ( (*l)->iaid_ == iaid) &&
- ( (*l)->subnet_id_ == subnet_id)) {
- return (*l);
- }
+ // We are going to use index #1 of the multi index container.
+ // We define SearchIndex locally in this function because
+ // currently only this function uses this index.
+ typedef Lease6Storage::nth_index<1>::type SearchIndex;
+ // Get the index.
+ const SearchIndex& idx = storage6_.get<1>();
+ // Try to get the lease using the DUID, IAID and Subnet ID.
+ SearchIndex::const_iterator lease =
+ idx.find(boost::make_tuple(duid.getDuid(), iaid, subnet_id));
+ // Lease was not found. Return empty pointer.
+ if (lease == idx.end()) {
+ return (Lease6Ptr());
}
- return (Lease6Ptr());
+ // Lease was found, return it to the caller.
+ return (Lease6Ptr(new Lease6(**lease)));
}
void Memfile_LeaseMgr::updateLease4(const Lease4Ptr& lease) {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_MEMFILE_UPDATE_ADDR4).arg(lease->addr_.toText());
+ Lease4Storage::iterator lease_it = storage4_.find(lease->addr_);
+ if (lease_it == storage4_.end()) {
+ isc_throw(NoSuchLease, "failed to update the lease with address "
+ << lease->addr_.toText() << " - no such lease");
+ }
+ **lease_it = *lease;
}
void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& lease) {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
DHCPSRV_MEMFILE_UPDATE_ADDR6).arg(lease->addr_.toText());
-
+ Lease6Storage::iterator lease_it = storage6_.find(lease->addr_);
+ if (lease_it == storage6_.end()) {
+ isc_throw(NoSuchLease, "failed to update the lease with address "
+ << lease->addr_.toText() << " - no such lease");
+ }
+ **lease_it = *lease;
}
bool Memfile_LeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.h b/src/lib/dhcpsrv/memfile_lease_mgr.h
index 81e5fe3..2f75a98 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.h
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -16,12 +16,14 @@
#define MEMFILE_LEASE_MGR_H
#include <dhcp/hwaddr.h>
+#include <dhcpsrv/key_from_key.h>
#include <dhcpsrv/lease_mgr.h>
#include <boost/multi_index/indexed_by.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/composite_key.hpp>
namespace isc {
namespace dhcp {
@@ -63,8 +65,10 @@ public:
/// @brief Returns existing IPv4 lease for specified IPv4 address.
///
- /// @todo Not implemented yet
- /// @param addr address of the searched lease
+ /// This function returns a copy of the lease. The modification in the
+ /// return lease does not affect the instance held in the lease storage.
+ ///
+ /// @param addr An address of the searched lease.
///
/// @return a collection of leases
virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr) const;
@@ -83,10 +87,11 @@ public:
/// @return lease collection
virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const;
- /// @brief Returns existing IPv4 leases for specified hardware address
+ /// @brief Returns existing IPv4 lease for specified hardware address
/// and a subnet
///
- /// @todo Not implemented yet
+ /// This function returns a copy of the lease. The modification in the
+ /// return lease does not affect the instance held in the lease storage.
///
/// There can be at most one lease for a given HW address in a single
/// pool, so this method with either return a single lease or NULL.
@@ -107,11 +112,12 @@ public:
/// @brief Returns existing IPv4 lease for specified client-id
///
+ /// This function returns a copy of the lease. The modification in the
+ /// return lease does not affect the instance held in the lease storage.
+ ///
/// There can be at most one lease for a given HW address in a single
/// pool, so this method with either return a single lease or NULL.
///
- /// @todo Not implemented yet
- ///
/// @param clientid client identifier
/// @param subnet_id identifier of the subnet that lease must belong to
///
@@ -121,7 +127,10 @@ public:
/// @brief Returns existing IPv6 lease for a given IPv6 address.
///
- /// @param addr address of the searched lease
+ /// This function returns a copy of the lease. The modification in the
+ /// return lease does not affect the instance held in the lease storage.
+ ///
+ /// @param addr An address of the searched lease.
///
/// @return smart pointer to the lease (or NULL if a lease is not found)
virtual Lease6Ptr getLease6(const isc::asiolink::IOAddress& addr) const;
@@ -138,27 +147,31 @@ public:
/// @brief Returns existing IPv6 lease for a given DUID+IA combination
///
- /// @todo Not implemented yet
+ /// This function returns a copy of the lease. The modification in the
+ /// return lease does not affect the instance held in the lease storage.
///
/// @param duid client DUID
/// @param iaid IA identifier
/// @param subnet_id identifier of the subnet the lease must belong to
///
/// @return smart pointer to the lease (or NULL if a lease is not found)
- virtual Lease6Ptr getLease6(const DUID& duid, uint32_t iaid, SubnetID subnet_id) const;
+ virtual Lease6Ptr getLease6(const DUID& duid, uint32_t iaid,
+ SubnetID subnet_id) const;
/// @brief Updates IPv4 lease.
///
- /// @todo Not implemented yet
+ /// @warning This function does not validate the pointer to the lease.
+ /// It is caller's responsibility to pass the valid pointer.
///
/// @param lease4 The lease to be updated.
///
/// If no such lease is present, an exception will be thrown.
virtual void updateLease4(const Lease4Ptr& lease4);
- /// @brief Updates IPv4 lease.
+ /// @brief Updates IPv6 lease.
///
- /// @todo Not implemented yet
+ /// @warning This function does not validate the pointer to the lease.
+ /// It is caller's responsibility to pass the valid pointer.
///
/// @param lease6 The lease to be updated.
///
@@ -220,29 +233,108 @@ public:
protected:
- typedef boost::multi_index_container< // this is a multi-index container...
- Lease6Ptr, // it will hold shared_ptr to leases6
- boost::multi_index::indexed_by< // and will be sorted by
- // IPv6 address that are unique. That particular key is a member
- // of the Lease6 structure, is of type IOAddress and can be accessed
- // by doing &Lease6::addr_
+ // This is a multi-index container, which holds elements that can
+ // be accessed using different search indexes.
+ typedef boost::multi_index_container<
+ // It holds pointers to Lease6 objects.
+ Lease6Ptr,
+ boost::multi_index::indexed_by<
+ // Specification of the first index starts here.
+ // This index sorts leases by IPv6 addresses represented as
+ // IOAddress objects.
boost::multi_index::ordered_unique<
boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
+ >,
+
+ // Specification of the second index starts here.
+ boost::multi_index::ordered_unique<
+ // This is a composite index that will be used to search for
+ // the lease using three attributes: DUID, IAID, Subnet Id.
+ boost::multi_index::composite_key<
+ Lease6,
+ // The DUID value can't be directly accessed from the Lease6
+ // object because it is wrapped with the DUID object (actually
+ // pointer to this object). Therefore we need to use
+ // KeyFromKeyExtractor class to extract the DUID value from
+ // this cascaded structure.
+ KeyFromKeyExtractor<
+ // The value of the DUID is accessed by the getDuid() method
+ // from the DUID object.
+ boost::multi_index::const_mem_fun<DUID, std::vector<uint8_t>,
+ &DUID::getDuid>,
+ // The DUID object is stored in the duid_ member of the
+ // Lease6 object.
+ boost::multi_index::member<Lease6, DuidPtr, &Lease6::duid_>
+ >,
+ // The two other ingredients of this index are IAID and
+ // subnet id.
+ boost::multi_index::member<Lease6, uint32_t, &Lease6::iaid_>,
+ boost::multi_index::member<Lease, SubnetID, &Lease::subnet_id_>
+ >
>
>
- > Lease6Storage; // Let the whole contraption be called Lease6Storage.
-
- typedef boost::multi_index_container< // this is a multi-index container...
- Lease4Ptr, // it will hold shared_ptr to leases6
- boost::multi_index::indexed_by< // and will be sorted by
- // IPv6 address that are unique. That particular key is a member
- // of the Lease6 structure, is of type IOAddress and can be accessed
- // by doing &Lease6::addr_
+ > Lease6Storage; // Specify the type name of this container.
+
+ // This is a multi-index container, which holds elements that can
+ // be accessed using different search indexes.
+ typedef boost::multi_index_container<
+ // It holds pointers to Lease4 objects.
+ Lease4Ptr,
+ // Specification of search indexes starts here.
+ boost::multi_index::indexed_by<
+ // Specification of the first index starts here.
+ // This index sorts leases by IPv4 addresses represented as
+ // IOAddress objects.
boost::multi_index::ordered_unique<
+ // The IPv4 address are held in addr_ members that belong to
+ // Lease class.
boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
+ >,
+
+ // Specification of the second index starts here.
+ boost::multi_index::ordered_unique<
+ // This is a composite index that combines two attributes of the
+ // Lease4 object: hardware address and subnet id.
+ boost::multi_index::composite_key<
+ Lease4,
+ // The hardware address is held in the hwaddr_ member of the
+ // Lease4 object.
+ boost::multi_index::member<Lease4, std::vector<uint8_t>,
+ &Lease4::hwaddr_>,
+ // The subnet id is held in the subnet_id_ member of Lease4
+ // class. Note that the subnet_id_ is defined in the base
+ // class (Lease) so we have to point to this class rather
+ // than derived class: Lease4.
+ boost::multi_index::member<Lease, SubnetID, &Lease::subnet_id_>
+ >
+ >,
+
+ // Specification of the third index starts here.
+ boost::multi_index::ordered_unique<
+ // This is a composite index that uses two values to search for a
+ // lease: client id and subnet id.
+ boost::multi_index::composite_key<
+ Lease4,
+ // The client id value is not directly accessible through the
+ // Lease4 object as it is wrapped with the ClientIdPtr object.
+ // Therefore we use the KeyFromKeyExtractor class to access
+ // client id through this cascaded structure. The client id
+ // is used as an index value.
+ KeyFromKeyExtractor<
+ // Specify that the vector holding client id value can be obtained
+ // from the ClientId object.
+ boost::multi_index::const_mem_fun<ClientId, std::vector<uint8_t>,
+ &ClientId::getClientId>,
+ // Specify that the ClientId object (actually pointer to it) can
+ // be accessed by the client_id_ member of Lease4 class.
+ boost::multi_index::member<Lease4, ClientIdPtr, &Lease4::client_id_>
+ >,
+ // The subnet id is accessed through the subnet_id_ member.
+ boost::multi_index::member<Lease, uint32_t, &Lease::subnet_id_>
+ >
>
>
- > Lease4Storage; // Let the whole contraption be called Lease6Storage.
+ > Lease4Storage; // Specify the type name for this container.
/// @brief stores IPv4 leases
Lease4Storage storage4_;
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc
index 6b6cde5..9828085 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc
@@ -21,7 +21,7 @@
#include <dhcpsrv/mysql_lease_mgr.h>
#include <boost/static_assert.hpp>
-#include <mysql/mysqld_error.h>
+#include <mysqld_error.h>
#include <iostream>
#include <iomanip>
@@ -94,9 +94,6 @@ namespace {
/// colon separators.
const size_t ADDRESS6_TEXT_MAX_LEN = 39;
-/// @brief Maximum size of a hardware address.
-const size_t HWADDR_MAX_LEN = 20;
-
/// @brief MySQL True/False constants
///
/// Declare typed values so as to avoid problems of data conversion. These
@@ -289,7 +286,7 @@ public:
memset(hwaddr_buffer_, 0, sizeof(hwaddr_buffer_));
memset(client_id_buffer_, 0, sizeof(client_id_buffer_));
std::fill(&error_[0], &error_[LEASE_COLUMNS], MLM_FALSE);
-
+
// Set the column names (for error messages)
columns_[0] = "address";
columns_[1] = "hwaddr";
@@ -315,6 +312,10 @@ public:
lease_ = lease;
// Initialize prior to constructing the array of MYSQL_BIND structures.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
memset(bind_, 0, sizeof(bind_));
// Set up the structures for the various components of the lease4
@@ -327,6 +328,8 @@ public:
bind_[0].buffer_type = MYSQL_TYPE_LONG;
bind_[0].buffer = reinterpret_cast<char*>(&addr4_);
bind_[0].is_unsigned = MLM_TRUE;
+ // bind_[0].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// hwaddr: varbinary(128)
// For speed, we avoid copying the data into temporary storage and
@@ -336,6 +339,8 @@ public:
bind_[1].buffer = reinterpret_cast<char*>(&(lease_->hwaddr_[0]));
bind_[1].buffer_length = hwaddr_length_;
bind_[1].length = &hwaddr_length_;
+ // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// client_id: varbinary(128)
if (lease_->client_id_) {
@@ -345,6 +350,8 @@ public:
bind_[2].buffer = reinterpret_cast<char*>(&client_id_[0]);
bind_[2].buffer_length = client_id_length_;
bind_[2].length = &client_id_length_;
+ // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
} else {
bind_[2].buffer_type = MYSQL_TYPE_NULL;
@@ -353,15 +360,17 @@ public:
// fields doesn't matter if type is set to MYSQL_TYPE_NULL,
// but let's set them to some sane values in case earlier versions
// didn't have that assumption.
- static my_bool no_clientid = MLM_TRUE;
+ client_id_null_ = MLM_TRUE;
bind_[2].buffer = NULL;
- bind_[2].is_null = &no_clientid;
+ bind_[2].is_null = &client_id_null_;
}
// valid lifetime: unsigned int
bind_[3].buffer_type = MYSQL_TYPE_LONG;
bind_[3].buffer = reinterpret_cast<char*>(&lease_->valid_lft_);
bind_[3].is_unsigned = MLM_TRUE;
+ // bind_[3].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// expire: timestamp
// The lease structure holds the client last transmission time (cltt_)
@@ -377,12 +386,16 @@ public:
bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
bind_[4].buffer = reinterpret_cast<char*>(&expire_);
bind_[4].buffer_length = sizeof(expire_);
+ // bind_[4].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// subnet_id: unsigned int
// Can use lease_->subnet_id_ directly as it is of type uint32_t.
bind_[5].buffer_type = MYSQL_TYPE_LONG;
bind_[5].buffer = reinterpret_cast<char*>(&lease_->subnet_id_);
bind_[5].is_unsigned = MLM_TRUE;
+ // bind_[5].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// Add the error flags
setErrorIndicators(bind_, error_, LEASE_COLUMNS);
@@ -404,12 +417,18 @@ public:
std::vector<MYSQL_BIND> createBindForReceive() {
// Initialize MYSQL_BIND array.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
memset(bind_, 0, sizeof(bind_));
// address: uint32_t
bind_[0].buffer_type = MYSQL_TYPE_LONG;
bind_[0].buffer = reinterpret_cast<char*>(&addr4_);
bind_[0].is_unsigned = MLM_TRUE;
+ // bind_[0].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// hwaddr: varbinary(20)
hwaddr_length_ = sizeof(hwaddr_buffer_);
@@ -417,6 +436,8 @@ public:
bind_[1].buffer = reinterpret_cast<char*>(hwaddr_buffer_);
bind_[1].buffer_length = hwaddr_length_;
bind_[1].length = &hwaddr_length_;
+ // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// client_id: varbinary(128)
client_id_length_ = sizeof(client_id_buffer_);
@@ -424,21 +445,30 @@ public:
bind_[2].buffer = reinterpret_cast<char*>(client_id_buffer_);
bind_[2].buffer_length = client_id_length_;
bind_[2].length = &client_id_length_;
+ bind_[2].is_null = &client_id_null_;
+ // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// lease_time: unsigned int
bind_[3].buffer_type = MYSQL_TYPE_LONG;
bind_[3].buffer = reinterpret_cast<char*>(&valid_lifetime_);
bind_[3].is_unsigned = MLM_TRUE;
+ // bind_[3].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// expire: timestamp
bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
bind_[4].buffer = reinterpret_cast<char*>(&expire_);
bind_[4].buffer_length = sizeof(expire_);
+ // bind_[4].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// subnet_id: unsigned int
bind_[5].buffer_type = MYSQL_TYPE_LONG;
bind_[5].buffer = reinterpret_cast<char*>(&subnet_id_);
bind_[5].is_unsigned = MLM_TRUE;
+ // bind_[5].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// Add the error flags
setErrorIndicators(bind_, error_, LEASE_COLUMNS);
@@ -465,6 +495,11 @@ public:
time_t cltt = 0;
MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
+ if (client_id_null_==MLM_TRUE) {
+ // There's no client-id, so we pass client-id_length_ set to 0
+ client_id_length_ = 0;
+ }
+
// note that T1 and T2 are not stored
return (Lease4Ptr(new Lease4(addr4_, hwaddr_buffer_, hwaddr_length_,
client_id_buffer_, client_id_length_,
@@ -495,13 +530,15 @@ private:
std::string columns_[LEASE_COLUMNS];///< Column names
my_bool error_[LEASE_COLUMNS]; ///< Error array
std::vector<uint8_t> hwaddr_; ///< Hardware address
- uint8_t hwaddr_buffer_[HWADDR_MAX_LEN];
+ uint8_t hwaddr_buffer_[HWAddr::MAX_HWADDR_LEN];
///< Hardware address buffer
unsigned long hwaddr_length_; ///< Hardware address length
std::vector<uint8_t> client_id_; ///< Client identification
uint8_t client_id_buffer_[ClientId::MAX_CLIENT_ID_LEN];
///< Client ID buffer
unsigned long client_id_length_; ///< Client ID address length
+ my_bool client_id_null_; ///< Is Client ID null?
+
MYSQL_TIME expire_; ///< Lease expiry time
Lease4Ptr lease_; ///< Pointer to lease object
uint32_t subnet_id_; ///< Subnet identification
@@ -536,7 +573,7 @@ public:
memset(addr6_buffer_, 0, sizeof(addr6_buffer_));
memset(duid_buffer_, 0, sizeof(duid_buffer_));
std::fill(&error_[0], &error_[LEASE_COLUMNS], MLM_FALSE);
-
+
// Set the column names (for error messages)
columns_[0] = "address";
columns_[1] = "duid";
@@ -564,6 +601,10 @@ public:
// Ensure bind_ array clear for constructing the MYSQL_BIND structures
// for this lease.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
memset(bind_, 0, sizeof(bind_));
// address: varchar(39)
@@ -588,6 +629,8 @@ public:
bind_[0].buffer = const_cast<char*>(addr6_.c_str());
bind_[0].buffer_length = addr6_length_;
bind_[0].length = &addr6_length_;
+ // bind_[0].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// duid: varchar(128)
duid_ = lease_->duid_->getDuid();
@@ -597,11 +640,15 @@ public:
bind_[1].buffer = reinterpret_cast<char*>(&(duid_[0]));
bind_[1].buffer_length = duid_length_;
bind_[1].length = &duid_length_;
+ // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// valid lifetime: unsigned int
bind_[2].buffer_type = MYSQL_TYPE_LONG;
bind_[2].buffer = reinterpret_cast<char*>(&lease_->valid_lft_);
bind_[2].is_unsigned = MLM_TRUE;
+ // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// expire: timestamp
// The lease structure holds the client last transmission time (cltt_)
@@ -616,18 +663,24 @@ public:
bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
bind_[3].buffer = reinterpret_cast<char*>(&expire_);
bind_[3].buffer_length = sizeof(expire_);
+ // bind_[3].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// subnet_id: unsigned int
// Can use lease_->subnet_id_ directly as it is of type uint32_t.
bind_[4].buffer_type = MYSQL_TYPE_LONG;
bind_[4].buffer = reinterpret_cast<char*>(&lease_->subnet_id_);
bind_[4].is_unsigned = MLM_TRUE;
+ // bind_[4].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// pref_lifetime: unsigned int
// Can use lease_->preferred_lft_ directly as it is of type uint32_t.
bind_[5].buffer_type = MYSQL_TYPE_LONG;
bind_[5].buffer = reinterpret_cast<char*>(&lease_->preferred_lft_);
bind_[5].is_unsigned = MLM_TRUE;
+ // bind_[5].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// lease_type: tinyint
// Must convert to uint8_t as lease_->type_ is a LeaseType variable.
@@ -635,18 +688,24 @@ public:
bind_[6].buffer_type = MYSQL_TYPE_TINY;
bind_[6].buffer = reinterpret_cast<char*>(&lease_type_);
bind_[6].is_unsigned = MLM_TRUE;
+ // bind_[6].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// iaid: unsigned int
// Can use lease_->iaid_ directly as it is of type uint32_t.
bind_[7].buffer_type = MYSQL_TYPE_LONG;
bind_[7].buffer = reinterpret_cast<char*>(&lease_->iaid_);
bind_[7].is_unsigned = MLM_TRUE;
+ // bind_[7].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// prefix_len: unsigned tinyint
// Can use lease_->prefixlen_ directly as it is uint32_t.
bind_[8].buffer_type = MYSQL_TYPE_TINY;
bind_[8].buffer = reinterpret_cast<char*>(&lease_->prefixlen_);
bind_[8].is_unsigned = MLM_TRUE;
+ // bind_[8].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// Add the error flags
setErrorIndicators(bind_, error_, LEASE_COLUMNS);
@@ -670,6 +729,10 @@ public:
std::vector<MYSQL_BIND> createBindForReceive() {
// Initialize MYSQL_BIND array.
+ // It sets all fields, including is_null, to zero, so we need to set
+ // is_null only if it should be true. This gives up minor performance
+ // benefit while being safe approach. For improved readability, the
+ // code that explicitly sets is_null is there, but is commented out.
memset(bind_, 0, sizeof(bind_));
// address: varchar(39)
@@ -681,6 +744,8 @@ public:
bind_[0].buffer = addr6_buffer_;
bind_[0].buffer_length = addr6_length_;
bind_[0].length = &addr6_length_;
+ // bind_[0].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// client_id: varbinary(128)
duid_length_ = sizeof(duid_buffer_);
@@ -688,41 +753,57 @@ public:
bind_[1].buffer = reinterpret_cast<char*>(duid_buffer_);
bind_[1].buffer_length = duid_length_;
bind_[1].length = &duid_length_;
+ // bind_[1].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// lease_time: unsigned int
bind_[2].buffer_type = MYSQL_TYPE_LONG;
bind_[2].buffer = reinterpret_cast<char*>(&valid_lifetime_);
bind_[2].is_unsigned = MLM_TRUE;
+ // bind_[2].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// expire: timestamp
bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
bind_[3].buffer = reinterpret_cast<char*>(&expire_);
bind_[3].buffer_length = sizeof(expire_);
+ // bind_[3].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// subnet_id: unsigned int
bind_[4].buffer_type = MYSQL_TYPE_LONG;
bind_[4].buffer = reinterpret_cast<char*>(&subnet_id_);
bind_[4].is_unsigned = MLM_TRUE;
+ // bind_[4].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// pref_lifetime: unsigned int
bind_[5].buffer_type = MYSQL_TYPE_LONG;
bind_[5].buffer = reinterpret_cast<char*>(&pref_lifetime_);
bind_[5].is_unsigned = MLM_TRUE;
+ // bind_[5].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// lease_type: tinyint
bind_[6].buffer_type = MYSQL_TYPE_TINY;
bind_[6].buffer = reinterpret_cast<char*>(&lease_type_);
bind_[6].is_unsigned = MLM_TRUE;
+ // bind_[6].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// iaid: unsigned int
bind_[7].buffer_type = MYSQL_TYPE_LONG;
bind_[7].buffer = reinterpret_cast<char*>(&iaid_);
bind_[7].is_unsigned = MLM_TRUE;
+ // bind_[7].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// prefix_len: unsigned tinyint
bind_[8].buffer_type = MYSQL_TYPE_TINY;
bind_[8].buffer = reinterpret_cast<char*>(&prefixlen_);
bind_[8].is_unsigned = MLM_TRUE;
+ // bind_[8].is_null = &MLM_FALSE; // commented out for performance
+ // reasons, see memset() above
// Add the error flags
setErrorIndicators(bind_, error_, LEASE_COLUMNS);
@@ -809,7 +890,7 @@ private:
// schema.
// Note: arrays are declared fixed length for speed of creation
std::string addr6_; ///< String form of address
- char addr6_buffer_[ADDRESS6_TEXT_MAX_LEN + 1]; ///< Character
+ char addr6_buffer_[ADDRESS6_TEXT_MAX_LEN + 1]; ///< Character
///< array form of V6 address
unsigned long addr6_length_; ///< Length of the address
MYSQL_BIND bind_[LEASE_COLUMNS]; ///< Bind array
@@ -831,7 +912,7 @@ private:
/// @brief Fetch and Release MySQL Results
///
-/// When a MySQL statement is exected, to fetch the results the function
+/// When a MySQL statement is expected, to fetch the results the function
/// mysql_stmt_fetch() must be called. As well as getting data, this
/// allocates internal state. Subsequent calls to mysql_stmt_fetch can be
/// made, but when all the data is retrieved, mysql_stmt_free_result must be
@@ -870,17 +951,10 @@ private:
MYSQL_STMT* statement_; ///< Statement for which results are freed
};
-
// MySqlLeaseMgr Constructor and Destructor
-MySqlLeaseMgr::MySqlLeaseMgr(const LeaseMgr::ParameterMap& parameters)
- : LeaseMgr(parameters), mysql_(NULL) {
-
- // Allocate context for MySQL - it is destroyed in the destructor.
- mysql_ = mysql_init(NULL);
- if (mysql_ == NULL) {
- isc_throw(DbOpenError, "unable to initialize MySQL");
- }
+MySqlLeaseMgr::MySqlLeaseMgr(const LeaseMgr::ParameterMap& parameters)
+ : LeaseMgr(parameters) {
// Open the database.
openDatabase();
@@ -916,9 +990,8 @@ MySqlLeaseMgr::~MySqlLeaseMgr() {
}
}
- // Close the database
- mysql_close(mysql_);
- mysql_ = NULL;
+ // There is no need to close the database in this destructor: it is
+ // closed in the destructor of the mysql_ member variable.
}
@@ -970,7 +1043,7 @@ MySqlLeaseMgr::convertFromDatabaseTime(const MYSQL_TIME& expire,
expire_tm.tm_hour = expire.hour;
expire_tm.tm_min = expire.minute;
expire_tm.tm_sec = expire.second;
- expire_tm.tm_isdst = -1; // Let the system work out about DST
+ expire_tm.tm_isdst = -1; // Let the system work out about DST
// Convert to local time
cltt = mktime(&expire_tm) - valid_lifetime;
@@ -1022,7 +1095,7 @@ MySqlLeaseMgr::openDatabase() {
}
// Set options for the connection:
- //
+ //
// Automatic reconnection: after a period of inactivity, the client will
// disconnect from the database. This option causes it to automatically
// reconnect when another operation is about to be done.
@@ -1033,6 +1106,17 @@ MySqlLeaseMgr::openDatabase() {
mysql_error(mysql_));
}
+ // Set SQL mode options for the connection: SQL mode governs how what
+ // constitutes insertable data for a given column, and how to handle
+ // invalid data. We want to ensure we get the strictest behavior and
+ // to reject invalid data with an error.
+ const char *sql_mode = "SET SESSION sql_mode ='STRICT_ALL_TABLES'";
+ result = mysql_options(mysql_, MYSQL_INIT_COMMAND, sql_mode);
+ if (result != 0) {
+ isc_throw(DbOpenError, "unable to set SQL mode options: " <<
+ mysql_error(mysql_));
+ }
+
// Open the database.
//
// The option CLIENT_FOUND_ROWS is specified so that in an UPDATE,
@@ -1088,7 +1172,7 @@ MySqlLeaseMgr::prepareStatements() {
// Allocate space for all statements
statements_.clear();
statements_.resize(NUM_STATEMENTS, NULL);
-
+
text_statements_.clear();
text_statements_.resize(NUM_STATEMENTS, std::string(""));
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h
index 62f4435..6d8eb8c 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.h
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.h
@@ -19,13 +19,62 @@
#include <dhcpsrv/lease_mgr.h>
#include <boost/scoped_ptr.hpp>
-#include <mysql/mysql.h>
+#include <boost/utility.hpp>
+#include <mysql.h>
#include <time.h>
namespace isc {
namespace dhcp {
+/// @brief MySQL Handle Holder
+///
+/// Small RAII object for safer initialization, will close the database
+/// connection upon destruction. This means that if an exception is thrown
+/// during database initialization, resources allocated to the database are
+/// guaranteed to be freed.
+///
+/// It makes no sense to copy an object of this class. After the copy, both
+/// objects would contain pointers to the same MySql context object. The
+/// destruction of one would invalid the context in the remaining object.
+/// For this reason, the class is declared noncopyable.
+class MySqlHolder : public boost::noncopyable {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Initialize MySql and store the associated context object.
+ ///
+ /// @throw DbOpenError Unable to initialize MySql handle.
+ MySqlHolder() : mysql_(mysql_init(NULL)) {
+ if (mysql_ == NULL) {
+ isc_throw(DbOpenError, "unable to initialize MySQL");
+ }
+ }
+
+ /// @brief Destructor
+ ///
+ /// Frees up resources allocated by the initialization of MySql.
+ ~MySqlHolder() {
+ if (mysql_ != NULL) {
+ mysql_close(mysql_);
+ }
+ // The library itself shouldn't be needed anymore
+ mysql_library_end();
+ }
+
+ /// @brief Conversion Operator
+ ///
+ /// Allows the MySqlHolder object to be passed as the context argument to
+ /// mysql_xxx functions.
+ operator MYSQL*() const {
+ return (mysql_);
+ }
+
+private:
+ MYSQL* mysql_; ///< Initialization context
+};
+
// Define the current database schema values
const uint32_t CURRENT_VERSION_VERSION = 1;
@@ -192,7 +241,7 @@ public:
/// @brief Returns existing IPv6 lease for a given IPv6 address.
///
/// For a given address, we assume that there will be only one lease.
- /// The assumtion here is that there will not be site or link-local
+ /// The assumption here is that there will not be site or link-local
/// addresses used, so there is no way of having address duplication.
///
/// @param addr address of the searched lease
@@ -379,7 +428,7 @@ public:
/// @param cltt Reference to location where client last transmit time
/// is put.
static
- void convertFromDatabaseTime(const MYSQL_TIME& expire,
+ void convertFromDatabaseTime(const MYSQL_TIME& expire,
uint32_t valid_lifetime, time_t& cltt);
///@}
@@ -616,7 +665,7 @@ private:
/// declare them as "mutable".)
boost::scoped_ptr<MySqlLease4Exchange> exchange4_; ///< Exchange object
boost::scoped_ptr<MySqlLease6Exchange> exchange6_; ///< Exchange object
- MYSQL* mysql_; ///< MySQL context object
+ MySqlHolder mysql_;
std::vector<MYSQL_STMT*> statements_; ///< Prepared statements
std::vector<std::string> text_statements_; ///< Raw text of statements
};
diff --git a/src/lib/dhcpsrv/pool.cc b/src/lib/dhcpsrv/pool.cc
index 7b8a4ea..7104c61 100644
--- a/src/lib/dhcpsrv/pool.cc
+++ b/src/lib/dhcpsrv/pool.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -64,7 +64,7 @@ Pool4::Pool4(const isc::asiolink::IOAddress& prefix,
Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
const isc::asiolink::IOAddress& last)
- :Pool(first, last), type_(type), prefix_len_(0) {
+ :Pool(first, last), type_(type) {
// check if specified address boundaries are sane
if (!first.isV6() || !last.isV6()) {
@@ -95,16 +95,15 @@ Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& prefix,
uint8_t prefix_len)
:Pool(prefix, IOAddress("::")),
- type_(type), prefix_len_(prefix_len) {
+ type_(type) {
// check if the prefix is sane
if (!prefix.isV6()) {
isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
}
- // check if the prefix length is sane (we use the member variable only
- // for silencing some compilers; see #2705 and #2789).
- if (prefix_len_ == 0 || prefix_len_ > 128) {
+ // check if the prefix length is sane
+ if (prefix_len == 0 || prefix_len > 128) {
isc_throw(BadValue, "Invalid prefix length");
}
diff --git a/src/lib/dhcpsrv/pool.h b/src/lib/dhcpsrv/pool.h
index e8dc3e3..e0a6f3c 100644
--- a/src/lib/dhcpsrv/pool.h
+++ b/src/lib/dhcpsrv/pool.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -168,9 +168,6 @@ private:
/// @brief defines a pool type
Pool6Type type_;
- /// @brief prefix length
- /// used by TYPE_PD only (zeroed for other types)
- uint8_t prefix_len_;
};
/// @brief a pointer an IPv6 Pool
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index daf3f9e..50a0fee 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -33,11 +33,13 @@ Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
last_allocated_(lastAddrInPrefix(prefix, len)) {
if ((prefix.isV6() && len > 128) ||
(prefix.isV4() && len > 32)) {
- isc_throw(BadValue, "Invalid prefix length specified for subnet: " << len);
+ isc_throw(BadValue,
+ "Invalid prefix length specified for subnet: " << len);
}
}
-bool Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
+bool
+Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
IOAddress first = firstAddrInPrefix(prefix_, prefix_len_);
IOAddress last = lastAddrInPrefix(prefix_, prefix_len_);
@@ -84,7 +86,8 @@ Subnet::getOptionDescriptor(const std::string& option_space,
return (*range.first);
}
-std::string Subnet::toText() const {
+std::string
+Subnet::toText() const {
std::stringstream tmp;
tmp << prefix_.toText() << "/" << static_cast<unsigned int>(prefix_len_);
return (tmp.str());
@@ -101,12 +104,14 @@ Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
}
}
-void Subnet::addPool(const PoolPtr& pool) {
+void
+Subnet::addPool(const PoolPtr& pool) {
IOAddress first_addr = pool->getFirstAddress();
IOAddress last_addr = pool->getLastAddress();
if (!inRange(first_addr) || !inRange(last_addr)) {
- isc_throw(BadValue, "Pool (" << first_addr.toText() << "-" << last_addr.toText()
+ isc_throw(BadValue, "Pool (" << first_addr.toText() << "-"
+ << last_addr.toText()
<< " does not belong in this (" << prefix_.toText() << "/"
<< static_cast<int>(prefix_len_) << ") subnet4");
}
@@ -119,15 +124,16 @@ void Subnet::addPool(const PoolPtr& pool) {
PoolPtr Subnet::getPool(isc::asiolink::IOAddress hint) {
PoolPtr candidate;
- for (PoolCollection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+ for (PoolCollection::iterator pool = pools_.begin();
+ pool != pools_.end(); ++pool) {
- // if we won't find anything better, then let's just use the first pool
+ // If we won't find anything better, then let's just use the first pool
if (!candidate) {
candidate = *pool;
}
- // if the client provided a pool and there's a pool that hint is valid in,
- // then let's use that pool
+ // If the client provided a pool and there's a pool that hint is valid
+ // in, then let's use that pool
if ((*pool)->inRange(hint)) {
return (*pool);
}
@@ -135,29 +141,44 @@ PoolPtr Subnet::getPool(isc::asiolink::IOAddress hint) {
return (candidate);
}
+void
+Subnet::setIface(const std::string& iface_name) {
+ iface_ = iface_name;
+}
+
+std::string
+Subnet::getIface() const {
+ return (iface_);
+}
+
+
void
Subnet4::validateOption(const OptionPtr& option) const {
if (!option) {
- isc_throw(isc::BadValue, "option configured for subnet must not be NULL");
+ isc_throw(isc::BadValue,
+ "option configured for subnet must not be NULL");
} else if (option->getUniverse() != Option::V4) {
- isc_throw(isc::BadValue, "expected V4 option to be added to the subnet");
+ isc_throw(isc::BadValue,
+ "expected V4 option to be added to the subnet");
}
}
-bool Subnet::inPool(const isc::asiolink::IOAddress& addr) const {
+bool
+Subnet::inPool(const isc::asiolink::IOAddress& addr) const {
// Let's start with checking if it even belongs to that subnet.
if (!inRange(addr)) {
return (false);
}
- for (PoolCollection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+ for (PoolCollection::const_iterator pool = pools_.begin();
+ pool != pools_.end(); ++pool) {
if ((*pool)->inRange(addr)) {
return (true);
}
}
- // there's no pool that address belongs to
+ // There's no pool that address belongs to
return (false);
}
@@ -177,21 +198,13 @@ Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
void
Subnet6::validateOption(const OptionPtr& option) const {
if (!option) {
- isc_throw(isc::BadValue, "option configured for subnet must not be NULL");
+ isc_throw(isc::BadValue,
+ "option configured for subnet must not be NULL");
} else if (option->getUniverse() != Option::V6) {
- isc_throw(isc::BadValue, "expected V6 option to be added to the subnet");
+ isc_throw(isc::BadValue,
+ "expected V6 option to be added to the subnet");
}
}
-
-void Subnet6::setIface(const std::string& iface_name) {
- iface_ = iface_name;
-}
-
-std::string Subnet6::getIface() const {
- return (iface_);
-}
-
-
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index cf29450..0ac5109 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -24,6 +24,7 @@
#include <asiolink/io_address.h>
#include <dhcp/option.h>
+#include <dhcpsrv/key_from_key.h>
#include <dhcpsrv/option_space_container.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/triplet.h>
@@ -46,7 +47,7 @@ namespace dhcp {
/// @todo: Implement support for options here
-/// @brief Unique indentifier for a subnet (both v4 and v6)
+/// @brief Unique identifier for a subnet (both v4 and v6)
typedef uint32_t SubnetID;
class Subnet {
@@ -82,55 +83,6 @@ public:
/// A pointer to option descriptor.
typedef boost::shared_ptr<OptionDescriptor> OptionDescriptorPtr;
- /// @brief Extractor class to extract key with another key.
- ///
- /// This class solves the problem of accessing index key values
- /// that are stored in objects nested in other objects.
- /// Each OptionDescriptor structure contains the OptionPtr object.
- /// The value retured by one of its accessors (getType) is used
- /// as an indexing value in the multi_index_container defined below.
- /// There is no easy way to mark that value returned by Option::getType
- /// should be an index of this multi_index_container. There are standard
- /// key extractors such as 'member' or 'mem_fun' but they are not
- /// sufficient here. The former can be used to mark that member of
- /// the structure that is held in the container should be used as an
- /// indexing value. The latter can be used if the indexing value is
- /// a product of the class being held in the container. In this complex
- /// scenario when the indexing value is a product of the function that
- /// is wrapped by the structure, this new extractor template has to be
- /// defined. The template class provides a 'chain' of two extractors
- /// to access the value returned by nested object and to use it as
- /// indexing value.
- /// For some more examples of complex keys see:
- /// http://www.cs.brown.edu/~jwicks/boost/libs/multi_index/doc/index.html
- ///
- /// @tparam KeyExtractor1 extractor used to access data in
- /// OptionDescriptor::option
- /// @tparam KeyExtractor2 extractor used to access
- /// OptionDescriptor::option member.
- template<typename KeyExtractor1, typename KeyExtractor2>
- class KeyFromKey {
- public:
- typedef typename KeyExtractor1::result_type result_type;
-
- /// @brief Constructor.
- KeyFromKey()
- : key1_(KeyExtractor1()), key2_(KeyExtractor2()) { };
-
- /// @brief Extract key with another key.
- ///
- /// @param arg the key value.
- ///
- /// @tparam key value type.
- template<typename T>
- result_type operator() (T& arg) const {
- return (key1_(key2_(arg)));
- }
- private:
- KeyExtractor1 key1_; ///< key 1.
- KeyExtractor2 key2_; ///< key 2.
- };
-
/// @brief Multi index container for DHCP option descriptors.
///
/// This container comprises three indexes to access option
@@ -169,10 +121,10 @@ public:
boost::multi_index::sequenced<>,
// Start definition of index #1.
boost::multi_index::hashed_non_unique<
- // KeyFromKey is the index key extractor that allows accessing
- // option type being held by the OptionPtr through
+ // KeyFromKeyExtractor is the index key extractor that allows
+ // accessing option type being held by the OptionPtr through
// OptionDescriptor structure.
- KeyFromKey<
+ KeyFromKeyExtractor<
// Use option type as the index key. The type is held
// in OptionPtr object so we have to call Option::getType
// to retrieve this key for each element.
@@ -208,7 +160,7 @@ public:
typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex;
/// Pair of iterators to represent the range of options having the
/// same option type value. The first element in this pair represents
- /// the begining of the range, the second element represents the end.
+ /// the beginning of the range, the second element represents the end.
typedef std::pair<OptionContainerTypeIndex::const_iterator,
OptionContainerTypeIndex::const_iterator> OptionContainerTypeRange;
/// Type of the index #2 - option persistency flag.
@@ -347,7 +299,18 @@ public:
return pools_;
}
- /// @brief returns textual representation of the subnet (e.g. "2001:db8::/64")
+ /// @brief sets name of the network interface for directly attached networks
+ ///
+ /// @param iface_name name of the interface
+ void setIface(const std::string& iface_name);
+
+ /// @brief network interface name used to reach subnet (or "" for remote
+ /// subnets)
+ /// @return network interface name for directly attached subnets or ""
+ std::string getIface() const;
+
+ /// @brief returns textual representation of the subnet (e.g.
+ /// "2001:db8::/64")
///
/// @return textual representation
virtual std::string toText() const;
@@ -499,17 +462,18 @@ public:
return (preferred_);
}
- /// @brief sets name of the network interface for directly attached networks
+ /// @brief sets interface-id option (if defined)
///
- /// A subnet may be reachable directly (not via relays). In DHCPv6 it is not
- /// possible to decide that based on addresses assigned to network interfaces,
- /// as DHCPv6 operates on link-local (and site local) addresses.
- /// @param iface_name name of the interface
- void setIface(const std::string& iface_name);
+ /// @param ifaceid pointer to interface-id option
+ void setInterfaceId(const OptionPtr& ifaceid) {
+ interface_id_ = ifaceid;
+ }
- /// @brief network interface name used to reach subnet (or "" for remote subnets)
- /// @return network interface name for directly attached subnets or ""
- std::string getIface() const;
+ /// @brief returns interface-id value (if specified)
+ /// @return interface-id option (if defined)
+ OptionPtr getInterfaceId() const {
+ return interface_id_;
+ }
protected:
@@ -526,6 +490,9 @@ protected:
return (isc::asiolink::IOAddress("::"));
}
+ /// @brief specifies optional interface-id
+ OptionPtr interface_id_;
+
/// @brief collection of pools in that list
Pool6Collection pools_;
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index e19fd87..087b28d 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -15,6 +15,7 @@ AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
if USE_STATIC_LINK
AM_LDFLAGS = -static
+TEST_LIBS_LDFLAGS = -Bshareable
endif
CLEANFILES = *.gcno *.gcda
@@ -24,22 +25,39 @@ TESTS_ENVIRONMENT = \
TESTS =
if HAVE_GTEST
+# Build shared libraries for testing.
+lib_LTLIBRARIES = libco1.la libco2.la
+
+libco1_la_SOURCES = callout_library.cc
+libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco1_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+libco1_la_LDFLAGS = $(TEST_LIBS_LDFLAGS)
+
+libco2_la_SOURCES = callout_library.cc
+libco2_la_CXXFLAGS = $(AM_CXXFLAGS)
+libco2_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+libco2_la_LDFLAGS = $(TEST_LIBS_LDFLAGS)
+
+
TESTS += libdhcpsrv_unittests
libdhcpsrv_unittests_SOURCES = run_unittests.cc
libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
+libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc
if HAVE_MYSQL
libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
endif
libdhcpsrv_unittests_SOURCES += pool_unittest.cc
libdhcpsrv_unittests_SOURCES += schema_copy.h
libdhcpsrv_unittests_SOURCES += subnet_unittest.cc
+libdhcpsrv_unittests_SOURCES += test_get_callout_handle.cc test_get_callout_handle.h
libdhcpsrv_unittests_SOURCES += triplet_unittest.cc
libdhcpsrv_unittests_SOURCES += test_utils.cc test_utils.h
@@ -66,8 +84,9 @@ libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
-libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
libdhcpsrv_unittests_LDADD += $(GTEST_LDADD)
endif
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
index ddc0f62..083c20f 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -25,18 +25,24 @@
#include <dhcpsrv/tests/test_utils.h>
+#include <hooks/server_hooks.h>
+#include <hooks/callout_manager.h>
+#include <hooks/hooks_manager.h>
+
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <iostream>
#include <sstream>
+#include <algorithm>
#include <set>
#include <time.h>
using namespace std;
using namespace isc;
using namespace isc::asiolink;
+using namespace isc::hooks;
using namespace isc::dhcp;
using namespace isc::dhcp::test;
@@ -105,7 +111,7 @@ public:
// @todo: check cltt
}
- ~AllocEngine6Test() {
+ virtual ~AllocEngine6Test() {
factory_.destroy();
}
@@ -149,31 +155,38 @@ public:
///
/// @param lease lease to be checked
void checkLease4(const Lease4Ptr& lease) {
- // that is belongs to the right subnet
+ // Check that is belongs to the right subnet
EXPECT_EQ(lease->subnet_id_, subnet_->getID());
EXPECT_TRUE(subnet_->inRange(lease->addr_));
EXPECT_TRUE(subnet_->inPool(lease->addr_));
- // that it have proper parameters
+ // Check that it has proper parameters
EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
EXPECT_EQ(subnet_->getT1(), lease->t1_);
EXPECT_EQ(subnet_->getT2(), lease->t2_);
- EXPECT_TRUE(false == lease->fqdn_fwd_);
- EXPECT_TRUE(false == lease->fqdn_rev_);
- EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ if (lease->client_id_ && !clientid_) {
+ ADD_FAILURE() << "Lease4 has a client-id, while it should have none.";
+ } else
+ if (!lease->client_id_ && clientid_) {
+ ADD_FAILURE() << "Lease4 has no client-id, but it was expected to have one.";
+ } else
+ if (lease->client_id_ && clientid_) {
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ }
EXPECT_TRUE(lease->hwaddr_ == hwaddr_->hwaddr_);
// @todo: check cltt
}
- ~AllocEngine4Test() {
+ virtual ~AllocEngine4Test() {
factory_.destroy();
}
- ClientIdPtr clientid_; ///< client-identifier (value used in tests)
- HWAddrPtr hwaddr_; ///< hardware address (value used in tests)
- Subnet4Ptr subnet_; ///< subnet4 (used in tests)
- Pool4Ptr pool_; ///< pool belonging to subnet_
- LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
+ ClientIdPtr clientid_; ///< Client-identifier (value used in tests)
+ HWAddrPtr hwaddr_; ///< Hardware address (value used in tests)
+ Subnet4Ptr subnet_; ///< Subnet4 (used in tests)
+ Pool4Ptr pool_; ///< Pool belonging to subnet_
+ LeaseMgrFactory factory_; ///< Pointer to LeaseMgr factory
+ Lease4Ptr old_lease_; ///< Holds previous instance of the lease.
};
// This test checks if the Allocation Engine can be instantiated and that it
@@ -194,13 +207,15 @@ TEST_F(AllocEngine6Test, simpleAlloc6) {
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
- Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
- false);
+ Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
+ IOAddress("::"), false,
+ false, "",
+ false, CalloutHandlePtr());
- // check that we got a lease
+ // Check that we got a lease
ASSERT_TRUE(lease);
- // do all checks on the lease
+ // Do all checks on the lease
checkLease6(lease);
// Check that the lease is indeed in LeaseMgr
@@ -217,13 +232,15 @@ TEST_F(AllocEngine6Test, fakeAlloc6) {
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
- Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
- true);
+ Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
+ IOAddress("::"), false,
+ false, "", true,
+ CalloutHandlePtr());
- // check that we got a lease
+ // Check that we got a lease
ASSERT_TRUE(lease);
- // do all checks on the lease
+ // Do all checks on the lease
checkLease6(lease);
// Check that the lease is NOT in LeaseMgr
@@ -240,15 +257,16 @@ TEST_F(AllocEngine6Test, allocWithValidHint6) {
Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
IOAddress("2001:db8:1::15"),
- false);
+ false, false, "",
+ false, CalloutHandlePtr());
- // check that we got a lease
+ // Check that we got a lease
ASSERT_TRUE(lease);
- // we should get what we asked for
+ // We should get what we asked for
EXPECT_EQ(lease->addr_.toText(), "2001:db8:1::15");
- // do all checks on the lease
+ // Do all checks on the lease
checkLease6(lease);
// Check that the lease is indeed in LeaseMgr
@@ -266,29 +284,30 @@ TEST_F(AllocEngine6Test, allocWithUsedHint6) {
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
- // let's create a lease and put it in the LeaseMgr
+ // Let's create a lease and put it in the LeaseMgr
DuidPtr duid2 = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0xff)));
time_t now = time(NULL);
Lease6Ptr used(new Lease6(Lease6::LEASE_IA_NA, IOAddress("2001:db8:1::1f"),
duid2, 1, 2, 3, 4, now, subnet_->getID()));
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
- // another client comes in and request an address that is in pool, but
+ // Another client comes in and request an address that is in pool, but
// unfortunately it is used already. The same address must not be allocated
// twice.
Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
IOAddress("2001:db8:1::1f"),
- false);
- // check that we got a lease
+ false, false, "",
+ false, CalloutHandlePtr());
+ // Check that we got a lease
ASSERT_TRUE(lease);
- // allocated address must be different
+ // Allocated address must be different
EXPECT_TRUE(used->addr_.toText() != lease->addr_.toText());
- // we should NOT get what we asked for, because it is used already
+ // We should NOT get what we asked for, because it is used already
EXPECT_TRUE(lease->addr_.toText() != "2001:db8:1::1f");
- // do all checks on the lease
+ // Do all checks on the lease
checkLease6(lease);
// Check that the lease is indeed in LeaseMgr
@@ -311,14 +330,15 @@ TEST_F(AllocEngine6Test, allocBogusHint6) {
// with the normal allocation
Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
IOAddress("3000::abc"),
- false);
- // check that we got a lease
+ false, false, "",
+ false, CalloutHandlePtr());
+ // Check that we got a lease
ASSERT_TRUE(lease);
- // we should NOT get what we asked for, because it is used already
+ // We should NOT get what we asked for, because it is used already
EXPECT_TRUE(lease->addr_.toText() != "3000::abc");
- // do all checks on the lease
+ // Do all checks on the lease
checkLease6(lease);
// Check that the lease is indeed in LeaseMgr
@@ -329,6 +349,28 @@ TEST_F(AllocEngine6Test, allocBogusHint6) {
detailCompareLease(lease, from_mgr);
}
+// This test checks that NULL values are handled properly
+TEST_F(AllocEngine6Test, allocateAddress6Nulls) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ // Allocations without subnet are not allowed
+ Lease6Ptr lease = engine->allocateAddress6(Subnet6Ptr(), duid_, iaid_,
+ IOAddress("::"),
+ false, false, "", false,
+ CalloutHandlePtr());
+ ASSERT_FALSE(lease);
+
+ // Allocations without DUID are not allowed either
+ lease = engine->allocateAddress6(subnet_, DuidPtr(), iaid_,
+ IOAddress("::"),
+ false, false, "", false,
+ CalloutHandlePtr());
+ ASSERT_FALSE(lease);
+}
+
+
// This test verifies that the allocator picks addresses that belong to the
// pool
TEST_F(AllocEngine6Test, IterativeAllocator) {
@@ -346,7 +388,7 @@ TEST_F(AllocEngine6Test, IterativeAllocator) {
// in all pools in specified subnet. It also must not pick the same address twice
// unless it runs out of pool space and must start over.
TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
- NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator();
+ NakedAllocEngine::IterativeAllocator alloc;
// let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
for (int i = 2; i < 10; ++i) {
@@ -357,18 +399,17 @@ TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, IOAddress(min.str()),
IOAddress(max.str())));
- // cout << "Adding pool: " << min.str() << "-" << max.str() << endl;
subnet_->addPool(pool);
}
- int total = 17 + 8*9; // first pool (::10 - ::20) has 17 addresses in it,
- // there are 8 extra pools with 9 addresses in each.
+ int total = 17 + 8 * 9; // First pool (::10 - ::20) has 17 addresses in it,
+ // there are 8 extra pools with 9 addresses in each.
// Let's keep picked addresses here and check their uniqueness.
std::set<IOAddress> generated_addrs;
int cnt = 0;
while (++cnt) {
- IOAddress candidate = alloc->pickAddress(subnet_, duid_, IOAddress("::"));
+ IOAddress candidate = alloc.pickAddress(subnet_, duid_, IOAddress("::"));
EXPECT_TRUE(subnet_->inPool(candidate));
// One way to easily verify that the iterative allocator really works is
@@ -377,13 +418,13 @@ TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
// cout << candidate.toText() << endl;
if (generated_addrs.find(candidate) == generated_addrs.end()) {
- // we haven't had this
+ // We haven't had this.
generated_addrs.insert(candidate);
} else {
- // we have seen this address before. That should mean that we
+ // We have seen this address before. That should mean that we
// iterated over all addresses.
if (generated_addrs.size() == total) {
- // we have exactly the number of address in all pools
+ // We have exactly the number of address in all pools.
break;
}
ADD_FAILURE() << "Too many or not enough unique addresses generated.";
@@ -395,8 +436,6 @@ TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
break;
}
}
-
- delete alloc;
}
// This test checks if really small pools are working
@@ -415,15 +454,17 @@ TEST_F(AllocEngine6Test, smallPool6) {
subnet_->addPool(pool_);
cfg_mgr.addSubnet6(subnet_);
- Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
- false);
+ Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
+ IOAddress("::"),
+ false, false, "",
+ false, CalloutHandlePtr());
// Check that we got that single lease
ASSERT_TRUE(lease);
EXPECT_EQ("2001:db8:1::ad", lease->addr_.toText());
- // do all checks on the lease
+ // Do all checks on the lease
checkLease6(lease);
// Check that the lease is indeed in LeaseMgr
@@ -462,7 +503,9 @@ TEST_F(AllocEngine6Test, outOfAddresses6) {
// There is just a single address in the pool and allocated it to someone
// else, so the allocation should fail
Lease6Ptr lease2 = engine->allocateAddress6(subnet_, duid_, iaid_,
- IOAddress("::"), false);
+ IOAddress("::"),
+ false, false, "", false,
+ CalloutHandlePtr());
EXPECT_FALSE(lease2);
}
@@ -496,7 +539,8 @@ TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
// CASE 1: Asking for any address
lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
- true);
+ false, false, "",
+ true, CalloutHandlePtr());
// Check that we got that single lease
ASSERT_TRUE(lease);
EXPECT_EQ(addr.toText(), lease->addr_.toText());
@@ -505,8 +549,10 @@ TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
checkLease6(lease);
// CASE 2: Asking specifically for this address
- lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress(addr.toText()),
- true);
+ lease = engine->allocateAddress6(subnet_, duid_, iaid_,
+ IOAddress(addr.toText()),
+ false, false, "",
+ true, CalloutHandlePtr());
// Check that we got that single lease
ASSERT_TRUE(lease);
EXPECT_EQ(addr.toText(), lease->addr_.toText());
@@ -540,7 +586,9 @@ TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
// A client comes along, asking specifically for this address
lease = engine->allocateAddress6(subnet_, duid_, iaid_,
- IOAddress(addr.toText()), false);
+ IOAddress(addr.toText()),
+ false, false, "", false,
+ CalloutHandlePtr());
// Check that he got that single lease
ASSERT_TRUE(lease);
@@ -563,7 +611,13 @@ TEST_F(AllocEngine4Test, simpleAlloc4) {
ASSERT_TRUE(engine);
Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
- IOAddress("0.0.0.0"), false);
+ IOAddress("0.0.0.0"),
+ false, true,
+ "somehost.example.com.",
+ false, CalloutHandlePtr(),
+ old_lease_);
+ // The new lease has been allocated, so the old lease should not exist.
+ EXPECT_FALSE(old_lease_);
// Check that we got a lease
ASSERT_TRUE(lease);
@@ -586,7 +640,13 @@ TEST_F(AllocEngine4Test, fakeAlloc4) {
ASSERT_TRUE(engine);
Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
- IOAddress("0.0.0.0"), true);
+ IOAddress("0.0.0.0"),
+ false, true, "host.example.com.",
+ true, CalloutHandlePtr(),
+ old_lease_);
+
+ // The new lease has been allocated, so the old lease should not exist.
+ EXPECT_FALSE(old_lease_);
// Check that we got a lease
ASSERT_TRUE(lease);
@@ -609,11 +669,15 @@ TEST_F(AllocEngine4Test, allocWithValidHint4) {
Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
IOAddress("192.0.2.105"),
- false);
-
+ true, true, "host.example.com.",
+ false, CalloutHandlePtr(),
+ old_lease_);
// Check that we got a lease
ASSERT_TRUE(lease);
+ // We have allocated the new lease, so the old lease should not exist.
+ EXPECT_FALSE(old_lease_);
+
// We should get what we asked for
EXPECT_EQ(lease->addr_.toText(), "192.0.2.105");
@@ -649,7 +713,13 @@ TEST_F(AllocEngine4Test, allocWithUsedHint4) {
// twice.
Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
IOAddress("192.0.2.106"),
- false);
+ false, false, "",
+ false, CalloutHandlePtr(),
+ old_lease_);
+
+ // New lease has been allocated, so the old lease should not exist.
+ EXPECT_FALSE(old_lease_);
+
// Check that we got a lease
ASSERT_TRUE(lease);
@@ -683,10 +753,15 @@ TEST_F(AllocEngine4Test, allocBogusHint4) {
// with the normal allocation
Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
IOAddress("10.1.1.1"),
- false);
+ false, false, "",
+ false, CalloutHandlePtr(),
+ old_lease_);
// Check that we got a lease
ASSERT_TRUE(lease);
+ // We have allocated a new lease, so the old lease should not exist.
+ EXPECT_FALSE(old_lease_);
+
// We should NOT get what we asked for, because it is used already
EXPECT_TRUE(lease->addr_.toText() != "10.1.1.1");
@@ -702,6 +777,54 @@ TEST_F(AllocEngine4Test, allocBogusHint4) {
}
+// This test checks that NULL values are handled properly
+TEST_F(AllocEngine4Test, allocateAddress4Nulls) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ // Allocations without subnet are not allowed
+ Lease4Ptr lease = engine->allocateAddress4(SubnetPtr(), clientid_, hwaddr_,
+ IOAddress("0.0.0.0"),
+ false, false, "",
+ false, CalloutHandlePtr(),
+ old_lease_);
+ EXPECT_FALSE(lease);
+
+ // Allocations without HW address are not allowed
+ lease = engine->allocateAddress4(subnet_, clientid_, HWAddrPtr(),
+ IOAddress("0.0.0.0"),
+ false, false, "",
+ false, CalloutHandlePtr(),
+ old_lease_);
+ EXPECT_FALSE(lease);
+ EXPECT_FALSE(old_lease_);
+
+ // Allocations without client-id are allowed
+ clientid_ = ClientIdPtr();
+ lease = engine->allocateAddress4(subnet_, ClientIdPtr(), hwaddr_,
+ IOAddress("0.0.0.0"),
+ true, true, "myhost.example.com.",
+ false, CalloutHandlePtr(),
+ old_lease_);
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+ // New lease has been allocated, so the old lease should not exist.
+ EXPECT_FALSE(old_lease_);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+
+
// This test verifies that the allocator picks addresses that belong to the
// pool
TEST_F(AllocEngine4Test, IterativeAllocator) {
@@ -720,7 +843,7 @@ TEST_F(AllocEngine4Test, IterativeAllocator) {
// in all pools in specified subnet. It also must not pick the same address twice
// unless it runs out of pool space and must start over.
TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
- NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator();
+ NakedAllocEngine::IterativeAllocator alloc;
// Let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
for (int i = 2; i < 10; ++i) {
@@ -742,7 +865,7 @@ TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
std::set<IOAddress> generated_addrs;
int cnt = 0;
while (++cnt) {
- IOAddress candidate = alloc->pickAddress(subnet_, clientid_, IOAddress("0.0.0.0"));
+ IOAddress candidate = alloc.pickAddress(subnet_, clientid_, IOAddress("0.0.0.0"));
EXPECT_TRUE(subnet_->inPool(candidate));
// One way to easily verify that the iterative allocator really works is
@@ -754,10 +877,10 @@ TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
// We haven't had this
generated_addrs.insert(candidate);
} else {
- // we have seen this address before. That should mean that we
+ // We have seen this address before. That should mean that we
// iterated over all addresses.
if (generated_addrs.size() == total) {
- // we have exactly the number of address in all pools
+ // We have exactly the number of address in all pools
break;
}
ADD_FAILURE() << "Too many or not enough unique addresses generated.";
@@ -769,8 +892,6 @@ TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
break;
}
}
-
- delete alloc;
}
@@ -790,12 +911,18 @@ TEST_F(AllocEngine4Test, smallPool4) {
subnet_->addPool(pool_);
cfg_mgr.addSubnet4(subnet_);
- Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
- false);
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"),
+ true, true, "host.example.com.",
+ false, CalloutHandlePtr(),
+ old_lease_);
// Check that we got that single lease
ASSERT_TRUE(lease);
+ // We have allocated new lease, so the old lease should not exist.
+ EXPECT_FALSE(old_lease_);
+
EXPECT_EQ("192.0.2.17", lease->addr_.toText());
// Do all checks on the lease
@@ -830,8 +957,9 @@ TEST_F(AllocEngine4Test, outOfAddresses4) {
uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
time_t now = time(NULL);
- Lease4Ptr lease(new Lease4(addr, hwaddr2, sizeof(hwaddr2), clientid2, sizeof(clientid2),
- 501, 502, 503, now, subnet_->getID()));
+ Lease4Ptr lease(new Lease4(addr, hwaddr2, sizeof(hwaddr2), clientid2,
+ sizeof(clientid2), 501, 502, 503, now,
+ subnet_->getID()));
lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
@@ -839,8 +967,12 @@ TEST_F(AllocEngine4Test, outOfAddresses4) {
// else, so the allocation should fail
Lease4Ptr lease2 = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
- IOAddress("0.0.0.0"), false);
+ IOAddress("0.0.0.0"),
+ false, false, "",
+ false, CalloutHandlePtr(),
+ old_lease_);
EXPECT_FALSE(lease2);
+ EXPECT_FALSE(old_lease_);
}
// This test checks if an expired lease can be reused in DISCOVER (fake allocation)
@@ -863,29 +995,51 @@ TEST_F(AllocEngine4Test, discoverReuseExpiredLease4) {
uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
time_t now = time(NULL) - 500; // Allocated 500 seconds ago
- Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2, sizeof(hwaddr2),
+ Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2),
+ hwaddr2, sizeof(hwaddr2),
495, 100, 200, now, subnet_->getID()));
+ // Copy the lease, so as it can be compared with the old lease returned
+ // by the allocation engine.
+ Lease4 original_lease(*lease);
+
// Lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
// is expired already
ASSERT_TRUE(lease->expired());
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
// CASE 1: Asking for any address
- lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
- true);
+ lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"),
+ false, false, "",
+ true, CalloutHandlePtr(),
+ old_lease_);
// Check that we got that single lease
ASSERT_TRUE(lease);
EXPECT_EQ(addr.toText(), lease->addr_.toText());
+ // We are reusing expired lease, the old (expired) instance should be
+ // returned. The returned instance should be the same as the original
+ // lease.
+ ASSERT_TRUE(old_lease_);
+ EXPECT_TRUE(original_lease == *old_lease_);
+
// Do all checks on the lease (if subnet-id, preferred/valid times are ok etc.)
checkLease4(lease);
// CASE 2: Asking specifically for this address
- lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress(addr.toText()),
- true);
+ lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress(addr.toText()),
+ false, false, "",
+ true, CalloutHandlePtr(),
+ old_lease_);
// Check that we got that single lease
ASSERT_TRUE(lease);
EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+ // We are updating expired lease. The copy of the old lease should be
+ // returned and it should be equal to the original lease.
+ ASSERT_TRUE(old_lease_);
+ EXPECT_TRUE(*old_lease_ == original_lease);
}
// This test checks if an expired lease can be reused in REQUEST (actual allocation)
@@ -900,16 +1054,24 @@ TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
time_t now = time(NULL) - 500; // Allocated 500 seconds ago
- Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2, sizeof(hwaddr2),
- 495, 100, 200, now, subnet_->getID()));
- // lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
+ Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2,
+ sizeof(hwaddr2), 495, 100, 200, now,
+ subnet_->getID()));
+ // Make a copy of the lease, so as we can comapre that with the old lease
+ // instance returned by the allocation engine.
+ Lease4 original_lease(*lease);
+
+ // Lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
// is expired already
ASSERT_TRUE(lease->expired());
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
// A client comes along, asking specifically for this address
lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
- IOAddress(addr.toText()), false);
+ IOAddress(addr.toText()),
+ false, true, "host.example.com.",
+ false, CalloutHandlePtr(),
+ old_lease_);
// Check that he got that single lease
ASSERT_TRUE(lease);
@@ -921,12 +1083,21 @@ TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
// Now check that the lease in LeaseMgr has the same parameters
detailCompareLease(lease, from_mgr);
+
+ // The allocation engine should return a copy of the old lease. This
+ // lease should be equal to the original lease.
+ ASSERT_TRUE(old_lease_);
+ EXPECT_TRUE(*old_lease_ == original_lease);
}
+/// @todo write renewLease6
+
// This test checks if a lease is really renewed when renewLease4 method is
// called
TEST_F(AllocEngine4Test, renewLease4) {
boost::scoped_ptr<AllocEngine> engine;
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -947,7 +1118,9 @@ TEST_F(AllocEngine4Test, renewLease4) {
// Lease was assigned 45 seconds ago and is valid for 100 seconds. Let's
// renew it.
ASSERT_FALSE(lease->expired());
- lease = engine->renewLease4(subnet_, clientid_, hwaddr_, lease, false);
+ lease = engine->renewLease4(subnet_, clientid_, hwaddr_, true,
+ true, "host.example.com.", lease,
+ callout_handle, false);
// Check that he got that single lease
ASSERT_TRUE(lease);
EXPECT_EQ(addr.toText(), lease->addr_.toText());
@@ -963,4 +1136,445 @@ TEST_F(AllocEngine4Test, renewLease4) {
detailCompareLease(lease, from_mgr);
}
-}; // end of anonymous namespace
+/// @brief helper class used in Hooks testing in AllocEngine6
+///
+/// It features a couple of callout functions and buffers to store
+/// the data that is accessible via callouts.
+class HookAllocEngine6Test : public AllocEngine6Test {
+public:
+ HookAllocEngine6Test() {
+ resetCalloutBuffers();
+ }
+
+ virtual ~HookAllocEngine6Test() {
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+ "lease6_select");
+ }
+
+ /// @brief clears out buffers, so callouts can store received arguments
+ void resetCalloutBuffers() {
+ callback_name_ = string("");
+ callback_subnet6_.reset();
+ callback_fake_allocation_ = false;
+ callback_lease6_.reset();
+ callback_argument_names_.clear();
+ callback_addr_original_ = IOAddress("::");
+ callback_addr_updated_ = IOAddress("::");
+ }
+
+ /// callback that stores received callout name and received values
+ static int
+ lease6_select_callout(CalloutHandle& callout_handle) {
+
+ callback_name_ = string("lease6_select");
+
+ callout_handle.getArgument("subnet6", callback_subnet6_);
+ callout_handle.getArgument("fake_allocation", callback_fake_allocation_);
+ callout_handle.getArgument("lease6", callback_lease6_);
+
+ callback_addr_original_ = callback_lease6_->addr_;
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ /// callback that overrides the lease with different values
+ static int
+ lease6_select_different_callout(CalloutHandle& callout_handle) {
+
+ // Let's call the basic callout, so it can record all parameters
+ lease6_select_callout(callout_handle);
+
+ // Now we need to tweak the least a bit
+ Lease6Ptr lease;
+ callout_handle.getArgument("lease6", lease);
+ callback_addr_updated_ = addr_override_;
+ lease->addr_ = callback_addr_updated_;
+ lease->t1_ = t1_override_;
+ lease->t2_ = t2_override_;
+ lease->preferred_lft_ = pref_override_;
+ lease->valid_lft_ = valid_override_;
+
+ return (0);
+ }
+
+ // Values to be used in callout to override lease6 content
+ static const IOAddress addr_override_;
+ static const uint32_t t1_override_;
+ static const uint32_t t2_override_;
+ static const uint32_t pref_override_;
+ static const uint32_t valid_override_;
+
+ // Callback will store original and overridden values here
+ static IOAddress callback_addr_original_;
+ static IOAddress callback_addr_updated_;
+
+ // Buffers (callback will store received values here)
+ static string callback_name_;
+ static Subnet6Ptr callback_subnet6_;
+ static Lease6Ptr callback_lease6_;
+ static bool callback_fake_allocation_;
+ static vector<string> callback_argument_names_;
+};
+
+// For some reason intialization within a class makes the linker confused.
+// linker complains about undefined references if they are defined within
+// the class declaration.
+const IOAddress HookAllocEngine6Test::addr_override_("2001:db8::abcd");
+const uint32_t HookAllocEngine6Test::t1_override_ = 6000;
+const uint32_t HookAllocEngine6Test::t2_override_ = 7000;
+const uint32_t HookAllocEngine6Test::pref_override_ = 8000;
+const uint32_t HookAllocEngine6Test::valid_override_ = 9000;
+
+IOAddress HookAllocEngine6Test::callback_addr_original_("::");
+IOAddress HookAllocEngine6Test::callback_addr_updated_("::");
+
+string HookAllocEngine6Test::callback_name_;
+Subnet6Ptr HookAllocEngine6Test::callback_subnet6_;
+Lease6Ptr HookAllocEngine6Test::callback_lease6_;
+bool HookAllocEngine6Test::callback_fake_allocation_;
+vector<string> HookAllocEngine6Test::callback_argument_names_;
+
+// This test checks if the lease6_select callout is executed and expected
+// parameters as passed.
+TEST_F(HookAllocEngine6Test, lease6_select) {
+
+ // Note: The following order is working as expected:
+ // 1. create AllocEngine (that register hook points)
+ // 2. call loadLibraries()
+ //
+ // This order, however, causes segfault in HooksManager
+ // 1. call loadLibraries()
+ // 2. create AllocEngine (that register hook points)
+
+ // Create allocation engine (hook names are registered in its ctor)
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ // Initialize Hooks Manager
+ vector<string> libraries; // no libraries at this time
+ HooksManager::loadLibraries(libraries);
+
+ // Install pkt6_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_select", lease6_select_callout));
+
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
+ false, false, "",
+ false, callout_handle);
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease6(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Check that callouts were indeed called
+ EXPECT_EQ("lease6_select", callback_name_);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ ASSERT_TRUE(callback_lease6_);
+ detailCompareLease(callback_lease6_, from_mgr);
+
+ ASSERT_TRUE(callback_subnet6_);
+ EXPECT_EQ(subnet_->toText(), callback_subnet6_->toText());
+
+ EXPECT_FALSE(callback_fake_allocation_);
+
+ // Check if all expected parameters are reported. It's a bit tricky, because
+ // order may be different. If the test starts failing, because someone tweaked
+ // hooks engine, we'll have to implement proper vector matching (ignoring order)
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("fake_allocation");
+ expected_argument_names.push_back("lease6");
+ expected_argument_names.push_back("subnet6");
+
+ sort(callback_argument_names_.begin(), callback_argument_names_.end());
+ sort(expected_argument_names.begin(), expected_argument_names.end());
+
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+}
+
+// This test checks if lease6_select callout is able to override the values
+// in a lease6.
+TEST_F(HookAllocEngine6Test, change_lease6_select) {
+
+ // Make sure that the overridden values are different than the ones from
+ // subnet originally used to create the lease
+ ASSERT_NE(t1_override_, subnet_->getT1());
+ ASSERT_NE(t2_override_, subnet_->getT2());
+ ASSERT_NE(pref_override_, subnet_->getPreferred());
+ ASSERT_NE(valid_override_, subnet_->getValid());
+ ASSERT_FALSE(subnet_->inRange(addr_override_));
+
+ // Create allocation engine (hook names are registered in its ctor)
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ // Initialize Hooks Manager
+ vector<string> libraries; // no libraries at this time
+ HooksManager::loadLibraries(libraries);
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease6_select", lease6_select_different_callout));
+
+ // Normally, dhcpv6_srv would passed the handle when calling allocateAddress6,
+ // but in tests we need to create it on our own.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ // Call allocateAddress6. Callouts should be triggered here.
+ Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
+ false, false, "",
+ false, callout_handle);
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // See if the values overridden by callout are there
+ EXPECT_TRUE(lease->addr_.equals(addr_override_));
+ EXPECT_EQ(t1_override_, lease->t1_);
+ EXPECT_EQ(t2_override_, lease->t2_);
+ EXPECT_EQ(pref_override_, lease->preferred_lft_);
+ EXPECT_EQ(valid_override_, lease->valid_lft_);
+
+ // Now check if the lease is in the database
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Check if values in the database are overridden
+ EXPECT_TRUE(from_mgr->addr_.equals(addr_override_));
+ EXPECT_EQ(t1_override_, from_mgr->t1_);
+ EXPECT_EQ(t2_override_, from_mgr->t2_);
+ EXPECT_EQ(pref_override_, from_mgr->preferred_lft_);
+ EXPECT_EQ(valid_override_, from_mgr->valid_lft_);
+}
+
+
+/// @brief helper class used in Hooks testing in AllocEngine4
+///
+/// It features a couple of callout functions and buffers to store
+/// the data that is accessible via callouts.
+///
+/// Note: lease4_renew callout is tested from DHCPv4 server.
+/// See HooksDhcpv4SrvTest.basic_lease4_renew in
+/// src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+class HookAllocEngine4Test : public AllocEngine4Test {
+public:
+ HookAllocEngine4Test() {
+ resetCalloutBuffers();
+ }
+
+ virtual ~HookAllocEngine4Test() {
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+ "lease4_select");
+ }
+
+ /// @brief clears out buffers, so callouts can store received arguments
+ void resetCalloutBuffers() {
+ callback_name_ = string("");
+ callback_subnet4_.reset();
+ callback_fake_allocation_ = false;
+ callback_lease4_.reset();
+ callback_argument_names_.clear();
+ callback_addr_original_ = IOAddress("::");
+ callback_addr_updated_ = IOAddress("::");
+ }
+
+ /// callback that stores received callout name and received values
+ static int
+ lease4_select_callout(CalloutHandle& callout_handle) {
+
+ callback_name_ = string("lease4_select");
+
+ callout_handle.getArgument("subnet4", callback_subnet4_);
+ callout_handle.getArgument("fake_allocation", callback_fake_allocation_);
+ callout_handle.getArgument("lease4", callback_lease4_);
+
+ callback_addr_original_ = callback_lease4_->addr_;
+
+ callback_argument_names_ = callout_handle.getArgumentNames();
+ return (0);
+ }
+
+ /// callback that overrides the lease with different values
+ static int
+ lease4_select_different_callout(CalloutHandle& callout_handle) {
+
+ // Let's call the basic callout, so it can record all parameters
+ lease4_select_callout(callout_handle);
+
+ // Now we need to tweak the least a bit
+ Lease4Ptr lease;
+ callout_handle.getArgument("lease4", lease);
+ callback_addr_updated_ = addr_override_;
+ lease->addr_ = callback_addr_updated_;
+ lease->t1_ = t1_override_;
+ lease->t2_ = t2_override_;
+ lease->valid_lft_ = valid_override_;
+
+ return (0);
+ }
+
+ // Values to be used in callout to override lease4 content
+ static const IOAddress addr_override_;
+ static const uint32_t t1_override_;
+ static const uint32_t t2_override_;
+ static const uint32_t valid_override_;
+
+ // Callback will store original and overridden values here
+ static IOAddress callback_addr_original_;
+ static IOAddress callback_addr_updated_;
+
+ // Buffers (callback will store received values here)
+ static string callback_name_;
+ static Subnet4Ptr callback_subnet4_;
+ static Lease4Ptr callback_lease4_;
+ static bool callback_fake_allocation_;
+ static vector<string> callback_argument_names_;
+};
+
+// For some reason intialization within a class makes the linker confused.
+// linker complains about undefined references if they are defined within
+// the class declaration.
+const IOAddress HookAllocEngine4Test::addr_override_("192.0.3.1");
+const uint32_t HookAllocEngine4Test::t1_override_ = 4000;
+const uint32_t HookAllocEngine4Test::t2_override_ = 7000;
+const uint32_t HookAllocEngine4Test::valid_override_ = 9000;
+
+IOAddress HookAllocEngine4Test::callback_addr_original_("::");
+IOAddress HookAllocEngine4Test::callback_addr_updated_("::");
+
+string HookAllocEngine4Test::callback_name_;
+Subnet4Ptr HookAllocEngine4Test::callback_subnet4_;
+Lease4Ptr HookAllocEngine4Test::callback_lease4_;
+bool HookAllocEngine4Test::callback_fake_allocation_;
+vector<string> HookAllocEngine4Test::callback_argument_names_;
+
+// This test checks if the lease4_select callout is executed and expected
+// parameters as passed.
+TEST_F(HookAllocEngine4Test, lease4_select) {
+
+ // Note: The following order is working as expected:
+ // 1. create AllocEngine (that register hook points)
+ // 2. call loadLibraries()
+ //
+ // This order, however, causes segfault in HooksManager
+ // 1. call loadLibraries()
+ // 2. create AllocEngine (that register hook points)
+
+ // Create allocation engine (hook names are registered in its ctor)
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ // Initialize Hooks Manager
+ vector<string> libraries; // no libraries at this time
+ HooksManager::loadLibraries(libraries);
+
+ // Install pkt4_receive_callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_select", lease4_select_callout));
+
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"),
+ false, false, "",
+ false, callout_handle,
+ old_lease_);
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Check that callouts were indeed called
+ EXPECT_EQ("lease4_select", callback_name_);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ ASSERT_TRUE(callback_lease4_);
+ detailCompareLease(callback_lease4_, from_mgr);
+
+ ASSERT_TRUE(callback_subnet4_);
+ EXPECT_EQ(subnet_->toText(), callback_subnet4_->toText());
+
+ EXPECT_EQ(callback_fake_allocation_, false);
+
+ // Check if all expected parameters are reported. It's a bit tricky, because
+ // order may be different. If the test starts failing, because someone tweaked
+ // hooks engine, we'll have to implement proper vector matching (ignoring order)
+ vector<string> expected_argument_names;
+ expected_argument_names.push_back("fake_allocation");
+ expected_argument_names.push_back("lease4");
+ expected_argument_names.push_back("subnet4");
+ EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+}
+
+// This test checks if lease4_select callout is able to override the values
+// in a lease4.
+TEST_F(HookAllocEngine4Test, change_lease4_select) {
+
+ // Make sure that the overridden values are different than the ones from
+ // subnet originally used to create the lease
+ ASSERT_NE(t1_override_, subnet_->getT1());
+ ASSERT_NE(t2_override_, subnet_->getT2());
+ ASSERT_NE(valid_override_, subnet_->getValid());
+ ASSERT_FALSE(subnet_->inRange(addr_override_));
+
+ // Create allocation engine (hook names are registered in its ctor)
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ // Initialize Hooks Manager
+ vector<string> libraries; // no libraries at this time
+ HooksManager::loadLibraries(libraries);
+
+ // Install a callout
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "lease4_select", lease4_select_different_callout));
+
+ // Normally, dhcpv4_srv would passed the handle when calling allocateAddress4,
+ // but in tests we need to create it on our own.
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ // Call allocateAddress4. Callouts should be triggered here.
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"),
+ false, false, "",
+ false, callout_handle,
+ old_lease_);
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // See if the values overridden by callout are there
+ EXPECT_TRUE(lease->addr_.equals(addr_override_));
+ EXPECT_EQ(t1_override_, lease->t1_);
+ EXPECT_EQ(t2_override_, lease->t2_);
+ EXPECT_EQ(valid_override_, lease->valid_lft_);
+
+ // Now check if the lease is in the database
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Check if values in the database are overridden
+ EXPECT_TRUE(from_mgr->addr_.equals(addr_override_));
+ EXPECT_EQ(t1_override_, from_mgr->t1_);
+ EXPECT_EQ(t2_override_, from_mgr->t2_);
+ EXPECT_EQ(valid_override_, from_mgr->valid_lft_);
+}
+
+
+
+}; // End of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/callout_handle_store_unittest.cc b/src/lib/dhcpsrv/tests/callout_handle_store_unittest.cc
new file mode 100644
index 0000000..71157c3
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/callout_handle_store_unittest.cc
@@ -0,0 +1,122 @@
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt4.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include "test_get_callout_handle.h"
+
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::hooks;
+
+namespace {
+
+TEST(CalloutHandleStoreTest, StoreRetrieve) {
+
+ // Create two DHCP4 packets during tests. The constructor arguments are
+ // arbitrary.
+ Pkt4Ptr pktptr_1(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr pktptr_2(new Pkt4(DHCPDISCOVER, 5678));
+
+ // Check that the pointers point to objects that are different, and that
+ // the pointers are the only pointers pointing to the packets.
+ ASSERT_TRUE(pktptr_1);
+ ASSERT_TRUE(pktptr_2);
+
+ ASSERT_TRUE(pktptr_1 != pktptr_2);
+ EXPECT_EQ(1, pktptr_1.use_count());
+ EXPECT_EQ(1, pktptr_2.use_count());
+
+ // Get the CalloutHandle for the first packet.
+ CalloutHandlePtr chptr_1 = getCalloutHandle(pktptr_1);
+ ASSERT_TRUE(chptr_1);
+
+ // Reference counts on both the callout handle and the packet should have
+ // been incremented because of the stored data. The reference count on the
+ // other Pkt4 object should not have changed.
+ EXPECT_EQ(2, chptr_1.use_count());
+ EXPECT_EQ(2, pktptr_1.use_count());
+ EXPECT_EQ(1, pktptr_2.use_count());
+
+ // Try getting another pointer for the same packet. Use a different
+ // pointer object to check that the function returns a handle based on the
+ // pointed-to data and not the pointer. (Clear the temporary pointer after
+ // use to avoid complicating reference counts.)
+ Pkt4Ptr pktptr_temp = pktptr_1;
+ CalloutHandlePtr chptr_2 = getCalloutHandle(pktptr_temp);
+ pktptr_temp.reset();
+
+ ASSERT_TRUE(chptr_2);
+ EXPECT_TRUE(chptr_1 == chptr_2);
+
+ // Reference count is now 3 on the callout handle - two for pointers here,
+ // one for the static pointer in the function. The count is 2 for the]
+ // object pointed to by pktptr_1 - one for that pointer and one for the
+ // pointer in the function.
+ EXPECT_EQ(3, chptr_1.use_count());
+ EXPECT_EQ(3, chptr_2.use_count());
+ EXPECT_EQ(2, pktptr_1.use_count());
+ EXPECT_EQ(1, pktptr_2.use_count());
+
+ // Now ask for a CalloutHandle for a different object. This should return
+ // a different CalloutHandle.
+ chptr_2 = getCalloutHandle(pktptr_2);
+ EXPECT_FALSE(chptr_1 == chptr_2);
+
+ // Check reference counts. The getCalloutHandle function should be storing
+ // pointers to the objects poiunted to by chptr_2 and pktptr_2.
+ EXPECT_EQ(1, chptr_1.use_count());
+ EXPECT_EQ(1, pktptr_1.use_count());
+ EXPECT_EQ(2, chptr_2.use_count());
+ EXPECT_EQ(2, pktptr_2.use_count());
+
+ // Now try clearing the stored pointers.
+ Pkt4Ptr pktptr_empty;
+ ASSERT_FALSE(pktptr_empty);
+
+ CalloutHandlePtr chptr_empty = getCalloutHandle(pktptr_empty);
+ EXPECT_FALSE(chptr_empty);
+
+ // Reference counts should be back to 1 for the CalloutHandles and the
+ // Packet pointers.
+ EXPECT_EQ(1, chptr_1.use_count());
+ EXPECT_EQ(1, pktptr_1.use_count());
+ EXPECT_EQ(1, chptr_2.use_count());
+ EXPECT_EQ(1, pktptr_2.use_count());
+}
+
+// The followings is a trival test to check that if the template function
+// is referred to in a separate compilation unit, only one copy of the static
+// objects stored in it are returned. (For a change, we'll use a Pkt6 as the
+// packet object.)
+
+TEST(CalloutHandleStoreTest, SeparateCompilationUnit) {
+
+ // Access the template function here.
+ Pkt6Ptr pktptr_1(new Pkt6(DHCPV6_ADVERTISE, 4321));
+ CalloutHandlePtr chptr_1 = getCalloutHandle(pktptr_1);
+ ASSERT_TRUE(chptr_1);
+
+ // Access it from within another compilation unit.
+ CalloutHandlePtr chptr_2 = isc::dhcp::test::testGetCalloutHandle(pktptr_1);
+ EXPECT_TRUE(chptr_1 == chptr_2);
+}
+
+} // Anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/callout_library.cc b/src/lib/dhcpsrv/tests/callout_library.cc
new file mode 100644
index 0000000..09da3cd
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/callout_library.cc
@@ -0,0 +1,31 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Callout Library
+///
+/// This is the source of a test library for the basic DHCP parser
+/// tests. It just has to load - nothing else.
+
+#include <hooks/hooks.h>
+
+extern "C" {
+
+// Framework functions
+int
+version() {
+ return (BIND10_HOOKS_VERSION);
+}
+
+};
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index 9b3d61b..38d2f0a 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -15,7 +15,9 @@
#include <config.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
#include <exceptions/exceptions.h>
+#include <dhcp/dhcp6.h>
#include <gtest/gtest.h>
@@ -36,12 +38,144 @@ using boost::scoped_ptr;
namespace {
+// This test verifies that BooleanStorage functions properly.
+TEST(ValueStorageTest, BooleanTesting) {
+ BooleanStorage testStore;
+
+ // Verify that we can add and retrieve parameters.
+ testStore.setParam("firstBool", false);
+ testStore.setParam("secondBool", true);
+
+ EXPECT_FALSE(testStore.getParam("firstBool"));
+ EXPECT_TRUE(testStore.getParam("secondBool"));
+
+ // Verify that we can update parameters.
+ testStore.setParam("firstBool", true);
+ testStore.setParam("secondBool", false);
+
+ EXPECT_TRUE(testStore.getParam("firstBool"));
+ EXPECT_FALSE(testStore.getParam("secondBool"));
+
+ // Verify that we can delete a parameter and it will no longer be found.
+ testStore.delParam("firstBool");
+ EXPECT_THROW(testStore.getParam("firstBool"), isc::dhcp::DhcpConfigError);
+
+ // Verify that the delete was safe and the store still operates.
+ EXPECT_FALSE(testStore.getParam("secondBool"));
+
+ // Verify that looking for a parameter that never existed throws.
+ ASSERT_THROW(testStore.getParam("bogusBool"), isc::dhcp::DhcpConfigError);
+
+ // Verify that attempting to delete a parameter that never existed does not throw.
+ EXPECT_NO_THROW(testStore.delParam("bogusBool"));
+
+ // Verify that we can empty the list.
+ testStore.clear();
+ EXPECT_THROW(testStore.getParam("secondBool"), isc::dhcp::DhcpConfigError);
+
+}
+
+// This test verifies that Uint32Storage functions properly.
+TEST(ValueStorageTest, Uint32Testing) {
+ Uint32Storage testStore;
+
+ uint32_t intOne = 77;
+ uint32_t intTwo = 33;
+
+ // Verify that we can add and retrieve parameters.
+ testStore.setParam("firstInt", intOne);
+ testStore.setParam("secondInt", intTwo);
+
+ EXPECT_EQ(testStore.getParam("firstInt"), intOne);
+ EXPECT_EQ(testStore.getParam("secondInt"), intTwo);
+
+ // Verify that we can update parameters.
+ testStore.setParam("firstInt", --intOne);
+ testStore.setParam("secondInt", ++intTwo);
+
+ EXPECT_EQ(testStore.getParam("firstInt"), intOne);
+ EXPECT_EQ(testStore.getParam("secondInt"), intTwo);
+
+ // Verify that we can delete a parameter and it will no longer be found.
+ testStore.delParam("firstInt");
+ EXPECT_THROW(testStore.getParam("firstInt"), isc::dhcp::DhcpConfigError);
+
+ // Verify that the delete was safe and the store still operates.
+ EXPECT_EQ(testStore.getParam("secondInt"), intTwo);
+
+ // Verify that looking for a parameter that never existed throws.
+ ASSERT_THROW(testStore.getParam("bogusInt"), isc::dhcp::DhcpConfigError);
+
+ // Verify that attempting to delete a parameter that never existed does not throw.
+ EXPECT_NO_THROW(testStore.delParam("bogusInt"));
+
+ // Verify that we can empty the list.
+ testStore.clear();
+ EXPECT_THROW(testStore.getParam("secondInt"), isc::dhcp::DhcpConfigError);
+}
+
+// This test verifies that StringStorage functions properly.
+TEST(ValueStorageTest, StringTesting) {
+ StringStorage testStore;
+
+ std::string stringOne = "seventy-seven";
+ std::string stringTwo = "thirty-three";
+
+ // Verify that we can add and retrieve parameters.
+ testStore.setParam("firstString", stringOne);
+ testStore.setParam("secondString", stringTwo);
+
+ EXPECT_EQ(testStore.getParam("firstString"), stringOne);
+ EXPECT_EQ(testStore.getParam("secondString"), stringTwo);
+
+ // Verify that we can update parameters.
+ stringOne.append("-boo");
+ stringTwo.append("-boo");
+
+ testStore.setParam("firstString", stringOne);
+ testStore.setParam("secondString", stringTwo);
+
+ EXPECT_EQ(testStore.getParam("firstString"), stringOne);
+ EXPECT_EQ(testStore.getParam("secondString"), stringTwo);
+
+ // Verify that we can delete a parameter and it will no longer be found.
+ testStore.delParam("firstString");
+ EXPECT_THROW(testStore.getParam("firstString"), isc::dhcp::DhcpConfigError);
+
+ // Verify that the delete was safe and the store still operates.
+ EXPECT_EQ(testStore.getParam("secondString"), stringTwo);
+
+ // Verify that looking for a parameter that never existed throws.
+ ASSERT_THROW(testStore.getParam("bogusString"), isc::dhcp::DhcpConfigError);
+
+ // Verify that attempting to delete a parameter that never existed does not throw.
+ EXPECT_NO_THROW(testStore.delParam("bogusString"));
+
+ // Verify that we can empty the list.
+ testStore.clear();
+ EXPECT_THROW(testStore.getParam("secondString"), isc::dhcp::DhcpConfigError);
+}
+
+
+
class CfgMgrTest : public ::testing::Test {
public:
CfgMgrTest() {
// make sure we start with a clean configuration
CfgMgr::instance().deleteSubnets4();
CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().deleteOptionDefs();
+ CfgMgr::instance().deleteActiveIfaces();
+ }
+
+ /// @brief generates interface-id option based on provided text
+ ///
+ /// @param text content of the option to be created
+ ///
+ /// @return pointer to the option object created
+ OptionPtr generateInterfaceId(const string& text) {
+ OptionBuffer buffer(text.begin(), text.end());
+ return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, buffer));
}
~CfgMgrTest() {
@@ -285,6 +419,95 @@ TEST_F(CfgMgrTest, subnet6) {
EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123")));
}
+// This test verifies if the configuration manager is able to hold, select
+// and return valid subnets, based on interface names.
+TEST_F(CfgMgrTest, subnet6Interface) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+ subnet1->setIface("foo");
+ subnet2->setIface("bar");
+ subnet3->setIface("foobar");
+
+ // There shouldn't be any subnet configured at this stage
+ EXPECT_FALSE(cfg_mgr.getSubnet6("foo"));
+
+ cfg_mgr.addSubnet6(subnet1);
+
+ // Now we have only one subnet, any request will be served from it
+ EXPECT_EQ(subnet1, cfg_mgr.getSubnet6("foo"));
+
+ // Check that the interface name is checked even when there is
+ // only one subnet defined.
+ EXPECT_FALSE(cfg_mgr.getSubnet6("bar"));
+
+ // If we have only a single subnet and the request came from a local
+ // address, let's use that subnet
+ EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef")));
+
+ cfg_mgr.addSubnet6(subnet2);
+ cfg_mgr.addSubnet6(subnet3);
+
+ EXPECT_EQ(subnet3, cfg_mgr.getSubnet6("foobar"));
+ EXPECT_EQ(subnet2, cfg_mgr.getSubnet6("bar"));
+ EXPECT_FALSE(cfg_mgr.getSubnet6("xyzzy")); // no such interface
+
+ // Check that deletion of the subnets works.
+ cfg_mgr.deleteSubnets6();
+ EXPECT_FALSE(cfg_mgr.getSubnet6("foo"));
+ EXPECT_FALSE(cfg_mgr.getSubnet6("bar"));
+ EXPECT_FALSE(cfg_mgr.getSubnet6("foobar"));
+}
+
+// This test verifies if the configuration manager is able to hold, select
+// and return valid leases, based on interface-id option values
+TEST_F(CfgMgrTest, subnet6InterfaceId) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+
+ // interface-id options used in subnets 1,2, and 3
+ OptionPtr ifaceid1 = generateInterfaceId("relay1.eth0");
+ OptionPtr ifaceid2 = generateInterfaceId("VL32");
+ // That's a strange interface-id, but this is a real life example
+ OptionPtr ifaceid3 = generateInterfaceId("ISAM144|299|ipv6|nt:vp:1:110");
+
+ // bogus interface-id
+ OptionPtr ifaceid_bogus = generateInterfaceId("non-existent");
+
+ subnet1->setInterfaceId(ifaceid1);
+ subnet2->setInterfaceId(ifaceid2);
+ subnet3->setInterfaceId(ifaceid3);
+
+ // There shouldn't be any subnet configured at this stage
+ EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1));
+
+ cfg_mgr.addSubnet6(subnet1);
+
+ // If we have only a single subnet and the request came from a local
+ // address, let's use that subnet
+ EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(ifaceid1));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2));
+
+ cfg_mgr.addSubnet6(subnet2);
+ cfg_mgr.addSubnet6(subnet3);
+
+ EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(ifaceid3));
+ EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(ifaceid2));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid_bogus));
+
+ // Check that deletion of the subnets works.
+ cfg_mgr.deleteSubnets6();
+ EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid3));
+}
+
+
// This test verifies that new DHCPv4 option spaces can be added to
// the configuration manager and that duplicated option space is
// rejected.
@@ -351,6 +574,50 @@ TEST_F(CfgMgrTest, optionSpace6) {
// @todo decide if a duplicate vendor space is allowed.
}
+// This test verifies that it is possible to specify interfaces that server
+// should listen on.
+TEST_F(CfgMgrTest, addActiveIface) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ cfg_mgr.addActiveIface("eth0");
+ cfg_mgr.addActiveIface("eth1");
+
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
+
+ cfg_mgr.deleteActiveIfaces();
+
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth0"));
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth1"));
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
+}
+
+// This test verifies that it is possible to set the flag which configures the
+// server to listen on all interfaces.
+TEST_F(CfgMgrTest, activateAllIfaces) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ cfg_mgr.addActiveIface("eth0");
+ cfg_mgr.addActiveIface("eth1");
+
+ ASSERT_TRUE(cfg_mgr.isActiveIface("eth0"));
+ ASSERT_TRUE(cfg_mgr.isActiveIface("eth1"));
+ ASSERT_FALSE(cfg_mgr.isActiveIface("eth2"));
+
+ cfg_mgr.activateAllIfaces();
+
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth2"));
+
+ cfg_mgr.deleteActiveIfaces();
+
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth0"));
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth1"));
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
+}
+
// No specific tests for getSubnet6. That method (2 overloaded versions) is tested
// in Dhcpv6SrvTest.selectSubnetAddr and Dhcpv6SrvTest.selectSubnetIface
// (see src/bin/dhcp6/tests/dhcp6_srv_unittest.cc)
diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
new file mode 100644
index 0000000..62f901c
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
@@ -0,0 +1,1000 @@
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <config/ccsession.h>
+#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <dhcpsrv/tests/test_libraries.h>
+#include <exceptions/exceptions.h>
+#include <hooks/hooks_manager.h>
+
+#include <gtest/gtest.h>
+#include <boost/foreach.hpp>
+#include <boost/pointer_cast.hpp>
+
+#include <map>
+#include <string>
+
+using namespace std;
+using namespace isc;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::hooks;
+
+namespace {
+
+/// @brief DHCP Parser test fixture class
+class DhcpParserTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ DhcpParserTest() {
+ CfgMgr::instance().deleteActiveIfaces();
+ }
+};
+
+
+/// @brief Check BooleanParser basic functionality.
+///
+/// Verifies that the parser:
+/// 1. Does not allow empty for storage.
+/// 2. Rejects a non-boolean element.
+/// 3. Builds with a valid true value.
+/// 4. Bbuils with a valid false value.
+/// 5. Updates storage upon commit.
+TEST_F(DhcpParserTest, booleanParserTest) {
+
+ const std::string name = "boolParm";
+
+ // Verify that parser does not allow empty for storage.
+ BooleanStoragePtr bs;
+ EXPECT_THROW(BooleanParser(name, bs), isc::dhcp::DhcpConfigError);
+
+ // Construct parser for testing.
+ BooleanStoragePtr storage(new BooleanStorage());
+ BooleanParser parser(name, storage);
+
+ // Verify that parser with rejects a non-boolean element.
+ ElementPtr wrong_element = Element::create("I am a string");
+ EXPECT_THROW(parser.build(wrong_element), isc::BadValue);
+
+ // Verify that parser will build with a valid true value.
+ bool test_value = true;
+ ElementPtr element = Element::create(test_value);
+ ASSERT_NO_THROW(parser.build(element));
+
+ // Verify that commit updates storage.
+ bool actual_value = !test_value;
+ parser.commit();
+ EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+ EXPECT_EQ(test_value, actual_value);
+
+ // Verify that parser will build with a valid false value.
+ test_value = false;
+ element->setValue(test_value);
+ EXPECT_NO_THROW(parser.build(element));
+
+ // Verify that commit updates storage.
+ actual_value = ~test_value;
+ parser.commit();
+ EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+ EXPECT_EQ(test_value, actual_value);
+}
+
+/// @brief Check StringParser basic functionality
+///
+/// Verifies that the parser:
+/// 1. Does not allow empty for storage.
+/// 2. Builds with a nont string value.
+/// 3. Builds with a string value.
+/// 4. Updates storage upon commit.
+TEST_F(DhcpParserTest, stringParserTest) {
+
+ const std::string name = "strParm";
+
+ // Verify that parser does not allow empty for storage.
+ StringStoragePtr bs;
+ EXPECT_THROW(StringParser(name, bs), isc::dhcp::DhcpConfigError);
+
+ // Construct parser for testing.
+ StringStoragePtr storage(new StringStorage());
+ StringParser parser(name, storage);
+
+ // Verify that parser with accepts a non-string element.
+ ElementPtr element = Element::create(9999);
+ EXPECT_NO_THROW(parser.build(element));
+
+ // Verify that commit updates storage.
+ parser.commit();
+ std::string actual_value;
+ EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+ EXPECT_EQ("9999", actual_value);
+
+ // Verify that parser will build with a string value.
+ const std::string test_value = "test value";
+ element = Element::create(test_value);
+ ASSERT_NO_THROW(parser.build(element));
+
+ // Verify that commit updates storage.
+ parser.commit();
+ EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+ EXPECT_EQ(test_value, actual_value);
+}
+
+/// @brief Check Uint32Parser basic functionality
+///
+/// Verifies that the parser:
+/// 1. Does not allow empty for storage.
+/// 2. Rejects a non-integer element.
+/// 3. Rejects a negative value.
+/// 4. Rejects too large a value.
+/// 5. Builds with value of zero.
+/// 6. Builds with a value greater than zero.
+/// 7. Updates storage upon commit.
+TEST_F(DhcpParserTest, uint32ParserTest) {
+
+ const std::string name = "intParm";
+
+ // Verify that parser does not allow empty for storage.
+ Uint32StoragePtr bs;
+ EXPECT_THROW(Uint32Parser(name, bs), isc::dhcp::DhcpConfigError);
+
+ // Construct parser for testing.
+ Uint32StoragePtr storage(new Uint32Storage());
+ Uint32Parser parser(name, storage);
+
+ // Verify that parser with rejects a non-interger element.
+ ElementPtr wrong_element = Element::create("I am a string");
+ EXPECT_THROW(parser.build(wrong_element), isc::BadValue);
+
+ // Verify that parser with rejects a negative value.
+ ElementPtr int_element = Element::create(-1);
+ EXPECT_THROW(parser.build(int_element), isc::BadValue);
+
+ // Verify that parser with rejects too large a value provided we are on
+ // 64-bit platform.
+ if (sizeof(long) > sizeof(uint32_t)) {
+ long max = (long)(std::numeric_limits<uint32_t>::max()) + 1;
+ int_element->setValue(max);
+ EXPECT_THROW(parser.build(int_element), isc::BadValue);
+ }
+
+ // Verify that parser will build with value of zero.
+ int test_value = 0;
+ int_element->setValue((long)test_value);
+ ASSERT_NO_THROW(parser.build(int_element));
+
+ // Verify that commit updates storage.
+ parser.commit();
+ uint32_t actual_value = 0;
+ EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+ EXPECT_EQ(test_value, actual_value);
+
+ // Verify that parser will build with a valid positive value.
+ test_value = 77;
+ int_element->setValue((long)test_value);
+ ASSERT_NO_THROW(parser.build(int_element));
+
+ // Verify that commit updates storage.
+ parser.commit();
+ EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+ EXPECT_EQ(test_value, actual_value);
+}
+
+/// @brief Check InterfaceListParser basic functionality
+///
+/// Verifies that the parser:
+/// 1. Does not allow empty for storage.
+/// 2. Does not allow name other than "interfaces"
+/// 3. Parses list of interfaces and adds them to CfgMgr
+/// 4. Parses wildcard interface name and sets a CfgMgr flag which indicates
+/// that server will listen on all interfaces.
+TEST_F(DhcpParserTest, interfaceListParserTest) {
+
+ const std::string name = "interfaces";
+
+ // Verify that parser constructor fails if parameter name isn't "interface"
+ EXPECT_THROW(InterfaceListConfigParser("bogus_name"), isc::BadValue);
+
+ boost::scoped_ptr<InterfaceListConfigParser>
+ parser(new InterfaceListConfigParser(name));
+ ElementPtr list_element = Element::createList();
+ list_element->add(Element::create("eth0"));
+ list_element->add(Element::create("eth1"));
+
+ // Make sure there are no interfaces added yet.
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
+ ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
+
+ // This should parse the configuration and add eth0 and eth1 to the list
+ // of interfaces that server should listen on.
+ parser->build(list_element);
+ parser->commit();
+
+ // Use CfgMgr instance to check if eth0 and eth1 was added, and that
+ // eth2 was not added.
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
+ EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
+
+ // Add keyword all to the configuration. This should activate all
+ // interfaces, including eth2, even though it has not been explicitly
+ // added.
+ list_element->add(Element::create("*"));
+
+ // Reset parser's state.
+ parser.reset(new InterfaceListConfigParser(name));
+ parser->build(list_element);
+ parser->commit();
+
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
+ EXPECT_TRUE(cfg_mgr.isActiveIface("eth2"));
+}
+
+/// @brief Test Implementation of abstract OptionDataParser class. Allows
+/// testing basic option parsing.
+class UtestOptionDataParser : public OptionDataParser {
+public:
+
+ UtestOptionDataParser(const std::string&,
+ OptionStoragePtr options, ParserContextPtr global_context)
+ :OptionDataParser("", options, global_context) {
+ }
+
+ static OptionDataParser* factory(const std::string& param_name,
+ OptionStoragePtr options, ParserContextPtr global_context) {
+ return (new UtestOptionDataParser(param_name, options, global_context));
+ }
+
+protected:
+ // Dummy out last two params since test derivation doesn't use them.
+ virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
+ std::string&, uint32_t) {
+ OptionDefinitionPtr def;
+ // always return empty
+ return (def);
+ }
+};
+
+/// @brief Test Fixture class which provides basic structure for testing
+/// configuration parsing. This is essentially the same structure provided
+/// by dhcp servers.
+class ParseConfigTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ParseConfigTest() {
+ reset_context();
+ }
+
+ ~ParseConfigTest() {
+ reset_context();
+ }
+
+ /// @brief Parses a configuration.
+ ///
+ /// Parse the given configuration, populating the context storage with
+ /// the parsed elements.
+ ///
+ /// @param config_set is the set of elements to parse.
+ /// @return returns an ConstElementPtr containing the numeric result
+ /// code and outcome comment.
+ isc::data::ConstElementPtr parseElementSet(isc::data::ConstElementPtr
+ config_set) {
+ // Answer will hold the result.
+ ConstElementPtr answer;
+ if (!config_set) {
+ answer = isc::config::createAnswer(1,
+ string("Can't parse NULL config"));
+ return (answer);
+ }
+
+ // option parsing must be done last, so save it if we hit if first
+ ParserPtr option_parser;
+
+ ConfigPair config_pair;
+ try {
+ // Iterate over the config elements.
+ const std::map<std::string, ConstElementPtr>& values_map =
+ config_set->mapValue();
+ BOOST_FOREACH(config_pair, values_map) {
+ // Create the parser based on element name.
+ ParserPtr parser(createConfigParser(config_pair.first));
+ // Options must be parsed last
+ if (config_pair.first == "option-data") {
+ option_parser = parser;
+ } else {
+ // Anything else we can call build straight away.
+ parser->build(config_pair.second);
+ parser->commit();
+ }
+ }
+
+ // The option values parser is the next one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator
+ option_config = values_map.find("option-data");
+ if (option_config != values_map.end()) {
+ option_parser->build(option_config->second);
+ option_parser->commit();
+ }
+
+ // Everything was fine. Configuration is successful.
+ answer = isc::config::createAnswer(0, "Configuration committed.");
+ } catch (const isc::Exception& ex) {
+ answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed: ") + ex.what());
+
+ } catch (...) {
+ answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed"));
+ }
+
+ return (answer);
+ }
+
+ /// @brief Create an element parser based on the element name.
+ ///
+ /// Creates a parser for the appropriate element and stores a pointer to it
+ /// in the appropriate class variable.
+ ///
+ /// Note that the method currently it only supports option-defs, option-data
+ /// and hooks-libraries.
+ ///
+ /// @param config_id is the name of the configuration element.
+ ///
+ /// @return returns a shared pointer to DhcpConfigParser.
+ ///
+ /// @throw throws NotImplemented if element name isn't supported.
+ ParserPtr createConfigParser(const std::string& config_id) {
+ ParserPtr parser;
+ if (config_id.compare("option-data") == 0) {
+ parser.reset(new OptionDataListParser(config_id,
+ parser_context_->options_,
+ parser_context_,
+ UtestOptionDataParser::factory));
+
+ } else if (config_id.compare("option-def") == 0) {
+ parser.reset(new OptionDefListParser(config_id,
+ parser_context_->option_defs_));
+
+ } else if (config_id.compare("hooks-libraries") == 0) {
+ parser.reset(new HooksLibrariesParser(config_id));
+ hooks_libraries_parser_ =
+ boost::dynamic_pointer_cast<HooksLibrariesParser>(parser);
+
+ } else {
+ isc_throw(NotImplemented,
+ "Parser error: configuration parameter not supported: "
+ << config_id);
+ }
+
+ return (parser);
+ }
+
+ /// @brief Convenience method for parsing a configuration
+ ///
+ /// Given a configuration string, convert it into Elements
+ /// and parse them.
+ /// @param config is the configuration string to parse
+ ///
+ /// @return retuns 0 if the configuration parsed successfully,
+ /// non-zero otherwise failure.
+ int parseConfiguration(const std::string& config) {
+ int rcode_ = 1;
+ // Turn config into elements.
+ // Test json just to make sure its valid.
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_TRUE(json);
+ if (json) {
+ ConstElementPtr status = parseElementSet(json);
+ ConstElementPtr comment = parseAnswer(rcode_, status);
+ error_text_ = comment->stringValue();
+ }
+
+ return (rcode_);
+ }
+
+ /// @brief Find an option definition for a given space and code within
+ /// the parser context.
+ /// @param space is the space name of the desired option.
+ /// @param code is the numeric "type" of the desired option.
+ /// @return returns an OptionDefinitionPtr which points to the found
+ /// definition or is empty.
+ /// ASSERT_ tests don't work inside functions that return values
+ OptionDefinitionPtr getOptionDef(std::string space, uint32_t code)
+ {
+ OptionDefinitionPtr def;
+ OptionDefContainerPtr defs =
+ parser_context_->option_defs_->getItems(space);
+ // Should always be able to get definitions list even if it is empty.
+ EXPECT_TRUE(defs);
+ if (defs) {
+ // Attempt to find desired definiton.
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range = idx.equal_range(code);
+ int cnt = std::distance(range.first, range.second);
+ EXPECT_EQ(1, cnt);
+ if (cnt == 1) {
+ def = *(idx.begin());
+ }
+ }
+ return (def);
+ }
+
+ /// @brief Find an option for a given space and code within the parser
+ /// context.
+ /// @param space is the space name of the desired option.
+ /// @param code is the numeric "type" of the desired option.
+ /// @return returns an OptionPtr which points to the found
+ /// option or is empty.
+ /// ASSERT_ tests don't work inside functions that return values
+ OptionPtr getOptionPtr(std::string space, uint32_t code)
+ {
+ OptionPtr option_ptr;
+ Subnet::OptionContainerPtr options =
+ parser_context_->options_->getItems(space);
+ // Should always be able to get options list even if it is empty.
+ EXPECT_TRUE(options);
+ if (options) {
+ // Attempt to find desired option.
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+ const Subnet::OptionContainerTypeRange& range =
+ idx.equal_range(code);
+ int cnt = std::distance(range.first, range.second);
+ EXPECT_EQ(1, cnt);
+ if (cnt == 1) {
+ Subnet::OptionDescriptor desc = *(idx.begin());
+ option_ptr = desc.option;
+ EXPECT_TRUE(option_ptr);
+ }
+ }
+
+ return (option_ptr);
+ }
+
+ /// @brief Wipes the contents of the context to allowing another parsing
+ /// during a given test if needed.
+ void reset_context(){
+ // Note set context universe to V6 as it has to be something.
+ CfgMgr::instance().deleteSubnets4();
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().deleteOptionDefs();
+ parser_context_.reset(new ParserContext(Option::V6));
+
+ // Ensure no hooks libraries are loaded.
+ HooksManager::unloadLibraries();
+ }
+
+ /// @brief Parsers used in the parsing of the configuration
+ ///
+ /// Allows the tests to interrogate the state of the parsers (if required).
+ boost::shared_ptr<HooksLibrariesParser> hooks_libraries_parser_;
+
+ /// @brief Parser context - provides storage for options and definitions
+ ParserContextPtr parser_context_;
+
+ /// @brief Error string if the parsing failed
+ std::string error_text_;
+};
+
+/// @brief Check Basic parsing of option definitions.
+///
+/// Note that this tests basic operation of the OptionDefinitionListParser and
+/// OptionDefinitionParser. It uses a simple configuration consisting of one
+/// one definition and verifies that it is parsed and committed to storage
+/// correctly.
+TEST_F(ParseConfigTest, basicOptionDefTest) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0);
+
+
+ // Verify that the option definition can be retrieved.
+ OptionDefinitionPtr def = getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Verify that the option definition is correct.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+ EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+}
+
+/// @brief Check Basic parsing of options.
+///
+/// Note that this tests basic operation of the OptionDataListParser and
+/// OptionDataParser. It uses a simple configuration consisting of one
+/// one definition and matching option data. It verifies that the option
+/// is parsed and committed to storage correctly.
+TEST_F(ParseConfigTest, basicOptionDataTest) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\","
+ " \"encapsulate\": \"\""
+ " } ], "
+ " \"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 100,"
+ " \"data\": \"192.168.2.1\","
+ " \"csv-format\": True"
+ " } ]"
+ "}";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0);
+
+ // Verify that the option can be retrieved.
+ OptionPtr opt_ptr = getOptionPtr("isc", 100);
+ ASSERT_TRUE(opt_ptr);
+
+ // Verify that the option definition is correct.
+ std::string val = "type=100, len=4, data fields:\n "
+ " #0 192.168.2.1 ( ipv4-address ) \n";
+
+ EXPECT_EQ(val, opt_ptr->toText());
+}
+
+}; // Anonymous namespace
+
+/// These tests check basic operation of the HooksLibrariesParser.
+
+// hooks-libraries that do not contain anything.
+TEST_F(ParseConfigTest, noHooksLibrariesTest) {
+
+ // Configuration with hooks-libraries not present.
+ string config = "{ \"hooks-libraries\": [] }";
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // Check that the parser recorded no change to the current state
+ // (as the test starts with no hooks libraries loaded).
+ std::vector<std::string> libraries;
+ bool changed;
+ hooks_libraries_parser_->getLibraries(libraries, changed);
+ EXPECT_TRUE(libraries.empty());
+ EXPECT_FALSE(changed);
+
+ // Load a single library and repeat the parse.
+ vector<string> basic_library;
+ basic_library.push_back(string(CALLOUT_LIBRARY_1));
+ HooksManager::loadLibraries(basic_library);
+
+ rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // This time the change should have been recorded.
+ hooks_libraries_parser_->getLibraries(libraries, changed);
+ EXPECT_TRUE(libraries.empty());
+ EXPECT_TRUE(changed);
+
+ // But repeating it again and we are back to no change.
+ rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+ hooks_libraries_parser_->getLibraries(libraries, changed);
+ EXPECT_TRUE(libraries.empty());
+ EXPECT_FALSE(changed);
+
+}
+
+
+TEST_F(ParseConfigTest, validHooksLibrariesTest) {
+
+ // Configuration string. This contains a set of valid libraries.
+ const std::string quote("\"");
+ const std::string comma(", ");
+
+ const std::string config =
+ std::string("{ ") +
+ std::string("\"hooks-libraries\": [") +
+ quote + std::string(CALLOUT_LIBRARY_1) + quote + comma +
+ quote + std::string(CALLOUT_LIBRARY_2) + quote +
+ std::string("]") +
+ std::string("}");
+
+ // Verify that the configuration string parses.
+ int rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // Check that the parser holds two libraries and the configuration is
+ // recorded as having changed.
+ std::vector<std::string> libraries;
+ bool changed;
+ hooks_libraries_parser_->getLibraries(libraries, changed);
+ EXPECT_EQ(2, libraries.size());
+ EXPECT_TRUE(changed);
+
+ // The expected libraries should be the list of libraries specified
+ // in the given order.
+ std::vector<std::string> expected;
+ expected.push_back(CALLOUT_LIBRARY_1);
+ expected.push_back(CALLOUT_LIBRARY_2);
+ EXPECT_TRUE(expected == libraries);
+
+ // Parse the string again.
+ rcode = parseConfiguration(config);
+ ASSERT_TRUE(rcode == 0) << error_text_;
+
+ // The list has not changed, and this is what we should see.
+ hooks_libraries_parser_->getLibraries(libraries, changed);
+ EXPECT_EQ(2, libraries.size());
+ EXPECT_FALSE(changed);
+}
+
+// Check with a set of libraries, some of which are invalid.
+TEST_F(ParseConfigTest, invalidHooksLibrariesTest) {
+
+ // @todo Initialize global library context to null
+
+ // Configuration string. This contains an invalid library which should
+ // trigger an error in the "build" stage.
+ const std::string quote("\"");
+ const std::string comma(", ");
+
+ const std::string config =
+ std::string("{ ") +
+ std::string("\"hooks-libraries\": [") +
+ quote + std::string(CALLOUT_LIBRARY_1) + quote + comma +
+ quote + std::string(NOT_PRESENT_LIBRARY) + quote + comma +
+ quote + std::string(CALLOUT_LIBRARY_2) + quote +
+ std::string("]") +
+ std::string("}");
+
+ // Verify that the configuration fails to parse. (Syntactically it's OK,
+ // but the library is invalid).
+ int rcode = parseConfiguration(config);
+ ASSERT_FALSE(rcode == 0) << error_text_;
+
+ // Check that the message contains the library in error.
+ EXPECT_FALSE(error_text_.find(NOT_PRESENT_LIBRARY) == string::npos) <<
+ "Error text returned from parse failure is " << error_text_;
+}
+
+/// @brief DHCP Configuration Parser Context test fixture.
+class ParserContextTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ParserContextTest() { }
+
+ /// @brief Check that the storages of the specific type hold the
+ /// same value.
+ ///
+ /// This function assumes that the ref_values storage holds exactly
+ /// one parameter called 'foo'.
+ ///
+ /// @param ref_values A storage holding reference value. In the typical
+ /// case it is a storage held in the original context, which is assigned
+ /// to another context.
+ /// @param values A storage holding value to be checked.
+ /// @tparam ContainerType A type of the storage.
+ /// @tparam ValueType A type of the value in the container.
+ template<typename ContainerType, typename ValueType>
+ void checkValueEq(const boost::shared_ptr<ContainerType>& ref_values,
+ const boost::shared_ptr<ContainerType>& values) {
+ ValueType param;
+ ASSERT_NO_THROW(param = values->getParam("foo"));
+ EXPECT_EQ(ref_values->getParam("foo"), param);
+ }
+
+ /// @brief Check that the storages of the specific type hold different
+ /// value.
+ ///
+ /// This function assumes that the ref_values storage holds exactly
+ /// one parameter called 'foo'.
+ ///
+ /// @param ref_values A storage holding reference value. In the typical
+ /// case it is a storage held in the original context, which is assigned
+ /// to another context.
+ /// @param values A storage holding value to be checked.
+ /// @tparam ContainerType A type of the storage.
+ /// @tparam ValueType A type of the value in the container.
+ template<typename ContainerType, typename ValueType>
+ void checkValueNeq(const boost::shared_ptr<ContainerType>& ref_values,
+ const boost::shared_ptr<ContainerType>& values) {
+ ValueType param;
+ ASSERT_NO_THROW(param = values->getParam("foo"));
+ EXPECT_NE(ref_values->getParam("foo"), param);
+ }
+
+ /// @brief Check that option definition storage in the context holds
+ /// one option definition of the specified type.
+ ///
+ /// @param ctx A pointer to a context.
+ /// @param opt_type Expected option type.
+ void checkOptionDefinitionType(const ParserContext& ctx,
+ const uint16_t opt_type) {
+ OptionDefContainerPtr opt_defs =
+ ctx.option_defs_->getItems("option-space");
+ ASSERT_TRUE(opt_defs);
+ OptionDefContainerTypeIndex& idx = opt_defs->get<1>();
+ OptionDefContainerTypeRange range = idx.equal_range(opt_type);
+ EXPECT_EQ(1, std::distance(range.first, range.second));
+ }
+
+ /// @brief Check that option storage in the context holds one option
+ /// of the specified type.
+ ///
+ /// @param ctx A pointer to a context.
+ /// @param opt_type Expected option type.
+ void checkOptionType(const ParserContext& ctx, const uint16_t opt_type) {
+ Subnet::OptionContainerPtr options =
+ ctx.options_->getItems("option-space");
+ ASSERT_TRUE(options);
+ Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+ Subnet::OptionContainerTypeRange range = idx.equal_range(opt_type);
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ }
+
+ /// @brief Test copy constructor or assignment operator when values
+ /// being copied are NULL.
+ ///
+ /// @param copy Indicates that copy constructor should be tested
+ /// (if true), or assignment operator (if false).
+ void testCopyAssignmentNull(const bool copy) {
+ ParserContext ctx(Option::V6);
+ // Release all pointers in the context.
+ ctx.boolean_values_.reset();
+ ctx.uint32_values_.reset();
+ ctx.string_values_.reset();
+ ctx.options_.reset();
+ ctx.option_defs_.reset();
+ ctx.hooks_libraries_.reset();
+
+ // Even if the fields of the context are NULL, it should get
+ // copied.
+ ParserContextPtr ctx_new(new ParserContext(Option::V6));
+ if (copy) {
+ ASSERT_NO_THROW(ctx_new.reset(new ParserContext(ctx)));
+ } else {
+ *ctx_new = ctx;
+ }
+
+ // The resulting context has its fields equal to NULL.
+ EXPECT_FALSE(ctx_new->boolean_values_);
+ EXPECT_FALSE(ctx_new->uint32_values_);
+ EXPECT_FALSE(ctx_new->string_values_);
+ EXPECT_FALSE(ctx_new->options_);
+ EXPECT_FALSE(ctx_new->option_defs_);
+ EXPECT_FALSE(ctx_new->hooks_libraries_);
+
+ }
+
+ /// @brief Test copy constructor or assignment operator.
+ ///
+ /// @param copy Indicates that copy constructor should be tested (if true),
+ /// or assignment operator (if false).
+ void testCopyAssignment(const bool copy) {
+ // Create new context. It will be later copied/assigned to another
+ // context.
+ ParserContext ctx(Option::V6);
+
+ // Set boolean parameter 'foo'.
+ ASSERT_TRUE(ctx.boolean_values_);
+ ctx.boolean_values_->setParam("foo", true);
+
+ // Set uint32 parameter 'foo'.
+ ASSERT_TRUE(ctx.uint32_values_);
+ ctx.uint32_values_->setParam("foo", 123);
+
+ // Ser string parameter 'foo'.
+ ASSERT_TRUE(ctx.string_values_);
+ ctx.string_values_->setParam("foo", "some string");
+
+ // Add new option, with option code 10, to the context.
+ ASSERT_TRUE(ctx.options_);
+ OptionPtr opt1(new Option(Option::V6, 10));
+ Subnet::OptionDescriptor desc1(opt1, false);
+ std::string option_space = "option-space";
+ ASSERT_TRUE(desc1.option);
+ ctx.options_->addItem(desc1, option_space);
+
+ // Add new option definition, with option code 123.
+ OptionDefinitionPtr def1(new OptionDefinition("option-foo", 123,
+ "string"));
+ ctx.option_defs_->addItem(def1, option_space);
+
+ // Allocate container for hooks libraries and add one library name.
+ ctx.hooks_libraries_.reset(new std::vector<std::string>());
+ ctx.hooks_libraries_->push_back("library1");
+
+ // We will use ctx_new to assign another context to it or copy
+ // construct.
+ ParserContextPtr ctx_new(new ParserContext(Option::V4));;
+ if (copy) {
+ ctx_new.reset(new ParserContext(ctx));
+ } else {
+ *ctx_new = ctx;
+ }
+
+ // New context has the same boolean value.
+ ASSERT_TRUE(ctx_new->boolean_values_);
+ {
+ SCOPED_TRACE("Check that boolean values are equal in both"
+ " contexts");
+ checkValueEq<BooleanStorage, bool>(ctx.boolean_values_,
+ ctx_new->boolean_values_);
+ }
+
+ // New context has the same uint32 value.
+ ASSERT_TRUE(ctx_new->uint32_values_);
+ {
+ SCOPED_TRACE("Check that uint32_t values are equal in both"
+ " contexts");
+ checkValueEq<Uint32Storage, uint32_t>(ctx.uint32_values_,
+ ctx_new->uint32_values_);
+ }
+
+ // New context has the same string value.
+ ASSERT_TRUE(ctx_new->string_values_);
+ {
+ SCOPED_TRACE("Check that string values are equal in both contexts");
+ checkValueEq<StringStorage, std::string>(ctx.string_values_,
+ ctx_new->string_values_);
+ }
+
+ // New context has the same option.
+ ASSERT_TRUE(ctx_new->options_);
+ {
+ SCOPED_TRACE("Check that the option values are equal in both"
+ " contexts");
+ checkOptionType(*ctx_new, 10);
+ }
+
+ // New context has the same option definition.
+ ASSERT_TRUE(ctx_new->option_defs_);
+ {
+ SCOPED_TRACE("check that the option definition is equal in both"
+ " contexts");
+ checkOptionDefinitionType(*ctx_new, 123);
+ }
+
+ // New context has the same hooks library.
+ ASSERT_TRUE(ctx_new->hooks_libraries_);
+ {
+ ASSERT_EQ(1, ctx_new->hooks_libraries_->size());
+ EXPECT_EQ("library1", (*ctx_new->hooks_libraries_)[0]);
+ }
+
+ // New context has the same universe.
+ EXPECT_EQ(ctx.universe_, ctx_new->universe_);
+
+ // Change the value of the boolean parameter. This should not affect the
+ // corresponding value in the new context.
+ {
+ SCOPED_TRACE("Check that boolean value isn't changed when original"
+ " value is changed");
+ ctx.boolean_values_->setParam("foo", false);
+ checkValueNeq<BooleanStorage, bool>(ctx.boolean_values_,
+ ctx_new->boolean_values_);
+ }
+
+ // Change the value of the uint32_t parameter. This should not affect
+ // the corresponding value in the new context.
+ {
+ SCOPED_TRACE("Check that uint32_t value isn't changed when original"
+ " value is changed");
+ ctx.uint32_values_->setParam("foo", 987);
+ checkValueNeq<Uint32Storage, uint32_t>(ctx.uint32_values_,
+ ctx_new->uint32_values_);
+ }
+
+ // Change the value of the string parameter. This should not affect the
+ // corresponding value in the new context.
+ {
+ SCOPED_TRACE("Check that string value isn't changed when original"
+ " value is changed");
+ ctx.string_values_->setParam("foo", "different string");
+ checkValueNeq<StringStorage, std::string>(ctx.string_values_,
+ ctx_new->string_values_);
+ }
+
+ // Change the option. This should not affect the option instance in the
+ // new context.
+ {
+ SCOPED_TRACE("Check that option remains the same in the new context"
+ " when the option in the original context is changed");
+ ctx.options_->clearItems();
+ Subnet::OptionDescriptor desc(OptionPtr(new Option(Option::V6,
+ 100)),
+ false);
+
+ ASSERT_TRUE(desc.option);
+ ctx.options_->addItem(desc, "option-space");
+ checkOptionType(*ctx_new, 10);
+ }
+
+ // Change the option definition. This should not affect the option
+ // definition in the new context.
+ {
+ SCOPED_TRACE("Check that option definition remains the same in"
+ " the new context when the option definition in the"
+ " original context is changed");
+ ctx.option_defs_->clearItems();
+ OptionDefinitionPtr def(new OptionDefinition("option-foo", 234,
+ "string"));
+
+ ctx.option_defs_->addItem(def, option_space);
+ checkOptionDefinitionType(*ctx_new, 123);
+ }
+
+ // Change the list of libraries. this should not affect the list in the
+ // new context.
+ ctx.hooks_libraries_->clear();
+ ctx.hooks_libraries_->push_back("library2");
+ ASSERT_EQ(1, ctx_new->hooks_libraries_->size());
+ EXPECT_EQ("library1", (*ctx_new->hooks_libraries_)[0]);
+
+ // Change the universe. This should not affect the universe value in the
+ // new context.
+ ctx.universe_ = Option::V4;
+ EXPECT_EQ(Option::V6, ctx_new->universe_);
+
+ }
+
+};
+
+// Check that the assignment operator of the ParserContext class copies all
+// fields correctly.
+TEST_F(ParserContextTest, assignment) {
+ testCopyAssignment(false);
+}
+
+// Check that the assignment operator of the ParserContext class copies all
+// fields correctly when these fields are NULL.
+TEST_F(ParserContextTest, assignmentNull) {
+ testCopyAssignmentNull(false);
+}
+
+// Check that the context is copy constructed correctly.
+TEST_F(ParserContextTest, copyConstruct) {
+ testCopyAssignment(true);
+}
+
+// Check that the context is copy constructed correctly, when context fields
+// are NULL.
+TEST_F(ParserContextTest, copyConstructNull) {
+ testCopyAssignmentNull(true);
+}
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
index 9924476..e439c87 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
@@ -149,7 +149,7 @@ TEST_F(LeaseMgrFactoryTest, redactAccessStringEmptyPassword) {
/// @brief redactConfigString test - no password
///
/// Checks that the redacted configuration string excludes the password if there
-/// was no password to begion with.
+/// was no password to begin with.
TEST_F(LeaseMgrFactoryTest, redactAccessStringNoPassword) {
LeaseMgr::ParameterMap parameters =
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
index 8d8c7f8..883874a 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -245,7 +245,7 @@ TEST(LeaseMgr, getParameter) {
///
/// Lease4 is also defined in lease_mgr.h, so is tested in this file as well.
// This test checks if the Lease4 structure can be instantiated correctly
-TEST(Lease4, Lease4Constructor) {
+TEST(Lease4, constructor) {
// Random values for the tests
const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
@@ -292,12 +292,79 @@ TEST(Lease4, Lease4Constructor) {
}
}
+// This test verfies that copy constructor copies Lease4 fields correctly.
+TEST(Lease4, copyConstructor) {
+
+ // Random values for the tests
+ const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
+ std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
+
+ const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
+ std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
+ ClientId clientid(clientid_vec);
+
+ // ...and a time
+ const time_t current_time = time(NULL);
+
+ // Other random constants.
+ const uint32_t SUBNET_ID = 42;
+ const uint32_t VALID_LIFETIME = 500;
+
+ // Create the lease
+ Lease4 lease(0xffffffff, HWADDR, sizeof(HWADDR),
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, 0, 0, current_time,
+ SUBNET_ID);
+
+ // Use copy constructor to copy the lease.
+ Lease4 copied_lease(lease);
+
+ // Both leases should be now equal. When doing this check we assume that
+ // the equality operator works correctly.
+ EXPECT_TRUE(lease == copied_lease);
+ // Client IDs are equal, but they should be in two distinct pointers.
+ EXPECT_FALSE(lease.client_id_ == copied_lease.client_id_);
+}
+
+// This test verfies that the assignment operator copies all Lease4 fields
+// correctly.
+TEST(Lease4, operatorAssign) {
+
+ // Random values for the tests
+ const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
+ std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
+
+ const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
+ std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
+ ClientId clientid(clientid_vec);
+
+ // ...and a time
+ const time_t current_time = time(NULL);
+
+ // Other random constants.
+ const uint32_t SUBNET_ID = 42;
+ const uint32_t VALID_LIFETIME = 500;
+
+ // Create the lease
+ Lease4 lease(0xffffffff, HWADDR, sizeof(HWADDR),
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, 0, 0, current_time,
+ SUBNET_ID);
+
+ // Use assignment operator to assign the lease.
+ Lease4 copied_lease = lease;
+
+ // Both leases should be now equal. When doing this check we assume that
+ // the equality operator works correctly.
+ EXPECT_TRUE(lease == copied_lease);
+ // Client IDs are equal, but they should be in two distinct pointers.
+ EXPECT_FALSE(lease.client_id_ == copied_lease.client_id_);
+}
+
/// @brief Lease4 Equality Test
///
/// Checks that the operator==() correctly compares two leases for equality.
/// As operator!=() is also defined for this class, every check on operator==()
/// is followed by the reverse check on operator!=().
-TEST(Lease4, OperatorEquals) {
+TEST(Lease4, operatorEquals) {
// Random values for the tests
const uint32_t ADDRESS = 0x01020304;
@@ -312,8 +379,8 @@ TEST(Lease4, OperatorEquals) {
// Check when the leases are equal.
Lease4 lease1(ADDRESS, HWADDR, sizeof(HWADDR),
- CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
- SUBNET_ID);
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0,
+ 0, SUBNET_ID);
Lease4 lease2(ADDRESS, HWADDR, sizeof(HWADDR),
CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
SUBNET_ID);
@@ -440,8 +507,8 @@ TEST(Lease6, Lease6Constructor) {
// Other values
uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
DuidPtr duid(new DUID(llt, sizeof(llt)));
- uint32_t iaid = 7; // just a number
- SubnetID subnet_id = 8; // just another number
+ uint32_t iaid = 7; // Just a number
+ SubnetID subnet_id = 8; // Just another number
for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
IOAddress addr(ADDRESS[i]);
@@ -462,9 +529,10 @@ TEST(Lease6, Lease6Constructor) {
// Lease6 must be instantiated with a DUID, not with NULL pointer
IOAddress addr(ADDRESS[0]);
- EXPECT_THROW(new Lease6(Lease6::LEASE_IA_NA, addr,
- DuidPtr(), iaid, 100, 200, 50, 80,
- subnet_id), InvalidOperation);
+ Lease6Ptr lease2;
+ EXPECT_THROW(lease2.reset(new Lease6(Lease6::LEASE_IA_NA, addr,
+ DuidPtr(), iaid, 100, 200, 50, 80,
+ subnet_id)), InvalidOperation);
}
/// @brief Lease6 Equality Test
@@ -474,7 +542,7 @@ TEST(Lease6, Lease6Constructor) {
/// is followed by the reverse check on operator!=().
TEST(Lease6, OperatorEquals) {
- // check a variety of addressemas with different bits set.
+ // check a variety of addresses with different bits set.
const IOAddress addr("2001:db8:1::456");
uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
@@ -486,6 +554,10 @@ TEST(Lease6, OperatorEquals) {
subnet_id);
Lease6 lease2(Lease6::LEASE_IA_NA, addr, duid, iaid, 100, 200, 50, 80,
subnet_id);
+
+ // cltt_ constructs with time(NULL), make sure they are always equal
+ lease1.cltt_ = lease2.cltt_;
+
EXPECT_TRUE(lease1 == lease2);
EXPECT_FALSE(lease1 != lease2);
@@ -611,21 +683,21 @@ TEST(Lease6, Lease6Expired) {
const IOAddress addr("2001:db8:1::456");
const uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
const DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
- const uint32_t iaid = 7; // just a number
- const SubnetID subnet_id = 8; // just another number
+ const uint32_t iaid = 7; // Just a number
+ const SubnetID subnet_id = 8; // Just another number
Lease6 lease(Lease6::LEASE_IA_NA, addr, duid, iaid, 100, 200, 50, 80,
subnet_id);
- // case 1: a second before expiration
+ // Case 1: a second before expiration
lease.cltt_ = time(NULL) - 100;
lease.valid_lft_ = 101;
EXPECT_FALSE(lease.expired());
- // case 2: the lease will expire after this second is concluded
+ // Case 2: the lease will expire after this second is concluded
lease.cltt_ = time(NULL) - 101;
EXPECT_FALSE(lease.expired());
- // case 3: the lease is expired
+ // Case 3: the lease is expired
lease.cltt_ = time(NULL) - 102;
EXPECT_TRUE(lease.expired());
}
diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
index ddb2645..5b2fa9e 100644
--- a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
@@ -18,6 +18,7 @@
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/mysql_lease_mgr.h>
#include <dhcpsrv/tests/test_utils.h>
+#include <exceptions/exceptions.h>
#include <gtest/gtest.h>
@@ -118,21 +119,16 @@ validConnectionString() {
// There is no error checking in this code: if something fails, one of the
// tests will (should) fall over.
void destroySchema() {
- // Initialise
- MYSQL handle;
- (void) mysql_init(&handle);
+ MySqlHolder mysql;
// Open database
- (void) mysql_real_connect(&handle, "localhost", "keatest",
+ (void) mysql_real_connect(mysql, "localhost", "keatest",
"keatest", "keatest", 0, NULL, 0);
// Get rid of everything in it.
for (int i = 0; destroy_statement[i] != NULL; ++i) {
- (void) mysql_query(&handle, destroy_statement[i]);
+ (void) mysql_query(mysql, destroy_statement[i]);
}
-
- // ... and close
- (void) mysql_close(&handle);
}
// @brief Create the Schema
@@ -142,21 +138,16 @@ void destroySchema() {
// There is no error checking in this code: if it fails, one of the tests
// will fall over.
void createSchema() {
- // Initialise
- MYSQL handle;
- (void) mysql_init(&handle);
+ MySqlHolder mysql;
// Open database
- (void) mysql_real_connect(&handle, "localhost", "keatest",
+ (void) mysql_real_connect(mysql, "localhost", "keatest",
"keatest", "keatest", 0, NULL, 0);
// Execute creation statements.
for (int i = 0; create_statement[i] != NULL; ++i) {
- (void) mysql_query(&handle, create_statement[i]);
+ (void) mysql_query(mysql, create_statement[i]);
}
-
- // ... and close
- (void) mysql_close(&handle);
}
/// @brief Test fixture class for testing MySQL Lease Manager
@@ -317,8 +308,7 @@ public:
} else if (address == straddress4_[7]) {
lease->hwaddr_ = vector<uint8_t>(); // Empty
- lease->client_id_ = ClientIdPtr(
- new ClientId(vector<uint8_t>())); // Empty
+ lease->client_id_ = ClientIdPtr(); // Empty
lease->valid_lft_ = 7975;
lease->cltt_ = 213876;
lease->subnet_id_ = 19;
@@ -712,6 +702,83 @@ TEST_F(MySqlLeaseMgrTest, basicLease4) {
detailCompareLease(leases[2], l_returned);
}
+/// @brief Basic Lease4 Checks
+///
+/// Checks that the addLease, getLease4(by address), getLease4(hwaddr,subnet_id),
+/// updateLease4() and deleteLease (IPv4 address) can handle NULL client-id.
+/// (client-id is optional and may not be present)
+TEST_F(MySqlLeaseMgrTest, lease4NullClientId) {
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Let's clear client-id pointers
+ leases[1]->client_id_ = ClientIdPtr();
+ leases[2]->client_id_ = ClientIdPtr();
+ leases[3]->client_id_ = ClientIdPtr();
+
+ // Start the tests. Add three leases to the database, read them back and
+ // check they are what we think they are.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ EXPECT_TRUE(lmptr_->addLease(leases[2]));
+ EXPECT_TRUE(lmptr_->addLease(leases[3]));
+ lmptr_->commit();
+
+ // Reopen the database to ensure that they actually got stored.
+ reopen();
+
+ Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ l_returned = lmptr_->getLease4(ioaddress4_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+ l_returned = lmptr_->getLease4(ioaddress4_[3]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[3], l_returned);
+
+ // Check that we can't add a second lease with the same address
+ EXPECT_FALSE(lmptr_->addLease(leases[1]));
+
+ // Check that we can get the lease by HWAddr
+ HWAddr tmp(leases[2]->hwaddr_, HTYPE_ETHER);
+ Lease4Collection returned = lmptr_->getLease4(tmp);
+ ASSERT_EQ(1, returned.size());
+ detailCompareLease(leases[2], *returned.begin());
+
+ l_returned = lmptr_->getLease4(tmp, leases[2]->subnet_id_);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+
+ // Check that we can update the lease
+ // Modify some fields in lease 1 (not the address) and update it.
+ ++leases[1]->subnet_id_;
+ leases[1]->valid_lft_ *= 2;
+ lmptr_->updateLease4(leases[1]);
+
+ // ... and check that the lease is indeed updated
+ l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+
+
+ // Delete a lease, check that it's gone, and that we can't delete it
+ // a second time.
+ EXPECT_TRUE(lmptr_->deleteLease(ioaddress4_[1]));
+ l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ EXPECT_FALSE(l_returned);
+ EXPECT_FALSE(lmptr_->deleteLease(ioaddress4_[1]));
+
+ // Check that the second address is still there.
+ l_returned = lmptr_->getLease4(ioaddress4_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+}
+
/// @brief Basic Lease6 Checks
///
/// Checks that the addLease, getLease6 (by address) and deleteLease (with an
@@ -791,14 +858,14 @@ TEST_F(MySqlLeaseMgrTest, getLease4Hwaddr) {
// Repeat test with just one expected match
// @todo: Simply use HWAddr directly once 2589 is implemented
returned = lmptr_->getLease4(HWAddr(leases[2]->hwaddr_, HTYPE_ETHER));
- EXPECT_EQ(1, returned.size());
+ ASSERT_EQ(1, returned.size());
detailCompareLease(leases[2], *returned.begin());
// Check that an empty vector is valid
EXPECT_TRUE(leases[7]->hwaddr_.empty());
// @todo: Simply use HWAddr directly once 2589 is implemented
returned = lmptr_->getLease4(HWAddr(leases[7]->hwaddr_, HTYPE_ETHER));
- EXPECT_EQ(1, returned.size());
+ ASSERT_EQ(1, returned.size());
detailCompareLease(leases[7], *returned.begin());
// Try to get something with invalid hardware address
@@ -817,28 +884,23 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
vector<Lease4Ptr> leases = createLeases4();
// Now add leases with increasing hardware address size.
- for (uint8_t i = 0; i <= Lease4::HWADDR_MAX; ++i) {
+ for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
leases[1]->hwaddr_.resize(i, i);
EXPECT_TRUE(lmptr_->addLease(leases[1]));
// @todo: Simply use HWAddr directly once 2589 is implemented
- Lease4Collection returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
+ Lease4Collection returned =
+ lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
+
ASSERT_EQ(1, returned.size());
detailCompareLease(leases[1], *returned.begin());
(void) lmptr_->deleteLease(leases[1]->addr_);
}
- // Expect some problem when accessing a lease that had too long a hardware
- // address. (The 42 is a random value put in each byte of the address.)
- // In fact the address is stored in a truncated form, so we won't find it
- // when we look.
- // @todo Check if there is some way of detecting that data added
- // to the database is truncated. There does not appear to
- // be any indication in the C API.
- leases[1]->hwaddr_.resize(Lease4::HWADDR_MAX + 100, 42);
- EXPECT_TRUE(lmptr_->addLease(leases[1]));
- // @todo: Simply use HWAddr directly once 2589 is implemented
- Lease4Collection returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
- EXPECT_EQ(0, returned.size());
+ // Database should not let us add one that is too big
+ // (The 42 is a random value put in each byte of the address.)
+ // @todo: 2589 will make this test impossible
+ leases[1]->hwaddr_.resize(HWAddr::MAX_HWADDR_LEN + 100, 42);
+ EXPECT_THROW(lmptr_->addLease(leases[1]), isc::dhcp::DbOperationError);
}
/// @brief Check GetLease4 methods - access by Hardware Address & Subnet ID
@@ -855,8 +917,9 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
// Get the leases matching the hardware address of lease 1 and
// subnet ID of lease 1. Result should be a single lease - lease 1.
// @todo: Simply use HWAddr directly once 2589 is implemented
- Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
- leases[1]->subnet_id_);
+ Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
+ HTYPE_ETHER), leases[1]->subnet_id_);
+
ASSERT_TRUE(returned);
detailCompareLease(leases[1], returned);
@@ -891,8 +954,9 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
leases[1]->addr_ = leases[2]->addr_;
EXPECT_TRUE(lmptr_->addLease(leases[1]));
// @todo: Simply use HWAddr directly once 2589 is implemented
- EXPECT_THROW(returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
- leases[1]->subnet_id_),
+ EXPECT_THROW(returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
+ HTYPE_ETHER),
+ leases[1]->subnet_id_),
isc::dhcp::MultipleRecords);
// Delete all leases in the database
@@ -913,28 +977,22 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetIdSize) {
// Now add leases with increasing hardware address size and check
// that they can be retrieved.
- for (uint8_t i = 0; i <= Lease4::HWADDR_MAX; ++i) {
+ for (uint8_t i = 0; i <= HWAddr::MAX_HWADDR_LEN; ++i) {
leases[1]->hwaddr_.resize(i, i);
EXPECT_TRUE(lmptr_->addLease(leases[1]));
// @todo: Simply use HWAddr directly once 2589 is implemented
- Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
+ Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_,
+ HTYPE_ETHER),
leases[1]->subnet_id_);
ASSERT_TRUE(returned);
detailCompareLease(leases[1], returned);
(void) lmptr_->deleteLease(leases[1]->addr_);
}
- // Expect some error when getting a lease with too long a hardware
- // address. Set the contents of each byte to 42, a random value.
- // @todo Check if there is some way of detecting that data added
- // to the database is truncated. There does not appear to
- // be any indication in the C API.
- leases[1]->hwaddr_.resize(Lease4::HWADDR_MAX + 100, 42);
- EXPECT_TRUE(lmptr_->addLease(leases[1]));
- // @todo: Simply use HWAddr directly once 2589 is implemented
- Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
- leases[1]->subnet_id_);
- EXPECT_FALSE(returned);
+ // Database should not let us add one that is too big
+ // (The 42 is a random value put in each byte of the address.)
+ leases[1]->hwaddr_.resize(HWAddr::MAX_HWADDR_LEN + 100, 42);
+ EXPECT_THROW(lmptr_->addLease(leases[1]), isc::dhcp::DbOperationError);
}
/// @brief Check GetLease4 methods - access by Client ID
@@ -968,13 +1026,14 @@ TEST_F(MySqlLeaseMgrTest, getLease4ClientId) {
// Repeat test with just one expected match
returned = lmptr_->getLease4(*leases[3]->client_id_);
- EXPECT_EQ(1, returned.size());
+ ASSERT_EQ(1, returned.size());
detailCompareLease(leases[3], *returned.begin());
- // Check that an empty vector is valid
- EXPECT_TRUE(leases[7]->client_id_->getClientId().empty());
- returned = lmptr_->getLease4(leases[7]->hwaddr_);
- EXPECT_EQ(1, returned.size());
+ // Check that client-id is NULL
+ EXPECT_FALSE(leases[7]->client_id_);
+ HWAddr tmp(leases[7]->hwaddr_, HTYPE_ETHER);
+ returned = lmptr_->getLease4(tmp);
+ ASSERT_EQ(1, returned.size());
detailCompareLease(leases[7], *returned.begin());
// Try to get something with invalid client ID
@@ -998,7 +1057,11 @@ TEST_F(MySqlLeaseMgrTest, getLease4ClientIdSize) {
// ClientId::MAX_CLIENT_ID_LEN is used in an EXPECT_EQ.
int client_id_max = ClientId::MAX_CLIENT_ID_LEN;
EXPECT_EQ(128, client_id_max);
- for (uint8_t i = 0; i <= client_id_max; i += 16) {
+
+ int client_id_min = ClientId::MIN_CLIENT_ID_LEN;
+ EXPECT_EQ(2, client_id_min); // See RFC2132, section 9.14
+
+ for (uint8_t i = client_id_min; i <= client_id_max; i += 16) {
vector<uint8_t> clientid_vec(i, i);
leases[1]->client_id_.reset(new ClientId(clientid_vec));
EXPECT_TRUE(lmptr_->addLease(leases[1]));
@@ -1107,13 +1170,17 @@ TEST_F(MySqlLeaseMgrTest, getLease6DuidIaidSize) {
// For speed, go from 0 to 128 is steps of 16.
int duid_max = DUID::MAX_DUID_LEN;
EXPECT_EQ(128, duid_max);
- for (uint8_t i = 0; i <= duid_max; i += 16) {
+
+ int duid_min = DUID::MIN_DUID_LEN;
+ EXPECT_EQ(1, duid_min);
+
+ for (uint8_t i = duid_min; i <= duid_max; i += 16) {
vector<uint8_t> duid_vec(i, i);
leases[1]->duid_.reset(new DUID(duid_vec));
EXPECT_TRUE(lmptr_->addLease(leases[1]));
Lease6Collection returned = lmptr_->getLease6(*leases[1]->duid_,
leases[1]->iaid_);
- EXPECT_EQ(1, returned.size());
+ ASSERT_EQ(1, returned.size());
detailCompareLease(leases[1], *returned.begin());
(void) lmptr_->deleteLease(leases[1]->addr_);
}
@@ -1172,7 +1239,11 @@ TEST_F(MySqlLeaseMgrTest, getLease6DuidIaidSubnetIdSize) {
// For speed, go from 0 to 128 is steps of 16.
int duid_max = DUID::MAX_DUID_LEN;
EXPECT_EQ(128, duid_max);
- for (uint8_t i = 0; i <= duid_max; i += 16) {
+
+ int duid_min = DUID::MIN_DUID_LEN;
+ EXPECT_EQ(1, duid_min);
+
+ for (uint8_t i = duid_min; i <= duid_max; i += 16) {
vector<uint8_t> duid_vec(i, i);
leases[1]->duid_.reset(new DUID(duid_vec));
EXPECT_TRUE(lmptr_->addLease(leases[1]));
diff --git a/src/lib/dhcpsrv/tests/run_unittests.cc b/src/lib/dhcpsrv/tests/run_unittests.cc
index d333a6f..dcc0204 100644
--- a/src/lib/dhcpsrv/tests/run_unittests.cc
+++ b/src/lib/dhcpsrv/tests/run_unittests.cc
@@ -12,6 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
#include <log/logger_support.h>
#include <gtest/gtest.h>
diff --git a/src/lib/dhcpsrv/tests/schema_copy.h b/src/lib/dhcpsrv/tests/schema_copy.h
index 48a11ca..f534fa8 100644
--- a/src/lib/dhcpsrv/tests/schema_copy.h
+++ b/src/lib/dhcpsrv/tests/schema_copy.h
@@ -42,6 +42,7 @@ const char* destroy_statement[] = {
// Creation of the new tables.
const char* create_statement[] = {
+ "START TRANSACTION",
"CREATE TABLE lease4 ("
"address INT UNSIGNED PRIMARY KEY NOT NULL,"
"hwaddr VARBINARY(20),"
@@ -51,6 +52,10 @@ const char* create_statement[] = {
"subnet_id INT UNSIGNED"
") ENGINE = INNODB",
+ "CREATE INDEX lease4_by_hwaddr_subnet_id ON lease4 (hwaddr, subnet_id)",
+
+ "CREATE INDEX lease4_by_client_id_subnet_id ON lease4 (client_id, subnet_id)",
+
"CREATE TABLE lease6 ("
"address VARCHAR(39) PRIMARY KEY NOT NULL,"
"duid VARBINARY(128),"
@@ -63,6 +68,8 @@ const char* create_statement[] = {
"prefix_len TINYINT UNSIGNED"
") ENGINE = INNODB",
+ "CREATE INDEX lease6_by_iaid_subnet_id_duid ON lease6 (iaid, subnet_id, duid)",
+
"CREATE TABLE lease6_types ("
"lease_type TINYINT PRIMARY KEY NOT NULL,"
"name VARCHAR(5)"
@@ -78,6 +85,7 @@ const char* create_statement[] = {
")",
"INSERT INTO schema_version VALUES (1, 0)",
+ "COMMIT",
NULL
};
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
index 9f06c89..fbcebcb 100644
--- a/src/lib/dhcpsrv/tests/subnet_unittest.cc
+++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc
@@ -16,6 +16,7 @@
#include <asiolink/io_address.h>
#include <dhcp/option.h>
+#include <dhcp/dhcp6.h>
#include <dhcpsrv/subnet.h>
#include <exceptions/exceptions.h>
@@ -398,7 +399,7 @@ TEST(Subnet6Test, addPersistentOption) {
// those that server sends to clients regardless if they ask
// for them or not. We pick 3 out of 10 options and mark them
// non-persistent and 7 other options persistent.
- // Code values: 102, 105 and 108 are divisable by 3
+ // Code values: 102, 105 and 108 are divisible by 3
// and options with these codes will be flagged non-persistent.
// Options with other codes will be flagged persistent.
bool persistent = (code % 3) ? true : false;
@@ -516,4 +517,19 @@ TEST(Subnet6Test, iface) {
EXPECT_EQ("en1", subnet.getIface());
}
+// This trivial test checks if the interface-id option can be set and
+// later retrieved for a subnet6 object.
+TEST(Subnet6Test, interfaceId) {
+ // Create as subnet to add options to it.
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+
+ EXPECT_FALSE(subnet->getInterfaceId());
+
+ OptionPtr option(new Option(Option::V6, D6O_INTERFACE_ID, OptionBuffer(10, 0xFF)));
+ subnet->setInterfaceId(option);
+
+ EXPECT_EQ(option, subnet->getInterfaceId());
+
+}
+
};
diff --git a/src/lib/dhcpsrv/tests/test_get_callout_handle.cc b/src/lib/dhcpsrv/tests/test_get_callout_handle.cc
new file mode 100644
index 0000000..5d7cd9d
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/test_get_callout_handle.cc
@@ -0,0 +1,31 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <dhcpsrv/callout_handle_store.h>
+#include "test_get_callout_handle.h"
+
+// Just instantiate the getCalloutHandle function and call it.
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+isc::hooks::CalloutHandlePtr
+testGetCalloutHandle(const Pkt6Ptr& pktptr) {
+ return (isc::dhcp::getCalloutHandle(pktptr));
+}
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/tests/test_get_callout_handle.h b/src/lib/dhcpsrv/tests/test_get_callout_handle.h
new file mode 100644
index 0000000..f29ab42
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/test_get_callout_handle.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TEST_GET_CALLOUT_HANDLE_H
+#define TEST_GET_CALLOUT_HANDLE_H
+
+#include <dhcp/pkt6.h>
+#include <hooks/callout_handle.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @file
+/// @brief Get Callout Handle
+///
+/// This function is a shall around getCalloutHandle. It's purpose is to
+/// ensure that the getCalloutHandle() template function is referred to by
+/// two separate compilation units, and so test that data stored in one unit
+/// can be accessed by another. (This should be the case, but some compilers
+/// mabe be odd when it comes to template instantiation.)
+///
+/// @param pktptr Pointer to a Pkt6 object.
+///
+/// @return CalloutHandlePtr pointing to CalloutHandle associated with the
+/// Pkt6 object.
+isc::hooks::CalloutHandlePtr
+testGetCalloutHandle(const Pkt6Ptr& pktptr);
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+
+#endif // TEST_GET_CALLOUT_HANDLE_H
diff --git a/src/lib/dhcpsrv/tests/test_libraries.h.in b/src/lib/dhcpsrv/tests/test_libraries.h.in
new file mode 100644
index 0000000..b5e80a0
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/test_libraries.h.in
@@ -0,0 +1,51 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TEST_LIBRARIES_H
+#define TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+
+// Take care of differences in DLL naming between operating systems.
+
+#ifdef OS_OSX
+#define DLL_SUFFIX ".dylib"
+
+#else
+#define DLL_SUFFIX ".so"
+
+#endif
+
+
+// Names of the libraries used in these tests. These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// shared library.
+
+// Library with load/unload functions creating marker files to check their
+// operation.
+static const char* CALLOUT_LIBRARY_1 = "@abs_builddir@/.libs/libco1"
+ DLL_SUFFIX;
+static const char* CALLOUT_LIBRARY_2 = "@abs_builddir@/.libs/libco2"
+ DLL_SUFFIX;
+
+// Name of a library which is not present.
+static const char* NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere"
+ DLL_SUFFIX;
+} // anonymous namespace
+
+
+#endif // TEST_LIBRARIES_H
diff --git a/src/lib/dhcpsrv/tests/test_utils.cc b/src/lib/dhcpsrv/tests/test_utils.cc
index 3c69dbe..d8794fb 100644
--- a/src/lib/dhcpsrv/tests/test_utils.cc
+++ b/src/lib/dhcpsrv/tests/test_utils.cc
@@ -27,10 +27,29 @@ detailCompareLease(const Lease4Ptr& first, const Lease4Ptr& second) {
// thrown for IPv6 addresses.
EXPECT_EQ(first->addr_.toText(), second->addr_.toText());
EXPECT_TRUE(first->hwaddr_ == second->hwaddr_);
- EXPECT_TRUE(*first->client_id_ == *second->client_id_);
+ if (first->client_id_ && second->client_id_) {
+ EXPECT_TRUE(*first->client_id_ == *second->client_id_);
+ } else {
+ if (first->client_id_ && !second->client_id_) {
+
+ ADD_FAILURE() << "Client-id present in first lease ("
+ << first->client_id_->getClientId().size()
+ << " bytes), but missing in second.";
+ }
+ if (!first->client_id_ && second->client_id_) {
+ ADD_FAILURE() << "Client-id missing in first lease, but present in second ("
+ << second->client_id_->getClientId().size()
+ << " bytes).";
+ }
+ // else here would mean that both leases do not have client_id_
+ // which makes them equal in that regard. It is ok.
+ }
EXPECT_EQ(first->valid_lft_, second->valid_lft_);
EXPECT_EQ(first->cltt_, second->cltt_);
EXPECT_EQ(first->subnet_id_, second->subnet_id_);
+ EXPECT_EQ(first->fqdn_fwd_, second->fqdn_fwd_);
+ EXPECT_EQ(first->fqdn_rev_, second->fqdn_rev_);
+ EXPECT_EQ(first->hostname_, second->hostname_);
}
void
@@ -51,6 +70,9 @@ detailCompareLease(const Lease6Ptr& first, const Lease6Ptr& second) {
EXPECT_EQ(first->valid_lft_, second->valid_lft_);
EXPECT_EQ(first->cltt_, second->cltt_);
EXPECT_EQ(first->subnet_id_, second->subnet_id_);
+ EXPECT_EQ(first->fqdn_fwd_, second->fqdn_fwd_);
+ EXPECT_EQ(first->fqdn_rev_, second->fqdn_rev_);
+ EXPECT_EQ(first->hostname_, second->hostname_);
}
};
diff --git a/src/lib/dns/gen-rdatacode.py.in b/src/lib/dns/gen-rdatacode.py.in
index 7ea2ae5..3fd3b33 100755
--- a/src/lib/dns/gen-rdatacode.py.in
+++ b/src/lib/dns/gen-rdatacode.py.in
@@ -1,6 +1,6 @@
#!@PYTHON@
-# Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -24,31 +24,6 @@ from os.path import getmtime
import re
import sys
-# new_rdata_factory_users[] is a list of tuples of the form (rrtype,
-# rrclass). Items in the list use the (new) RdataFactory class, and
-# items which are not in the list use OldRdataFactory class.
-# Note: rrtype and rrclass must be specified in lowercase in
-# new_rdata_factory_users.
-#
-# Example:
-# new_rdata_factory_users = [('a', 'in'), ('a', 'ch'), ('soa', 'generic')]
-new_rdata_factory_users = [('a', 'in'), ('aaaa', 'in'),
- ('cname', 'generic'),
- ('dlv', 'generic'),
- ('dname', 'generic'),
- ('ds', 'generic'),
- ('hinfo', 'generic'),
- ('naptr', 'generic'),
- ('mx', 'generic'),
- ('ns', 'generic'),
- ('nsec', 'generic'),
- ('ptr', 'generic'),
- ('soa', 'generic'),
- ('spf', 'generic'),
- ('srv', 'in'),
- ('txt', 'generic')
- ]
-
re_typecode = re.compile('([\da-z\-]+)_(\d+)')
classcode2txt = {}
typecode2txt = {}
@@ -58,7 +33,7 @@ meta_types = {
# Real meta types. We won't have Rdata implement for them, but we need
# RRType constants.
'251': 'ixfr', '252': 'axfr', '255': 'any',
- # Obsolete types. We probalby won't implement Rdata for them, but it's
+ # Obsolete types. We probably won't implement Rdata for them, but it's
# better to have RRType constants.
'3': 'md', '4': 'mf', '7': 'mb', '8': 'mg', '9': 'mr', '30': 'nxt',
'38': 'a6', '254': 'maila',
@@ -186,7 +161,7 @@ def import_definitions(classcode2txt, typecode2txt, typeandclass):
# list is processed in the same order on all systems. The resulting
# files should compile regardless of the order in which the components
# are included but... Having a fixed order for the directories should
- # eliminate system-dependent problems. (Note that the drectory names
+ # eliminate system-dependent problems. (Note that the directory names
# in BIND 10 are ASCII, so the order should be locale-independent.)
dirlist = os.listdir('@srcdir@/rdata')
dirlist.sort()
@@ -367,28 +342,15 @@ def generate_rrparam(fileprefix, basemtime):
indent = ' ' * 8
typeandclassparams += indent
- # By default, we use OldRdataFactory (see bug #2497). If you
- # want to pick RdataFactory for a particular type, add it to
- # new_rdata_factory_users. Note that we explicitly generate (for
- # optimization) class-independent ("generic") factories for class IN
- # for optimization.
- if (((type_txt.lower(), class_txt.lower()) in
- new_rdata_factory_users) or
- ((class_txt.lower() == 'in') and
- ((type_txt.lower(), 'generic') in new_rdata_factory_users))):
- rdf_class = 'RdataFactory'
- else:
- rdf_class = 'OldRdataFactory'
-
if class_tuple[1] != 'generic':
typeandclassparams += 'add("' + type_utxt + '", '
typeandclassparams += str(type_code) + ', "' + class_utxt
typeandclassparams += '", ' + str(class_code)
- typeandclassparams += ', RdataFactoryPtr(new ' + rdf_class + '<'
+ typeandclassparams += ', RdataFactoryPtr(new ' + 'RdataFactory' + '<'
typeandclassparams += class_txt + '::' + type_utxt + '>()));\n'
else:
typeandclassparams += 'add("' + type_utxt + '", ' + str(type_code)
- typeandclassparams += ', RdataFactoryPtr(new ' + rdf_class + '<'
+ typeandclassparams += ', RdataFactoryPtr(new ' + 'RdataFactory' + '<'
typeandclassparams += class_txt + '::' + type_utxt + '>()));\n'
typeandclassparams += indent + '// Meta and non-implemented RR types\n'
diff --git a/src/lib/dns/master_lexer.h b/src/lib/dns/master_lexer.h
index 31c6443..e33f964 100644
--- a/src/lib/dns/master_lexer.h
+++ b/src/lib/dns/master_lexer.h
@@ -22,6 +22,8 @@
#include <stdint.h>
+#include <boost/noncopyable.hpp>
+
namespace isc {
namespace dns {
namespace master_lexer_internal {
@@ -303,7 +305,7 @@ private:
/// implementation of the exception handling). For these reasons, some of
/// this class does not throw for an error that would be reported as an
/// exception in other classes.
-class MasterLexer {
+class MasterLexer : public boost::noncopyable {
friend class master_lexer_internal::State;
public:
/// \brief Exception thrown when we fail to read from the input
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
index 1c822ea..5c96563 100644
--- a/src/lib/dns/master_loader.cc
+++ b/src/lib/dns/master_loader.cc
@@ -400,7 +400,7 @@ private:
const Name zone_origin_;
Name active_origin_; // The origin used during parsing
// (modifiable by $ORIGIN)
- shared_ptr<Name> last_name_; // Last seen name (for INITAL_WS handling)
+ shared_ptr<Name> last_name_; // Last seen name (for INITIAL_WS handling)
const RRClass zone_class_;
MasterLoaderCallbacks callbacks_;
const AddRRCallback add_callback_;
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h
index 8aaaa48..1c83e1e 100644
--- a/src/lib/dns/message.h
+++ b/src/lib/dns/message.h
@@ -98,10 +98,10 @@ struct SectionIteratorImpl;
template <typename T>
class SectionIterator : public std::iterator<std::input_iterator_tag, T> {
public:
- SectionIterator<T>() : impl_(NULL) {}
- SectionIterator<T>(const SectionIteratorImpl<T>& impl);
- ~SectionIterator<T>();
- SectionIterator<T>(const SectionIterator<T>& source);
+ SectionIterator() : impl_(NULL) {}
+ SectionIterator(const SectionIteratorImpl<T>& impl);
+ ~SectionIterator();
+ SectionIterator(const SectionIterator<T>& source);
void operator=(const SectionIterator<T>& source);
SectionIterator<T>& operator++();
SectionIterator<T> operator++(int);
diff --git a/src/lib/dns/messagerenderer.cc b/src/lib/dns/messagerenderer.cc
index 0cd11ce..bc11cb4 100644
--- a/src/lib/dns/messagerenderer.cc
+++ b/src/lib/dns/messagerenderer.cc
@@ -182,7 +182,7 @@ struct MessageRenderer::MessageRendererImpl {
size_t hash, bool case_sensitive) const
{
// Find a matching entry, if any. We use some heuristics here: often
- // the same name appers consecutively (like repeating the same owner
+ // the same name appears consecutively (like repeating the same owner
// name for a single RRset), so in case there's a collision in the
// bucket it will be more likely to find it in the tail side of the
// bucket.
diff --git a/src/lib/dns/nsec3hash.cc b/src/lib/dns/nsec3hash.cc
index 8fe3cf1..0e03798 100644
--- a/src/lib/dns/nsec3hash.cc
+++ b/src/lib/dns/nsec3hash.cc
@@ -45,7 +45,7 @@ namespace {
/// calculation specified in RFC5155.
///
/// Currently the only pre-defined algorithm in the RFC is SHA1. So we don't
-/// over-generalize it at the moment, and rather hardocde it and assume that
+/// over-generalize it at the moment, and rather hardcode it and assume that
/// specific algorithm.
///
/// The implementation details are only open within this file, but to avoid
diff --git a/src/lib/dns/python/pydnspp_common.cc b/src/lib/dns/python/pydnspp_common.cc
index e9d62e0..4250db7 100644
--- a/src/lib/dns/python/pydnspp_common.cc
+++ b/src/lib/dns/python/pydnspp_common.cc
@@ -56,9 +56,8 @@ PyObject* po_DNSMessageBADVERS;
int
readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence) {
- PyObject* el = NULL;
for (size_t i = 0; i < len; i++) {
- el = PySequence_GetItem(sequence, i);
+ PyObject *el = PySequence_GetItem(sequence, i);
if (!el) {
PyErr_SetString(PyExc_TypeError,
"sequence too short");
diff --git a/src/lib/dns/python/pydnspp_common.h b/src/lib/dns/python/pydnspp_common.h
index 4095f54..8e498b3 100644
--- a/src/lib/dns/python/pydnspp_common.h
+++ b/src/lib/dns/python/pydnspp_common.h
@@ -50,7 +50,7 @@ int addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
/// \brief Initialize a wrapped class type, and add to module
///
-/// The type object is initalized, and its refcount is increased after
+/// The type object is initialized, and its refcount is increased after
/// successful addition to the module.
///
/// \param type The type object to initialize
diff --git a/src/lib/dns/python/rrset_python.cc b/src/lib/dns/python/rrset_python.cc
index 2992522..dc2af22 100644
--- a/src/lib/dns/python/rrset_python.cc
+++ b/src/lib/dns/python/rrset_python.cc
@@ -293,8 +293,16 @@ RRset_addRdata(PyObject* self, PyObject* args) {
PyErr_Clear();
PyErr_SetString(PyExc_TypeError,
"Rdata type to add must match type of RRset");
- return (NULL);
+ } catch (const exception& ex) {
+ const string ex_what =
+ "Unexpected failure adding rrset Rdata: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure adding rrset Rdata");
}
+ return (NULL);
}
PyObject*
diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am
index de6b010..4574303 100644
--- a/src/lib/dns/python/tests/Makefile.am
+++ b/src/lib/dns/python/tests/Makefile.am
@@ -28,7 +28,7 @@ EXTRA_DIST += testutil.py
# 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/dns/python/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/dns/python/tests/message_python_test.py b/src/lib/dns/python/tests/message_python_test.py
index bf39a83..996ef89 100644
--- a/src/lib/dns/python/tests/message_python_test.py
+++ b/src/lib/dns/python/tests/message_python_test.py
@@ -520,7 +520,7 @@ test.example.com. 3600 IN A 192.0.2.2
data = read_wire_data("message_fromWire22.wire")
self.assertRaises(DNSMessageFORMERR, self.p.from_wire, data[:-1])
- def test_from_wire_combind_rrs(self):
+ def test_from_wire_combined_rrs(self):
factoryFromFile(self.p, "message_fromWire19.wire")
rrset = self.p.get_section(Message.SECTION_ANSWER)[0]
self.assertEqual(RRType("A"), rrset.get_type())
diff --git a/src/lib/dns/python/tests/nsec3hash_python_test.py b/src/lib/dns/python/tests/nsec3hash_python_test.py
index 320529a..f3d1cfb 100644
--- a/src/lib/dns/python/tests/nsec3hash_python_test.py
+++ b/src/lib/dns/python/tests/nsec3hash_python_test.py
@@ -41,7 +41,7 @@ class NSEC3HashTest(unittest.TestCase):
RRClass.IN,
"1 0 12 aabbccdd"), 1)
- # Invaid type of RDATA
+ # Invalid type of RDATA
self.assertRaises(TypeError, NSEC3Hash, Rdata(RRType.A, RRClass.IN,
"192.0.2.1"))
diff --git a/src/lib/dns/python/tests/rrset_collection_python_test.py b/src/lib/dns/python/tests/rrset_collection_python_test.py
index 1bbbc80..20d147f 100644
--- a/src/lib/dns/python/tests/rrset_collection_python_test.py
+++ b/src/lib/dns/python/tests/rrset_collection_python_test.py
@@ -17,7 +17,7 @@ import os
import unittest
from pydnspp import *
-# This should refer to the testdata diretory for the C++ tests.
+# This should refer to the testdata directory for the C++ tests.
TESTDATA_DIR = os.environ["TESTDATA_PATH"].split(':')[0]
class RRsetCollectionBaseTest(unittest.TestCase):
diff --git a/src/lib/dns/python/tests/rrset_python_test.py b/src/lib/dns/python/tests/rrset_python_test.py
index 010b60c..0ffcdbe 100644
--- a/src/lib/dns/python/tests/rrset_python_test.py
+++ b/src/lib/dns/python/tests/rrset_python_test.py
@@ -78,7 +78,12 @@ class TestModuleSpec(unittest.TestCase):
def test_add_rdata(self):
# no iterator to read out yet (TODO: add addition test once implemented)
- self.assertRaises(TypeError, self.rrset_a.add_rdata,
+ # This should result in TypeError, but FreeBSD 9.1 cannot correctly
+ # catch the expected internal C++ exception, resulting in SystemError.
+ # In general it's not a good practice to weaken the test condition for
+ # a limited set of buggy environment, but this seems to be the only
+ # case it could fail this way, so we'd live with it. See #2887.
+ self.assertRaises((TypeError, SystemError), self.rrset_a.add_rdata,
Rdata(RRType("NS"), RRClass("IN"), "test.name."))
def test_to_text(self):
diff --git a/src/lib/dns/rdata.cc b/src/lib/dns/rdata.cc
index 081f855..f42c349 100644
--- a/src/lib/dns/rdata.cc
+++ b/src/lib/dns/rdata.cc
@@ -310,6 +310,9 @@ Generic::Generic(const Generic& source) :
{}
Generic&
+// Our check is better than the usual if (this == &source),
+// but cppcheck doesn't recognize it.
+// cppcheck-suppress operatorEqToSelf
Generic::operator=(const Generic& source) {
if (impl_ == source.impl_) {
return (*this);
diff --git a/src/lib/dns/rdata/any_255/tsig_250.cc b/src/lib/dns/rdata/any_255/tsig_250.cc
index ff848fa..796e320 100644
--- a/src/lib/dns/rdata/any_255/tsig_250.cc
+++ b/src/lib/dns/rdata/any_255/tsig_250.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -19,26 +19,28 @@
#include <boost/lexical_cast.hpp>
#include <util/buffer.h>
-#include <util/strutil.h>
#include <util/encode/base64.h>
#include <dns/messagerenderer.h>
#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rcode.h>
#include <dns/tsigerror.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
using namespace std;
using boost::lexical_cast;
using namespace isc::util;
using namespace isc::util::encode;
-using namespace isc::util::str;
+using namespace isc::dns;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-/// This is a straightforward representation of TSIG RDATA fields.
-struct TSIG::TSIGImpl {
+// straightforward representation of TSIG RDATA fields
+struct TSIGImpl {
TSIGImpl(const Name& algorithm, uint64_t time_signed, uint16_t fudge,
vector<uint8_t>& mac, uint16_t original_id, uint16_t error,
vector<uint8_t>& other_data) :
@@ -68,99 +70,184 @@ struct TSIG::TSIGImpl {
const vector<uint8_t> other_data_;
};
+// helper function for string and lexer constructors
+TSIGImpl*
+TSIG::constructFromLexer(MasterLexer& lexer, const Name* origin) {
+ const Name& algorithm =
+ createNameFromLexer(lexer, origin ? origin : &Name::ROOT_NAME());
+
+ const string& time_txt =
+ lexer.getNextToken(MasterToken::STRING).getString();
+ uint64_t time_signed;
+ try {
+ time_signed = boost::lexical_cast<uint64_t>(time_txt);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(InvalidRdataText, "Invalid TSIG Time");
+ }
+ if ((time_signed >> 48) != 0) {
+ isc_throw(InvalidRdataText, "TSIG Time out of range");
+ }
+
+ const uint32_t fudge = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (fudge > 0xffff) {
+ isc_throw(InvalidRdataText, "TSIG Fudge out of range");
+ }
+ const uint32_t macsize =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (macsize > 0xffff) {
+ isc_throw(InvalidRdataText, "TSIG MAC Size out of range");
+ }
+
+ const string& mac_txt = (macsize > 0) ?
+ lexer.getNextToken(MasterToken::STRING).getString() : "";
+ vector<uint8_t> mac;
+ decodeBase64(mac_txt, mac);
+ if (mac.size() != macsize) {
+ isc_throw(InvalidRdataText, "TSIG MAC Size and data are inconsistent");
+ }
+
+ const uint32_t orig_id =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (orig_id > 0xffff) {
+ isc_throw(InvalidRdataText, "TSIG Original ID out of range");
+ }
+
+ const string& error_txt =
+ lexer.getNextToken(MasterToken::STRING).getString();
+ uint32_t error = 0;
+ // XXX: In the initial implementation we hardcode the mnemonics.
+ // We'll soon generalize this.
+ if (error_txt == "NOERROR") {
+ error = Rcode::NOERROR_CODE;
+ } else if (error_txt == "BADSIG") {
+ error = TSIGError::BAD_SIG_CODE;
+ } else if (error_txt == "BADKEY") {
+ error = TSIGError::BAD_KEY_CODE;
+ } else if (error_txt == "BADTIME") {
+ error = TSIGError::BAD_TIME_CODE;
+ } else {
+ /// we cast to uint32_t and range-check, because casting directly to
+ /// uint16_t will convert negative numbers to large positive numbers
+ try {
+ error = boost::lexical_cast<uint32_t>(error_txt);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(InvalidRdataText, "Invalid TSIG Error");
+ }
+ if (error > 0xffff) {
+ isc_throw(InvalidRdataText, "TSIG Error out of range");
+ }
+ }
+
+ const uint32_t otherlen =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (otherlen > 0xffff) {
+ isc_throw(InvalidRdataText, "TSIG Other Len out of range");
+ }
+ const string otherdata_txt = (otherlen > 0) ?
+ lexer.getNextToken(MasterToken::STRING).getString() : "";
+ vector<uint8_t> other_data;
+ decodeBase64(otherdata_txt, other_data);
+ if (other_data.size() != otherlen) {
+ isc_throw(InvalidRdataText,
+ "TSIG Other Data length does not match Other Len");
+ }
+ // RFC2845 says Other Data is "empty unless Error == BADTIME".
+ // However, we don't enforce that.
+
+ return (new TSIGImpl(algorithm, time_signed, fudge, mac, orig_id,
+ error, other_data));
+}
+
/// \brief Constructor from string.
///
+/// The given string must represent a valid TSIG RDATA. There can be extra
+/// space characters at the beginning or end of the text (which are simply
+/// ignored), but other extra text, including a new line, will make the
+/// construction fail with an exception.
+///
/// \c tsig_str must be formatted as follows:
-/// \code <Alg> <Time> <Fudge> <MACsize> [<MAC>] <OrigID> <Error> <OtherLen> [<OtherData>]
+/// \code <Algorithm Name> <Time Signed> <Fudge> <MAC Size> [<MAC>]
+/// <Original ID> <Error> <Other Len> [<Other Data>]
/// \endcode
-/// where
-/// - <Alg> is a valid textual representation of domain name.
-/// - <Time> is an unsigned 48-bit decimal integer.
-/// - <MACSize>, <OrigID>, and <OtherLen> are an unsigned
-/// 16-bit decimal
-/// integer.
-/// - <Error> is an unsigned 16-bit decimal integer or a valid mnemonic
-/// for the Error field specified in RFC2845. Currently, "BADSIG", "BADKEY",
-/// and "BADTIME" are supported (case sensitive). In future versions
-/// other representations that are compatible with the DNS RCODE will be
-/// supported.
-/// - <MAC> and <OtherData> is a BASE-64 encoded string that does
-/// not contain space characters.
-/// When <MACSize> and <OtherLen> is 0, <MAC> and
-/// <OtherData> must not appear in \c tsig_str, respectively.
-/// - The decoded data of <MAC> is <MACSize> bytes of binary
-/// stream.
-/// - The decoded data of <OtherData> is <OtherLen> bytes of
-/// binary stream.
+///
+/// Note that, since the Algorithm Name field is defined to be "in domain name
+/// syntax", but it is not actually a domain name, it does not have to be
+/// fully qualified.
+///
+/// The Error field is an unsigned 16-bit decimal integer or a valid mnemonic
+/// as specified in RFC2845. Currently, "NOERROR", "BADSIG", "BADKEY", and
+/// "BADTIME" are supported (case sensitive). In future versions other
+/// representations that are compatible with the DNS RCODE may be supported.
+///
+/// The MAC and Other Data fields are base-64 encoded strings that do not
+/// contain space characters.
+/// If the MAC Size field is 0, the MAC field must not appear in \c tsig_str.
+/// If the Other Len field is 0, the Other Data field must not appear in
+/// \c tsig_str.
+/// The decoded data of the MAC field is MAC Size bytes of binary stream.
+/// The decoded data of the Other Data field is Other Len bytes of binary
+/// stream.
///
/// An example of valid string is:
/// \code "hmac-sha256. 853804800 300 3 AAAA 2845 0 0" \endcode
-/// In this example <OtherData> is missing because <OtherLen> is 0.
+/// In this example Other Data is missing because Other Len is 0.
///
/// Note that RFC2845 does not define the standard presentation format
/// of %TSIG RR, so the above syntax is implementation specific.
/// This is, however, compatible with the format acceptable to BIND 9's
/// RDATA parser.
///
-/// <b>Exceptions</b>
+/// \throw Others Exception from the Name constructors.
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+/// \throw BadValue if MAC or Other Data is not validly encoded in base-64.
///
-/// If <Alg> is not a valid domain name, a corresponding exception from
-/// the \c Name class will be thrown;
-/// if <MAC> or <OtherData> is not validly encoded in BASE-64, an
-/// exception of class \c isc::BadValue will be thrown;
-/// if %any of the other bullet points above is not met, an exception of
-/// class \c InvalidRdataText will be thrown.
-/// This constructor internally involves resource allocation, and if it fails
-/// a corresponding standard exception will be thrown.
+/// \param tsig_str A string containing the RDATA to be created
TSIG::TSIG(const std::string& tsig_str) : impl_(NULL) {
- istringstream iss(tsig_str);
+ // We use auto_ptr here because if there is an exception in this
+ // constructor, the destructor is not called and there could be a
+ // leak of the TSIGImpl that constructFromLexer() returns.
+ std::auto_ptr<TSIGImpl> impl_ptr(NULL);
try {
- const Name algorithm(getToken(iss));
- const int64_t time_signed = tokenToNum<int64_t, 48>(getToken(iss));
- const int32_t fudge = tokenToNum<int32_t, 16>(getToken(iss));
- const int32_t macsize = tokenToNum<int32_t, 16>(getToken(iss));
-
- const string mac_txt = (macsize > 0) ? getToken(iss) : "";
- vector<uint8_t> mac;
- decodeBase64(mac_txt, mac);
- if (mac.size() != macsize) {
- isc_throw(InvalidRdataText, "TSIG MAC size and data are inconsistent");
- }
-
- const int32_t orig_id = tokenToNum<int32_t, 16>(getToken(iss));
-
- const string error_txt = getToken(iss);
- int32_t error = 0;
- // XXX: In the initial implementation we hardcode the mnemonics.
- // We'll soon generalize this.
- if (error_txt == "BADSIG") {
- error = 16;
- } else if (error_txt == "BADKEY") {
- error = 17;
- } else if (error_txt == "BADTIME") {
- error = 18;
- } else {
- error = tokenToNum<int32_t, 16>(error_txt);
- }
+ std::istringstream ss(tsig_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
- const int32_t otherlen = tokenToNum<int32_t, 16>(getToken(iss));
- const string otherdata_txt = (otherlen > 0) ? getToken(iss) : "";
- vector<uint8_t> other_data;
- decodeBase64(otherdata_txt, other_data);
+ impl_ptr.reset(constructFromLexer(lexer, NULL));
- if (!iss.eof()) {
- isc_throw(InvalidRdataText, "Unexpected input for TSIG RDATA: " <<
- tsig_str);
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for TSIG: " << tsig_str);
}
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct TSIG from '" << tsig_str << "': "
+ << ex.what());
+ }
- impl_ = new TSIGImpl(algorithm, time_signed, fudge, mac, orig_id,
- error, other_data);
+ impl_ = impl_ptr.release();
+}
- } catch (const StringTokenError& ste) {
- isc_throw(InvalidRdataText, "Invalid TSIG text: " << ste.what() <<
- ": " << tsig_str);
- }
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an TSIG RDATA.
+///
+/// See \c TSIG::TSIG(const std::string&) for description of the
+/// expected RDATA fields.
+///
+/// \throw MasterLexer::LexerError General parsing error such as
+/// missing field.
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+TSIG::TSIG(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(constructFromLexer(lexer, origin))
+{
}
/// \brief Constructor from wire-format data.
@@ -183,7 +270,9 @@ TSIG::TSIG(const std::string& tsig_str) : impl_(NULL) {
/// But this constructor does not use this parameter; if necessary, the caller
/// must check consistency between the length parameter and the actual
/// RDATA length.
-TSIG::TSIG(InputBuffer& buffer, size_t) : impl_(NULL) {
+TSIG::TSIG(InputBuffer& buffer, size_t) :
+ impl_(NULL)
+{
Name algorithm(buffer);
uint8_t time_signed_buf[6];
@@ -249,7 +338,7 @@ TSIG::TSIG(const TSIG& source) : Rdata(), impl_(new TSIGImpl(*source.impl_))
TSIG&
TSIG::operator=(const TSIG& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
@@ -281,13 +370,13 @@ TSIG::toText() const {
lexical_cast<string>(impl_->time_signed_) + " " +
lexical_cast<string>(impl_->fudge_) + " " +
lexical_cast<string>(impl_->mac_.size()) + " ";
- if (impl_->mac_.size() > 0) {
+ if (!impl_->mac_.empty()) {
result += encodeBase64(impl_->mac_) + " ";
}
result += lexical_cast<string>(impl_->original_id_) + " ";
result += TSIGError(impl_->error_).toText() + " ";
result += lexical_cast<string>(impl_->other_data_.size());
- if (impl_->other_data_.size() > 0) {
+ if (!impl_->other_data_.empty()) {
result += " " + encodeBase64(impl_->other_data_);
}
@@ -298,7 +387,7 @@ TSIG::toText() const {
// toWire().
template <typename Output>
void
-TSIG::TSIGImpl::toWireCommon(Output& output) const {
+TSIGImpl::toWireCommon(Output& output) const {
output.writeUint16(time_signed_ >> 32);
output.writeUint32(time_signed_ & 0xffffffff);
output.writeUint16(fudge_);
@@ -431,7 +520,7 @@ TSIG::getMACSize() const {
const void*
TSIG::getMAC() const {
- if (impl_->mac_.size() > 0) {
+ if (!impl_->mac_.empty()) {
return (&impl_->mac_[0]);
} else {
return (NULL);
@@ -455,7 +544,7 @@ TSIG::getOtherLen() const {
const void*
TSIG::getOtherData() const {
- if (impl_->other_data_.size() > 0) {
+ if (!impl_->other_data_.empty()) {
return (&impl_->other_data_[0]);
} else {
return (NULL);
diff --git a/src/lib/dns/rdata/any_255/tsig_250.h b/src/lib/dns/rdata/any_255/tsig_250.h
index ff24925..62f599a 100644
--- a/src/lib/dns/rdata/any_255/tsig_250.h
+++ b/src/lib/dns/rdata/any_255/tsig_250.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -18,14 +18,9 @@
#include <string>
+#include <dns/name.h>
#include <dns/rdata.h>
-namespace isc {
-namespace dns {
-class Name;
-}
-}
-
// BEGIN_ISC_NAMESPACE
// BEGIN_COMMON_DECLARATIONS
@@ -33,6 +28,8 @@ class Name;
// BEGIN_RDATA_NAMESPACE
+struct TSIGImpl;
+
/// \brief \c rdata::TSIG class represents the TSIG RDATA as defined %in
/// RFC2845.
///
@@ -145,7 +142,8 @@ public:
/// This method never throws an exception.
const void* getOtherData() const;
private:
- struct TSIGImpl;
+ TSIGImpl* constructFromLexer(MasterLexer& lexer, const Name* origin);
+
TSIGImpl* impl_;
};
diff --git a/src/lib/dns/rdata/ch_3/a_1.cc b/src/lib/dns/rdata/ch_3/a_1.cc
index 3d13a9e..5e2852f 100644
--- a/src/lib/dns/rdata/ch_3/a_1.cc
+++ b/src/lib/dns/rdata/ch_3/a_1.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -31,6 +31,12 @@ A::A(const std::string&) {
// TBD
}
+A::A(MasterLexer&, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&)
+{
+ // TBD
+}
+
A::A(InputBuffer&, size_t) {
// TBD
}
diff --git a/src/lib/dns/rdata/generic/afsdb_18.cc b/src/lib/dns/rdata/generic/afsdb_18.cc
index ec76ee0..cf6a35c 100644
--- a/src/lib/dns/rdata/generic/afsdb_18.cc
+++ b/src/lib/dns/rdata/generic/afsdb_18.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -25,9 +25,12 @@
#include <boost/lexical_cast.hpp>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
using namespace std;
+using boost::lexical_cast;
using namespace isc::util;
-using namespace isc::util::str;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -52,24 +55,58 @@ using namespace isc::util::str;
AFSDB::AFSDB(const std::string& afsdb_str) :
subtype_(0), server_(Name::ROOT_NAME())
{
- istringstream iss(afsdb_str);
-
try {
- const uint32_t subtype = tokenToNum<int32_t, 16>(getToken(iss));
- const Name servername(getToken(iss));
+ std::istringstream ss(afsdb_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
- if (!iss.eof()) {
- isc_throw(InvalidRdataText, "Unexpected input for AFSDB"
- "RDATA: " << afsdb_str);
+ createFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for AFSDB: "
+ << afsdb_str);
}
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct AFSDB from '" <<
+ afsdb_str << "': " << ex.what());
+ }
+}
- subtype_ = subtype;
- server_ = servername;
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an AFSDB RDATA. The SERVER field can be non-absolute if \c origin
+/// is non-NULL, in which case \c origin is used to make it absolute.
+/// It must not be represented as a quoted string.
+///
+/// The SUBTYPE field must be a valid decimal representation of an
+/// unsigned 16-bit integer.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and RRTTL constructors if
+/// construction of textual fields as these objects fail.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin If non NULL, specifies the origin of SERVER when it
+/// is non-absolute.
+AFSDB::AFSDB(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ subtype_(0), server_(".")
+{
+ createFromLexer(lexer, origin);
+}
- } catch (const StringTokenError& ste) {
- isc_throw(InvalidRdataText, "Invalid AFSDB text: " <<
- ste.what() << ": " << afsdb_str);
+void
+AFSDB::createFromLexer(MasterLexer& lexer, const Name* origin)
+{
+ const uint32_t num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (num > 65535) {
+ isc_throw(InvalidRdataText, "Invalid AFSDB subtype: " << num);
}
+ subtype_ = static_cast<uint16_t>(num);
+
+ server_ = createNameFromLexer(lexer, origin);
}
/// \brief Constructor from wire-format data.
@@ -111,7 +148,7 @@ AFSDB::operator=(const AFSDB& source) {
/// \return A \c string object that represents the \c AFSDB object.
string
AFSDB::toText() const {
- return (boost::lexical_cast<string>(subtype_) + " " + server_.toText());
+ return (lexical_cast<string>(subtype_) + " " + server_.toText());
}
/// \brief Render the \c AFSDB in the wire format without name compression.
diff --git a/src/lib/dns/rdata/generic/afsdb_18.h b/src/lib/dns/rdata/generic/afsdb_18.h
index 4a46775..359a6e4 100644
--- a/src/lib/dns/rdata/generic/afsdb_18.h
+++ b/src/lib/dns/rdata/generic/afsdb_18.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -61,6 +61,8 @@ public:
uint16_t getSubtype() const;
private:
+ void createFromLexer(MasterLexer& lexer, const Name* origin);
+
uint16_t subtype_;
Name server_;
};
diff --git a/src/lib/dns/rdata/generic/detail/ds_like.h b/src/lib/dns/rdata/generic/detail/ds_like.h
index a7bebbf..c143cab 100644
--- a/src/lib/dns/rdata/generic/detail/ds_like.h
+++ b/src/lib/dns/rdata/generic/detail/ds_like.h
@@ -188,12 +188,12 @@ public:
/// \brief The copy constructor.
///
/// Trivial for now, we could've used the default one.
- DSLikeImpl(const DSLikeImpl& source) {
- digest_ = source.digest_;
- tag_ = source.tag_;
- algorithm_ = source.algorithm_;
- digest_type_ = source.digest_type_;
- }
+ DSLikeImpl(const DSLikeImpl& source) :
+ tag_(source.tag_),
+ algorithm_(source.algorithm_),
+ digest_type_(source.digest_type_),
+ digest_(source.digest_)
+ {}
/// \brief Convert the DS-like data to a string.
///
diff --git a/src/lib/dns/rdata/generic/detail/nsec3param_common.cc b/src/lib/dns/rdata/generic/detail/nsec3param_common.cc
index 6eea2c7..caa92ce 100644
--- a/src/lib/dns/rdata/generic/detail/nsec3param_common.cc
+++ b/src/lib/dns/rdata/generic/detail/nsec3param_common.cc
@@ -39,41 +39,33 @@ namespace detail {
namespace nsec3 {
ParseNSEC3ParamResult
-parseNSEC3ParamText(const char* const rrtype_name,
- const string& rdata_str, istringstream& iss,
- vector<uint8_t>& salt)
+parseNSEC3ParamFromLexer(const char* const rrtype_name,
+ MasterLexer& lexer, vector<uint8_t>& salt)
{
- unsigned int hashalg, flags, iterations;
- string iterations_str, salthex;
-
- iss >> hashalg >> flags >> iterations_str >> salthex;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid " << rrtype_name <<
- " text: " << rdata_str);
- }
+ const uint32_t hashalg =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (hashalg > 0xff) {
isc_throw(InvalidRdataText, rrtype_name <<
" hash algorithm out of range: " << hashalg);
}
+
+ const uint32_t flags =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (flags > 0xff) {
isc_throw(InvalidRdataText, rrtype_name << " flags out of range: " <<
flags);
}
- // Convert iteration. To reject an invalid case where there's no space
- // between iteration and salt, we extract this field as string and convert
- // to integer.
- try {
- iterations = boost::lexical_cast<unsigned int>(iterations_str);
- } catch (const boost::bad_lexical_cast&) {
- isc_throw(InvalidRdataText, "Bad " << rrtype_name <<
- " iteration: " << iterations_str);
- }
+
+ const uint32_t iterations =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (iterations > 0xffff) {
isc_throw(InvalidRdataText, rrtype_name <<
- " iterations out of range: " <<
- iterations);
+ " iterations out of range: " << iterations);
}
+ const string salthex =
+ lexer.getNextToken(MasterToken::STRING).getString();
+
// Salt is up to 255 bytes, and space is not allowed in the HEX encoding,
// so the encoded string cannot be longer than the double of max length
// of the actual salt.
diff --git a/src/lib/dns/rdata/generic/detail/nsec3param_common.h b/src/lib/dns/rdata/generic/detail/nsec3param_common.h
index 1891fae..a8b848d 100644
--- a/src/lib/dns/rdata/generic/detail/nsec3param_common.h
+++ b/src/lib/dns/rdata/generic/detail/nsec3param_common.h
@@ -15,12 +15,11 @@
#ifndef NSEC3PARAM_COMMON_H
#define NSEC3PARAM_COMMON_H 1
+#include <dns/master_lexer.h>
+
#include <util/buffer.h>
#include <stdint.h>
-
-#include <sstream>
-#include <string>
#include <vector>
namespace isc {
@@ -59,7 +58,7 @@ struct ParseNSEC3ParamResult {
/// \brief Convert textual representation of NSEC3 parameters.
///
-/// This function takes an input string stream that consists of a complete
+/// This function takes an input MasterLexer that points at a complete
/// textual representation of an NSEC3 or NSEC3PARAM RDATA and parses it
/// extracting the hash algorithm, flags, iterations, and salt fields.
///
@@ -67,28 +66,26 @@ struct ParseNSEC3ParamResult {
/// The salt will be stored in the given vector. The vector is expected
/// to be empty, but if not, the existing content will be overridden.
///
-/// On successful return the given input stream will reach the end of the
+/// On successful return the given MasterLexer will reach the end of the
/// salt field.
///
/// \exception isc::BadValue The salt is not a valid hex string.
-/// \exception InvalidRdataText The given string is otherwise invalid for
+/// \exception InvalidRdataText The given RDATA is otherwise invalid for
/// NSEC3 or NSEC3PARAM fields.
+/// \exception MasterLexer::LexerError There was a syntax error reading
+/// a field from the MasterLexer.
///
/// \param rrtype_name Either "NSEC3" or "NSEC3PARAM"; used as part of
/// exception messages.
-/// \param rdata_str A complete textual string of the RDATA; used as part of
-/// exception messages.
-/// \param iss Input stream that consists of a complete textual string of
-/// the RDATA.
+/// \param lexer The MasterLexer to read NSEC3 parameter fields from.
/// \param salt A placeholder for the salt field value of the RDATA.
/// Expected to be empty, but it's not checked (and will be overridden).
///
/// \return The hash algorithm, flags, iterations in the form of
/// ParseNSEC3ParamResult.
-ParseNSEC3ParamResult parseNSEC3ParamText(const char* const rrtype_name,
- const std::string& rdata_str,
- std::istringstream& iss,
- std::vector<uint8_t>& salt);
+ParseNSEC3ParamResult parseNSEC3ParamFromLexer(const char* const rrtype_name,
+ isc::dns::MasterLexer& lexer,
+ std::vector<uint8_t>& salt);
/// \brief Extract NSEC3 parameters from wire-format data.
///
diff --git a/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc b/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
index 319056e..5deaa69 100644
--- a/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
+++ b/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
@@ -78,68 +78,28 @@ checkRRTypeBitmaps(const char* const rrtype_name,
}
void
-buildBitmapsFromText(const char* const rrtype_name,
- istringstream& iss, vector<uint8_t>& typebits)
-{
- uint8_t bitmap[8 * 1024]; // 64k bits
- memset(bitmap, 0, sizeof(bitmap));
-
- do {
- string type;
- iss >> type;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Unexpected input for "
- << rrtype_name << " bitmap");
- }
- try {
- const int code = RRType(type).getCode();
- bitmap[code / 8] |= (0x80 >> (code % 8));
- } catch (const InvalidRRType&) {
- isc_throw(InvalidRdataText, "Invalid RRtype in "
- << rrtype_name << " bitmap: " << type);
- }
- } while (!iss.eof());
-
- for (int window = 0; window < 256; ++window) {
- int octet;
- for (octet = 31; octet >= 0; octet--) {
- if (bitmap[window * 32 + octet] != 0) {
- break;
- }
- }
- if (octet < 0) {
- continue;
- }
- typebits.push_back(window);
- typebits.push_back(octet + 1);
- for (int i = 0; i <= octet; ++i) {
- typebits.push_back(bitmap[window * 32 + i]);
- }
- }
-}
-
-// Note: this function shares common code with buildBitmapsFromText()
-// above, but it is expected that buildBitmapsFromText() will be deleted
-// entirely once the transition to MasterLexer is done for all dependent
-// RR types. So a common method is not made from the two.
-void
buildBitmapsFromLexer(const char* const rrtype_name,
- MasterLexer& lexer, vector<uint8_t>& typebits)
+ MasterLexer& lexer, vector<uint8_t>& typebits,
+ bool allow_empty)
{
uint8_t bitmap[8 * 1024]; // 64k bits
memset(bitmap, 0, sizeof(bitmap));
bool have_rrtypes = false;
+ std::string type_str;
while (true) {
- const MasterToken& token = lexer.getNextToken();
- if (token.getType() != MasterToken::STRING) {
+ const MasterToken& token =
+ lexer.getNextToken(MasterToken::STRING, true);
+ if ((token.getType() == MasterToken::END_OF_FILE) ||
+ (token.getType() == MasterToken::END_OF_LINE)) {
break;
}
+ // token is now assured to be of type STRING.
+
have_rrtypes = true;
- std::string type_str;
+ token.getString(type_str);
try {
- type_str = token.getString();
const int code = RRType(type_str).getCode();
bitmap[code / 8] |= (0x80 >> (code % 8));
} catch (const InvalidRRType&) {
@@ -151,8 +111,12 @@ buildBitmapsFromLexer(const char* const rrtype_name,
lexer.ungetToken();
if (!have_rrtypes) {
+ if (allow_empty) {
+ return;
+ }
isc_throw(InvalidRdataText,
- rrtype_name << " record does not end with RR type mnemonic");
+ rrtype_name <<
+ " record does not end with RR type mnemonic");
}
for (int window = 0; window < 256; ++window) {
diff --git a/src/lib/dns/rdata/generic/detail/nsec_bitmap.h b/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
index ea5cad0..5525384 100644
--- a/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
+++ b/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
@@ -54,33 +54,11 @@ namespace nsec {
void checkRRTypeBitmaps(const char* const rrtype_name,
const std::vector<uint8_t>& typebits);
-/// \brief Convert textual sequence of RR types into type bitmaps.
-///
-/// This function extracts a sequence of strings, converts each sequence
-/// into an RR type, and builds NSEC/NSEC3 type bitmaps with the corresponding
-/// bits for the extracted types being on. The resulting bitmaps (which are
-/// in the wire format according to RFC4034 and RFC5155) are stored in the
-/// given vector. This function expects the given string stream ends with
-/// the sequence.
-///
-/// \exception InvalidRdataText The given input stream does not meet the
-/// assumption (e.g. including invalid form of RR type, not ending with
-/// an RR type string).
-///
-/// \param rrtype_name Either "NSEC" or "NSEC3"; used as part of exception
-/// messages.
-/// \param iss Input stream that consists of a complete sequence of textual
-/// RR types for which the corresponding bits are set.
-/// \param typebits A placeholder for the resulting bitmaps. Expected to be
-/// empty, but it's not checked.
-void buildBitmapsFromText(const char* const rrtype_name,
- std::istringstream& iss,
- std::vector<uint8_t>& typebits);
-
/// \brief Convert textual sequence of RR types read from a lexer into
/// type bitmaps.
///
-/// See the other variant above for description.
+/// See the other variant above for description. If \c allow_empty is
+/// true and there are no mnemonics, \c typebits is left untouched.
///
/// \exception InvalidRdataText Data read from the given lexer does not
/// meet the assumption (e.g. including invalid form of RR type, not
@@ -93,9 +71,13 @@ void buildBitmapsFromText(const char* const rrtype_name,
/// bits are set.
/// \param typebits A placeholder for the resulting bitmaps. Expected to be
/// empty, but it's not checked.
+/// \param allow_empty If true, the function simply returns if no RR
+/// type mnemonics are found. Otherwise, it throws an exception if no RR
+/// type mnemonics are found.
void buildBitmapsFromLexer(const char* const rrtype_name,
isc::dns::MasterLexer& lexer,
- std::vector<uint8_t>& typebits);
+ std::vector<uint8_t>& typebits,
+ bool allow_empty = false);
/// \brief Convert type bitmaps to textual sequence of RR types.
///
diff --git a/src/lib/dns/rdata/generic/dlv_32769.cc b/src/lib/dns/rdata/generic/dlv_32769.cc
index 89a62e1..97b9d1a 100644
--- a/src/lib/dns/rdata/generic/dlv_32769.cc
+++ b/src/lib/dns/rdata/generic/dlv_32769.cc
@@ -62,7 +62,7 @@ DLV::DLV(const DLV& source) :
/// PIMPL-induced logic
DLV&
DLV::operator=(const DLV& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/dnskey_48.cc b/src/lib/dns/rdata/generic/dnskey_48.cc
index 054ac18..3ef6c72 100644
--- a/src/lib/dns/rdata/generic/dnskey_48.cc
+++ b/src/lib/dns/rdata/generic/dnskey_48.cc
@@ -27,6 +27,8 @@
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <memory>
+
#include <stdio.h>
#include <time.h>
@@ -51,51 +53,157 @@ struct DNSKEYImpl {
const vector<uint8_t> keydata_;
};
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid DNSKEY RDATA. There can be
+/// extra space characters at the beginning or end of the text (which
+/// are simply ignored), but other extra text, including a new line,
+/// will make the construction fail with an exception.
+///
+/// The Protocol and Algorithm fields must be within their valid
+/// ranges. The Public Key field must be present and must contain a
+/// Base64 encoding of the public key. Whitespace is allowed within the
+/// Base64 text.
+///
+/// It is okay for the key data to be missing. Note: BIND 9 also accepts
+/// DNSKEY missing key data. While the RFC is silent in this case, and it
+/// may be debatable what an implementation should do, but since this field
+/// is algorithm dependent and this implementations doesn't reject unknown
+/// algorithms, it's lenient here.
+///
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param dnskey_str A string containing the RDATA to be created
DNSKEY::DNSKEY(const std::string& dnskey_str) :
impl_(NULL)
{
- istringstream iss(dnskey_str);
- unsigned int flags, protocol, algorithm;
- stringbuf keydatabuf;
+ // We use auto_ptr here because if there is an exception in this
+ // constructor, the destructor is not called and there could be a
+ // leak of the DNSKEYImpl that constructFromLexer() returns.
+ std::auto_ptr<DNSKEYImpl> impl_ptr(NULL);
+
+ try {
+ std::istringstream ss(dnskey_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for DNSKEY: " << dnskey_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct DNSKEY from '" << dnskey_str << "': "
+ << ex.what());
+ }
- iss >> flags >> protocol >> algorithm >> &keydatabuf;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid DNSKEY text");
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid DNSKEY RDATA.
+///
+/// The Protocol and Algorithm fields are not checked for unknown
+/// values. It is okay for the key data to be missing (see the description
+/// of the constructor from string).
+DNSKEY::DNSKEY(InputBuffer& buffer, size_t rdata_len) :
+ impl_(NULL)
+{
+ if (rdata_len < 4) {
+ isc_throw(InvalidRdataLength, "DNSKEY too short: " << rdata_len);
}
+
+ const uint16_t flags = buffer.readUint16();
+ const uint16_t protocol = buffer.readUint8();
+ const uint16_t algorithm = buffer.readUint8();
+
+ rdata_len -= 4;
+
+ vector<uint8_t> keydata;
+ // If key data is missing, it's OK. See the API documentation of the
+ // constructor.
+ if (rdata_len > 0) {
+ keydata.resize(rdata_len);
+ buffer.readData(&keydata[0], rdata_len);
+ }
+
+ impl_ = new DNSKEYImpl(flags, protocol, algorithm, keydata);
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an DNSKEY RDATA.
+///
+/// See \c DNSKEY::DNSKEY(const std::string&) for description of the
+/// expected RDATA fields.
+///
+/// \throw MasterLexer::LexerError General parsing error such as
+/// missing field.
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+DNSKEY::DNSKEY(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(NULL)
+{
+ impl_ = constructFromLexer(lexer);
+}
+
+DNSKEYImpl*
+DNSKEY::constructFromLexer(MasterLexer& lexer) {
+ const uint32_t flags = lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (flags > 0xffff) {
- isc_throw(InvalidRdataText, "DNSKEY flags out of range");
+ isc_throw(InvalidRdataText,
+ "DNSKEY flags out of range: " << flags);
}
+
+ const uint32_t protocol =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (protocol > 0xff) {
- isc_throw(InvalidRdataText, "DNSKEY protocol out of range");
+ isc_throw(InvalidRdataText,
+ "DNSKEY protocol out of range: " << protocol);
}
+
+ const uint32_t algorithm =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (algorithm > 0xff) {
- isc_throw(InvalidRdataText, "DNSKEY algorithm out of range");
+ isc_throw(InvalidRdataText,
+ "DNSKEY algorithm out of range: " << algorithm);
}
- vector<uint8_t> keydata;
- decodeBase64(keydatabuf.str(), keydata);
-
- if (algorithm == 1 && keydata.size() < 3) {
- isc_throw(InvalidRdataLength, "DNSKEY keydata too short");
- }
+ std::string keydata_str;
+ std::string keydata_substr;
+ while (true) {
+ const MasterToken& token =
+ lexer.getNextToken(MasterToken::STRING, true);
+ if ((token.getType() == MasterToken::END_OF_FILE) ||
+ (token.getType() == MasterToken::END_OF_LINE)) {
+ break;
+ }
- impl_ = new DNSKEYImpl(flags, protocol, algorithm, keydata);
-}
+ // token is now assured to be of type STRING.
-DNSKEY::DNSKEY(InputBuffer& buffer, size_t rdata_len) {
- if (rdata_len < 4) {
- isc_throw(InvalidRdataLength, "DNSKEY too short: " << rdata_len);
+ token.getString(keydata_substr);
+ keydata_str.append(keydata_substr);
}
- uint16_t flags = buffer.readUint16();
- uint16_t protocol = buffer.readUint8();
- uint16_t algorithm = buffer.readUint8();
+ lexer.ungetToken();
- rdata_len -= 4;
- vector<uint8_t> keydata(rdata_len);
- buffer.readData(&keydata[0], rdata_len);
+ vector<uint8_t> keydata;
+ // If key data is missing, it's OK. See the API documentation of the
+ // constructor.
+ if (keydata_str.size() > 0) {
+ decodeBase64(keydata_str, keydata);
+ }
- impl_ = new DNSKEYImpl(flags, protocol, algorithm, keydata);
+ return (new DNSKEYImpl(flags, protocol, algorithm, keydata));
}
DNSKEY::DNSKEY(const DNSKEY& source) :
@@ -104,7 +212,7 @@ DNSKEY::DNSKEY(const DNSKEY& source) :
DNSKEY&
DNSKEY::operator=(const DNSKEY& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
@@ -157,11 +265,11 @@ DNSKEY::compare(const Rdata& other) const {
return (impl_->algorithm_ < other_dnskey.impl_->algorithm_ ? -1 : 1);
}
- size_t this_len = impl_->keydata_.size();
- size_t other_len = other_dnskey.impl_->keydata_.size();
- size_t cmplen = min(this_len, other_len);
- int cmp = memcmp(&impl_->keydata_[0], &other_dnskey.impl_->keydata_[0],
- cmplen);
+ const size_t this_len = impl_->keydata_.size();
+ const size_t other_len = other_dnskey.impl_->keydata_.size();
+ const size_t cmplen = min(this_len, other_len);
+ const int cmp = memcmp(&impl_->keydata_[0],
+ &other_dnskey.impl_->keydata_[0], cmplen);
if (cmp != 0) {
return (cmp);
} else {
@@ -172,15 +280,24 @@ DNSKEY::compare(const Rdata& other) const {
uint16_t
DNSKEY::getTag() const {
if (impl_->algorithm_ == 1) {
- int len = impl_->keydata_.size();
+ // See RFC 4034 appendix B.1 for why the key data must contain
+ // at least 4 bytes with RSA/MD5: 3 trailing bytes to extract
+ // the tag from, and 1 byte of exponent length subfield before
+ // modulus.
+ const int len = impl_->keydata_.size();
+ if (len < 4) {
+ isc_throw(isc::OutOfRange,
+ "DNSKEY keydata too short for tag extraction");
+ }
+
return ((impl_->keydata_[len - 3] << 8) + impl_->keydata_[len - 2]);
}
uint32_t ac = impl_->flags_;
ac += (impl_->protocol_ << 8);
ac += impl_->algorithm_;
-
- size_t size = impl_->keydata_.size();
+
+ const size_t size = impl_->keydata_.size();
for (size_t i = 0; i < size; i ++) {
ac += (i & 1) ? impl_->keydata_[i] : (impl_->keydata_[i] << 8);
}
diff --git a/src/lib/dns/rdata/generic/dnskey_48.h b/src/lib/dns/rdata/generic/dnskey_48.h
index 14fad9f..3ef18e6 100644
--- a/src/lib/dns/rdata/generic/dnskey_48.h
+++ b/src/lib/dns/rdata/generic/dnskey_48.h
@@ -20,6 +20,7 @@
#include <dns/rrtype.h>
#include <dns/rrttl.h>
#include <dns/rdata.h>
+#include <dns/master_lexer.h>
// BEGIN_HEADER_GUARD
@@ -42,11 +43,19 @@ public:
///
/// Specialized methods
///
+
+ /// \brief Returns the key tag
+ ///
+ /// \throw isc::OutOfRange if the key data for RSA/MD5 is too short
+ /// to support tag extraction.
uint16_t getTag() const;
+
uint16_t getFlags() const;
uint8_t getAlgorithm() const;
private:
+ DNSKEYImpl* constructFromLexer(isc::dns::MasterLexer& lexer);
+
DNSKEYImpl* impl_;
};
diff --git a/src/lib/dns/rdata/generic/ds_43.cc b/src/lib/dns/rdata/generic/ds_43.cc
index 3492388..21bf8f3 100644
--- a/src/lib/dns/rdata/generic/ds_43.cc
+++ b/src/lib/dns/rdata/generic/ds_43.cc
@@ -50,7 +50,7 @@ DS::DS(const DS& source) :
DS&
DS::operator=(const DS& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/minfo_14.cc b/src/lib/dns/rdata/generic/minfo_14.cc
index aa5272c..58d6f3c 100644
--- a/src/lib/dns/rdata/generic/minfo_14.cc
+++ b/src/lib/dns/rdata/generic/minfo_14.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -21,10 +21,12 @@
#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
using namespace std;
using namespace isc::dns;
using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -39,12 +41,10 @@ using namespace isc::util;
/// An example of valid string is:
/// \code "rmail.example.com. email.example.com." \endcode
///
-/// <b>Exceptions</b>
-///
-/// \exception InvalidRdataText The number of RDATA fields (must be 2) is
+/// \throw InvalidRdataText The number of RDATA fields (must be 2) is
/// incorrect.
-/// \exception std::bad_alloc Memory allocation for names fails.
-/// \exception Other The constructor of the \c Name class will throw if the
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
/// names in the string is invalid.
MINFO::MINFO(const std::string& minfo_str) :
// We cannot construct both names in the initialization list due to the
@@ -52,21 +52,44 @@ MINFO::MINFO(const std::string& minfo_str) :
// name and replace them later.
rmailbox_(Name::ROOT_NAME()), emailbox_(Name::ROOT_NAME())
{
- istringstream iss(minfo_str);
- string rmailbox_str, emailbox_str;
- iss >> rmailbox_str >> emailbox_str;
-
- // Validation: A valid MINFO RR must have exactly two fields.
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid MINFO text: " << minfo_str);
- }
- if (!iss.eof()) {
- isc_throw(InvalidRdataText, "Invalid MINFO text (redundant field): "
- << minfo_str);
+ try {
+ std::istringstream ss(minfo_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ rmailbox_ = createNameFromLexer(lexer, NULL);
+ emailbox_ = createNameFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for MINFO: "
+ << minfo_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct MINFO from '" <<
+ minfo_str << "': " << ex.what());
}
+}
- rmailbox_ = Name(rmailbox_str);
- emailbox_ = Name(emailbox_str);
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an MINFO RDATA. The RMAILBOX and EMAILBOX fields can be non-absolute
+/// if \c origin is non-NULL, in which case \c origin is used to make them
+/// absolute.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and constructors if construction of
+/// textual fields as these objects fail.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin If non NULL, specifies the origin of SERVER when it
+/// is non-absolute.
+MINFO::MINFO(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ rmailbox_(createNameFromLexer(lexer, origin)),
+ emailbox_(createNameFromLexer(lexer, origin))
+{
}
/// \brief Constructor from wire-format data.
@@ -75,8 +98,8 @@ MINFO::MINFO(const std::string& minfo_str) :
/// length) for parsing.
/// If necessary, the caller will check consistency.
///
-/// \exception std::bad_alloc Memory allocation for names fails.
-/// \exception Other The constructor of the \c Name class will throw if the
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
/// names in the wire is invalid.
MINFO::MINFO(InputBuffer& buffer, size_t) :
rmailbox_(buffer), emailbox_(buffer)
@@ -84,7 +107,7 @@ MINFO::MINFO(InputBuffer& buffer, size_t) :
/// \brief Copy constructor.
///
-/// \exception std::bad_alloc Memory allocation fails in copying internal
+/// \throw std::bad_alloc Memory allocation fails in copying internal
/// member variables (this should be very rare).
MINFO::MINFO(const MINFO& other) :
Rdata(), rmailbox_(other.rmailbox_), emailbox_(other.emailbox_)
@@ -95,7 +118,7 @@ MINFO::MINFO(const MINFO& other) :
/// The output of this method is formatted as described in the "from string"
/// constructor (\c MINFO(const std::string&))).
///
-/// \exception std::bad_alloc Internal resource allocation fails.
+/// \throw std::bad_alloc Internal resource allocation fails.
///
/// \return A \c string object that represents the \c MINFO object.
std::string
@@ -105,7 +128,7 @@ MINFO::toText() const {
/// \brief Render the \c MINFO in the wire format without name compression.
///
-/// \exception std::bad_alloc Internal resource allocation fails.
+/// \throw std::bad_alloc Internal resource allocation fails.
///
/// \param buffer An output buffer to store the wire data.
void
@@ -128,7 +151,7 @@ MINFO::operator=(const MINFO& source) {
/// As specified in RFC3597, TYPE MINFO is "well-known", the rmailbox and
/// emailbox fields (domain names) will be compressed.
///
-/// \exception std::bad_alloc Internal resource allocation fails.
+/// \throw std::bad_alloc Internal resource allocation fails.
///
/// \param renderer DNS message rendering context that encapsulates the
/// output buffer and name compression information.
diff --git a/src/lib/dns/rdata/generic/minfo_14.h b/src/lib/dns/rdata/generic/minfo_14.h
index f3ee1d0..ce4586f 100644
--- a/src/lib/dns/rdata/generic/minfo_14.h
+++ b/src/lib/dns/rdata/generic/minfo_14.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -45,7 +45,7 @@ public:
/// \brief Return the value of the rmailbox field.
///
- /// \exception std::bad_alloc If resource allocation for the returned
+ /// \throw std::bad_alloc If resource allocation for the returned
/// \c Name fails.
///
/// \note
@@ -64,7 +64,7 @@ public:
/// \brief Return the value of the emailbox field.
///
- /// \exception std::bad_alloc If resource allocation for the returned
+ /// \throw std::bad_alloc If resource allocation for the returned
/// \c Name fails.
Name getEmailbox() const { return (emailbox_); }
diff --git a/src/lib/dns/rdata/generic/mx_15.cc b/src/lib/dns/rdata/generic/mx_15.cc
index 12ada97..ae9978e 100644
--- a/src/lib/dns/rdata/generic/mx_15.cc
+++ b/src/lib/dns/rdata/generic/mx_15.cc
@@ -68,15 +68,7 @@ MX::MX(const std::string& mx_str) :
MasterLexer lexer;
lexer.pushSource(ss);
- const uint32_t num =
- lexer.getNextToken(MasterToken::NUMBER).getNumber();
- if (num > 65535) {
- isc_throw(InvalidRdataText, "Invalid MX preference in: "
- << mx_str);
- }
- preference_ = static_cast<uint16_t>(num);
-
- mxname_ = createNameFromLexer(lexer, NULL);
+ constructFromLexer(lexer, NULL);
if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
isc_throw(InvalidRdataText, "extra input text for MX: "
@@ -108,8 +100,13 @@ MX::MX(const std::string& mx_str) :
/// is non-absolute.
MX::MX(MasterLexer& lexer, const Name* origin,
MasterLoader::Options, MasterLoaderCallbacks&) :
- preference_(0), mxname_(".")
+ preference_(0), mxname_(Name::ROOT_NAME())
{
+ constructFromLexer(lexer, origin);
+}
+
+void
+MX::constructFromLexer(MasterLexer& lexer, const Name* origin) {
const uint32_t num = lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (num > 65535) {
isc_throw(InvalidRdataText, "Invalid MX preference: " << num);
diff --git a/src/lib/dns/rdata/generic/mx_15.h b/src/lib/dns/rdata/generic/mx_15.h
index 1381f18..84e7112 100644
--- a/src/lib/dns/rdata/generic/mx_15.h
+++ b/src/lib/dns/rdata/generic/mx_15.h
@@ -42,6 +42,9 @@ public:
uint16_t getMXPref() const;
private:
+ void constructFromLexer(isc::dns::MasterLexer& lexer,
+ const isc::dns::Name* origin);
+
/// Note: this is a prototype version; we may reconsider
/// this representation later.
uint16_t preference_;
diff --git a/src/lib/dns/rdata/generic/nsec3_50.cc b/src/lib/dns/rdata/generic/nsec3_50.cc
index fb92246..667f4a4 100644
--- a/src/lib/dns/rdata/generic/nsec3_50.cc
+++ b/src/lib/dns/rdata/generic/nsec3_50.cc
@@ -35,6 +35,8 @@
#include <dns/rdata/generic/detail/nsec_bitmap.h>
#include <dns/rdata/generic/detail/nsec3param_common.h>
+#include <memory>
+
#include <stdio.h>
#include <time.h>
@@ -64,24 +66,86 @@ struct NSEC3Impl {
const vector<uint8_t> typebits_;
};
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid NSEC3 RDATA. There
+/// can be extra space characters at the beginning or end of the
+/// text (which are simply ignored), but other extra text, including
+/// a new line, will make the construction fail with an exception.
+///
+/// The Hash Algorithm, Flags and Iterations fields must be within their
+/// valid ranges. The Salt field may contain "-" to indicate that the
+/// salt is of length 0. The Salt field must not contain any whitespace.
+/// The type mnemonics must be valid, and separated by whitespace. If
+/// any invalid mnemonics are found, InvalidRdataText exception is
+/// thrown.
+///
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param nsec3_str A string containing the RDATA to be created
NSEC3::NSEC3(const std::string& nsec3_str) :
impl_(NULL)
{
- istringstream iss(nsec3_str);
+ // We use auto_ptr here because if there is an exception in this
+ // constructor, the destructor is not called and there could be a
+ // leak of the NSEC3Impl that constructFromLexer() returns.
+ std::auto_ptr<NSEC3Impl> impl_ptr(NULL);
+
+ try {
+ std::istringstream ss(nsec3_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for NSEC3: " << nsec3_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct NSEC3 from '" << nsec3_str << "': "
+ << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an NSEC3 RDATA.
+///
+/// See \c NSEC3::NSEC3(const std::string&) for description of the
+/// expected RDATA fields.
+///
+/// \throw MasterLexer::LexerError General parsing error such as
+/// missing field.
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+NSEC3::NSEC3(MasterLexer& lexer, const Name*, MasterLoader::Options,
+ MasterLoaderCallbacks&) :
+ impl_(NULL)
+{
+ impl_ = constructFromLexer(lexer);
+}
+
+NSEC3Impl*
+NSEC3::constructFromLexer(MasterLexer& lexer) {
vector<uint8_t> salt;
const ParseNSEC3ParamResult params =
- parseNSEC3ParamText("NSEC3", nsec3_str, iss, salt);
+ parseNSEC3ParamFromLexer("NSEC3", lexer, salt);
- // Extract Next hash. It must be an unpadded base32hex string.
- string nexthash;
- iss >> nexthash;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid NSEC3 text: " << nsec3_str);
- }
- assert(!nexthash.empty());
+ const string& nexthash =
+ lexer.getNextToken(MasterToken::STRING).getString();
if (*nexthash.rbegin() == '=') {
- isc_throw(InvalidRdataText, "NSEC3 hash has padding: " << nsec3_str);
+ isc_throw(InvalidRdataText, "NSEC3 hash has padding: " << nexthash);
}
+
vector<uint8_t> next;
decodeBase32Hex(nexthash, next);
if (next.size() > 255) {
@@ -89,22 +153,16 @@ NSEC3::NSEC3(const std::string& nsec3_str) :
<< next.size() << " bytes");
}
- // For NSEC3 empty bitmap is possible and allowed.
- if (iss.eof()) {
- impl_ = new NSEC3Impl(params.algorithm, params.flags,
- params.iterations, salt, next,
- vector<uint8_t>());
- return;
- }
-
vector<uint8_t> typebits;
- buildBitmapsFromText("NSEC3", iss, typebits);
-
- impl_ = new NSEC3Impl(params.algorithm, params.flags, params.iterations,
- salt, next, typebits);
+ // For NSEC3 empty bitmap is possible and allowed.
+ buildBitmapsFromLexer("NSEC3", lexer, typebits, true);
+ return (new NSEC3Impl(params.algorithm, params.flags, params.iterations,
+ salt, next, typebits));
}
-NSEC3::NSEC3(InputBuffer& buffer, size_t rdata_len) {
+NSEC3::NSEC3(InputBuffer& buffer, size_t rdata_len) :
+ impl_(NULL)
+{
vector<uint8_t> salt;
const ParseNSEC3ParamResult params =
parseNSEC3ParamWire("NSEC3", buffer, rdata_len, salt);
@@ -142,7 +200,7 @@ NSEC3::NSEC3(const NSEC3& source) :
NSEC3&
NSEC3::operator=(const NSEC3& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/nsec3_50.h b/src/lib/dns/rdata/generic/nsec3_50.h
index c766ade..6a1dcb5 100644
--- a/src/lib/dns/rdata/generic/nsec3_50.h
+++ b/src/lib/dns/rdata/generic/nsec3_50.h
@@ -21,6 +21,7 @@
#include <dns/rrtype.h>
#include <dns/rrttl.h>
#include <dns/rdata.h>
+#include <dns/master_lexer.h>
// BEGIN_HEADER_GUARD
@@ -47,6 +48,8 @@ public:
const std::vector<uint8_t>& getNext() const;
private:
+ NSEC3Impl* constructFromLexer(isc::dns::MasterLexer& lexer);
+
NSEC3Impl* impl_;
};
diff --git a/src/lib/dns/rdata/generic/nsec3param_51.cc b/src/lib/dns/rdata/generic/nsec3param_51.cc
index 5686353..f07c569 100644
--- a/src/lib/dns/rdata/generic/nsec3param_51.cc
+++ b/src/lib/dns/rdata/generic/nsec3param_51.cc
@@ -22,6 +22,7 @@
#include <boost/lexical_cast.hpp>
+#include <memory>
#include <string>
#include <sstream>
#include <vector>
@@ -46,24 +47,84 @@ struct NSEC3PARAMImpl {
const vector<uint8_t> salt_;
};
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid NSEC3PARAM RDATA. There
+/// can be extra space characters at the beginning or end of the
+/// text (which are simply ignored), but other extra text, including
+/// a new line, will make the construction fail with an exception.
+///
+/// The Hash Algorithm, Flags and Iterations fields must be within their
+/// valid ranges. The Salt field may contain "-" to indicate that the
+/// salt is of length 0. The Salt field must not contain any whitespace.
+///
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param nsec3param_str A string containing the RDATA to be created
NSEC3PARAM::NSEC3PARAM(const std::string& nsec3param_str) :
impl_(NULL)
{
- istringstream iss(nsec3param_str);
+ // We use auto_ptr here because if there is an exception in this
+ // constructor, the destructor is not called and there could be a
+ // leak of the NSEC3PARAMImpl that constructFromLexer() returns.
+ std::auto_ptr<NSEC3PARAMImpl> impl_ptr(NULL);
+
+ try {
+ std::istringstream ss(nsec3param_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Extra input text for NSEC3PARAM: " << nsec3param_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText,
+ "Failed to construct NSEC3PARAM from '" << nsec3param_str
+ << "': " << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual
+/// representation of an NSEC3PARAM RDATA.
+///
+/// See \c NSEC3PARAM::NSEC3PARAM(const std::string&) for description of
+/// the expected RDATA fields.
+///
+/// \throw MasterLexer::LexerError General parsing error such as
+/// missing field.
+/// \throw InvalidRdataText if any fields are out of their valid range,
+/// or are incorrect.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+NSEC3PARAM::NSEC3PARAM(MasterLexer& lexer, const Name*, MasterLoader::Options,
+ MasterLoaderCallbacks&) :
+ impl_(NULL)
+{
+ impl_ = constructFromLexer(lexer);
+}
+
+NSEC3PARAMImpl*
+NSEC3PARAM::constructFromLexer(MasterLexer& lexer) {
vector<uint8_t> salt;
const ParseNSEC3ParamResult params =
- parseNSEC3ParamText("NSEC3PARAM", nsec3param_str, iss, salt);
-
- if (!iss.eof()) {
- isc_throw(InvalidRdataText, "Invalid NSEC3PARAM (redundant text): "
- << nsec3param_str);
- }
+ parseNSEC3ParamFromLexer("NSEC3PARAM", lexer, salt);
- impl_ = new NSEC3PARAMImpl(params.algorithm, params.flags,
- params.iterations, salt);
+ return (new NSEC3PARAMImpl(params.algorithm, params.flags,
+ params.iterations, salt));
}
-NSEC3PARAM::NSEC3PARAM(InputBuffer& buffer, size_t rdata_len) {
+NSEC3PARAM::NSEC3PARAM(InputBuffer& buffer, size_t rdata_len) :
+ impl_(NULL)
+{
vector<uint8_t> salt;
const ParseNSEC3ParamResult params =
parseNSEC3ParamWire("NSEC3PARAM", buffer, rdata_len, salt);
@@ -78,7 +139,7 @@ NSEC3PARAM::NSEC3PARAM(const NSEC3PARAM& source) :
NSEC3PARAM&
NSEC3PARAM::operator=(const NSEC3PARAM& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/nsec3param_51.h b/src/lib/dns/rdata/generic/nsec3param_51.h
index 130c759..bd2ce75 100644
--- a/src/lib/dns/rdata/generic/nsec3param_51.h
+++ b/src/lib/dns/rdata/generic/nsec3param_51.h
@@ -21,6 +21,7 @@
#include <dns/rrtype.h>
#include <dns/rrttl.h>
#include <dns/rdata.h>
+#include <dns/master_lexer.h>
// BEGIN_HEADER_GUARD
@@ -47,7 +48,10 @@ public:
uint8_t getFlags() const;
uint16_t getIterations() const;
const std::vector<uint8_t>& getSalt() const;
+
private:
+ NSEC3PARAMImpl* constructFromLexer(isc::dns::MasterLexer& lexer);
+
NSEC3PARAMImpl* impl_;
};
diff --git a/src/lib/dns/rdata/generic/nsec_47.cc b/src/lib/dns/rdata/generic/nsec_47.cc
index c474b02..c2dca32 100644
--- a/src/lib/dns/rdata/generic/nsec_47.cc
+++ b/src/lib/dns/rdata/generic/nsec_47.cc
@@ -136,15 +136,17 @@ NSEC::NSEC(InputBuffer& buffer, size_t rdata_len) {
///
/// \param lexer A \c MasterLexer object parsing a master file for the
/// RDATA to be created
+/// \param origin The origin to use with a relative Next Domain Name
+/// field
NSEC::NSEC(MasterLexer& lexer, const Name* origin, MasterLoader::Options,
MasterLoaderCallbacks&)
{
- const Name origin_name(createNameFromLexer(lexer, origin));
+ const Name next_name(createNameFromLexer(lexer, origin));
vector<uint8_t> typebits;
buildBitmapsFromLexer("NSEC", lexer, typebits);
- impl_ = new NSECImpl(origin_name, typebits);
+ impl_ = new NSECImpl(next_name, typebits);
}
NSEC::NSEC(const NSEC& source) :
@@ -153,7 +155,7 @@ NSEC::NSEC(const NSEC& source) :
NSEC&
NSEC::operator=(const NSEC& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/opt_41.cc b/src/lib/dns/rdata/generic/opt_41.cc
index d64effb..136bdf9 100644
--- a/src/lib/dns/rdata/generic/opt_41.cc
+++ b/src/lib/dns/rdata/generic/opt_41.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -27,10 +27,26 @@ using namespace isc::util;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
+/// \brief Constructor from string.
+///
+/// This constructor cannot be used, and always throws an exception.
+///
+/// \throw InvalidRdataText OPT RR cannot be constructed from text.
OPT::OPT(const std::string&) {
isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
}
+/// \brief Constructor with a context of MasterLexer.
+///
+/// This constructor cannot be used, and always throws an exception.
+///
+/// \throw InvalidRdataText OPT RR cannot be constructed from text.
+OPT::OPT(MasterLexer&, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&)
+{
+ isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text");
+}
+
OPT::OPT(InputBuffer& buffer, size_t rdata_len) {
// setPosition() will throw against a short buffer anyway, but it's safer
// to check it explicitly here.
@@ -48,6 +64,7 @@ OPT::OPT(const OPT&) : Rdata() {
std::string
OPT::toText() const {
+ // OPT records do not have a text format.
return ("");
}
diff --git a/src/lib/dns/rdata/generic/rp_17.cc b/src/lib/dns/rdata/generic/rp_17.cc
index 781b55d..d3d86fd 100644
--- a/src/lib/dns/rdata/generic/rp_17.cc
+++ b/src/lib/dns/rdata/generic/rp_17.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -21,10 +21,12 @@
#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
using namespace std;
using namespace isc::dns;
using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -36,32 +38,54 @@ using namespace isc::util;
/// \endcode
/// where both fields must represent a valid domain name.
///
-/// \exception InvalidRdataText The number of RDATA fields (must be 2) is
+/// \throw InvalidRdataText The number of RDATA fields (must be 2) is
/// incorrect.
-/// \exception Other The constructor of the \c Name class will throw if the
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
/// given name is invalid.
-/// \exception std::bad_alloc Memory allocation for names fails.
RP::RP(const std::string& rp_str) :
// We cannot construct both names in the initialization list due to the
// necessary text processing, so we have to initialize them with a dummy
// name and replace them later.
mailbox_(Name::ROOT_NAME()), text_(Name::ROOT_NAME())
{
- istringstream iss(rp_str);
- string mailbox_str, text_str;
- iss >> mailbox_str >> text_str;
-
- // Validation: A valid RP RR must have exactly two fields.
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid RP text: " << rp_str);
- }
- if (!iss.eof()) {
- isc_throw(InvalidRdataText, "Invalid RP text (redundant field): "
- << rp_str);
+ try {
+ std::istringstream ss(rp_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ mailbox_ = createNameFromLexer(lexer, NULL);
+ text_ = createNameFromLexer(lexer, NULL);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for RP: "
+ << rp_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct RP from '" <<
+ rp_str << "': " << ex.what());
}
+}
- mailbox_ = Name(mailbox_str);
- text_ = Name(text_str);
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an RP RDATA. The MAILBOX and TEXT fields can be non-absolute if \c
+/// origin is non-NULL, in which case \c origin is used to make them absolute.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and constructors if construction of
+/// textual fields as these objects fail.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin If non NULL, specifies the origin of SERVER when it
+/// is non-absolute.
+RP::RP(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ mailbox_(createNameFromLexer(lexer, origin)),
+ text_(createNameFromLexer(lexer, origin))
+{
}
/// \brief Constructor from wire-format data.
@@ -70,15 +94,15 @@ RP::RP(const std::string& rp_str) :
/// length) for parsing.
/// If necessary, the caller will check consistency.
///
-/// \exception std::bad_alloc Memory allocation for names fails.
-/// \exception Other The constructor of the \c Name class will throw if the
+/// \throw std::bad_alloc Memory allocation for names fails.
+/// \throw Other The constructor of the \c Name class will throw if the
/// names in the wire is invalid.
RP::RP(InputBuffer& buffer, size_t) : mailbox_(buffer), text_(buffer) {
}
/// \brief Copy constructor.
///
-/// \exception std::bad_alloc Memory allocation fails in copying internal
+/// \throw std::bad_alloc Memory allocation fails in copying internal
/// member variables (this should be very rare).
RP::RP(const RP& other) :
Rdata(), mailbox_(other.mailbox_), text_(other.text_)
@@ -89,7 +113,7 @@ RP::RP(const RP& other) :
/// The output of this method is formatted as described in the "from string"
/// constructor (\c RP(const std::string&))).
///
-/// \exception std::bad_alloc Internal resource allocation fails.
+/// \throw std::bad_alloc Internal resource allocation fails.
///
/// \return A \c string object that represents the \c RP object.
std::string
@@ -97,20 +121,36 @@ RP::toText() const {
return (mailbox_.toText() + " " + text_.toText());
}
+/// \brief Render the \c RP in the wire format without name compression.
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \param buffer An output buffer to store the wire data.
void
RP::toWire(OutputBuffer& buffer) const {
mailbox_.toWire(buffer);
text_.toWire(buffer);
}
+/// \brief Render the \c RP in the wire format with taking into account
+/// compression.
+///
+// Type RP is not "well-known", and name compression must be disabled
+// per RFC3597.
+///
+/// \throw std::bad_alloc Internal resource allocation fails.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer and name compression information.
void
RP::toWire(AbstractMessageRenderer& renderer) const {
- // Type RP is not "well-known", and name compression must be disabled
- // per RFC3597.
renderer.writeName(mailbox_, false);
renderer.writeName(text_, false);
}
+/// \brief Compare two instances of \c RP RDATA.
+///
+/// See documentation in \c Rdata.
int
RP::compare(const Rdata& other) const {
const RP& other_rp = dynamic_cast<const RP&>(other);
diff --git a/src/lib/dns/rdata/generic/rp_17.h b/src/lib/dns/rdata/generic/rp_17.h
index a90a530..2fb89df 100644
--- a/src/lib/dns/rdata/generic/rp_17.h
+++ b/src/lib/dns/rdata/generic/rp_17.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -49,9 +49,8 @@ public:
/// \brief Return the value of the mailbox field.
///
- /// This method normally does not throw an exception, but if resource
- /// allocation for the returned \c Name object fails, a corresponding
- /// standard exception will be thrown.
+ /// \throw std::bad_alloc If resource allocation for the returned
+ /// \c Name fails.
///
/// \note
/// Unlike the case of some other RDATA classes (such as
@@ -69,9 +68,8 @@ public:
/// \brief Return the value of the text field.
///
- /// This method normally does not throw an exception, but if resource
- /// allocation for the returned \c Name object fails, a corresponding
- /// standard exception will be thrown.
+ /// \throw std::bad_alloc If resource allocation for the returned
+ /// \c Name fails.
Name getText() const { return (text_); }
private:
diff --git a/src/lib/dns/rdata/generic/rrsig_46.cc b/src/lib/dns/rdata/generic/rrsig_46.cc
index e0137b9..505c388 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.cc
+++ b/src/lib/dns/rdata/generic/rrsig_46.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -26,9 +26,9 @@
#include <dns/messagerenderer.h>
#include <dns/name.h>
#include <dns/rrtype.h>
-#include <dns/rrttl.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
#include <stdio.h>
#include <time.h>
@@ -36,6 +36,7 @@
using namespace std;
using namespace isc::util;
using namespace isc::util::encode;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -52,8 +53,8 @@ const size_t RRSIG_MINIMUM_LEN = 2 * sizeof(uint8_t) + 2 * sizeof(uint16_t) +
struct RRSIGImpl {
// straightforward representation of RRSIG RDATA fields
RRSIGImpl(const RRType& covered, uint8_t algorithm, uint8_t labels,
- uint32_t originalttl, uint32_t timeexpire, uint32_t timeinception,
- uint16_t tag, const Name& signer,
+ uint32_t originalttl, uint32_t timeexpire,
+ uint32_t timeinception, uint16_t tag, const Name& signer,
const vector<uint8_t>& signature) :
covered_(covered), algorithm_(algorithm), labels_(labels),
originalttl_(originalttl), timeexpire_(timeexpire),
@@ -72,38 +73,125 @@ struct RRSIGImpl {
const vector<uint8_t> signature_;
};
-RRSIG::RRSIG(const std::string& rrsig_str) :
- impl_(NULL)
-{
- istringstream iss(rrsig_str);
- string covered_txt, signer_txt, expire_txt, inception_txt;
- unsigned int algorithm, labels;
- uint32_t originalttl;
- uint16_t tag;
- stringbuf signaturebuf;
-
- iss >> covered_txt >> algorithm >> labels >> originalttl
- >> expire_txt >> inception_txt >> tag >> signer_txt
- >> &signaturebuf;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid RRSIG text");
- }
+// helper function for string and lexer constructors
+RRSIGImpl*
+RRSIG::constructFromLexer(MasterLexer& lexer, const Name* origin) {
+ const RRType covered(lexer.getNextToken(MasterToken::STRING).getString());
+ const uint32_t algorithm =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (algorithm > 0xff) {
isc_throw(InvalidRdataText, "RRSIG algorithm out of range");
}
+ const uint32_t labels =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
if (labels > 0xff) {
isc_throw(InvalidRdataText, "RRSIG labels out of range");
}
-
- const uint32_t timeexpire = timeFromText32(expire_txt);
- const uint32_t timeinception = timeFromText32(inception_txt);
+ const uint32_t originalttl =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ const uint32_t timeexpire =
+ timeFromText32(lexer.getNextToken(MasterToken::STRING).getString());
+ const uint32_t timeinception =
+ timeFromText32(lexer.getNextToken(MasterToken::STRING).getString());
+ const uint32_t tag =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (tag > 0xffff) {
+ isc_throw(InvalidRdataText, "RRSIG key tag out of range");
+ }
+ const Name& signer = createNameFromLexer(lexer, origin);
+
+ string signature_txt;
+ string signature_part;
+ // Whitespace is allowed within base64 text, so read to the end of input.
+ while (true) {
+ const MasterToken& token =
+ lexer.getNextToken(MasterToken::STRING, true);
+ if ((token.getType() == MasterToken::END_OF_FILE) ||
+ (token.getType() == MasterToken::END_OF_LINE)) {
+ break;
+ }
+ token.getString(signature_part);
+ signature_txt.append(signature_part);
+ }
+ lexer.ungetToken();
vector<uint8_t> signature;
- decodeBase64(signaturebuf.str(), signature);
+ // missing signature is okay
+ if (signature_txt.size() > 0) {
+ decodeBase64(signature_txt, signature);
+ }
- impl_ = new RRSIGImpl(RRType(covered_txt), algorithm, labels,
- originalttl, timeexpire, timeinception, tag,
- Name(signer_txt), signature);
+ return (new RRSIGImpl(covered, algorithm, labels,
+ originalttl, timeexpire, timeinception,
+ static_cast<uint16_t>(tag), signer, signature));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid RRSIG RDATA. There can be extra
+/// space characters at the beginning or end of the text (which are simply
+/// ignored), but other extra text, including a new line, will make the
+/// construction fail with an exception.
+///
+/// The Signer's Name must be absolute since there's no parameter that
+/// specifies the origin name; if this is not absolute, \c MissingNameOrigin
+/// exception will be thrown. This must not be represented as a quoted
+/// string.
+///
+/// See the construction that takes \c MasterLexer for other fields.
+///
+/// \throw Others Exception from the Name constructor.
+/// \throw InvalidRdataText Other general syntax errors.
+RRSIG::RRSIG(const std::string& rrsig_str) :
+ impl_(NULL)
+{
+ // We use auto_ptr here because if there is an exception in this
+ // constructor, the destructor is not called and there could be a
+ // leak of the RRSIGImpl that constructFromLexer() returns.
+ std::auto_ptr<RRSIGImpl> impl_ptr(NULL);
+
+ try {
+ std::istringstream iss(rrsig_str);
+ MasterLexer lexer;
+ lexer.pushSource(iss);
+
+ impl_ptr.reset(constructFromLexer(lexer, NULL));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for RRSIG: "
+ << rrsig_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct RRSIG from '" <<
+ rrsig_str << "': " << ex.what());
+ }
+
+ impl_ = impl_ptr.release();
+}
+
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an RRSIG RDATA. The Signer's Name fields can be non absolute if \c
+/// origin is non NULL, in which case \c origin is used to make it absolute.
+/// This must not be represented as a quoted string.
+///
+/// The Original TTL field is a valid decimal representation of an unsigned
+/// 32-bit integer. Note that alternate textual representations of \c RRTTL,
+/// such as "1H" for 3600 seconds, are not allowed here.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name constructor if
+/// construction of textual fields as these objects fail.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin If non NULL, specifies the origin of Signer's Name when
+/// it is non absolute.
+RRSIG::RRSIG(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(constructFromLexer(lexer, origin))
+{
}
RRSIG::RRSIG(InputBuffer& buffer, size_t rdata_len) {
@@ -142,7 +230,7 @@ RRSIG::RRSIG(const RRSIG& source) :
RRSIG&
RRSIG::operator=(const RRSIG& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/rrsig_46.h b/src/lib/dns/rdata/generic/rrsig_46.h
index b32c17f..de72f64 100644
--- a/src/lib/dns/rdata/generic/rrsig_46.h
+++ b/src/lib/dns/rdata/generic/rrsig_46.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -18,7 +18,6 @@
#include <dns/name.h>
#include <dns/rrtype.h>
-#include <dns/rrttl.h>
#include <dns/rdata.h>
// BEGIN_HEADER_GUARD
@@ -32,6 +31,12 @@
struct RRSIGImpl;
+/// \brief \c rdata::RRSIG class represents the RRSIG RDATA as defined %in
+/// RFC4034.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// RRSIG RDATA.
class RRSIG : public Rdata {
public:
// BEGIN_COMMON_MEMBERS
@@ -42,6 +47,9 @@ public:
// specialized methods
const RRType& typeCovered() const;
private:
+ // helper function for string and lexer constructors
+ RRSIGImpl* constructFromLexer(MasterLexer& lexer, const Name* origin);
+
RRSIGImpl* impl_;
};
diff --git a/src/lib/dns/rdata/generic/spf_99.cc b/src/lib/dns/rdata/generic/spf_99.cc
index 4bf24e9..17c4e3c 100644
--- a/src/lib/dns/rdata/generic/spf_99.cc
+++ b/src/lib/dns/rdata/generic/spf_99.cc
@@ -42,7 +42,7 @@ using namespace isc::util;
/// This method never throws an exception otherwise.
SPF&
SPF::operator=(const SPF& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/generic/sshfp_44.cc b/src/lib/dns/rdata/generic/sshfp_44.cc
index 64b3cc1..2865ed1 100644
--- a/src/lib/dns/rdata/generic/sshfp_44.cc
+++ b/src/lib/dns/rdata/generic/sshfp_44.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -35,122 +35,239 @@ using namespace isc::util::encode;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-SSHFP::SSHFP(InputBuffer& buffer, size_t rdata_len) {
- if (rdata_len < 2) {
- isc_throw(InvalidRdataLength, "SSHFP record too short");
+struct SSHFPImpl {
+ // straightforward representation of SSHFP RDATA fields
+ SSHFPImpl(uint8_t algorithm, uint8_t fingerprint_type,
+ vector<uint8_t>& fingerprint) :
+ algorithm_(algorithm),
+ fingerprint_type_(fingerprint_type),
+ fingerprint_(fingerprint)
+ {}
+
+ uint8_t algorithm_;
+ uint8_t fingerprint_type_;
+ const vector<uint8_t> fingerprint_;
+};
+
+// helper function for string and lexer constructors
+SSHFPImpl*
+SSHFP::constructFromLexer(MasterLexer& lexer) {
+ const uint32_t algorithm =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (algorithm > 255) {
+ isc_throw(InvalidRdataText, "SSHFP algorithm number out of range");
}
- algorithm_ = buffer.readUint8();
- fingerprint_type_ = buffer.readUint8();
-
- rdata_len -= 2;
- if (rdata_len > 0) {
- fingerprint_.resize(rdata_len);
- buffer.readData(&fingerprint_[0], rdata_len);
+ const uint32_t fingerprint_type =
+ lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ if (fingerprint_type > 255) {
+ isc_throw(InvalidRdataText, "SSHFP fingerprint type out of range");
}
-}
-
-SSHFP::SSHFP(const std::string& sshfp_str) {
- std::istringstream iss(sshfp_str);
- std::stringbuf fingerprintbuf;
- uint32_t algorithm, fingerprint_type;
- iss >> algorithm >> fingerprint_type;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid SSHFP text");
+ std::string fingerprint_str;
+ std::string fingerprint_substr;
+ while (true) {
+ const MasterToken& token =
+ lexer.getNextToken(MasterToken::STRING, true);
+ if ((token.getType() == MasterToken::END_OF_FILE) ||
+ (token.getType() == MasterToken::END_OF_LINE)) {
+ break;
+ }
+ token.getString(fingerprint_substr);
+ fingerprint_str.append(fingerprint_substr);
}
+ lexer.ungetToken();
- if (algorithm > 255) {
- isc_throw(InvalidRdataText, "SSHFP algorithm number out of range");
+ vector<uint8_t> fingerprint;
+ // If fingerprint is missing, it's OK. See the API documentation of the
+ // constructor.
+ if (fingerprint_str.size() > 0) {
+ decodeHex(fingerprint_str, fingerprint);
}
- if (fingerprint_type > 255) {
- isc_throw(InvalidRdataText, "SSHFP fingerprint type out of range");
- }
+ return (new SSHFPImpl(algorithm, fingerprint_type, fingerprint));
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid SSHFP RDATA. There can be
+/// extra space characters at the beginning or end of the text (which
+/// are simply ignored), but other extra text, including a new line,
+/// will make the construction fail with an exception.
+///
+/// The Algorithm and Fingerprint Type fields must be within their valid
+/// ranges, but are not constrained to the values defined in RFC4255.
+///
+/// The Fingerprint field may be absent, but if present it must contain a
+/// valid hex encoding of the fingerprint. For compatibility with BIND 9,
+/// whitespace is allowed in the hex text (RFC4255 is silent on the matter).
+///
+/// \throw InvalidRdataText if any fields are missing, out of their valid
+/// ranges, or incorrect.
+///
+/// \param sshfp_str A string containing the RDATA to be created
+SSHFP::SSHFP(const string& sshfp_str) :
+ impl_(NULL)
+{
+ // We use auto_ptr here because if there is an exception in this
+ // constructor, the destructor is not called and there could be a
+ // leak of the SSHFPImpl that constructFromLexer() returns.
+ std::auto_ptr<SSHFPImpl> impl_ptr(NULL);
- iss >> &fingerprintbuf;
try {
- decodeHex(fingerprintbuf.str(), fingerprint_);
+ std::istringstream ss(sshfp_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ impl_ptr.reset(constructFromLexer(lexer));
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for SSHFP: "
+ << sshfp_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct SSHFP from '" <<
+ sshfp_str << "': " << ex.what());
} catch (const isc::BadValue& e) {
isc_throw(InvalidRdataText,
"Bad SSHFP fingerprint: " << e.what());
}
- algorithm_ = algorithm;
- fingerprint_type_ = fingerprint_type;
+ impl_ = impl_ptr.release();
}
-SSHFP::SSHFP(uint8_t algorithm, uint8_t fingerprint_type,
- const std::string& fingerprint)
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an SSHFP RDATA.
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw InvalidRdataText Fields are out of their valid range, or are
+/// incorrect.
+/// \throw BadValue Fingerprint is not a valid hex string.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+SSHFP::SSHFP(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(constructFromLexer(lexer))
{
- algorithm_ = algorithm;
- fingerprint_type_ = fingerprint_type;
+}
+
+/// \brief Constructor from InputBuffer.
+///
+/// The passed buffer must contain a valid SSHFP RDATA.
+///
+/// The Algorithm and Fingerprint Type fields are not checked for unknown
+/// values. It is okay for the fingerprint data to be missing (see the
+/// description of the constructor from string).
+SSHFP::SSHFP(InputBuffer& buffer, size_t rdata_len) {
+ if (rdata_len < 2) {
+ isc_throw(InvalidRdataLength, "SSHFP record too short");
+ }
+
+ const uint8_t algorithm = buffer.readUint8();
+ const uint8_t fingerprint_type = buffer.readUint8();
+
+ vector<uint8_t> fingerprint;
+ rdata_len -= 2;
+ if (rdata_len > 0) {
+ fingerprint.resize(rdata_len);
+ buffer.readData(&fingerprint[0], rdata_len);
+ }
+
+ impl_ = new SSHFPImpl(algorithm, fingerprint_type, fingerprint);
+}
+SSHFP::SSHFP(uint8_t algorithm, uint8_t fingerprint_type,
+ const string& fingerprint_txt) :
+ impl_(NULL)
+{
+ vector<uint8_t> fingerprint;
try {
- decodeHex(fingerprint, fingerprint_);
+ decodeHex(fingerprint_txt, fingerprint);
} catch (const isc::BadValue& e) {
isc_throw(InvalidRdataText, "Bad SSHFP fingerprint: " << e.what());
}
+
+ impl_ = new SSHFPImpl(algorithm, fingerprint_type, fingerprint);
}
SSHFP::SSHFP(const SSHFP& other) :
- Rdata(), algorithm_(other.algorithm_),
- fingerprint_type_(other.fingerprint_type_),
- fingerprint_(other.fingerprint_)
+ Rdata(), impl_(new SSHFPImpl(*other.impl_))
{}
+SSHFP&
+SSHFP::operator=(const SSHFP& source) {
+ if (this == &source) {
+ return (*this);
+ }
+
+ SSHFPImpl* newimpl = new SSHFPImpl(*source.impl_);
+ delete impl_;
+ impl_ = newimpl;
+
+ return (*this);
+}
+
+SSHFP::~SSHFP() {
+ delete impl_;
+}
+
void
SSHFP::toWire(OutputBuffer& buffer) const {
- buffer.writeUint8(algorithm_);
- buffer.writeUint8(fingerprint_type_);
+ buffer.writeUint8(impl_->algorithm_);
+ buffer.writeUint8(impl_->fingerprint_type_);
- if (!fingerprint_.empty()) {
- buffer.writeData(&fingerprint_[0], fingerprint_.size());
+ if (!impl_->fingerprint_.empty()) {
+ buffer.writeData(&impl_->fingerprint_[0],
+ impl_->fingerprint_.size());
}
}
void
SSHFP::toWire(AbstractMessageRenderer& renderer) const {
- renderer.writeUint8(algorithm_);
- renderer.writeUint8(fingerprint_type_);
+ renderer.writeUint8(impl_->algorithm_);
+ renderer.writeUint8(impl_->fingerprint_type_);
- if (!fingerprint_.empty()) {
- renderer.writeData(&fingerprint_[0], fingerprint_.size());
+ if (!impl_->fingerprint_.empty()) {
+ renderer.writeData(&impl_->fingerprint_[0],
+ impl_->fingerprint_.size());
}
}
string
SSHFP::toText() const {
- return (lexical_cast<string>(static_cast<int>(algorithm_)) +
- " " + lexical_cast<string>(static_cast<int>(fingerprint_type_)) +
- (fingerprint_.empty() ? "" : " " + encodeHex(fingerprint_)));
+ return (lexical_cast<string>(static_cast<int>(impl_->algorithm_)) + " " +
+ lexical_cast<string>(static_cast<int>(impl_->fingerprint_type_)) +
+ (impl_->fingerprint_.empty() ? "" :
+ " " + encodeHex(impl_->fingerprint_)));
}
int
SSHFP::compare(const Rdata& other) const {
const SSHFP& other_sshfp = dynamic_cast<const SSHFP&>(other);
- /* This doesn't really make any sort of sense, but in the name of
- consistency... */
-
- if (algorithm_ < other_sshfp.algorithm_) {
+ if (impl_->algorithm_ < other_sshfp.impl_->algorithm_) {
return (-1);
- } else if (algorithm_ > other_sshfp.algorithm_) {
+ } else if (impl_->algorithm_ > other_sshfp.impl_->algorithm_) {
return (1);
}
- if (fingerprint_type_ < other_sshfp.fingerprint_type_) {
+ if (impl_->fingerprint_type_ < other_sshfp.impl_->fingerprint_type_) {
return (-1);
- } else if (fingerprint_type_ > other_sshfp.fingerprint_type_) {
+ } else if (impl_->fingerprint_type_ >
+ other_sshfp.impl_->fingerprint_type_) {
return (1);
}
- const size_t this_len = fingerprint_.size();
- const size_t other_len = other_sshfp.fingerprint_.size();
+ const size_t this_len = impl_->fingerprint_.size();
+ const size_t other_len = other_sshfp.impl_->fingerprint_.size();
const size_t cmplen = min(this_len, other_len);
if (cmplen > 0) {
- const int cmp = memcmp(&fingerprint_[0], &other_sshfp.fingerprint_[0],
+ const int cmp = memcmp(&impl_->fingerprint_[0],
+ &other_sshfp.impl_->fingerprint_[0],
cmplen);
if (cmp != 0) {
return (cmp);
@@ -168,17 +285,17 @@ SSHFP::compare(const Rdata& other) const {
uint8_t
SSHFP::getAlgorithmNumber() const {
- return (algorithm_);
+ return (impl_->algorithm_);
}
uint8_t
SSHFP::getFingerprintType() const {
- return (fingerprint_type_);
+ return (impl_->fingerprint_type_);
}
size_t
SSHFP::getFingerprintLen() const {
- return (fingerprint_.size());
+ return (impl_->fingerprint_.size());
}
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/sshfp_44.h b/src/lib/dns/rdata/generic/sshfp_44.h
index d9ebea4..28ce0f3 100644
--- a/src/lib/dns/rdata/generic/sshfp_44.h
+++ b/src/lib/dns/rdata/generic/sshfp_44.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -28,12 +28,17 @@
// BEGIN_RDATA_NAMESPACE
+struct SSHFPImpl;
+
class SSHFP : public Rdata {
public:
// BEGIN_COMMON_MEMBERS
// END_COMMON_MEMBERS
- SSHFP(uint8_t algorithm, uint8_t fingerprint_type, const std::string& fingerprint);
+ SSHFP(uint8_t algorithm, uint8_t fingerprint_type,
+ const std::string& fingerprint);
+ SSHFP& operator=(const SSHFP& source);
+ ~SSHFP();
///
/// Specialized methods
@@ -43,11 +48,9 @@ public:
size_t getFingerprintLen() const;
private:
- /// Note: this is a prototype version; we may reconsider
- /// this representation later.
- uint8_t algorithm_;
- uint8_t fingerprint_type_;
- std::vector<uint8_t> fingerprint_;
+ SSHFPImpl* constructFromLexer(MasterLexer& lexer);
+
+ SSHFPImpl* impl_;
};
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/txt_16.cc b/src/lib/dns/rdata/generic/txt_16.cc
index 1bd2eb1..ff5d0e1 100644
--- a/src/lib/dns/rdata/generic/txt_16.cc
+++ b/src/lib/dns/rdata/generic/txt_16.cc
@@ -33,7 +33,7 @@ using namespace isc::util;
TXT&
TXT::operator=(const TXT& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rdata/hs_4/a_1.cc b/src/lib/dns/rdata/hs_4/a_1.cc
index 3d13a9e..5e2852f 100644
--- a/src/lib/dns/rdata/hs_4/a_1.cc
+++ b/src/lib/dns/rdata/hs_4/a_1.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -31,6 +31,12 @@ A::A(const std::string&) {
// TBD
}
+A::A(MasterLexer&, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&)
+{
+ // TBD
+}
+
A::A(InputBuffer&, size_t) {
// TBD
}
diff --git a/src/lib/dns/rdata/in_1/dhcid_49.cc b/src/lib/dns/rdata/in_1/dhcid_49.cc
index 7745161..184866c 100644
--- a/src/lib/dns/rdata/in_1/dhcid_49.cc
+++ b/src/lib/dns/rdata/in_1/dhcid_49.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -15,8 +15,6 @@
#include <stdint.h>
#include <string.h>
-#include <string>
-
#include <exceptions/exceptions.h>
#include <util/buffer.h>
@@ -28,53 +26,77 @@
using namespace std;
using namespace isc::util;
+using namespace isc::util::encode;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
+void
+DHCID::constructFromLexer(MasterLexer& lexer) {
+ string digest_txt = lexer.getNextToken(MasterToken::STRING).getString();
+
+ // Whitespace is allowed within base64 text, so read to the end of input.
+ string digest_part;
+ while (true) {
+ const MasterToken& token =
+ lexer.getNextToken(MasterToken::STRING, true);
+ if ((token.getType() == MasterToken::END_OF_FILE) ||
+ (token.getType() == MasterToken::END_OF_LINE)) {
+ break;
+ }
+ token.getString(digest_part);
+ digest_txt.append(digest_part);
+ }
+ lexer.ungetToken();
+
+ decodeBase64(digest_txt, digest_);
+}
+
/// \brief Constructor from string.
///
/// \param dhcid_str A base-64 representation of the DHCID binary data.
-/// The data is considered to be opaque, but a sanity check is performed.
-///
-/// <b>Exceptions</b>
///
-/// \c dhcid_str must be a valid BASE-64 string, otherwise an exception
-/// of class \c isc::BadValue will be thrown;
-/// the binary data should consist of at leat of 3 octets as per RFC4701:
-/// < 2 octets > Identifier type code
-/// < 1 octet > Digest type code
-/// < n octets > Digest (length depends on digest type)
-/// If the data is less than 3 octets (i.e. it cannot contain id type code and
-/// digest type code), an exception of class \c InvalidRdataLength is thrown.
+/// \throw InvalidRdataText if the string could not be parsed correctly.
DHCID::DHCID(const std::string& dhcid_str) {
- istringstream iss(dhcid_str);
- stringbuf digestbuf;
-
- iss >> &digestbuf;
- isc::util::encode::decodeBase64(digestbuf.str(), digest_);
-
- // RFC4701 states DNS software should consider the RDATA section to
- // be opaque, but there must be at least three bytes in the data:
- // < 2 octets > Identifier type code
- // < 1 octet > Digest type code
- if (digest_.size() < 3) {
- isc_throw(InvalidRdataLength, "DHCID length " << digest_.size() <<
- " too short, need at least 3 bytes");
+ try {
+ std::istringstream iss(dhcid_str);
+ MasterLexer lexer;
+ lexer.pushSource(iss);
+
+ constructFromLexer(lexer);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for DHCID: "
+ << dhcid_str);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct DHCID from '" <<
+ dhcid_str << "': " << ex.what());
}
}
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of a DHCID RDATA.
+///
+/// \throw BadValue if the text is not valid base-64.
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+DHCID::DHCID(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) {
+ constructFromLexer(lexer);
+}
+
/// \brief Constructor from wire-format data.
///
/// \param buffer A buffer storing the wire format data.
/// \param rdata_len The length of the RDATA in bytes
-///
-/// <b>Exceptions</b>
-/// \c InvalidRdataLength is thrown if \c rdata_len is than minimum of 3 octets
DHCID::DHCID(InputBuffer& buffer, size_t rdata_len) {
- if (rdata_len < 3) {
- isc_throw(InvalidRdataLength, "DHCID length " << rdata_len <<
- " too short, need at least 3 bytes");
+ if (rdata_len == 0) {
+ isc_throw(InvalidRdataLength, "Missing DHCID rdata");
}
digest_.resize(rdata_len);
@@ -112,7 +134,7 @@ DHCID::toWire(AbstractMessageRenderer& renderer) const {
/// \return A string representation of \c DHCID.
string
DHCID::toText() const {
- return (isc::util::encode::encodeBase64(digest_));
+ return (encodeBase64(digest_));
}
/// \brief Compare two instances of \c DHCID RDATA.
diff --git a/src/lib/dns/rdata/in_1/dhcid_49.h b/src/lib/dns/rdata/in_1/dhcid_49.h
index 90f5fab..81041b0 100644
--- a/src/lib/dns/rdata/in_1/dhcid_49.h
+++ b/src/lib/dns/rdata/in_1/dhcid_49.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -43,6 +43,9 @@ public:
const std::vector<uint8_t>& getDigest() const;
private:
+ // helper for string and lexer constructors
+ void constructFromLexer(MasterLexer& lexer);
+
/// \brief Private data representation
///
/// Opaque data at least 3 octets long as per RFC4701.
diff --git a/src/lib/dns/rdata/in_1/srv_33.cc b/src/lib/dns/rdata/in_1/srv_33.cc
index ac62071..fdb8f22 100644
--- a/src/lib/dns/rdata/in_1/srv_33.cc
+++ b/src/lib/dns/rdata/in_1/srv_33.cc
@@ -190,7 +190,7 @@ SRV::SRV(const SRV& source) :
SRV&
SRV::operator=(const SRV& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/rrparamregistry-placeholder.cc b/src/lib/dns/rrparamregistry-placeholder.cc
index 5960759..ae735bc 100644
--- a/src/lib/dns/rrparamregistry-placeholder.cc
+++ b/src/lib/dns/rrparamregistry-placeholder.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -41,35 +41,6 @@ using namespace isc::dns::rdata;
namespace isc {
namespace dns {
-namespace rdata {
-
-RdataPtr
-AbstractRdataFactory::create(MasterLexer& lexer, const Name*,
- MasterLoader::Options,
- MasterLoaderCallbacks&) const
-{
- std::string s;
-
- while (true) {
- const MasterToken& token = lexer.getNextToken();
- if ((token.getType() == MasterToken::END_OF_FILE) ||
- (token.getType() == MasterToken::END_OF_LINE)) {
- lexer.ungetToken(); // let the upper layer handle the end-of token
- break;
- }
-
- if (!s.empty()) {
- s += " ";
- }
-
- s += token.getString();
- }
-
- return (create(s));
-}
-
-} // end of namespace isc::dns::rdata
-
namespace {
///
/// The following function and class are a helper to define case-insensitive
@@ -190,10 +161,8 @@ typedef map<RRTypeClass, RdataFactoryPtr> RdataFactoryMap;
typedef map<RRType, RdataFactoryPtr> GenericRdataFactoryMap;
template <typename T>
-class OldRdataFactory : public AbstractRdataFactory {
+class RdataFactory : public AbstractRdataFactory {
public:
- using AbstractRdataFactory::create;
-
virtual RdataPtr create(const string& rdata_str) const
{
return (RdataPtr(new T(rdata_str)));
@@ -208,16 +177,11 @@ public:
{
return (RdataPtr(new T(dynamic_cast<const T&>(source))));
}
-};
-
-template <typename T>
-class RdataFactory : public OldRdataFactory<T> {
-public:
- using OldRdataFactory<T>::create;
virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
MasterLoader::Options options,
- MasterLoaderCallbacks& callbacks) const {
+ MasterLoaderCallbacks& callbacks) const
+ {
return (RdataPtr(new T(lexer, origin, options, callbacks)));
}
};
diff --git a/src/lib/dns/rrparamregistry.h b/src/lib/dns/rrparamregistry.h
index bf86436..1d59e01 100644
--- a/src/lib/dns/rrparamregistry.h
+++ b/src/lib/dns/rrparamregistry.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -55,11 +55,11 @@ namespace rdata {
/// \brief The \c AbstractRdataFactory class is an abstract base class to
/// encapsulate a set of Rdata factory methods in a polymorphic way.
///
-/// An external developers who want to introduce a new or experimental RR type
-/// are expected to define a corresponding derived class of \c
+/// An external developer who wants to introduce a new or experimental RR type
+/// is expected to define a corresponding derived class of \c
/// AbstractRdataFactory and register it via \c RRParamRegistry.
///
-/// For other users of this API normally do not have to care about this class
+/// Other users of this API normally do not have to care about this class
/// or its derived classes; this class is generally intended to be used
/// as an internal utility of the API implementation.
class AbstractRdataFactory {
@@ -125,16 +125,9 @@ public:
/// of a specific RR type and class for \c RRParamRegistry::createRdata()
/// that uses \c MasterLexer. See its description for the expected
/// behavior and meaning of the parameters.
- ///
- /// \note Right now this is not defined as a pure virtual method and
- /// provides the default implementation. This is an intermediate
- /// workaround until we implement the underlying constructor for all
- /// supported \c Rdata classes; once it's completed the workaround
- /// default implementation should be removed and this method should become
- /// pure virtual.
virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
MasterLoader::Options options,
- MasterLoaderCallbacks& callbacks) const;
+ MasterLoaderCallbacks& callbacks) const = 0;
//@}
};
diff --git a/src/lib/dns/rrttl.cc b/src/lib/dns/rrttl.cc
index e7f8441..f4b02c0 100644
--- a/src/lib/dns/rrttl.cc
+++ b/src/lib/dns/rrttl.cc
@@ -74,10 +74,10 @@ parseTTLString(const string& ttlstr, uint32_t& ttlval, string* error_txt) {
const string::const_iterator end = ttlstr.end();
string::const_iterator pos = ttlstr.begin();
- // When we detect we have some units
- bool units_mode = false;
-
try {
+ // When we detect we have some units
+ bool units_mode = false;
+
while (pos != end) {
// Find the first unit, if there's any.
const string::const_iterator unit = find_if(pos, end, myIsalpha);
diff --git a/src/lib/dns/serial.h b/src/lib/dns/serial.h
index 678fb22..0cd6833 100644
--- a/src/lib/dns/serial.h
+++ b/src/lib/dns/serial.h
@@ -59,7 +59,10 @@ public:
/// \brief Direct assignment from other Serial
///
/// \param other The Serial to assign the value from
- void operator=(const Serial& other) { value_ = other.getValue(); }
+ Serial& operator=(const Serial& other) {
+ value_ = other.getValue();
+ return (*this);
+ }
/// \brief Direct assignment from value
///
diff --git a/src/lib/dns/tests/labelsequence_unittest.cc b/src/lib/dns/tests/labelsequence_unittest.cc
index 62cbcec..a3ac767 100644
--- a/src/lib/dns/tests/labelsequence_unittest.cc
+++ b/src/lib/dns/tests/labelsequence_unittest.cc
@@ -949,7 +949,7 @@ public:
foo_example("foo.example."),
org("org")
{
- // explicitely set to non-zero data, to make sure
+ // explicitly set to non-zero data, to make sure
// we don't try to use data we don't set
memset(buf, 0xff, LabelSequence::MAX_SERIALIZED_LENGTH);
}
diff --git a/src/lib/dns/tests/master_loader_unittest.cc b/src/lib/dns/tests/master_loader_unittest.cc
index 653da05..ce9b8f7 100644
--- a/src/lib/dns/tests/master_loader_unittest.cc
+++ b/src/lib/dns/tests/master_loader_unittest.cc
@@ -167,8 +167,8 @@ TEST_F(MasterLoaderTest, basicLoad) {
// Hardcode expected values taken from the test data file, assuming it
// won't change too often.
- EXPECT_EQ(549, loader_->getSize());
- EXPECT_EQ(549, loader_->getPosition());
+ EXPECT_EQ(550, loader_->getSize());
+ EXPECT_EQ(550, loader_->getPosition());
checkBasicRRs();
}
@@ -227,20 +227,20 @@ TEST_F(MasterLoaderTest, includeAndIncremental) {
EXPECT_EQ(zone_data.size(), loader_->getSize());
EXPECT_EQ(first_rr.size(), loader_->getPosition());
- // Read next 4. It includes $INCLUDE processing. Magic number of 549
- // is the size of the test zone file (see above); 506 is the position in
+ // Read next 4. It includes $INCLUDE processing. Magic number of 550
+ // is the size of the test zone file (see above); 507 is the position in
// the file at the end of 4th RR (due to extra comments it's smaller than
// the file size).
loader_->loadIncremental(4);
- EXPECT_EQ(zone_data.size() + 549, loader_->getSize());
- EXPECT_EQ(first_rr.size() + include_str.size() + 506,
+ EXPECT_EQ(zone_data.size() + 550, loader_->getSize());
+ EXPECT_EQ(first_rr.size() + include_str.size() + 507,
loader_->getPosition());
// Read the last one. At this point getSize and getPosition return
// the same value, indicating progress of 100%.
loader_->loadIncremental(1);
- EXPECT_EQ(zone_data.size() + 549, loader_->getSize());
- EXPECT_EQ(zone_data.size() + 549, loader_->getPosition());
+ EXPECT_EQ(zone_data.size() + 550, loader_->getSize());
+ EXPECT_EQ(zone_data.size() + 550, loader_->getPosition());
// we were not interested in checking RRs in this test. clear them to
// not confuse TearDown().
diff --git a/src/lib/dns/tests/masterload_unittest.cc b/src/lib/dns/tests/masterload_unittest.cc
index 7f1961c..dfd901a 100644
--- a/src/lib/dns/tests/masterload_unittest.cc
+++ b/src/lib/dns/tests/masterload_unittest.cc
@@ -167,7 +167,11 @@ TEST_F(MasterLoadTest, loadRRsigs) {
EXPECT_EQ(2, results.size());
}
-TEST_F(MasterLoadTest, loadRRWithComment) {
+// This test was disabled by #2387, because the test data has trailing
+// comments and it (eventually) uses the string RDATA constructor which
+// doesn't support them. This test should be fixed and re-enabled by
+// #2381, or deleted.
+TEST_F(MasterLoadTest, DISABLED_loadRRWithComment) {
// Comment at the end of line should be ignored and the RR should be
// accepted.
rr_stream << "example.com. 3600 IN DNSKEY 256 3 7 "
@@ -180,7 +184,11 @@ TEST_F(MasterLoadTest, loadRRWithComment) {
dnskey_rdata)));
}
-TEST_F(MasterLoadTest, loadRRWithCommentNoSpace) {
+// This test was disabled by #2387, because the test data has trailing
+// comments and it (eventually) uses the string RDATA constructor which
+// doesn't support them. This test should be fixed and re-enabled by
+// #2381, or deleted.
+TEST_F(MasterLoadTest, DISABLED_loadRRWithCommentNoSpace) {
// Similar to the previous one, but there's no space before comments.
// It should still work.
rr_stream << "example.com. 3600 IN DNSKEY 256 3 7 "
@@ -193,7 +201,11 @@ TEST_F(MasterLoadTest, loadRRWithCommentNoSpace) {
dnskey_rdata)));
}
-TEST_F(MasterLoadTest, loadRRWithCommentEmptyComment) {
+// This test was disabled by #2387, because the test data has trailing
+// comments and it (eventually) uses the string RDATA constructor which
+// doesn't support them. This test should be fixed and re-enabled by
+// #2381, or deleted.
+TEST_F(MasterLoadTest, DISABLED_loadRRWithCommentEmptyComment) {
// Similar to the previous one, but there's no data after the ;
// It should still work.
rr_stream << "example.com. 3600 IN DNSKEY 256 3 7 "
@@ -206,7 +218,11 @@ TEST_F(MasterLoadTest, loadRRWithCommentEmptyComment) {
dnskey_rdata)));
}
-TEST_F(MasterLoadTest, loadRRWithCommentEmptyCommentNoSpace) {
+// This test was disabled by #2387, because the test data has trailing
+// comments and it (eventually) uses the string RDATA constructor which
+// doesn't support them. This test should be fixed and re-enabled by
+// #2381, or deleted.
+TEST_F(MasterLoadTest, DISABLED_loadRRWithCommentEmptyCommentNoSpace) {
// Similar to the previous one, but there's no space before or after ;
// It should still work.
rr_stream << "example.com. 3600 IN DNSKEY 256 3 7 "
diff --git a/src/lib/dns/tests/message_unittest.cc b/src/lib/dns/tests/message_unittest.cc
index 835aa48..8aaebaa 100644
--- a/src/lib/dns/tests/message_unittest.cc
+++ b/src/lib/dns/tests/message_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -98,7 +98,7 @@ protected:
rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(),
RRType::RRSIG(), RRTTL(3600)));
rrset_rrsig->addRdata(generic::RRSIG("AAAA 5 3 7200 20100322084538 "
- "20100220084538 1 example.com "
+ "20100220084538 1 example.com. "
"FAKEFAKEFAKEFAKE"));
rrset_aaaa->addRRsig(rrset_rrsig);
}
diff --git a/src/lib/dns/tests/rdata_afsdb_unittest.cc b/src/lib/dns/tests/rdata_afsdb_unittest.cc
index 9bb64b7..9a628cd 100644
--- a/src/lib/dns/tests/rdata_afsdb_unittest.cc
+++ b/src/lib/dns/tests/rdata_afsdb_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -34,7 +34,7 @@ using namespace isc::dns::rdata;
const char* const afsdb_text = "1 afsdb.example.com.";
const char* const afsdb_text2 = "0 root.example.com.";
const char* const too_long_label("012345678901234567890123456789"
- "0123456789012345678901234567890123");
+ "0123456789012345678901234567890123.");
namespace {
class Rdata_AFSDB_Test : public RdataTest {
@@ -68,9 +68,17 @@ TEST_F(Rdata_AFSDB_Test, badText) {
// number of fields (must be 2) is incorrect
EXPECT_THROW(const generic::AFSDB rdata_afsdb("10 afsdb. example.com."),
InvalidRdataText);
+ // No origin and relative
+ EXPECT_THROW(const generic::AFSDB rdata_afsdb("1 afsdb.example.com"),
+ MissingNameOrigin);
// bad name
EXPECT_THROW(const generic::AFSDB rdata_afsdb("1 afsdb.example.com." +
- string(too_long_label)), TooLongLabel);
+ string(too_long_label)), TooLongLabel);
+}
+
+TEST_F(Rdata_AFSDB_Test, copy) {
+ const generic::AFSDB rdata_afsdb2(rdata_afsdb);
+ EXPECT_EQ(0, rdata_afsdb.compare(rdata_afsdb2));
}
TEST_F(Rdata_AFSDB_Test, assignment) {
@@ -119,9 +127,24 @@ TEST_F(Rdata_AFSDB_Test, createFromLexer) {
*test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
afsdb_text)));
+ // test::createRdataUsingLexer() constructs relative to
+ // "example.org." origin.
+ generic::AFSDB tmp = generic::AFSDB("1 afsdb2.example.org.");
+ EXPECT_EQ(0, tmp.compare(
+ *test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
+ "1 afsdb2")));
+
// Exceptions cause NULL to be returned.
EXPECT_FALSE(test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
"1root.example.com."));
+
+ // 65536 is larger than maximum possible subtype
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
+ "65536 afsdb.example.com."));
+
+ // Extra text at end of line
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
+ "1 afsdb.example.com. extra."));
}
TEST_F(Rdata_AFSDB_Test, toWireBuffer) {
@@ -197,9 +220,9 @@ TEST_F(Rdata_AFSDB_Test, compare) {
EXPECT_EQ(0, rdata_afsdb.compare(generic::AFSDB("1 "
"AFSDB.example.com.")));
- const generic::AFSDB small1("10 afsdb.example.com");
- const generic::AFSDB large1("65535 afsdb.example.com");
- const generic::AFSDB large2("256 afsdb.example.com");
+ const generic::AFSDB small1("10 afsdb.example.com.");
+ const generic::AFSDB large1("65535 afsdb.example.com.");
+ const generic::AFSDB large2("256 afsdb.example.com.");
// confirm these are compared as unsigned values
EXPECT_GT(0, rdata_afsdb.compare(large1));
@@ -210,7 +233,7 @@ TEST_F(Rdata_AFSDB_Test, compare) {
EXPECT_LT(0, large2.compare(small1));
// another AFSDB whose server name is larger than that of rdata_afsdb.
- const generic::AFSDB large3("256 zzzzz.example.com");
+ const generic::AFSDB large3("256 zzzzz.example.com.");
EXPECT_GT(0, large2.compare(large3));
EXPECT_LT(0, large3.compare(large2));
diff --git a/src/lib/dns/tests/rdata_dhcid_unittest.cc b/src/lib/dns/tests/rdata_dhcid_unittest.cc
index 8d56c0e..77baccd 100644
--- a/src/lib/dns/tests/rdata_dhcid_unittest.cc
+++ b/src/lib/dns/tests/rdata_dhcid_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <exceptions/exceptions.h>
+
#include <util/buffer.h>
#include <dns/rdataclass.h>
#include <util/encode/base64.h>
@@ -23,6 +25,7 @@
using isc::UnitTestUtil;
using namespace std;
+using namespace isc;
using namespace isc::dns;
using namespace isc::util;
using namespace isc::util::encode;
@@ -30,25 +33,69 @@ using namespace isc::dns::rdata;
namespace {
-const string string_dhcid(
- "0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=");
-
-const in::DHCID rdata_dhcid(string_dhcid);
-
class Rdata_DHCID_Test : public RdataTest {
+protected:
+ Rdata_DHCID_Test() :
+ dhcid_txt("0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA="),
+ rdata_dhcid(dhcid_txt)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<in::DHCID, isc::Exception, isc::Exception>(
+ rdata_str, rdata_dhcid, false, false);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<in::DHCID, BadValue, BadValue>(
+ rdata_str, rdata_dhcid, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <in::DHCID, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_dhcid, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <in::DHCID, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_dhcid, true, false);
+ }
+
+ const string dhcid_txt;
+ const in::DHCID rdata_dhcid;
};
-TEST_F(Rdata_DHCID_Test, createFromString) {
- const in::DHCID rdata_dhcid2(string_dhcid);
- EXPECT_EQ(0, rdata_dhcid2.compare(rdata_dhcid));
-}
+TEST_F(Rdata_DHCID_Test, fromText) {
+ EXPECT_EQ(dhcid_txt, rdata_dhcid.toText());
+
+ // Space in digest data is OK
+ checkFromText_None(
+ "0LIg0LvQtdGB0YMg 0YDQvtC00LjQu9Cw 0YHRjCDRkdC70L7R h9C60LA=");
+
+ // Multi-line digest data is OK, if enclosed in parentheses
+ checkFromText_None(
+ "( 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw\n0YHRjCDRkdC70L7R h9C60LA= )");
-TEST_F(Rdata_DHCID_Test, badBase64) {
- EXPECT_THROW(const in::DHCID rdata_dhcid_bad("00"), isc::BadValue);
+ // Trailing garbage. This should cause only the string constructor
+ // to fail, but the lexer constructor must be able to continue
+ // parsing from it.
+ checkFromText_BadString(
+ "0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA="
+ " ; comment\n"
+ "AAIBY2/AuCccgoJbsaxcQc9TUapptP69lOjxfNuVAA2kjEA=");
}
-TEST_F(Rdata_DHCID_Test, badLength) {
- EXPECT_THROW(const in::DHCID rdata_dhcid_bad("MDA="), InvalidRdataLength);
+TEST_F(Rdata_DHCID_Test, badText) {
+ // missing digest data
+ checkFromText_LexerError("");
+
+ // invalid base64
+ checkFromText_BadValue("EEeeeeeeEEEeeeeeeGaaahAAAAAAAAHHHHHHHHHHH!=");
+
+ // unterminated multi-line base64
+ checkFromText_LexerError(
+ "( 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw\n0YHRjCDRkdC70L7R h9C60LA=");
}
TEST_F(Rdata_DHCID_Test, copy) {
@@ -60,17 +107,17 @@ TEST_F(Rdata_DHCID_Test, createFromWire) {
EXPECT_EQ(0, rdata_dhcid.compare(
*rdataFactoryFromFile(RRType("DHCID"), RRClass("IN"),
"rdata_dhcid_fromWire")));
+
+ InputBuffer buffer(NULL, 0);
+ EXPECT_THROW(in::DHCID(buffer, 0), InvalidRdataLength);
+
// TBD: more tests
}
TEST_F(Rdata_DHCID_Test, createFromLexer) {
EXPECT_EQ(0, rdata_dhcid.compare(
*test::createRdataUsingLexer(RRType::DHCID(), RRClass::IN(),
- string_dhcid)));
-
- // Exceptions cause NULL to be returned.
- EXPECT_FALSE(test::createRdataUsingLexer(RRType::DHCID(), RRClass::IN(),
- "00"));
+ dhcid_txt)));
}
TEST_F(Rdata_DHCID_Test, toWireRenderer) {
@@ -92,13 +139,13 @@ TEST_F(Rdata_DHCID_Test, toWireBuffer) {
}
TEST_F(Rdata_DHCID_Test, toText) {
- EXPECT_EQ(string_dhcid, rdata_dhcid.toText());
+ EXPECT_EQ(dhcid_txt, rdata_dhcid.toText());
}
TEST_F(Rdata_DHCID_Test, getDHCIDDigest) {
- const string string_dhcid1(encodeBase64(rdata_dhcid.getDigest()));
+ const string dhcid_txt1(encodeBase64(rdata_dhcid.getDigest()));
- EXPECT_EQ(string_dhcid, string_dhcid1);
+ EXPECT_EQ(dhcid_txt, dhcid_txt1);
}
TEST_F(Rdata_DHCID_Test, compare) {
@@ -117,6 +164,6 @@ TEST_F(Rdata_DHCID_Test, compare) {
EXPECT_GT(rdata_dhcid3.compare(rdata_dhcid2), 0);
// comparison attempt between incompatible RR types should be rejected
- EXPECT_THROW(rdata_dhcid.compare(*rdata_nomatch), bad_cast);
+ EXPECT_THROW(rdata_dhcid.compare(*rdata_nomatch), bad_cast);
}
}
diff --git a/src/lib/dns/tests/rdata_dnskey_unittest.cc b/src/lib/dns/tests/rdata_dnskey_unittest.cc
index 58d29bf..872dc2a 100644
--- a/src/lib/dns/tests/rdata_dnskey_unittest.cc
+++ b/src/lib/dns/tests/rdata_dnskey_unittest.cc
@@ -37,98 +37,168 @@ using namespace isc::dns::rdata;
namespace {
class Rdata_DNSKEY_Test : public RdataTest {
- // there's nothing to specialize
+protected:
+ Rdata_DNSKEY_Test() :
+ dnskey_txt("257 3 5 BEAAAAOhHQDBrhQbtphgq2wQUpEQ5t4DtUHxoMV"
+ "Fu2hWLDMvoOMRXjGrhhCeFvAZih7yJHf8ZGfW6hd38hXG/x"
+ "ylYCO6Krpbdojwx8YMXLA5/kA+u50WIL8ZR1R6KTbsYVMf/"
+ "Qx5RiNbPClw+vT+U8eXEJmO20jIS1ULgqy347cBB1zMnnz/"
+ "4LJpA0da9CbKj3A254T515sNIMcwsB8/2+2E63/zZrQzBkj"
+ "0BrN/9Bexjpiks3jRhZatEsXn3dTy47R09Uix5WcJt+xzqZ"
+ "7+ysyLKOOedS39Z7SDmsn2eA0FKtQpwA6LXeG2w+jxmw3oA"
+ "8lVUgEf/rzeC/bByBNsO70aEFTd"),
+ dnskey_txt2("257 3 5 YmluZDEwLmlzYy5vcmc="),
+ rdata_dnskey(dnskey_txt),
+ rdata_dnskey2(dnskey_txt2)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::DNSKEY, isc::Exception, isc::Exception>(
+ rdata_str, rdata_dnskey2, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::DNSKEY, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_dnskey2, true, true);
+ }
+
+ void checkFromText_InvalidLength(const string& rdata_str) {
+ checkFromText<generic::DNSKEY, InvalidRdataLength, InvalidRdataLength>(
+ rdata_str, rdata_dnskey2, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<generic::DNSKEY, BadValue, BadValue>(
+ rdata_str, rdata_dnskey2, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::DNSKEY, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_dnskey2, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::DNSKEY, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_dnskey2, true, false);
+ }
+
+ const string dnskey_txt;
+ const string dnskey_txt2;
+ const generic::DNSKEY rdata_dnskey;
+ const generic::DNSKEY rdata_dnskey2;
};
-string dnskey_txt("257 3 5 BEAAAAOhHQDBrhQbtphgq2wQUpEQ5t4DtUHxoMV"
- "Fu2hWLDMvoOMRXjGrhhCeFvAZih7yJHf8ZGfW6hd38hXG/x"
- "ylYCO6Krpbdojwx8YMXLA5/kA+u50WIL8ZR1R6KTbsYVMf/"
- "Qx5RiNbPClw+vT+U8eXEJmO20jIS1ULgqy347cBB1zMnnz/"
- "4LJpA0da9CbKj3A254T515sNIMcwsB8/2+2E63/zZrQzBkj"
- "0BrN/9Bexjpiks3jRhZatEsXn3dTy47R09Uix5WcJt+xzqZ"
- "7+ysyLKOOedS39Z7SDmsn2eA0FKtQpwA6LXeG2w+jxmw3oA"
- "8lVUgEf/rzeC/bByBNsO70aEFTd");
-
TEST_F(Rdata_DNSKEY_Test, fromText) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
EXPECT_EQ(dnskey_txt, rdata_dnskey.toText());
-}
-TEST_F(Rdata_DNSKEY_Test, assign) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
- generic::DNSKEY rdata_dnskey2 = rdata_dnskey;
- EXPECT_EQ(0, rdata_dnskey.compare(rdata_dnskey2));
-}
+ // Space in key data is OK
+ checkFromText_None("257 3 5 YmluZDEw LmlzYy5vcmc=");
+
+ // Delimited number in key data is OK
+ checkFromText_None("257 3 5 YmluZDEwLmlzYy 5 vcmc=");
+
+ // Missing keydata is OK
+ EXPECT_NO_THROW(const generic::DNSKEY rdata_dnskey3("257 3 5"));
+
+ // Key data too short for RSA/MD5 algorithm is OK when
+ // constructing. But getTag() on this object would throw (see
+ // .getTag tests).
+ EXPECT_NO_THROW(const generic::DNSKEY rdata_dnskey4("1 1 1 YQ=="));
+
+ // Flags field out of range
+ checkFromText_InvalidText("65536 3 5 YmluZDEwLmlzYy5vcmc=");
+
+ // Protocol field out of range
+ checkFromText_InvalidText("257 256 5 YmluZDEwLmlzYy5vcmc=");
+
+ // Algorithm field out of range
+ checkFromText_InvalidText("257 3 256 YmluZDEwLmlzYy5vcmc=");
+
+ // Missing algorithm field
+ checkFromText_LexerError("257 3 YmluZDEwLmlzYy5vcmc=");
-TEST_F(Rdata_DNSKEY_Test, badText) {
- EXPECT_THROW(generic::DNSKEY("257 3 5"),
- InvalidRdataText);
- EXPECT_THROW(generic::DNSKEY("99999 3 5 BAAAAAAAAAAAD"),
- InvalidRdataText);
- EXPECT_THROW(generic::DNSKEY("257 300 5 BAAAAAAAAAAAD"),
- InvalidRdataText);
- EXPECT_THROW(generic::DNSKEY("257 3 500 BAAAAAAAAAAAD"),
- InvalidRdataText);
- EXPECT_THROW(generic::DNSKEY("257 3 5 BAAAAAAAAAAAD"), BadValue);
+ // Invalid key data field (not Base64)
+ checkFromText_BadValue("257 3 5 BAAAAAAAAAAAD");
+
+ // String instead of number
+ checkFromText_LexerError("foo 3 5 YmluZDEwLmlzYy5vcmc=");
+ checkFromText_LexerError("257 foo 5 YmluZDEwLmlzYy5vcmc=");
+ checkFromText_LexerError("257 3 foo YmluZDEwLmlzYy5vcmc=");
+
+ // Trailing garbage. This should cause only the string constructor
+ // to fail, but the lexer constructor must be able to continue
+ // parsing from it.
+ checkFromText_BadString("257 3 5 YmluZDEwLmlzYy5vcmc= ; comment\n"
+ "257 3 4 YmluZDEwLmlzYy5vcmc=");
+
+ // Unmatched parenthesis should cause a lexer error
+ checkFromText_LexerError("257 3 5 )YmluZDEwLmlzYy5vcmc=");
}
-TEST_F(Rdata_DNSKEY_Test, DISABLED_badText) {
- // Should this be allowed? Probably not. But the test currently fails.
- EXPECT_THROW(generic::DNSKEY("257 3 5BEAAEFTd"),
- InvalidRdataText);
- // How about this? It's even more confusing for the parser because
- // it could be ambiguous '51 EAAA' vs '5 1EAA..'
- EXPECT_THROW(generic::DNSKEY("257 3 51EAAEFTd"),
- InvalidRdataText);
+TEST_F(Rdata_DNSKEY_Test, assign) {
+ generic::DNSKEY rdata_dnskey2("257 3 5 YQ==");
+ rdata_dnskey2 = rdata_dnskey;
+ EXPECT_EQ(0, rdata_dnskey.compare(rdata_dnskey2));
}
TEST_F(Rdata_DNSKEY_Test, createFromLexer) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
EXPECT_EQ(0, rdata_dnskey.compare(
*test::createRdataUsingLexer(RRType::DNSKEY(), RRClass::IN(),
dnskey_txt)));
-
- // Exceptions cause NULL to be returned.
- EXPECT_FALSE(test::createRdataUsingLexer(RRType::DNSKEY(), RRClass::IN(),
- "257 3 5"));
}
TEST_F(Rdata_DNSKEY_Test, toWireRenderer) {
renderer.skip(2);
- generic::DNSKEY rdata_dnskey(dnskey_txt);
rdata_dnskey.toWire(renderer);
vector<unsigned char> data;
- UnitTestUtil::readWireData("rdata_dnskey_fromWire", data);
+ UnitTestUtil::readWireData("rdata_dnskey_fromWire.wire", data);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
static_cast<const uint8_t *>(renderer.getData()) + 2,
renderer.getLength() - 2, &data[2], data.size() - 2);
}
TEST_F(Rdata_DNSKEY_Test, toWireBuffer) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
rdata_dnskey.toWire(obuffer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_dnskey_fromWire.wire", data);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ obuffer.getData(), obuffer.getLength(),
+ &data[2], data.size() - 2);
}
TEST_F(Rdata_DNSKEY_Test, createFromWire) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
EXPECT_EQ(0, rdata_dnskey.compare(
*rdataFactoryFromFile(RRType("DNSKEY"), RRClass("IN"),
- "rdata_dnskey_fromWire")));
+ "rdata_dnskey_fromWire.wire")));
+
+ // Missing keydata is OK
+ const generic::DNSKEY rdata_dnskey_missing_keydata("257 3 5");
+ EXPECT_EQ(0, rdata_dnskey_missing_keydata.compare(
+ *rdataFactoryFromFile(RRType("DNSKEY"), RRClass("IN"),
+ "rdata_dnskey_empty_keydata_fromWire.wire")));
}
TEST_F(Rdata_DNSKEY_Test, getTag) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
EXPECT_EQ(12892, rdata_dnskey.getTag());
+
+ // Short keydata with algorithm RSA/MD5 must throw.
+ const generic::DNSKEY rdata_dnskey_short_keydata1("1 1 1 YQ==");
+ EXPECT_THROW(rdata_dnskey_short_keydata1.getTag(), isc::OutOfRange);
+
+ // Short keydata with algorithm not RSA/MD5 must not throw.
+ const generic::DNSKEY rdata_dnskey_short_keydata2("257 3 5 YQ==");
+ EXPECT_NO_THROW(rdata_dnskey_short_keydata2.getTag());
}
TEST_F(Rdata_DNSKEY_Test, getAlgorithm) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
EXPECT_EQ(5, rdata_dnskey.getAlgorithm());
}
TEST_F(Rdata_DNSKEY_Test, getFlags) {
- generic::DNSKEY rdata_dnskey(dnskey_txt);
EXPECT_EQ(257, rdata_dnskey.getFlags());
}
diff --git a/src/lib/dns/tests/rdata_ds_like_unittest.cc b/src/lib/dns/tests/rdata_ds_like_unittest.cc
index c797199..ae6a360 100644
--- a/src/lib/dns/tests/rdata_ds_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_ds_like_unittest.cc
@@ -47,15 +47,15 @@ template<> RRTYPE<generic::DLV>::RRTYPE() : RRType(RRType::DLV()) {}
template <class DS_LIKE>
class Rdata_DS_LIKE_Test : public RdataTest {
protected:
- static DS_LIKE const rdata_ds_like;
+ Rdata_DS_LIKE_Test() :
+ ds_like_txt("12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5"),
+ rdata_ds_like(ds_like_txt)
+ {}
+ const string ds_like_txt;
+ const DS_LIKE rdata_ds_like;
};
-string ds_like_txt("12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
- "5F0EB5C777586DE18DA6B5");
-
-template <class DS_LIKE>
-DS_LIKE const Rdata_DS_LIKE_Test<DS_LIKE>::rdata_ds_like(ds_like_txt);
-
// The list of types we want to test.
typedef testing::Types<generic::DS, generic::DLV> Implementations;
@@ -70,7 +70,7 @@ TYPED_TEST(Rdata_DS_LIKE_Test, createFromText) {
}
TYPED_TEST(Rdata_DS_LIKE_Test, toText_DS_LIKE) {
- EXPECT_EQ(ds_like_txt, this->rdata_ds_like.toText());
+ EXPECT_EQ(this->ds_like_txt, this->rdata_ds_like.toText());
}
TYPED_TEST(Rdata_DS_LIKE_Test, badText_DS_LIKE) {
@@ -96,7 +96,7 @@ TYPED_TEST(Rdata_DS_LIKE_Test, createFromWire_DS_LIKE) {
TYPED_TEST(Rdata_DS_LIKE_Test, createFromLexer_DS_LIKE) {
EXPECT_EQ(0, this->rdata_ds_like.compare(
*test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
- ds_like_txt)));
+ this->ds_like_txt)));
// Whitespace is okay
EXPECT_EQ(0, this->rdata_ds_like.compare(
@@ -121,13 +121,13 @@ TYPED_TEST(Rdata_DS_LIKE_Test, createFromLexer_DS_LIKE) {
}
TYPED_TEST(Rdata_DS_LIKE_Test, assignment_DS_LIKE) {
- TypeParam copy((string(ds_like_txt)));
+ TypeParam copy(this->ds_like_txt);
copy = this->rdata_ds_like;
EXPECT_EQ(0, copy.compare(this->rdata_ds_like));
// Check if the copied data is valid even after the original is deleted
TypeParam* copy2 = new TypeParam(this->rdata_ds_like);
- TypeParam copy3((string(ds_like_txt)));
+ TypeParam copy3(this->ds_like_txt);
copy3 = *copy2;
delete copy2;
EXPECT_EQ(0, copy3.compare(this->rdata_ds_like));
@@ -143,7 +143,7 @@ TYPED_TEST(Rdata_DS_LIKE_Test, getTag_DS_LIKE) {
TYPED_TEST(Rdata_DS_LIKE_Test, toWireRenderer) {
Rdata_DS_LIKE_Test<TypeParam>::renderer.skip(2);
- TypeParam rdata_ds_like(ds_like_txt);
+ TypeParam rdata_ds_like(this->ds_like_txt);
rdata_ds_like.toWire(this->renderer);
vector<unsigned char> data;
@@ -156,7 +156,7 @@ TYPED_TEST(Rdata_DS_LIKE_Test, toWireRenderer) {
}
TYPED_TEST(Rdata_DS_LIKE_Test, toWireBuffer) {
- TypeParam rdata_ds_like(ds_like_txt);
+ TypeParam rdata_ds_like(this->ds_like_txt);
rdata_ds_like.toWire(this->obuffer);
}
@@ -179,8 +179,33 @@ string ds_like_txt6("12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
"5F0EB5C777586DE18DA6B555");
TYPED_TEST(Rdata_DS_LIKE_Test, compare) {
+ const string ds_like_txt1(
+ "12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+ // different tag
+ const string ds_like_txt2(
+ "12893 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+ // different algorithm
+ const string ds_like_txt3(
+ "12892 6 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+ // different digest type
+ const string ds_like_txt4(
+ "12892 5 3 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+ // different digest
+ const string ds_like_txt5(
+ "12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B5");
+ // different digest length
+ const string ds_like_txt6(
+ "12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+ "5F0EB5C777586DE18DA6B555");
+
// trivial case: self equivalence
- EXPECT_EQ(0, TypeParam(ds_like_txt).compare(TypeParam(ds_like_txt)));
+ EXPECT_EQ(0, TypeParam(this->ds_like_txt).
+ compare(TypeParam(this->ds_like_txt)));
// non-equivalence tests
EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt2)), 0);
diff --git a/src/lib/dns/tests/rdata_minfo_unittest.cc b/src/lib/dns/tests/rdata_minfo_unittest.cc
index 2f717fe..3ce6a6c 100644
--- a/src/lib/dns/tests/rdata_minfo_unittest.cc
+++ b/src/lib/dns/tests/rdata_minfo_unittest.cc
@@ -1,6 +1,6 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
-// Permission to use, copy, modify, and/or distribute this software for generic
+// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <string>
+
#include <util/buffer.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
@@ -27,22 +29,62 @@
using isc::UnitTestUtil;
using namespace std;
-using namespace isc::dns;
using namespace isc::util;
+using namespace isc::dns;
using namespace isc::dns::rdata;
-// minfo text
-const char* const minfo_txt = "rmailbox.example.com. emailbox.example.com.";
-const char* const minfo_txt2 = "root.example.com. emailbox.example.com.";
-const char* const too_long_label = "01234567890123456789012345678901234567"
- "89012345678901234567890123";
-
namespace {
class Rdata_MINFO_Test : public RdataTest {
-public:
+protected:
Rdata_MINFO_Test():
- rdata_minfo(string(minfo_txt)), rdata_minfo2(string(minfo_txt2)) {}
-
+ minfo_txt("rmailbox.example.com. emailbox.example.com."),
+ minfo_txt2("root.example.com. emailbox.example.com."),
+ too_long_label("01234567890123456789012345678901234567"
+ "89012345678901234567890123."),
+ rdata_minfo(minfo_txt),
+ rdata_minfo2(minfo_txt2)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::MINFO, isc::Exception, isc::Exception>(
+ rdata_str, rdata_minfo, false, false);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::MINFO, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_minfo, true, true);
+ }
+
+ void checkFromText_TooLongLabel(const string& rdata_str) {
+ checkFromText<generic::MINFO, TooLongLabel, TooLongLabel>(
+ rdata_str, rdata_minfo, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText<generic::MINFO, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_minfo, true, false);
+ }
+
+ void checkFromText_EmptyLabel(const string& rdata_str) {
+ checkFromText<generic::MINFO, EmptyLabel, EmptyLabel>(
+ rdata_str, rdata_minfo, true, true);
+ }
+
+ void checkFromText_MissingOrigin(const string& rdata_str) {
+ checkFromText
+ <generic::MINFO, MissingNameOrigin, MissingNameOrigin>(
+ rdata_str, rdata_minfo, true, true);
+ }
+
+ void checkFromText_Origin(const string& rdata_str, const Name* origin) {
+ checkFromText<generic::MINFO, MissingNameOrigin, isc::Exception>(
+ rdata_str, rdata_minfo, true, false, origin);
+ }
+
+ const string minfo_txt;
+ const string minfo_txt2;
+ const string too_long_label;
const generic::MINFO rdata_minfo;
const generic::MINFO rdata_minfo2;
};
@@ -54,24 +96,35 @@ TEST_F(Rdata_MINFO_Test, createFromText) {
EXPECT_EQ(Name("root.example.com."), rdata_minfo2.getRmailbox());
EXPECT_EQ(Name("emailbox.example.com."), rdata_minfo2.getEmailbox());
+
+ checkFromText_None(minfo_txt);
+
+ // origin defined for lexer constructor, but not string constructor
+ const Name origin("example.com");
+ checkFromText_Origin("rmailbox emailbox", &origin);
+
+ // lexer constructor accepts extra text, but string constructor doesn't
+ checkFromText_BadString("rmailbox.example.com. emailbox.example.com. "
+ "extra.example.com.");
}
TEST_F(Rdata_MINFO_Test, badText) {
- // incomplete text
- EXPECT_THROW(generic::MINFO("root.example.com."),
- InvalidRdataText);
- // number of fields (must be 2) is incorrect
- EXPECT_THROW(generic::MINFO("root.example.com emailbox.example.com. "
- "example.com."),
- InvalidRdataText);
- // bad rmailbox name
- EXPECT_THROW(generic::MINFO("root.example.com. emailbox.example.com." +
- string(too_long_label)),
- TooLongLabel);
- // bad emailbox name
- EXPECT_THROW(generic::MINFO("root.example.com." +
- string(too_long_label) + " emailbox.example.com."),
- TooLongLabel);
+ // too long names
+ checkFromText_TooLongLabel("root.example.com." + too_long_label +
+ " emailbox.example.com.");
+ checkFromText_TooLongLabel("root.example.com. emailbox.example.com." +
+ too_long_label);
+
+ // invalid names
+ checkFromText_EmptyLabel("root..example.com. emailbox.example.com.");
+ checkFromText_EmptyLabel("root.example.com. emailbox..example.com.");
+
+ // missing name
+ checkFromText_LexerError("root.example.com.");
+
+ // missing origin
+ checkFromText_MissingOrigin("root.example.com emailbox.example.com.");
+ checkFromText_MissingOrigin("root.example.com. emailbox.example.com");
}
TEST_F(Rdata_MINFO_Test, createFromWire) {
@@ -103,12 +156,6 @@ TEST_F(Rdata_MINFO_Test, createFromWire) {
DNSMessageFORMERR);
}
-TEST_F(Rdata_MINFO_Test, createFromLexer) {
- EXPECT_EQ(0, rdata_minfo.compare(
- *test::createRdataUsingLexer(RRType::MINFO(), RRClass::IN(),
- minfo_txt)));
-}
-
TEST_F(Rdata_MINFO_Test, assignment) {
generic::MINFO copy((string(minfo_txt2)));
copy = rdata_minfo;
diff --git a/src/lib/dns/tests/rdata_nsec3_unittest.cc b/src/lib/dns/tests/rdata_nsec3_unittest.cc
index 0fec3eb..1f35713 100644
--- a/src/lib/dns/tests/rdata_nsec3_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3_unittest.cc
@@ -17,7 +17,6 @@
#include <exceptions/exceptions.h>
#include <util/buffer.h>
-#include <util/encode/hex.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
@@ -35,7 +34,6 @@ using namespace std;
using namespace isc;
using namespace isc::dns;
using namespace isc::util;
-using namespace isc::util::encode;
using namespace isc::dns::rdata;
namespace {
@@ -43,55 +41,114 @@ namespace {
// Note: some tests can be shared with NSEC3PARAM. They are unified as
// typed tests defined in nsec3param_like_unittest.
class Rdata_NSEC3_Test : public RdataTest {
- // there's nothing to specialize
-public:
+protected:
Rdata_NSEC3_Test() :
nsec3_txt("1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
- "NS SOA RRSIG DNSKEY NSEC3PARAM"),
- nsec3_nosalt_txt("1 1 1 - H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A" )
+ "A NS SOA"),
+ nsec3_nosalt_txt("1 1 1 - H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA"),
+ nsec3_notype_txt("1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6"),
+ rdata_nsec3(nsec3_txt)
{}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::NSEC3, isc::Exception, isc::Exception>(
+ rdata_str, rdata_nsec3, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::NSEC3, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_nsec3, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<generic::NSEC3, BadValue, BadValue>(
+ rdata_str, rdata_nsec3, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::NSEC3, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_nsec3, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::NSEC3, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_nsec3, true, false);
+ }
+
const string nsec3_txt;
const string nsec3_nosalt_txt;
+ const string nsec3_notype_txt;
+ const generic::NSEC3 rdata_nsec3;
};
TEST_F(Rdata_NSEC3_Test, fromText) {
- // A normal case: the test constructor should successfully parse the
- // text and construct nsec3_txt. It will be tested against the wire format
- // representation in the createFromWire test.
-
- // hash that has the possible max length (see badText about the magic
- // numbers)
+ // Hash that has the possible max length
EXPECT_EQ(255, generic::NSEC3("1 1 1 D399EAAB " +
string((255 * 8) / 5, '0') +
" NS").getNext().size());
- // type bitmap is empty. it's possible and allowed for NSEC3.
- EXPECT_NO_THROW(generic::NSEC3(
- "1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6"));
-}
-
-TEST_F(Rdata_NSEC3_Test, badText) {
- EXPECT_THROW(generic::NSEC3("1 1 1 ADDAFEEE "
- "0123456789ABCDEFGHIJKLMNOPQRSTUV "
- "BIFF POW SPOON"),
- InvalidRdataText);
- EXPECT_THROW(generic::NSEC3("1 1 1 ADDAFEEE "
- "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW A NS SOA"),
- BadValue); // bad base32hex
- EXPECT_THROW(generic::NSEC3("1 1 1000000 ADDAFEEE "
- "0123456789ABCDEFGHIJKLMNOPQRSTUV A NS SOA"),
- InvalidRdataText);
-
- // Next hash shouldn't be padded
- EXPECT_THROW(generic::NSEC3("1 1 1 ADDAFEEE CPNMU=== A NS SOA"),
- InvalidRdataText);
-
// Hash is too long. Max = 255 bytes, base32-hex converts each 5 bytes
// of the original to 8 characters, so 260 * 8 / 5 is the smallest length
// of the encoded string that exceeds the max and doesn't require padding.
- EXPECT_THROW(generic::NSEC3("1 1 1 D399EAAB " + string((260 * 8) / 5, '0') +
- " NS"),
- InvalidRdataText);
+ checkFromText_InvalidText("1 1 1 D399EAAB " + string((260 * 8) / 5, '0') +
+ " A NS SOA");
+
+ // Type bitmap is empty. it's possible and allowed for NSEC3.
+ EXPECT_NO_THROW(const generic::NSEC3 rdata_notype_nsec3(nsec3_notype_txt));
+
+ // Empty salt is also okay.
+ EXPECT_NO_THROW(const generic::NSEC3 rdata_nosalt_nsec3(nsec3_nosalt_txt));
+
+ // Bad type mnemonics
+ checkFromText_InvalidText("1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6"
+ " BIFF POW SPOON");
+
+ // Bad base32hex
+ checkFromText_BadValue("1 1 1 D399EAAB "
+ "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW A NS SOA");
+
+ // Hash algorithm out of range
+ checkFromText_InvalidText("256 1 1 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Flags out of range
+ checkFromText_InvalidText("1 256 1 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Iterations out of range
+ checkFromText_InvalidText("1 1 65536 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Space is not allowed in salt or the next hash. This actually
+ // causes the Base32 decoder that parses the next hash that comes
+ // afterwards, to throw.
+ checkFromText_BadValue("1 1 1 D399 EAAB H9RSFB7FPF2L8"
+ "HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Next hash must not contain padding (trailing '=' characters)
+ checkFromText_InvalidText("1 1 1 D399EAAB "
+ "AAECAwQFBgcICQoLDA0ODw== A NS SOA");
+
+ // String instead of number
+ checkFromText_LexerError("foo 1 1 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+ checkFromText_LexerError("1 foo 1 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+ checkFromText_LexerError("1 1 foo D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Trailing garbage. This should cause only the string constructor
+ // to fail, but the lexer constructor must be able to continue
+ // parsing from it.
+ checkFromText_BadString(
+ "1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA ;comment\n"
+ "1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A NS SOA");
+
+ // Unmatched parenthesis should cause a lexer error
+ checkFromText_LexerError("1 1 1 D399EAAB "
+ "H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A ) NS SOA");
}
TEST_F(Rdata_NSEC3_Test, createFromWire) {
@@ -131,19 +188,18 @@ TEST_F(Rdata_NSEC3_Test, createFromWire) {
}
TEST_F(Rdata_NSEC3_Test, createFromLexer) {
- const generic::NSEC3 rdata_nsec3(nsec3_txt);
EXPECT_EQ(0, rdata_nsec3.compare(
*test::createRdataUsingLexer(RRType::NSEC3(), RRClass::IN(),
nsec3_txt)));
- // Exceptions cause NULL to be returned.
- EXPECT_FALSE(test::createRdataUsingLexer(RRType::NSEC3(), RRClass::IN(),
- "1 1 1 ADDAFEEE CPNMU=== "
- "A NS SOA"));
+ // empty salt is also okay.
+ const generic::NSEC3 rdata_nosalt_nsec3(nsec3_nosalt_txt);
+ EXPECT_EQ(0, rdata_nosalt_nsec3.compare(
+ *test::createRdataUsingLexer(RRType::NSEC3(), RRClass::IN(),
+ nsec3_nosalt_txt)));
}
TEST_F(Rdata_NSEC3_Test, assign) {
- generic::NSEC3 rdata_nsec3(nsec3_txt);
generic::NSEC3 other_nsec3 = rdata_nsec3;
EXPECT_EQ(0, rdata_nsec3.compare(other_nsec3));
}
diff --git a/src/lib/dns/tests/rdata_nsec3param_unittest.cc b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
index 115d3d3..4fccbf3 100644
--- a/src/lib/dns/tests/rdata_nsec3param_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
@@ -16,8 +16,6 @@
#include <exceptions/exceptions.h>
-#include <util/encode/base32hex.h>
-#include <util/encode/hex.h>
#include <util/buffer.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
@@ -35,39 +33,107 @@ using namespace std;
using namespace isc;
using namespace isc::dns;
using namespace isc::util;
-using namespace isc::util::encode;
using namespace isc::dns::rdata;
namespace {
class Rdata_NSEC3PARAM_Test : public RdataTest {
-public:
- Rdata_NSEC3PARAM_Test() : nsec3param_txt("1 1 1 D399EAAB") {}
+protected:
+ Rdata_NSEC3PARAM_Test() :
+ nsec3param_txt("1 1 1 D399EAAB"),
+ nsec3param_nosalt_txt("1 1 1 -"),
+ rdata_nsec3param(nsec3param_txt)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::NSEC3PARAM, isc::Exception, isc::Exception>(
+ rdata_str, rdata_nsec3param, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::NSEC3PARAM, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_nsec3param, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<generic::NSEC3PARAM, BadValue, BadValue>(
+ rdata_str, rdata_nsec3param, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::NSEC3PARAM, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_nsec3param, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str,
+ const generic::NSEC3PARAM& rdata)
+ {
+ checkFromText
+ <generic::NSEC3PARAM, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata, true, false);
+ }
+
const string nsec3param_txt;
+ const string nsec3param_nosalt_txt;
+ const generic::NSEC3PARAM rdata_nsec3param;
};
TEST_F(Rdata_NSEC3PARAM_Test, fromText) {
- // With a salt
- EXPECT_EQ(1, generic::NSEC3PARAM(nsec3param_txt).getHashalg());
- EXPECT_EQ(1, generic::NSEC3PARAM(nsec3param_txt).getFlags());
- // (salt is checked in the toText test)
+ // Empty salt is okay.
+ EXPECT_EQ(0, generic::NSEC3PARAM(nsec3param_nosalt_txt).getSalt().size());
+
+ // Salt is missing.
+ checkFromText_LexerError("1 1 1");
+
+ // Salt has whitespace within. This only fails in the string
+ // constructor, as the lexer constructor stops reading at the end of
+ // its RDATA.
+ const generic::NSEC3PARAM rdata_nsec3param2("1 1 1 D399");
+ checkFromText_BadString("1 1 1 D399 EAAB", rdata_nsec3param2);
+
+ // Hash algorithm out of range.
+ checkFromText_InvalidText("256 1 1 D399EAAB");
+
+ // Flags out of range.
+ checkFromText_InvalidText("1 256 1 D399EAAB");
+
+ // Iterations out of range.
+ checkFromText_InvalidText("1 1 65536 D399EAAB");
+
+ // Bad hex sequence
+ checkFromText_BadValue("1 1 256 D399EAABZOO");
- // With an empty salt
- EXPECT_EQ(0, generic::NSEC3PARAM("1 0 0 -").getSalt().size());
+ // String instead of number
+ checkFromText_LexerError("foo 1 256 D399EAAB");
+ checkFromText_LexerError("1 foo 256 D399EAAB");
+ checkFromText_LexerError("1 1 foo D399EAAB");
+
+ // Trailing garbage. This should cause only the string constructor
+ // to fail, but the lexer constructor must be able to continue
+ // parsing from it.
+ checkFromText_BadString("1 1 1 D399EAAB ; comment\n"
+ "1 1 1 D399EAAB", rdata_nsec3param);
}
TEST_F(Rdata_NSEC3PARAM_Test, toText) {
- const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
EXPECT_EQ(nsec3param_txt, rdata_nsec3param.toText());
-}
-TEST_F(Rdata_NSEC3PARAM_Test, badText) {
- // garbage space at the end
- EXPECT_THROW(generic::NSEC3PARAM("1 1 1 D399EAAB "),
- InvalidRdataText);
+ // Garbage space at the end should be ok. RFC5155 only forbids
+ // whitespace within the salt field, but any whitespace afterwards
+ // should be fine.
+ EXPECT_NO_THROW(generic::NSEC3PARAM("1 1 1 D399EAAB "));
+
+ // Hash algorithm in range.
+ EXPECT_NO_THROW(generic::NSEC3PARAM("255 1 1 D399EAAB"));
+
+ // Flags in range.
+ EXPECT_NO_THROW(generic::NSEC3PARAM("1 255 1 D399EAAB"));
+
+ // Iterations in range.
+ EXPECT_NO_THROW(generic::NSEC3PARAM("1 1 65535 D399EAAB"));
}
TEST_F(Rdata_NSEC3PARAM_Test, createFromWire) {
- const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
EXPECT_EQ(0, rdata_nsec3param.compare(
*rdataFactoryFromFile(RRType::NSEC3PARAM(), RRClass::IN(),
"rdata_nsec3param_fromWire1")));
@@ -87,15 +153,19 @@ TEST_F(Rdata_NSEC3PARAM_Test, createFromWire) {
}
TEST_F(Rdata_NSEC3PARAM_Test, createFromLexer) {
- const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
EXPECT_EQ(0, rdata_nsec3param.compare(
*test::createRdataUsingLexer(RRType::NSEC3PARAM(), RRClass::IN(),
nsec3param_txt)));
+
+ // empty salt is also okay.
+ const generic::NSEC3PARAM rdata_nosalt_nsec3param(nsec3param_nosalt_txt);
+ EXPECT_EQ(0, rdata_nosalt_nsec3param.compare(
+ *test::createRdataUsingLexer(RRType::NSEC3PARAM(), RRClass::IN(),
+ nsec3param_nosalt_txt)));
}
TEST_F(Rdata_NSEC3PARAM_Test, toWireRenderer) {
renderer.skip(2);
- const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
rdata_nsec3param.toWire(renderer);
vector<unsigned char> data;
@@ -106,13 +176,26 @@ TEST_F(Rdata_NSEC3PARAM_Test, toWireRenderer) {
}
TEST_F(Rdata_NSEC3PARAM_Test, toWireBuffer) {
- const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
rdata_nsec3param.toWire(obuffer);
+
+ vector<unsigned char> data;
+ UnitTestUtil::readWireData("rdata_nsec3param_fromWire1", data);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ obuffer.getData(), obuffer.getLength(),
+ &data[2], data.size() - 2);
+}
+
+TEST_F(Rdata_NSEC3PARAM_Test, getHashAlg) {
+ EXPECT_EQ(1, rdata_nsec3param.getHashalg());
+}
+
+TEST_F(Rdata_NSEC3PARAM_Test, getFlags) {
+ EXPECT_EQ(1, rdata_nsec3param.getFlags());
}
TEST_F(Rdata_NSEC3PARAM_Test, assign) {
- generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
- generic::NSEC3PARAM other_nsec3param = rdata_nsec3param;
+ generic::NSEC3PARAM other_nsec3param("1 1 1 -");
+ other_nsec3param = rdata_nsec3param;
EXPECT_EQ(0, rdata_nsec3param.compare(other_nsec3param));
}
diff --git a/src/lib/dns/tests/rdata_opt_unittest.cc b/src/lib/dns/tests/rdata_opt_unittest.cc
index 5699259..20ccfe4 100644
--- a/src/lib/dns/tests/rdata_opt_unittest.cc
+++ b/src/lib/dns/tests/rdata_opt_unittest.cc
@@ -84,6 +84,6 @@ TEST_F(Rdata_OPT_Test, compare) {
"rdata_opt_fromWire", 2)));
// comparison attempt between incompatible RR types should be rejected
- EXPECT_THROW(rdata_opt.compare(*RdataTest::rdata_nomatch), bad_cast);
+ EXPECT_THROW(rdata_opt.compare(*RdataTest::rdata_nomatch), bad_cast);
}
}
diff --git a/src/lib/dns/tests/rdata_rp_unittest.cc b/src/lib/dns/tests/rdata_rp_unittest.cc
index 5508d9c..38bec04 100644
--- a/src/lib/dns/tests/rdata_rp_unittest.cc
+++ b/src/lib/dns/tests/rdata_rp_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -41,6 +41,38 @@ protected:
obuffer(0)
{}
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::RP, isc::Exception, isc::Exception>(
+ rdata_str, rdata_rp, false, false);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::RP, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_rp, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText<generic::RP, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_rp, true, false);
+ }
+
+ void checkFromText_EmptyLabel(const string& rdata_str) {
+ checkFromText<generic::RP, EmptyLabel, EmptyLabel>(
+ rdata_str, rdata_rp, true, true);
+ }
+
+ void checkFromText_MissingOrigin(const string& rdata_str) {
+ checkFromText
+ <generic::RP, MissingNameOrigin, MissingNameOrigin>(
+ rdata_str, rdata_rp, true, true);
+ }
+
+ void checkFromText_Origin(const string& rdata_str, const Name* origin) {
+ checkFromText<generic::RP, MissingNameOrigin, isc::Exception>(
+ rdata_str, rdata_rp, true, false, origin);
+ }
+
const Name mailbox_name, text_name;
const generic::RP rdata_rp; // commonly used test RDATA
OutputBuffer obuffer;
@@ -52,17 +84,28 @@ TEST_F(Rdata_RP_Test, createFromText) {
EXPECT_EQ(mailbox_name, rdata_rp.getMailbox());
EXPECT_EQ(text_name, rdata_rp.getText());
- // Invalid textual input cases follow:
- // names are invalid
- EXPECT_THROW(generic::RP("bad..name. rp-text.example.com"), EmptyLabel);
- EXPECT_THROW(generic::RP("mailbox.example.com. bad..name"), EmptyLabel);
+ checkFromText_None("root.example.com. rp-text.example.com.");
+
+ // origin defined for lexer constructor, but not string constructor
+ const Name origin("example.com");
+ checkFromText_Origin("root rp-text", &origin);
+
+ // lexer constructor accepts extra text, but string constructor doesn't
+ checkFromText_BadString("root.example.com. rp-text.example.com. "
+ "extra.example.com.");
+}
+
+TEST_F(Rdata_RP_Test, badText) {
+ // invalid names
+ checkFromText_EmptyLabel("root..example.com. rp-text.example.com.");
+ checkFromText_EmptyLabel("root.example.com. rp-text..example.com.");
// missing field
- EXPECT_THROW(generic::RP("mailbox.example.com."), InvalidRdataText);
+ checkFromText_LexerError("root.example.com.");
- // redundant field
- EXPECT_THROW(generic::RP("mailbox.example.com. rp-text.example.com. "
- "redundant.example."), InvalidRdataText);
+ // missing origin
+ checkFromText_MissingOrigin("root.example.com rp-text.example.com.");
+ checkFromText_MissingOrigin("root.example.com. rp-text.example.com");
}
TEST_F(Rdata_RP_Test, createFromWire) {
@@ -106,17 +149,6 @@ TEST_F(Rdata_RP_Test, createFromParams) {
EXPECT_EQ(text_name, generic::RP(mailbox_name, text_name).getText());
}
-TEST_F(Rdata_RP_Test, createFromLexer) {
- EXPECT_EQ(0, rdata_rp.compare(
- *test::createRdataUsingLexer(RRType::RP(), RRClass::IN(),
- "root.example.com. "
- "rp-text.example.com.")));
-
- // Exceptions cause NULL to be returned.
- EXPECT_FALSE(test::createRdataUsingLexer(RRType::RP(), RRClass::IN(),
- "mailbox.example.com."));
-}
-
TEST_F(Rdata_RP_Test, toWireBuffer) {
// construct expected data
UnitTestUtil::readWireData("rdata_rp_toWire1.wire", expected_wire);
diff --git a/src/lib/dns/tests/rdata_rrsig_unittest.cc b/src/lib/dns/tests/rdata_rrsig_unittest.cc
index d758ff3..2d075ec 100644
--- a/src/lib/dns/tests/rdata_rrsig_unittest.cc
+++ b/src/lib/dns/tests/rdata_rrsig_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -36,7 +36,7 @@ using namespace isc::dns::rdata;
namespace {
class Rdata_RRSIG_Test : public RdataTest {
-public:
+protected:
Rdata_RRSIG_Test() :
rrsig_txt("A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
"evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
@@ -46,6 +46,49 @@ public:
rdata_rrsig(rrsig_txt)
{}
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::RRSIG, isc::Exception, isc::Exception>(
+ rdata_str, rdata_rrsig, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::RRSIG, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_InvalidType(const string& rdata_str) {
+ checkFromText<generic::RRSIG, InvalidRRType, InvalidRRType>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_InvalidTime(const string& rdata_str) {
+ checkFromText<generic::RRSIG, InvalidTime, InvalidTime>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<generic::RRSIG, BadValue, BadValue>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::RRSIG, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_MissingOrigin(const string& rdata_str) {
+ checkFromText
+ <generic::RRSIG, MissingNameOrigin, MissingNameOrigin>(
+ rdata_str, rdata_rrsig, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::RRSIG, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_rrsig, true, false);
+ }
+
const string rrsig_txt;
const generic::RRSIG rdata_rrsig;
};
@@ -53,52 +96,196 @@ public:
TEST_F(Rdata_RRSIG_Test, fromText) {
EXPECT_EQ(rrsig_txt, rdata_rrsig.toText());
EXPECT_EQ(isc::dns::RRType::A(), rdata_rrsig.typeCovered());
+
+ // Missing signature is OK
+ EXPECT_NO_THROW(const generic::RRSIG sig(
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org."));
+
+ // Space in signature data is OK
+ checkFromText_None(
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz "
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/ "
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU "
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+
+ // Multi-line signature data is OK, if enclosed in parentheses
+ checkFromText_None(
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "( evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz\n"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/\n"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU\n"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc= )");
+
+ // Trailing garbage. This should cause only the string constructor
+ // to fail, but the lexer constructor must be able to continue
+ // parsing from it.
+ checkFromText_BadString(
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc= ; comment\n"
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_missingFields) {
+ checkFromText_LexerError("A");
+ checkFromText_LexerError("A 5");
+ checkFromText_LexerError("A 5 4");
+ checkFromText_LexerError("A 5 4 43200");
+ checkFromText_LexerError("A 5 4 43200 20100223214617");
+ checkFromText_LexerError("A 5 4 43200 20100223214617 20100222214617");
+ checkFromText_LexerError("A 5 4 43200 20100223214617 20100222214617 "
+ "8496");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_coveredType) {
+ checkFromText_InvalidType("SPORK");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_algorithm) {
+ checkFromText_InvalidText(
+ "A 555 4 43200 "
+ "20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_LexerError(
+ "A FIVE 4 43200 "
+ "20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
}
-TEST_F(Rdata_RRSIG_Test, badText) {
- EXPECT_THROW(const generic::RRSIG sig("SPORK"), InvalidRdataText);
- EXPECT_THROW(const generic::RRSIG sig("A 555 4 43200 "
+TEST_F(Rdata_RRSIG_Test, badText_labels) {
+ checkFromText_InvalidText(
+ "A 5 4444 43200 "
"20100223214617 20100222214617 8496 isc.org. "
"evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
"diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
"NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
- "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidRdataText);
- EXPECT_THROW(const generic::RRSIG sig("A 5 4444 43200 "
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_LexerError(
+ "A 5 FOUR 43200 "
"20100223214617 20100222214617 8496 isc.org. "
"evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
"diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
"NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
- "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidRdataText);
- EXPECT_THROW(const generic::RRSIG sig("A 5 4 999999999999 "
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_ttl) {
+ checkFromText_LexerError(
+ "A 5 4 999999999999 "
"20100223214617 20100222214617 8496 isc.org. "
"evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
"diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
"NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
- "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidRdataText);
- EXPECT_THROW(const generic::RRSIG sig("A 5 4 43200 "
- "20100223 20100227 8496 isc.org. "
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_LexerError(
+ "A 5 4 TTL "
+ "20100223214617 20100222214617 8496 isc.org. "
"evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
"diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
"NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
- "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidTime);
- EXPECT_THROW(const generic::RRSIG sig("A 5 4 43200 "
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+
+ // alternate form of TTL is not okay
+ checkFromText_LexerError(
+ "A 5 4 12H 20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz "
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/ "
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU "
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_expiration) {
+ checkFromText_InvalidTime(
+ "A 5 4 43200 "
+ "201002232 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_InvalidTime(
+ "A 5 4 43200 "
+ "EXPIRATION 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_inception) {
+ checkFromText_InvalidTime(
+ "A 5 4 43200 "
+ "20100223214617 20100227 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_InvalidTime(
+ "A 5 4 43200 "
+ "20100223214617 INCEPTION 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_keytag) {
+ checkFromText_InvalidText(
+ "A 5 4 43200 "
"20100223214617 20100222214617 999999 isc.org. "
"evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
"diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
"NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
- "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidRdataText);
- EXPECT_THROW(const generic::RRSIG sig(
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+ checkFromText_LexerError(
"A 5 4 43200 "
- "20100223214617 20100222214617 8496 isc.org. "
- "EEeeeeeeEEEeeeeeeGaaahAAAAAAAAHHHHHHHHHHH!="),
- BadValue); // bad base64 input
+ "20100223214617 20100222214617 TAG isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
+}
+
+TEST_F(Rdata_RRSIG_Test, badText_signer) {
+ checkFromText_MissingOrigin(
+ "A 5 4 43200 "
+ "20100223214617 20100222214617 8496 isc.org "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
}
-TEST_F(Rdata_RRSIG_Test, DISABLED_badText) {
- // this currently fails
+TEST_F(Rdata_RRSIG_Test, badText_signature) {
+ checkFromText_BadValue(
+ "A 5 4 43200 "
+ "20100223214617 20100222214617 8496 isc.org. "
+ "EEeeeeeeEEEeeeeeeGaaahAAAAAAAAHHHHHHHHHHH!=");
+
// no space between the tag and signer
- EXPECT_THROW(generic::RRSIG("A 5 4 43200 20100223214617 20100222214617 "
- "8496isc.org. ofc="), InvalidRdataText);
+ checkFromText_LexerError(
+ "A 5 4 43200 20100223214617 20100222214617 "
+ "8496isc.org. ofc=");
+
+ // unterminated multi-line base64
+ checkFromText_LexerError(
+ "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "( evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz\n"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/\n"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU\n"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc=");
}
TEST_F(Rdata_RRSIG_Test, createFromLexer) {
diff --git a/src/lib/dns/tests/rdata_sshfp_unittest.cc b/src/lib/dns/tests/rdata_sshfp_unittest.cc
index 6c13ad9..b85dfa5 100644
--- a/src/lib/dns/tests/rdata_sshfp_unittest.cc
+++ b/src/lib/dns/tests/rdata_sshfp_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -30,17 +30,50 @@
using isc::UnitTestUtil;
using namespace std;
+using namespace isc;
using namespace isc::dns;
using namespace isc::util;
using namespace isc::dns::rdata;
namespace {
class Rdata_SSHFP_Test : public RdataTest {
- // there's nothing to specialize
+protected:
+ Rdata_SSHFP_Test() :
+ sshfp_txt("2 1 123456789abcdef67890123456789abcdef67890"),
+ rdata_sshfp(sshfp_txt)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<generic::SSHFP, isc::Exception, isc::Exception>(
+ rdata_str, rdata_sshfp, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<generic::SSHFP, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_sshfp, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <generic::SSHFP, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_sshfp, true, true);
+ }
+
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<generic::SSHFP, InvalidRdataText, BadValue>(
+ rdata_str, rdata_sshfp, true, true);
+ }
+
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <generic::SSHFP, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_sshfp, true, false);
+ }
+
+ const string sshfp_txt;
+ const generic::SSHFP rdata_sshfp;
};
-const string sshfp_txt("2 1 123456789abcdef67890123456789abcdef67890");
-const generic::SSHFP rdata_sshfp(2, 1, "123456789abcdef67890123456789abcdef67890");
const uint8_t rdata_sshfp_wiredata[] = {
// algorithm
0x02,
@@ -56,22 +89,23 @@ const uint8_t rdata_sshfp_wiredata[] = {
TEST_F(Rdata_SSHFP_Test, createFromText) {
// Basic test
- const generic::SSHFP rdata_sshfp2(sshfp_txt);
- EXPECT_EQ(0, rdata_sshfp2.compare(rdata_sshfp));
+ checkFromText_None(sshfp_txt);
// With different spacing
- const generic::SSHFP rdata_sshfp3("2 1 123456789abcdef67890123456789abcdef67890");
- EXPECT_EQ(0, rdata_sshfp3.compare(rdata_sshfp));
+ checkFromText_None("2 1 123456789abcdef67890123456789abcdef67890");
// Combination of lowercase and uppercase
- const generic::SSHFP rdata_sshfp4("2 1 123456789ABCDEF67890123456789abcdef67890");
- EXPECT_EQ(0, rdata_sshfp4.compare(rdata_sshfp));
-}
+ checkFromText_None("2 1 123456789ABCDEF67890123456789abcdef67890");
-TEST_F(Rdata_SSHFP_Test, createFromLexer) {
- EXPECT_EQ(0, rdata_sshfp.compare(
- *test::createRdataUsingLexer(RRType::SSHFP(), RRClass::IN(),
- "2 1 123456789abcdef67890123456789abcdef67890")));
+ // spacing in the fingerprint field
+ checkFromText_None("2 1 123456789abcdef67890 123456789abcdef67890");
+
+ // multi-line fingerprint field
+ checkFromText_None("2 1 ( 123456789abcdef67890\n 123456789abcdef67890 )");
+
+ // string constructor throws if there's extra text,
+ // but lexer constructor doesn't
+ checkFromText_BadString(sshfp_txt + "\n" + sshfp_txt);
}
TEST_F(Rdata_SSHFP_Test, algorithmTypes) {
@@ -101,13 +135,30 @@ TEST_F(Rdata_SSHFP_Test, algorithmTypes) {
}
TEST_F(Rdata_SSHFP_Test, badText) {
- EXPECT_THROW(const generic::SSHFP rdata_sshfp("1"), InvalidRdataText);
- EXPECT_THROW(const generic::SSHFP rdata_sshfp("BUCKLE MY SHOES"), InvalidRdataText);
- EXPECT_THROW(const generic::SSHFP rdata_sshfp("1 2 foo bar"), InvalidRdataText);
+ checkFromText_LexerError("1");
+ checkFromText_LexerError("ONE 2 123456789abcdef67890123456789abcdef67890");
+ checkFromText_LexerError("1 TWO 123456789abcdef67890123456789abcdef67890");
+ checkFromText_BadValue("1 2 BUCKLEMYSHOE");
+ checkFromText_BadValue(sshfp_txt + " extra text");
+
+ // yes, these are redundant to the last test cases in algorithmTypes
+ checkFromText_InvalidText(
+ "2345 1 123456789abcdef67890123456789abcdef67890");
+ checkFromText_InvalidText(
+ "2 1234 123456789abcdef67890123456789abcdef67890");
+
+ // negative values are trapped in the lexer rather than the constructor
+ checkFromText_LexerError("-2 1 123456789abcdef67890123456789abcdef67890");
+ checkFromText_LexerError("2 -1 123456789abcdef67890123456789abcdef67890");
}
-TEST_F(Rdata_SSHFP_Test, copy) {
- const generic::SSHFP rdata_sshfp2(rdata_sshfp);
+TEST_F(Rdata_SSHFP_Test, copyAndAssign) {
+ // Copy construct
+ generic::SSHFP rdata_sshfp2(rdata_sshfp);
+ EXPECT_EQ(0, rdata_sshfp.compare(rdata_sshfp2));
+
+ // Assignment, mainly to confirm it doesn't cause disruption.
+ rdata_sshfp2 = rdata_sshfp;
EXPECT_EQ(0, rdata_sshfp.compare(rdata_sshfp2));
}
@@ -160,6 +211,12 @@ TEST_F(Rdata_SSHFP_Test, createFromWire) {
InvalidBufferPosition);
}
+TEST_F(Rdata_SSHFP_Test, createFromParams) {
+ const generic::SSHFP rdata_sshfp2(
+ 2, 1, "123456789abcdef67890123456789abcdef67890");
+ EXPECT_EQ(0, rdata_sshfp2.compare(rdata_sshfp));
+}
+
TEST_F(Rdata_SSHFP_Test, toText) {
EXPECT_TRUE(boost::iequals(sshfp_txt, rdata_sshfp.toText()));
diff --git a/src/lib/dns/tests/rdata_tsig_unittest.cc b/src/lib/dns/tests/rdata_tsig_unittest.cc
index df35842..d351b40 100644
--- a/src/lib/dns/tests/rdata_tsig_unittest.cc
+++ b/src/lib/dns/tests/rdata_tsig_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -23,6 +23,7 @@
#include <dns/rdataclass.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>
+#include <dns/tsigerror.h>
#include <gtest/gtest.h>
@@ -31,34 +32,84 @@
using isc::UnitTestUtil;
using namespace std;
+using namespace isc;
using namespace isc::dns;
using namespace isc::util;
using namespace isc::dns::rdata;
namespace {
+
class Rdata_TSIG_Test : public RdataTest {
protected:
- vector<uint8_t> expect_data;
-};
+ Rdata_TSIG_Test() :
+ // no MAC or Other Data
+ valid_text1("hmac-md5.sig-alg.reg.int. 1286779327 300 "
+ "0 16020 BADKEY 0"),
+ // MAC but no Other Data
+ valid_text2("hmac-sha256. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 BADSIG 0"),
+ // MAC and Other Data
+ valid_text3("hmac-sha1. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 BADTIME 6 FAKEFAKE"),
+ // MAC and Other Data (with Error that doesn't expect Other Data)
+ valid_text4("hmac-sha1. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 BADSIG 6 FAKEFAKE"),
+ // numeric error code
+ valid_text5("hmac-sha256. 1286779327 300 12 "
+ "FAKEFAKEFAKEFAKE 16020 2845 0"),
+ rdata_tsig(valid_text1)
+ {}
+
+ void checkFromText_None(const string& rdata_str) {
+ checkFromText<any::TSIG, isc::Exception, isc::Exception>(
+ rdata_str, rdata_tsig, false, false);
+ }
+
+ void checkFromText_InvalidText(const string& rdata_str) {
+ checkFromText<any::TSIG, InvalidRdataText, InvalidRdataText>(
+ rdata_str, rdata_tsig, true, true);
+ }
-const char* const valid_text1 = "hmac-md5.sig-alg.reg.int. 1286779327 300 "
- "0 16020 BADKEY 0";
-const char* const valid_text2 = "hmac-sha256. 1286779327 300 12 "
- "FAKEFAKEFAKEFAKE 16020 BADSIG 0";
+ void checkFromText_BadValue(const string& rdata_str) {
+ checkFromText<any::TSIG, BadValue, BadValue>(
+ rdata_str, rdata_tsig, true, true);
+ }
+
+ void checkFromText_LexerError(const string& rdata_str) {
+ checkFromText
+ <any::TSIG, InvalidRdataText, MasterLexer::LexerError>(
+ rdata_str, rdata_tsig, true, true);
+ }
-const char* const valid_text3 = "hmac-sha1. 1286779327 300 12 "
- "FAKEFAKEFAKEFAKE 16020 BADTIME 6 FAKEFAKE";
-const char* const valid_text4 = "hmac-sha1. 1286779327 300 12 "
- "FAKEFAKEFAKEFAKE 16020 BADSIG 6 FAKEFAKE";
-const char* const valid_text5 = "hmac-sha256. 1286779327 300 12 "
- "FAKEFAKEFAKEFAKE 16020 2845 0"; // using numeric error code
-const char* const too_long_label = "012345678901234567890123456789"
- "0123456789012345678901234567890123";
+ void checkFromText_TooLongLabel(const string& rdata_str) {
+ checkFromText<any::TSIG, TooLongLabel, TooLongLabel>(
+ rdata_str, rdata_tsig, true, true);
+ }
-// commonly used test RDATA
-const any::TSIG rdata_tsig((string(valid_text1)));
+ void checkFromText_EmptyLabel(const string& rdata_str) {
+ checkFromText<any::TSIG, EmptyLabel, EmptyLabel>(
+ rdata_str, rdata_tsig, true, true);
+ }
-TEST_F(Rdata_TSIG_Test, createFromText) {
+ void checkFromText_BadString(const string& rdata_str) {
+ checkFromText
+ <any::TSIG, InvalidRdataText, isc::Exception>(
+ rdata_str, rdata_tsig, true, false);
+ }
+
+ template <typename Output>
+ void toWireCommonChecks(Output& output) const;
+
+ const string valid_text1;
+ const string valid_text2;
+ const string valid_text3;
+ const string valid_text4;
+ const string valid_text5;
+ vector<uint8_t> expect_data;
+ const any::TSIG rdata_tsig; // commonly used test RDATA
+};
+
+TEST_F(Rdata_TSIG_Test, fromText) {
// normal case. it also tests getter methods.
EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), rdata_tsig.getAlgorithm());
EXPECT_EQ(1286779327, rdata_tsig.getTimeSigned());
@@ -66,59 +117,80 @@ TEST_F(Rdata_TSIG_Test, createFromText) {
EXPECT_EQ(0, rdata_tsig.getMACSize());
EXPECT_EQ(static_cast<void*>(NULL), rdata_tsig.getMAC());
EXPECT_EQ(16020, rdata_tsig.getOriginalID());
- EXPECT_EQ(17, rdata_tsig.getError()); // TODO: use constant
+ EXPECT_EQ(TSIGError::BAD_KEY_CODE, rdata_tsig.getError());
EXPECT_EQ(0, rdata_tsig.getOtherLen());
EXPECT_EQ(static_cast<void*>(NULL), rdata_tsig.getOtherData());
- any::TSIG tsig2((string(valid_text2)));
+ any::TSIG tsig2(valid_text2);
EXPECT_EQ(12, tsig2.getMACSize());
- EXPECT_EQ(16, tsig2.getError()); // TODO: use constant
+ EXPECT_EQ(TSIGError::BAD_SIG_CODE, tsig2.getError());
- any::TSIG tsig3((string(valid_text3)));
+ any::TSIG tsig3(valid_text3);
EXPECT_EQ(6, tsig3.getOtherLen());
// The other data is unusual, but we don't reject it.
- EXPECT_NO_THROW(any::TSIG(string(valid_text4)));
+ EXPECT_NO_THROW(any::TSIG tsig4(valid_text4));
// numeric representation of TSIG error
- any::TSIG tsig5((string(valid_text5)));
+ any::TSIG tsig5(valid_text5);
EXPECT_EQ(2845, tsig5.getError());
- //
- // invalid cases
- //
- // there's a garbage parameter at the end
- EXPECT_THROW(any::TSIG("foo 0 0 0 0 BADKEY 0 0"), InvalidRdataText);
- // input is too short
- EXPECT_THROW(any::TSIG("foo 0 0 0 0 BADKEY"), InvalidRdataText);
+ // not fully qualified algorithm name
+ any::TSIG tsig1("hmac-md5.sig-alg.reg.int 1286779327 300 "
+ "0 16020 BADKEY 0");
+ EXPECT_EQ(0, tsig1.compare(rdata_tsig));
+
+ // multi-line rdata
+ checkFromText_None("hmac-md5.sig-alg.reg.int. ( 1286779327 300 \n"
+ "0 16020 BADKEY 0 )");
+};
+
+TEST_F(Rdata_TSIG_Test, badText) {
+ // too many fields
+ checkFromText_BadString(valid_text1 + " 0 0");
+ // not enough fields
+ checkFromText_LexerError("foo 0 0 0 0 BADKEY");
// bad domain name
- EXPECT_THROW(any::TSIG(string(too_long_label) + "0 0 0 0 BADKEY 0"),
- TooLongLabel);
+ checkFromText_TooLongLabel(
+ "0123456789012345678901234567890123456789012345678901234567890123"
+ " 0 0 0 0 BADKEY 0");
+ checkFromText_EmptyLabel("foo..bar 0 0 0 0 BADKEY");
// time is too large (2814...6 is 2^48)
- EXPECT_THROW(any::TSIG("foo 281474976710656 0 0 0 BADKEY 0"),
- InvalidRdataText);
+ checkFromText_InvalidText("foo 281474976710656 0 0 0 BADKEY 0");
// invalid time (negative)
- EXPECT_THROW(any::TSIG("foo -1 0 0 0 BADKEY 0"), InvalidRdataText);
+ checkFromText_InvalidText("foo -1 0 0 0 BADKEY 0");
+ // invalid time (not a number)
+ checkFromText_InvalidText("foo TIME 0 0 0 BADKEY 0");
// fudge is too large
- EXPECT_THROW(any::TSIG("foo 0 65536 0 0 BADKEY 0"), InvalidRdataText);
+ checkFromText_InvalidText("foo 0 65536 0 0 BADKEY 0");
// invalid fudge (negative)
- EXPECT_THROW(any::TSIG("foo 0 -1 0 0 BADKEY 0"), InvalidRdataText);
+ checkFromText_LexerError("foo 0 -1 0 0 BADKEY 0");
+ // invalid fudge (not a number)
+ checkFromText_LexerError("foo 0 FUDGE 0 0 BADKEY 0");
// MAC size is too large
- EXPECT_THROW(any::TSIG("foo 0 0 65536 0 BADKEY 0"), InvalidRdataText);
+ checkFromText_InvalidText("foo 0 0 65536 0 BADKEY 0");
+ // invalide MAC size (negative)
+ checkFromText_LexerError("foo 0 0 -1 0 BADKEY 0");
+ // invalid MAC size (not a number)
+ checkFromText_LexerError("foo 0 0 MACSIZE 0 BADKEY 0");
// MAC size and MAC mismatch
- EXPECT_THROW(any::TSIG("foo 0 0 9 FAKE 0 BADKEY 0"), InvalidRdataText);
- EXPECT_THROW(any::TSIG("foo 0 0 0 FAKE 0 BADKEY 0"), InvalidRdataText);
+ checkFromText_InvalidText("foo 0 0 9 FAKE 0 BADKEY 0");
// MAC is bad base64
- EXPECT_THROW(any::TSIG("foo 0 0 3 FAK= 0 BADKEY 0"), isc::BadValue);
+ checkFromText_BadValue("foo 0 0 3 FAK= 0 BADKEY 0");
// Unknown error code
- EXPECT_THROW(any::TSIG("foo 0 0 0 0 TEST 0"), InvalidRdataText);
+ checkFromText_InvalidText("foo 0 0 0 0 TEST 0");
// Numeric error code is too large
- EXPECT_THROW(any::TSIG("foo 0 0 0 0 65536 0"), InvalidRdataText);
+ checkFromText_InvalidText("foo 0 0 0 0 65536 0");
+ // Numeric error code is negative
+ checkFromText_InvalidText("foo 0 0 0 0 -1 0");
// Other len is too large
- EXPECT_THROW(any::TSIG("foo 0 0 0 0 NOERROR 65536 FAKE"), InvalidRdataText);
+ checkFromText_InvalidText("foo 0 0 0 0 NOERROR 65536 FAKE");
+ // Other len is negative
+ checkFromText_LexerError("foo 0 0 0 0 NOERROR -1 FAKE");
+ // invalid Other len
+ checkFromText_LexerError("foo 0 0 0 0 NOERROR LEN FAKE");
// Other len and data mismatch
- EXPECT_THROW(any::TSIG("foo 0 0 0 0 NOERROR 9 FAKE"), InvalidRdataText);
- EXPECT_THROW(any::TSIG("foo 0 0 0 0 NOERROR 0 FAKE"), InvalidRdataText);
+ checkFromText_InvalidText("foo 0 0 0 0 NOERROR 9 FAKE");
}
void
@@ -221,12 +293,12 @@ TEST_F(Rdata_TSIG_Test, createFromParams) {
const uint8_t fake_data[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84,
0x14, 0x02, 0x84, 0x14, 0x02, 0x84 };
- EXPECT_EQ(0, any::TSIG((string(valid_text2))).compare(
+ EXPECT_EQ(0, any::TSIG(valid_text2).compare(
any::TSIG(Name("hmac-sha256"), 1286779327, 300, 12,
fake_data, 16020, 16, 0, NULL)));
const uint8_t fake_data2[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84 };
- EXPECT_EQ(0, any::TSIG((string(valid_text3))).compare(
+ EXPECT_EQ(0, any::TSIG(valid_text3).compare(
any::TSIG(Name("hmac-sha1"), 1286779327, 300, 12,
fake_data, 16020, 18, 6, fake_data2)));
@@ -247,24 +319,14 @@ TEST_F(Rdata_TSIG_Test, createFromParams) {
isc::InvalidParameter);
}
-TEST_F(Rdata_TSIG_Test, createFromLexer) {
- EXPECT_EQ(0, rdata_tsig.compare(
- *test::createRdataUsingLexer(RRType::TSIG(), RRClass::ANY(),
- valid_text1)));
-
- // Exceptions cause NULL to be returned.
- EXPECT_FALSE(test::createRdataUsingLexer(RRType::TSIG(), RRClass::ANY(),
- "foo 0 0 0 0 BADKEY 0 0"));
-}
-
TEST_F(Rdata_TSIG_Test, assignment) {
- any::TSIG copy((string(valid_text2)));
+ any::TSIG copy(valid_text2);
copy = rdata_tsig;
EXPECT_EQ(0, copy.compare(rdata_tsig));
// Check if the copied data is valid even after the original is deleted
any::TSIG* copy2 = new any::TSIG(rdata_tsig);
- any::TSIG copy3((string(valid_text2)));
+ any::TSIG copy3(valid_text2);
copy3 = *copy2;
delete copy2;
EXPECT_EQ(0, copy3.compare(rdata_tsig));
@@ -276,7 +338,7 @@ TEST_F(Rdata_TSIG_Test, assignment) {
template <typename Output>
void
-toWireCommonChecks(Output& output) {
+Rdata_TSIG_Test::toWireCommonChecks(Output& output) const {
vector<uint8_t> expect_data;
output.clear();
@@ -291,7 +353,7 @@ toWireCommonChecks(Output& output) {
expect_data.clear();
output.clear();
- any::TSIG(string(valid_text2)).toWire(output);
+ any::TSIG(valid_text2).toWire(output);
UnitTestUtil::readWireData("rdata_tsig_toWire2.wire", expect_data);
expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
@@ -300,7 +362,7 @@ toWireCommonChecks(Output& output) {
expect_data.clear();
output.clear();
- any::TSIG(string(valid_text3)).toWire(output);
+ any::TSIG(valid_text3).toWire(output);
UnitTestUtil::readWireData("rdata_tsig_toWire3.wire", expect_data);
expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
@@ -339,10 +401,10 @@ TEST_F(Rdata_TSIG_Test, toWireRenderer) {
}
TEST_F(Rdata_TSIG_Test, toText) {
- EXPECT_EQ(string(valid_text1), rdata_tsig.toText());
- EXPECT_EQ(string(valid_text2), any::TSIG(string(valid_text2)).toText());
- EXPECT_EQ(string(valid_text3), any::TSIG(string(valid_text3)).toText());
- EXPECT_EQ(string(valid_text5), any::TSIG(string(valid_text5)).toText());
+ EXPECT_EQ(valid_text1, rdata_tsig.toText());
+ EXPECT_EQ(valid_text2, any::TSIG(valid_text2).toText());
+ EXPECT_EQ(valid_text3, any::TSIG(valid_text3).toText());
+ EXPECT_EQ(valid_text5, any::TSIG(valid_text5).toText());
}
TEST_F(Rdata_TSIG_Test, compare) {
diff --git a/src/lib/dns/tests/rrcollator_unittest.cc b/src/lib/dns/tests/rrcollator_unittest.cc
index e66f87c..9123dc5 100644
--- a/src/lib/dns/tests/rrcollator_unittest.cc
+++ b/src/lib/dns/tests/rrcollator_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -59,10 +59,10 @@ protected:
txt_rdata_(createRdata(RRType::TXT(), rrclass_, "test")),
sig_rdata1_(createRdata(RRType::RRSIG(), rrclass_,
"A 5 3 3600 20000101000000 20000201000000 "
- "12345 example.com. FAKE\n")),
+ "12345 example.com. FAKE")),
sig_rdata2_(createRdata(RRType::RRSIG(), rrclass_,
"NS 5 3 3600 20000101000000 20000201000000 "
- "12345 example.com. FAKE\n"))
+ "12345 example.com. FAKE"))
{}
void checkRRset(const Name& expected_name, const RRClass& expected_class,
diff --git a/src/lib/dns/tests/rrparamregistry_unittest.cc b/src/lib/dns/tests/rrparamregistry_unittest.cc
index 08e0af1..b0d43a8 100644
--- a/src/lib/dns/tests/rrparamregistry_unittest.cc
+++ b/src/lib/dns/tests/rrparamregistry_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -108,13 +108,16 @@ TEST_F(RRParamRegistryTest, addError) {
class TestRdataFactory : public AbstractRdataFactory {
public:
- using AbstractRdataFactory::create;
virtual RdataPtr create(const string& rdata_str) const
{ return (RdataPtr(new in::A(rdata_str))); }
virtual RdataPtr create(InputBuffer& buffer, size_t rdata_len) const
{ return (RdataPtr(new in::A(buffer, rdata_len))); }
virtual RdataPtr create(const Rdata& source) const
{ return (RdataPtr(new in::A(dynamic_cast<const in::A&>(source)))); }
+ virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options options,
+ MasterLoaderCallbacks& callbacks) const
+ { return (RdataPtr(new in::A(lexer, origin, options, callbacks))); }
};
TEST_F(RRParamRegistryTest, addRemoveFactory) {
diff --git a/src/lib/dns/tests/rrset_unittest.cc b/src/lib/dns/tests/rrset_unittest.cc
index d16ce3c..a605caf 100644
--- a/src/lib/dns/tests/rrset_unittest.cc
+++ b/src/lib/dns/tests/rrset_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2010-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -289,7 +289,7 @@ protected:
rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(),
RRType::RRSIG(), RRTTL(3600)));
rrset_rrsig->addRdata(generic::RRSIG("AAAA 5 3 7200 20100322084538 "
- "20100220084538 1 example.com "
+ "20100220084538 1 example.com. "
"FAKEFAKEFAKEFAKE"));
rrset_aaaa->addRRsig(rrset_rrsig);
}
diff --git a/src/lib/dns/tests/testdata/.gitignore b/src/lib/dns/tests/testdata/.gitignore
index e8879e1..a0a14f4 100644
--- a/src/lib/dns/tests/testdata/.gitignore
+++ b/src/lib/dns/tests/testdata/.gitignore
@@ -41,6 +41,8 @@
/rdata_minfo_toWire2.wire
/rdata_minfo_toWireUncompressed1.wire
/rdata_minfo_toWireUncompressed2.wire
+/rdata_dnskey_fromWire.wire
+/rdata_dnskey_empty_keydata_fromWire.wire
/rdata_nsec3_fromWire10.wire
/rdata_nsec3_fromWire11.wire
/rdata_nsec3_fromWire12.wire
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index 52acb7c..b6d7e35 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -16,6 +16,7 @@ BUILT_SOURCES += message_toText3.wire
BUILT_SOURCES += name_toWire5.wire name_toWire6.wire
BUILT_SOURCES += rdatafields1.wire rdatafields2.wire rdatafields3.wire
BUILT_SOURCES += rdatafields4.wire rdatafields5.wire rdatafields6.wire
+BUILT_SOURCES += rdata_dnskey_fromWire.wire rdata_dnskey_empty_keydata_fromWire.wire
BUILT_SOURCES += rdata_nsec_fromWire4.wire rdata_nsec_fromWire5.wire
BUILT_SOURCES += rdata_nsec_fromWire6.wire rdata_nsec_fromWire7.wire
BUILT_SOURCES += rdata_nsec_fromWire8.wire rdata_nsec_fromWire9.wire
@@ -101,7 +102,8 @@ EXTRA_DIST += name_toWire7 name_toWire8 name_toWire9
EXTRA_DIST += question_fromWire question_toWire1 question_toWire2
EXTRA_DIST += rdatafields1.spec rdatafields2.spec rdatafields3.spec
EXTRA_DIST += rdatafields4.spec rdatafields5.spec rdatafields6.spec
-EXTRA_DIST += rdata_cname_fromWire rdata_dname_fromWire rdata_dnskey_fromWire
+EXTRA_DIST += rdata_cname_fromWire rdata_dname_fromWire
+EXTRA_DIST += rdata_dnskey_fromWire.spec rdata_dnskey_empty_keydata_fromWire.spec
EXTRA_DIST += rdata_dhcid_fromWire rdata_dhcid_toWire
EXTRA_DIST += rdata_ds_fromWire rdata_in_a_fromWire rdata_in_aaaa_fromWire
EXTRA_DIST += rdata_mx_fromWire rdata_mx_toWire1 rdata_mx_toWire2
diff --git a/src/lib/dns/tests/testdata/example.org b/src/lib/dns/tests/testdata/example.org
index 4163fc0..2708ef4 100644
--- a/src/lib/dns/tests/testdata/example.org
+++ b/src/lib/dns/tests/testdata/example.org
@@ -13,5 +13,5 @@ example.org. 3600 IN SOA ( ; The SOA, split across lines for testing
; Some empty lines here. They are to make sure the loader can skip them.
www 3600 IN A 192.0.2.1 ; Test a relative name as well.
- 3600 IN AAAA 2001:db8::1 ; And initial whitespace hanling
+ 3600 IN AAAA 2001:db8::1 ; And initial whitespace handling
; Here be just some space, no RRs
diff --git a/src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.spec b/src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.spec
new file mode 100644
index 0000000..b65271d
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_dnskey_empty_keydata_fromWire.spec
@@ -0,0 +1,7 @@
+# DNSKEY test data with empty digest
+
+[custom]
+sections: dnskey
+
+[dnskey]
+digest:
diff --git a/src/lib/dns/tests/testdata/rdata_dnskey_fromWire b/src/lib/dns/tests/testdata/rdata_dnskey_fromWire
deleted file mode 100644
index b703da1..0000000
--- a/src/lib/dns/tests/testdata/rdata_dnskey_fromWire
+++ /dev/null
@@ -1,24 +0,0 @@
-# RDLENGTH = 265 bytes
- 01 09
-# DNSKEY, flags 257
- 01 01
-# protocol 3, algorithm 5
- 03 05
-# keydata:
- 04 40 00 00 03 a1 1d 00 c1 ae 14 1b b6 98 60 ab
- 6c 10 52 91 10 e6 de 03 b5 41 f1 a0 c5 45 bb 68
- 56 2c 33 2f a0 e3 11 5e 31 ab 86 10 9e 16 f0 19
- 8a 1e f2 24 77 fc 64 67 d6 ea 17 77 f2 15 c6 ff
- 1c a5 60 23 ba 2a ba 5b 76 88 f0 c7 c6 0c 5c b0
- 39 fe 40 3e bb 9d 16 20 bf 19 47 54 7a 29 36 ec
- 61 53 1f fd 0c 79 46 23 5b 3c 29 70 fa f4 fe 53
- c7 97 10 99 8e db 48 c8 4b 55 0b 82 ac b7 e3 b7
- 01 07 5c cc 9e 7c ff e0 b2 69 03 47 5a f4 26 ca
- 8f 70 36 e7 84 f9 d7 9b 0d 20 c7 30 b0 1f 3f db
- ed 84 eb 7f f3 66 b4 33 06 48 f4 06 b3 7f f4 17
- b1 8e 98 a4 b3 78 d1 85 96 ad 12 c5 e7 dd d4 f2
- e3 b4 74 f5 48 b1 e5 67 09 b7 ec 73 a9 9e fe ca
- cc 8b 28 e3 9e 75 2d fd 67 b4 83 9a c9 f6 78 0d
- 05 2a d4 29 c0 0e 8b 5d e1 b6 c3 e8 f1 9b 0d e8
- 03 c9 55 52 01 1f fe bc de 0b f6 c1 c8 13 6c 3b
- bd 1a 10 54 dd
diff --git a/src/lib/dns/tests/testdata/rdata_dnskey_fromWire.spec b/src/lib/dns/tests/testdata/rdata_dnskey_fromWire.spec
new file mode 100644
index 0000000..87e66db
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_dnskey_fromWire.spec
@@ -0,0 +1,7 @@
+# DNSKEY test data
+
+[custom]
+sections: dnskey
+
+[dnskey]
+digest: BEAAAAOhHQDBrhQbtphgq2wQUpEQ5t4DtUHxoMVFu2hWLDMvoOMRXjGrhhCeFvAZih7yJHf8ZGfW6hd38hXG/xylYCO6Krpbdojwx8YMXLA5/kA+u50WIL8ZR1R6KTbsYVMf/Qx5RiNbPClw+vT+U8eXEJmO20jIS1ULgqy347cBB1zMnnz/4LJpA0da9CbKj3A254T515sNIMcwsB8/2+2E63/zZrQzBkj0BrN/9Bexjpiks3jRhZatEsXn3dTy47R09Uix5WcJt+xzqZ7+ysyLKOOedS39Z7SDmsn2eA0FKtQpwA6LXeG2w+jxmw3oA8lVUgEf/rzeC/bByBNsO70aEFTd
diff --git a/src/lib/dns/tests/testdata/rrset_toWire2 b/src/lib/dns/tests/testdata/rrset_toWire2
index 601de8b..ca6483f 100644
--- a/src/lib/dns/tests/testdata/rrset_toWire2
+++ b/src/lib/dns/tests/testdata/rrset_toWire2
@@ -18,7 +18,7 @@
# RDATA: 192.0.2.1
c0 00 02 01
#
-# 2nd RR: the owner name is compresed
+# 2nd RR: the owner name is compressed
c0 00
00 01 00 01
00 00 0e 10
diff --git a/src/lib/dns/tsigkey.cc b/src/lib/dns/tsigkey.cc
index e55cce3..7075203 100644
--- a/src/lib/dns/tsigkey.cc
+++ b/src/lib/dns/tsigkey.cc
@@ -148,7 +148,7 @@ TSIGKey::TSIGKey(const TSIGKey& source) : impl_(new TSIGKeyImpl(*source.impl_))
TSIGKey&
TSIGKey::operator=(const TSIGKey& source) {
- if (impl_ == source.impl_) {
+ if (this == &source) {
return (*this);
}
diff --git a/src/lib/dns/tsigrecord.cc b/src/lib/dns/tsigrecord.cc
index 9dd3f78..ba30c0a 100644
--- a/src/lib/dns/tsigrecord.cc
+++ b/src/lib/dns/tsigrecord.cc
@@ -59,13 +59,14 @@ namespace {
// of the constructor below.
const any::TSIG&
castToTSIGRdata(const rdata::Rdata& rdata) {
- try {
- return (dynamic_cast<const any::TSIG&>(rdata));
- } catch (std::bad_cast&) {
+ const any::TSIG* tsig_rdata =
+ dynamic_cast<const any::TSIG*>(&rdata);
+ if (!tsig_rdata) {
isc_throw(DNSMessageFORMERR,
"TSIG record is being constructed from "
"incompatible RDATA:" << rdata.toText());
}
+ return (*tsig_rdata);
}
}
diff --git a/src/lib/hooks/.gitignore b/src/lib/hooks/.gitignore
new file mode 100644
index 0000000..5a9364c
--- /dev/null
+++ b/src/lib/hooks/.gitignore
@@ -0,0 +1,2 @@
+/hooks_messages.cc
+/hooks_messages.h
diff --git a/src/lib/hooks/Makefile.am b/src/lib/hooks/Makefile.am
new file mode 100644
index 0000000..d9ea39e
--- /dev/null
+++ b/src/lib/hooks/Makefile.am
@@ -0,0 +1,64 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+
+# Define rule to build logging source files from message file
+hooks_messages.h hooks_messages.cc: hooks_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/hooks/hooks_messages.mes
+
+# Tell automake that the message files are built as part of the build process
+# (so that they are built before the main library is built).
+BUILT_SOURCES = hooks_messages.h hooks_messages.cc
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = hooks_messages.mes
+
+# Get rid of generated message files on a clean
+CLEANFILES = *.gcno *.gcda hooks_messages.h hooks_messages.cc
+
+lib_LTLIBRARIES = libb10-hooks.la
+libb10_hooks_la_SOURCES =
+libb10_hooks_la_SOURCES += callout_handle.cc callout_handle.h
+libb10_hooks_la_SOURCES += callout_manager.cc callout_manager.h
+libb10_hooks_la_SOURCES += hooks.h
+libb10_hooks_la_SOURCES += hooks_log.cc hooks_log.h
+libb10_hooks_la_SOURCES += hooks_manager.cc hooks_manager.h
+libb10_hooks_la_SOURCES += library_handle.cc library_handle.h
+libb10_hooks_la_SOURCES += library_manager.cc library_manager.h
+libb10_hooks_la_SOURCES += library_manager_collection.cc library_manager_collection.h
+libb10_hooks_la_SOURCES += pointer_converter.h
+libb10_hooks_la_SOURCES += server_hooks.cc server_hooks.h
+
+nodist_libb10_hooks_la_SOURCES = hooks_messages.cc hooks_messages.h
+
+libb10_hooks_la_CXXFLAGS = $(AM_CXXFLAGS)
+libb10_hooks_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+libb10_hooks_la_LDFLAGS = $(AM_LDFLAGS)
+libb10_hooks_la_LIBADD =
+libb10_hooks_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
+libb10_hooks_la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
+libb10_hooks_la_LIBADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+
+# Specify the headers for copying into the installation directory tree. User-
+# written libraries only need the definitions from the headers for the
+# CalloutHandle and LibraryHandle objects.
+libb10_hooks_includedir = $(pkgincludedir)/hooks
+libb10_hooks_include_HEADERS = \
+ callout_handle.h \
+ library_handle.h \
+ hooks.h
+
+if USE_CLANGPP
+# Disable unused parameter warning caused by some of the
+# Boost headers when compiling with clang.
+libb10_hooks_la_CXXFLAGS += -Wno-unused-parameter
+endif
diff --git a/src/lib/hooks/callout_handle.cc b/src/lib/hooks/callout_handle.cc
new file mode 100644
index 0000000..ce9ef82
--- /dev/null
+++ b/src/lib/hooks/callout_handle.cc
@@ -0,0 +1,162 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
+
+#include <string>
+#include <utility>
+#include <vector>
+
+using namespace std;
+
+namespace isc {
+namespace hooks {
+
+// Constructor.
+CalloutHandle::CalloutHandle(const boost::shared_ptr<CalloutManager>& manager,
+ const boost::shared_ptr<LibraryManagerCollection>& lmcoll)
+ : lm_collection_(lmcoll), arguments_(), context_collection_(),
+ manager_(manager), skip_(false) {
+
+ // Call the "context_create" hook. We should be OK doing this - although
+ // the constructor has not finished running, all the member variables
+ // have been created.
+ manager_->callCallouts(ServerHooks::CONTEXT_CREATE, *this);
+}
+
+// Destructor
+CalloutHandle::~CalloutHandle() {
+
+ // Call the "context_destroy" hook. We should be OK doing this - although
+ // the destructor is being called, all the member variables are still in
+ // existence.
+ manager_->callCallouts(ServerHooks::CONTEXT_DESTROY, *this);
+
+ // Explicitly clear the argument and context objects. This should free up
+ // all memory that could have been allocated by libraries that were loaded.
+ arguments_.clear();
+ context_collection_.clear();
+
+ // Normal destruction of the remaining variables will include the
+ // destruction of lm_collection_, an action that decrements the reference
+ // count on the library manager collection (which holds the libraries that
+ // could have allocated memory in the argument and context members.) When
+ // that goes to zero, the libraries will be unloaded: at that point nothing
+ // in the hooks framework will be pointing to memory in the libraries'
+ // address space.
+ //
+ // It is possible that some other data structure in the server (the program
+ // using the hooks library) still references the address space and attempts
+ // to access it causing a segmentation fault. That issue is outside the
+ // scope of this framework and is not addressed by it.
+}
+
+// Return the name of all argument items.
+
+vector<string>
+CalloutHandle::getArgumentNames() const {
+
+ vector<string> names;
+ for (ElementCollection::const_iterator i = arguments_.begin();
+ i != arguments_.end(); ++i) {
+ names.push_back(i->first);
+ }
+
+ return (names);
+}
+
+// Return the library handle allowing the callout to access the CalloutManager
+// registration/deregistration functions.
+
+LibraryHandle&
+CalloutHandle::getLibraryHandle() const {
+ return (manager_->getLibraryHandle());
+}
+
+// Return the context for the currently pointed-to library. This version is
+// used by the "setContext()" method and creates a context for the current
+// library if it does not exist.
+
+CalloutHandle::ElementCollection&
+CalloutHandle::getContextForLibrary() {
+ int libindex = manager_->getLibraryIndex();
+
+ // Access a reference to the element collection for the given index,
+ // creating a new element collection if necessary, and return it.
+ return (context_collection_[libindex]);
+}
+
+// The "const" version of the above, used by the "getContext()" method. If
+// the context for the current library doesn't exist, throw an exception.
+
+const CalloutHandle::ElementCollection&
+CalloutHandle::getContextForLibrary() const {
+ int libindex = manager_->getLibraryIndex();
+
+ ContextCollection::const_iterator libcontext =
+ context_collection_.find(libindex);
+ if (libcontext == context_collection_.end()) {
+ isc_throw(NoSuchCalloutContext, "unable to find callout context "
+ "associated with the current library index (" << libindex <<
+ ")");
+ }
+
+ // Return a reference to the context's element collection.
+ return (libcontext->second);
+}
+
+// Return the name of all items in the context associated with the current]
+// library.
+
+vector<string>
+CalloutHandle::getContextNames() const {
+
+ vector<string> names;
+
+ const ElementCollection& elements = getContextForLibrary();
+ for (ElementCollection::const_iterator i = elements.begin();
+ i != elements.end(); ++i) {
+ names.push_back(i->first);
+ }
+
+ return (names);
+}
+
+// Return name of current hook (the hook to which the current callout is
+// attached) or the empty string if not called within the context of a
+// callout.
+
+string
+CalloutHandle::getHookName() const {
+ // Get the current hook index.
+ int index = manager_->getHookIndex();
+
+ // ... and look up the hook.
+ string hook = "";
+ try {
+ hook = ServerHooks::getServerHooks().getName(index);
+ } catch (const NoSuchHook&) {
+ // Hook index is invalid, so this methods probably called from outside
+ // a callout being executed via a call to CalloutManager::callCallouts.
+ // In this case, the empty string is returned.
+ }
+
+ return (hook);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/hooks/callout_handle.h b/src/lib/hooks/callout_handle.h
new file mode 100644
index 0000000..eb57fd4
--- /dev/null
+++ b/src/lib/hooks/callout_handle.h
@@ -0,0 +1,383 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 CALLOUT_HANDLE_H
+#define CALLOUT_HANDLE_H
+
+#include <exceptions/exceptions.h>
+#include <hooks/library_handle.h>
+
+#include <boost/any.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace hooks {
+
+/// @brief No such argument
+///
+/// Thrown if an attempt is made access an argument that does not exist.
+
+class NoSuchArgument : public Exception {
+public:
+ NoSuchArgument(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief No such callout context item
+///
+/// Thrown if an attempt is made to get an item of data from this callout's
+/// context and either the context or an item in the context with that name
+/// does not exist.
+
+class NoSuchCalloutContext : public Exception {
+public:
+ NoSuchCalloutContext(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+// Forward declaration of the library handle and related collection classes.
+
+class CalloutManager;
+class LibraryHandle;
+class LibraryManagerCollection;
+
+/// @brief Per-packet callout handle
+///
+/// An object of this class is associated with every packet (or request)
+/// processed by the server. It forms the principle means of passing data
+/// between the server and the user-library callouts.
+///
+/// The class allows access to the following information:
+///
+/// - Arguments. When the callouts associated with a hook are called, they
+/// are passed information by the server (and can return information to it)
+/// through name/value pairs. Each of these pairs is an argument and the
+/// information is accessed through the {get,set}Argument() methods.
+///
+/// - Per-packet context. Each packet has a context associated with it, this
+/// context being on a per-library basis. In other words, As a packet passes
+/// through the callouts associated with a given library, the callouts can
+/// associate and retrieve information with the packet. The per-library
+/// nature of the context means that the callouts within a given library can
+/// pass packet-specific information between one another, but they cannot pass
+/// information to callous within another library. Typically such context
+/// is created in the "context_create" callout and destroyed in the
+/// "context_destroy" callout. The information is accessed through the
+/// {get,set}Context() methods.
+///
+/// - Per-library handle (LibraryHandle). The library handle allows the
+/// callout to dynamically register and deregister callouts. In the latter
+/// case, only functions registered by functions in the same library as the
+/// callout doing the deregistration can be removed: callouts registered by
+/// other libraries cannot be modified.
+
+class CalloutHandle {
+public:
+
+ /// Typedef to allow abbreviation of iterator specification in methods.
+ /// The std::string is the argument name and the "boost::any" is the
+ /// corresponding value associated with it.
+ typedef std::map<std::string, boost::any> ElementCollection;
+
+ /// Typedef to allow abbreviations in specifications when accessing
+ /// context. The ElementCollection is the name/value collection for
+ /// a particular context. The "int" corresponds to the index of an
+ /// associated library - there is a 1:1 correspondence between libraries
+ /// and a name.value collection.
+ ///
+ /// The collection of contexts is stored in a map, as not every library
+ /// will require creation of a context associated with each packet. In
+ /// addition, the structure is more flexible in that the size does not
+ /// need to be set when the CalloutHandle is constructed.
+ typedef std::map<int, ElementCollection> ContextCollection;
+
+ /// @brief Constructor
+ ///
+ /// Creates the object and calls the callouts on the "context_create"
+ /// hook.
+ ///
+ /// Of the two arguments passed, only the pointer to the callout manager is
+ /// actively used. The second argument, the pointer to the library manager
+ /// collection, is used for lifetime control: after use, the callout handle
+ /// may contain pointers to memory allocated by the loaded libraries. The
+ /// used of a shared pointer to the collection of library managers means
+ /// that the libraries that could have allocated memory in a callout handle
+ /// will not be unloaded until all such handles have been destroyed. This
+ /// issue is discussed in more detail in the documentation for
+ /// isc::hooks::LibraryManager.
+ ///
+ /// @param manager Pointer to the callout manager object.
+ /// @param lmcoll Pointer to the library manager collection. This has a
+ /// null default for testing purposes.
+ CalloutHandle(const boost::shared_ptr<CalloutManager>& manager,
+ const boost::shared_ptr<LibraryManagerCollection>& lmcoll =
+ boost::shared_ptr<LibraryManagerCollection>());
+
+ /// @brief Destructor
+ ///
+ /// Calls the context_destroy callback to release any per-packet context.
+ /// It also clears stored data to avoid problems during member destruction.
+ ~CalloutHandle();
+
+ /// @brief Set argument
+ ///
+ /// Sets the value of an argument. The argument is created if it does not
+ /// already exist.
+ ///
+ /// @param name Name of the argument.
+ /// @param value Value to set. That can be of any data type.
+ template <typename T>
+ void setArgument(const std::string& name, T value) {
+ arguments_[name] = value;
+ }
+
+ /// @brief Get argument
+ ///
+ /// Gets the value of an argument.
+ ///
+ /// @param name Name of the element in the argument list to get.
+ /// @param value [out] Value to set. The type of "value" is important:
+ /// it must match the type of the value set.
+ ///
+ /// @throw NoSuchArgument No argument with the given name is present.
+ /// @throw boost::bad_any_cast An argument with the given name is present,
+ /// but the data type of the value is not the same as the type of
+ /// the variable provided to receive the value.
+ template <typename T>
+ void getArgument(const std::string& name, T& value) const {
+ ElementCollection::const_iterator element_ptr = arguments_.find(name);
+ if (element_ptr == arguments_.end()) {
+ isc_throw(NoSuchArgument, "unable to find argument with name " <<
+ name);
+ }
+
+ value = boost::any_cast<T>(element_ptr->second);
+ }
+
+ /// @brief Get argument names
+ ///
+ /// Returns a vector holding the names of arguments in the argument
+ /// vector.
+ ///
+ /// @return Vector of strings reflecting argument names.
+ std::vector<std::string> getArgumentNames() const;
+
+ /// @brief Delete argument
+ ///
+ /// Deletes an argument of the given name. If an argument of that name
+ /// does not exist, the method is a no-op.
+ ///
+ /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted
+ /// by this method.
+ ///
+ /// @param name Name of the element in the argument list to set.
+ void deleteArgument(const std::string& name) {
+ static_cast<void>(arguments_.erase(name));
+ }
+
+ /// @brief Delete all arguments
+ ///
+ /// Deletes all arguments associated with this context.
+ ///
+ /// N.B. If any elements are raw pointers, the pointed-to data is NOT
+ /// deleted by this method.
+ void deleteAllArguments() {
+ arguments_.clear();
+ }
+
+ /// @brief Set skip flag
+ ///
+ /// Sets the "skip" variable in the callout handle. This variable is
+ /// interrogated by the server to see if the remaining callouts associated
+ /// with the current hook should be bypassed.
+ ///
+ /// @param skip New value of the "skip" flag.
+ void setSkip(bool skip) {
+ skip_ = skip;
+ }
+
+ /// @brief Get skip flag
+ ///
+ /// Gets the current value of the "skip" flag.
+ ///
+ /// @return Current value of the skip flag.
+ bool getSkip() const {
+ return (skip_);
+ }
+
+ /// @brief Access current library handle
+ ///
+ /// Returns a reference to the current library handle. This function is
+ /// only available when called by a callout (which in turn is called
+ /// through the "callCallouts" method), as it is only then that the current
+ /// library index is valid. A callout uses the library handle to
+ /// dynamically register or deregister callouts.
+ ///
+ /// @return Reference to the library handle.
+ ///
+ /// @throw InvalidIndex thrown if this method is called when the current
+ /// library index is invalid (typically if it is called outside of
+ /// the active callout).
+ LibraryHandle& getLibraryHandle() const;
+
+ /// @brief Set context
+ ///
+ /// Sets an element in the context associated with the current library. If
+ /// an element of the name is already present, it is replaced.
+ ///
+ /// @param name Name of the element in the context to set.
+ /// @param value Value to set.
+ template <typename T>
+ void setContext(const std::string& name, T value) {
+ getContextForLibrary()[name] = value;
+ }
+
+ /// @brief Get context
+ ///
+ /// Gets an element from the context associated with the current library.
+ ///
+ /// @param name Name of the element in the context to get.
+ /// @param value [out] Value to set. The type of "value" is important:
+ /// it must match the type of the value set.
+ ///
+ /// @throw NoSuchCalloutContext Thrown if no context element with the name
+ /// "name" is present.
+ /// @throw boost::bad_any_cast Thrown if the context element is present
+ /// but the type of the data is not the same as the type of the
+ /// variable provided to receive its value.
+ template <typename T>
+ void getContext(const std::string& name, T& value) const {
+ const ElementCollection& lib_context = getContextForLibrary();
+
+ ElementCollection::const_iterator element_ptr = lib_context.find(name);
+ if (element_ptr == lib_context.end()) {
+ isc_throw(NoSuchCalloutContext, "unable to find callout context "
+ "item " << name << " in the context associated with "
+ "current library");
+ }
+
+ value = boost::any_cast<T>(element_ptr->second);
+ }
+
+ /// @brief Get context names
+ ///
+ /// Returns a vector holding the names of items in the context associated
+ /// with the current library.
+ ///
+ /// @return Vector of strings reflecting the names of items in the callout
+ /// context associated with the current library.
+ std::vector<std::string> getContextNames() const;
+
+ /// @brief Delete context element
+ ///
+ /// Deletes an item of the given name from the context associated with the
+ /// current library. If an item of that name does not exist, the method is
+ /// a no-op.
+ ///
+ /// N.B. If the element is a raw pointer, the pointed-to data is NOT deleted
+ /// by this.
+ ///
+ /// @param name Name of the context item to delete.
+ void deleteContext(const std::string& name) {
+ static_cast<void>(getContextForLibrary().erase(name));
+ }
+
+ /// @brief Delete all context items
+ ///
+ /// Deletes all items from the context associated with the current library.
+ ///
+ /// N.B. If any elements are raw pointers, the pointed-to data is NOT
+ /// deleted by this.
+ void deleteAllContext() {
+ getContextForLibrary().clear();
+ }
+
+ /// @brief Get hook name
+ ///
+ /// Get the name of the hook to which the current callout is attached.
+ /// This can be the null string if the CalloutHandle is being accessed
+ /// outside of the CalloutManager's "callCallouts" method.
+ ///
+ /// @return Name of the current hook or the empty string if none.
+ std::string getHookName() const;
+
+private:
+ /// @brief Check index
+ ///
+ /// Gets the current library index, throwing an exception if it is not set
+ /// or is invalid for the current library collection.
+ ///
+ /// @return Current library index, valid for this library collection.
+ ///
+ /// @throw InvalidIndex current library index is not valid for the library
+ /// handle collection.
+ int getLibraryIndex() const;
+
+ /// @brief Return reference to context for current library
+ ///
+ /// Called by all context-setting functions, this returns a reference to
+ /// the callout context for the current library, creating a context if it
+ /// does not exist.
+ ///
+ /// @return Reference to the collection of name/value pairs associated
+ /// with the current library.
+ ///
+ /// @throw InvalidIndex current library index is not valid for the library
+ /// handle collection.
+ ElementCollection& getContextForLibrary();
+
+ /// @brief Return reference to context for current library (const version)
+ ///
+ /// Called by all context-accessing functions, this a reference to the
+ /// callout context for the current library. An exception is thrown if
+ /// it does not exist.
+ ///
+ /// @return Reference to the collection of name/value pairs associated
+ /// with the current library.
+ ///
+ /// @throw NoSuchCalloutContext Thrown if there is no ElementCollection
+ /// associated with the current library.
+ const ElementCollection& getContextForLibrary() const;
+
+ // Member variables
+
+ /// Pointer to the collection of libraries for which this handle has been
+ /// created.
+ boost::shared_ptr<LibraryManagerCollection> lm_collection_;
+
+ /// Collection of arguments passed to the callouts
+ ElementCollection arguments_;
+
+ /// Context collection - there is one entry per library context.
+ ContextCollection context_collection_;
+
+ /// Callout manager.
+ boost::shared_ptr<CalloutManager> manager_;
+
+ /// "Skip" flag, indicating if the caller should bypass remaining callouts.
+ bool skip_;
+};
+
+/// A shared pointer to a CalloutHandle object.
+typedef boost::shared_ptr<CalloutHandle> CalloutHandlePtr;
+
+} // namespace hooks
+} // namespace isc
+
+
+#endif // CALLOUT_HANDLE_H
diff --git a/src/lib/hooks/callout_manager.cc b/src/lib/hooks/callout_manager.cc
new file mode 100644
index 0000000..166fda1
--- /dev/null
+++ b/src/lib/hooks/callout_manager.cc
@@ -0,0 +1,260 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/hooks_log.h>
+#include <hooks/pointer_converter.h>
+
+#include <boost/static_assert.hpp>
+
+#include <algorithm>
+#include <climits>
+#include <functional>
+#include <utility>
+
+using namespace std;
+
+namespace isc {
+namespace hooks {
+
+// Constructor
+CalloutManager::CalloutManager(int num_libraries)
+ : current_hook_(-1), current_library_(-1),
+ hook_vector_(ServerHooks::getServerHooks().getCount()),
+ library_handle_(this), pre_library_handle_(this, 0),
+ post_library_handle_(this, INT_MAX), num_libraries_(num_libraries)
+{
+ if (num_libraries < 0) {
+ isc_throw(isc::BadValue, "number of libraries passed to the "
+ "CalloutManager must be >= 0");
+ }
+}
+
+// Check that the index of a library is valid. It can range from 1 - n
+// (n is the number of libraries), 0 (pre-user library callouts), or INT_MAX
+// (post-user library callouts). It can also be -1 to indicate an invalid
+// value.
+
+void
+CalloutManager::checkLibraryIndex(int library_index) const {
+ if (((library_index >= -1) && (library_index <= num_libraries_)) ||
+ (library_index == INT_MAX)) {
+ return;
+ }
+
+ isc_throw(NoSuchLibrary, "library index " << library_index <<
+ " is not valid for the number of loaded libraries (" <<
+ num_libraries_ << ")");
+}
+
+// Register a callout for the current library.
+
+void
+CalloutManager::registerCallout(const std::string& name, CalloutPtr callout) {
+ // Note the registration.
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUT_REGISTRATION)
+ .arg(current_library_).arg(name);
+
+ // Sanity check that the current library index is set to a valid value.
+ checkLibraryIndex(current_library_);
+
+ // Get the index associated with this hook (validating the name in the
+ // process).
+ int hook_index = ServerHooks::getServerHooks().getIndex(name);
+
+ // Iterate through the callout vector for the hook from start to end,
+ // looking for the first entry where the library index is greater than
+ // the present index.
+ for (CalloutVector::iterator i = hook_vector_[hook_index].begin();
+ i != hook_vector_[hook_index].end(); ++i) {
+ if (i->first > current_library_) {
+ // Found an element whose library index number is greater than the
+ // current index, so insert the new element ahead of this one.
+ hook_vector_[hook_index].insert(i, make_pair(current_library_,
+ callout));
+ return;
+ }
+ }
+
+ // Reached the end of the vector, so there is no element in the (possibly
+ // empty) set of callouts with a library index greater than the current
+ // library index. Inset the callout at the end of the list.
+ hook_vector_[hook_index].push_back(make_pair(current_library_, callout));
+}
+
+// Check if callouts are present for a given hook index.
+
+bool
+CalloutManager::calloutsPresent(int hook_index) const {
+ // Validate the hook index.
+ if ((hook_index < 0) || (hook_index >= hook_vector_.size())) {
+ isc_throw(NoSuchHook, "hook index " << hook_index <<
+ " is not valid for the list of registered hooks");
+ }
+
+ // Valid, so are there any callouts associated with that hook?
+ return (!hook_vector_[hook_index].empty());
+}
+
+// Call all the callouts for a given hook.
+
+void
+CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
+
+ // Clear the "skip" flag so we don't carry state from a previous call.
+ // This is done regardless of whether callouts are present to avoid passing
+ // any state from the previous call of callCallouts().
+ callout_handle.setSkip(false);
+
+ // Only initialize and iterate if there are callouts present. This check
+ // also catches the case of an invalid index.
+ if (calloutsPresent(hook_index)) {
+
+ // Set the current hook index. This is used should a callout wish to
+ // determine to what hook it is attached.
+ current_hook_ = hook_index;
+
+ // Duplicate the callout vector for this hook and work through that.
+ // This step is needed because we allow dynamic registration and
+ // deregistration of callouts. If a callout attached to a hook modified
+ // the list of callouts on that hook, the underlying CalloutVector would
+ // change and potentially affect the iteration through that vector.
+ CalloutVector callouts(hook_vector_[hook_index]);
+
+ // Call all the callouts.
+ for (CalloutVector::const_iterator i = callouts.begin();
+ i != callouts.end(); ++i) {
+ // In case the callout tries to register or deregister a callout,
+ // set the current library index to the index associated with the
+ // library that registered the callout being called.
+ current_library_ = i->first;
+
+ // Call the callout
+ try {
+ int status = (*i->second)(callout_handle);
+ if (status == 0) {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_EXTENDED_CALLS,
+ HOOKS_CALLOUT_CALLED).arg(current_library_)
+ .arg(ServerHooks::getServerHooks()
+ .getName(current_hook_))
+ .arg(PointerConverter(i->second).dlsymPtr());
+ } else {
+ LOG_ERROR(hooks_logger, HOOKS_CALLOUT_ERROR)
+ .arg(current_library_)
+ .arg(ServerHooks::getServerHooks()
+ .getName(current_hook_))
+ .arg(PointerConverter(i->second).dlsymPtr());
+ }
+ } catch (const std::exception& e) {
+ // Any exception, not just ones based on isc::Exception
+ LOG_ERROR(hooks_logger, HOOKS_CALLOUT_EXCEPTION)
+ .arg(current_library_)
+ .arg(ServerHooks::getServerHooks().getName(current_hook_))
+ .arg(PointerConverter(i->second).dlsymPtr())
+ .arg(e.what());
+ }
+
+ }
+
+ // Reset the current hook and library indexs to an invalid value to
+ // catch any programming errors.
+ current_hook_ = -1;
+ current_library_ = -1;
+ }
+}
+
+// Deregister a callout registered by the current library on a particular hook.
+
+bool
+CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout) {
+ // Sanity check that the current library index is set to a valid value.
+ checkLibraryIndex(current_library_);
+
+ // Get the index associated with this hook (validating the name in the
+ // process).
+ int hook_index = ServerHooks::getServerHooks().getIndex(name);
+
+ /// Construct a CalloutEntry matching the current library and the callout
+ /// we want to remove.
+ CalloutEntry target(current_library_, callout);
+
+ /// To decide if any entries were removed, we'll record the initial size
+ /// of the callout vector for the hook, and compare it with the size after
+ /// the removal.
+ size_t initial_size = hook_vector_[hook_index].size();
+
+ // The next bit is standard STL (see "Item 33" in "Effective STL" by
+ // Scott Meyers).
+ //
+ // remove_if reorders the hook vector so that all items not matching
+ // the predicate are at the start of the vector and returns a pointer
+ // to the next element. (In this case, the predicate is that the item
+ // is equal to the value of the passed callout.) The erase() call
+ // removes everything from that element to the end of the vector, i.e.
+ // all the matching elements.
+ hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
+ hook_vector_[hook_index].end(),
+ bind1st(equal_to<CalloutEntry>(),
+ target)),
+ hook_vector_[hook_index].end());
+
+ // Return an indication of whether anything was removed.
+ bool removed = initial_size != hook_vector_[hook_index].size();
+ if (removed) {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_EXTENDED_CALLS,
+ HOOKS_CALLOUT_DEREGISTERED).arg(current_library_).arg(name);
+ }
+
+ return (removed);
+}
+
+// Deregister all callouts on a given hook.
+
+bool
+CalloutManager::deregisterAllCallouts(const std::string& name) {
+
+ // Get the index associated with this hook (validating the name in the
+ // process).
+ int hook_index = ServerHooks::getServerHooks().getIndex(name);
+
+ /// Construct a CalloutEntry matching the current library (the callout
+ /// pointer is NULL as we are not checking that).
+ CalloutEntry target(current_library_, NULL);
+
+ /// To decide if any entries were removed, we'll record the initial size
+ /// of the callout vector for the hook, and compare it with the size after
+ /// the removal.
+ size_t initial_size = hook_vector_[hook_index].size();
+
+ // Remove all callouts matching this library.
+ hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
+ hook_vector_[hook_index].end(),
+ bind1st(CalloutLibraryEqual(),
+ target)),
+ hook_vector_[hook_index].end());
+
+ // Return an indication of whether anything was removed.
+ bool removed = initial_size != hook_vector_[hook_index].size();
+ if (removed) {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_EXTENDED_CALLS,
+ HOOKS_ALL_CALLOUTS_DEREGISTERED).arg(current_library_)
+ .arg(name);
+ }
+
+ return (removed);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/hooks/callout_manager.h b/src/lib/hooks/callout_manager.h
new file mode 100644
index 0000000..e1b9e57
--- /dev/null
+++ b/src/lib/hooks/callout_manager.h
@@ -0,0 +1,377 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 CALLOUT_MANAGER_H
+#define CALLOUT_MANAGER_H
+
+#include <exceptions/exceptions.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <climits>
+#include <map>
+#include <string>
+
+namespace isc {
+namespace hooks {
+
+/// @brief No such library
+///
+/// Thrown if an attempt is made to set the current library index to a value
+/// that is invalid for the number of loaded libraries.
+class NoSuchLibrary : public Exception {
+public:
+ NoSuchLibrary(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Callout Manager
+///
+/// This class manages the registration, deregistration and execution of the
+/// library callouts. It is part of the hooks framework used by the BIND 10
+/// server, and is not for use by user-written code in a hooks library.
+///
+/// In operation, the class needs to know two items of data:
+///
+/// - The list of server hooks, which is used in two ways. Firstly, when a
+/// callout registers or deregisters a hook, it does so by name: the
+/// @ref isc::util::ServerHooks object supplies the names of registered
+/// hooks. Secondly, when the callouts associated with a hook are called by
+/// the server, the server supplies the index of the relevant hook: this is
+/// validated by reference to the list of hook.
+///
+/// - The number of loaded libraries. Each callout registered by a user
+/// library is associated with that library, the callout manager storing both
+/// a pointer to the callout and the index of the library in the list of
+/// loaded libraries. Callouts are allowed to dynamically register and
+/// deregister callouts in the same library (including themselves): they
+/// cannot affect callouts registered by another library. When calling a
+/// callout, the callout manager maintains the idea of a "current library
+/// index": if the callout calls one of the callout registration functions
+/// (indirectly via the @ref LibraryHandle object), the registration
+/// functions use the "current library index" in their processing.
+///
+/// These two items of data are supplied when an object of this class is
+/// constructed. The latter (number of libraries) can be updated after the
+/// class is constructed. (Such an update is used during library loading where
+/// the CalloutManager has to be constructed before the libraries are loaded,
+/// but one of the libraries subsequently fails to load.)
+///
+/// The library index is important because it determines in what order callouts
+/// on a particular hook are called. For each hook, the CalloutManager
+/// maintains a vector of callouts ordered by library index. When a callout
+/// is added to the list, it is added at the end of the callouts associated
+/// with the current library. To clarify this further, suppose that three
+/// libraries are loaded, A (assigned an index 1), B (assigned an index 2) and
+/// C (assigned an index 3). Suppose A registers two callouts on a given hook,
+/// A1 and A2 (in that order) B registers B1 and B2 (in that order) and C
+/// registers C1 and C2 (in that order). Internally, the callouts are stored
+/// in the order A1, A2, B1, B2, C1, and C2: this is also the order in which
+/// the are called. If B now registers another callout (B3), it is added to
+/// the vector after the list of callouts associated with B: the new order is
+/// therefore A1, A2, B1, B2, B3, C1 and C2.
+///
+/// Indexes range between 1 and n (where n is the number of the libraries
+/// loaded) and are assigned to libraries based on the order the libraries
+/// presented to the hooks framework for loading (something that occurs in the
+/// isc::util::HooksManager) class. However, two other indexes are recognised,
+/// 0 and INT_MAX. These are used when the server itself registers callouts -
+/// the server is able to register callouts that get called before any
+/// user-library callouts, and ones that get called after user-library callouts.
+/// In other words, assuming the callouts on a hook are A1, A2, B1, B2, B3, C2,
+/// C2 as before, and that the server registers S1 (to run before the
+/// user-registered callouts) and S2 (to run after them), the callouts are
+/// stored (and executed) in the order S1, A1, A2, B1, B2, B3, C2, C2, S2. In
+/// summary, the recognised index values are:
+///
+/// - < 0: invalid.
+/// - 0: used for server-registered callouts that are called before
+/// user-registered callouts.
+/// - 1 - n: callouts from user libraries.
+/// - INT_MAX: used for server-registered callouts called after
+/// user-registered callouts.
+///
+/// Note that the callout functions do not access the CalloutManager: instead,
+/// they use a LibraryHandle object. This contains an internal pointer to
+/// the CalloutManager, but provides a restricted interface. In that way,
+/// callouts are unable to affect callouts supplied by other libraries.
+
+class CalloutManager {
+private:
+
+ // Private typedefs
+
+ /// Element in the vector of callouts. The elements in the pair are the
+ /// index of the library from which this callout was registered, and a#
+ /// pointer to the callout itself.
+ typedef std::pair<int, CalloutPtr> CalloutEntry;
+
+ /// An element in the hook vector. Each element is a vector of callouts
+ /// associated with a given hook.
+ typedef std::vector<CalloutEntry> CalloutVector;
+
+public:
+
+ /// @brief Constructor
+ ///
+ /// Initializes member variables, in particular sizing the hook vector
+ /// (the vector of callout vectors) to the appropriate size.
+ ///
+ /// @param num_libraries Number of loaded libraries.
+ ///
+ /// @throw isc::BadValue if the number of libraries is less than 0,
+ CalloutManager(int num_libraries = 0);
+
+ /// @brief Register a callout on a hook for the current library
+ ///
+ /// Registers a callout function for the current library with a given hook
+ /// (the index of the "current library" being given by the current_library_
+ /// member). The callout is added to the end of the callouts for this
+ /// library that are associated with that hook.
+ ///
+ /// @param name Name of the hook to which the callout is added.
+ /// @param callout Pointer to the callout function to be registered.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognised.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ void registerCallout(const std::string& name, CalloutPtr callout);
+
+ /// @brief De-Register a callout on a hook for the current library
+ ///
+ /// Searches through the functions registered by the the current library
+ /// (the index of the "current library" being given by the current_library_
+ /// member) with the named hook and removes all entries matching the
+ /// callout.
+ ///
+ /// @param name Name of the hook from which the callout is removed.
+ /// @param callout Pointer to the callout function to be removed.
+ ///
+ /// @return true if a one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognised.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ bool deregisterCallout(const std::string& name, CalloutPtr callout);
+
+ /// @brief Removes all callouts on a hook for the current library
+ ///
+ /// Removes all callouts associated with a given hook that were registered
+ /// by the current library (the index of the "current library" being given
+ /// by the current_library_ member).
+ ///
+ /// @param name Name of the hook from which the callouts are removed.
+ ///
+ /// @return true if one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook Thrown if the hook name is unrecognised.
+ bool deregisterAllCallouts(const std::string& name);
+
+ /// @brief Checks if callouts are present on a hook
+ ///
+ /// Checks all loaded libraries and returns true if at least one callout
+ /// has been registered by any of them for the given hook.
+ ///
+ /// @param hook_index Hook index for which callouts are checked.
+ ///
+ /// @return true if callouts are present, false if not.
+ ///
+ /// @throw NoSuchHook Given index does not correspond to a valid hook.
+ bool calloutsPresent(int hook_index) const;
+
+ /// @brief Calls the callouts for a given hook
+ ///
+ /// Iterates through the libray handles and calls the callouts associated
+ /// with the given hook index.
+ ///
+ /// @note This method invalidates the current library index set with
+ /// setLibraryIndex().
+ ///
+ /// @param hook_index Index of the hook to call.
+ /// @param callout_handle Reference to the CalloutHandle object for the
+ /// current object being processed.
+ void callCallouts(int hook_index, CalloutHandle& callout_handle);
+
+ /// @brief Get current hook index
+ ///
+ /// Made available during callCallouts, this is the index of the hook
+ /// on which callouts are being called.
+ int getHookIndex() const {
+ return (current_hook_);
+ }
+
+ /// @brief Get number of libraries
+ ///
+ /// Returns the number of libraries that this CalloutManager is expected
+ /// to serve. This is the number passed to its constructor.
+ ///
+ /// @return Number of libraries served by this CalloutManager.
+ int getNumLibraries() const {
+ return (num_libraries_);
+ }
+
+ /// @brief Get current library index
+ ///
+ /// Returns the index of the "current" library. This the index associated
+ /// with the currently executing callout when callCallouts is executing.
+ /// When callCallouts() is not executing (as is the case when the load()
+ /// function in a user-library is called during the library load process),
+ /// the index can be set by setLibraryIndex().
+ ///
+ /// @note The value set by this method is lost after a call to
+ /// callCallouts.
+ ///
+ /// @return Current library index.
+ int getLibraryIndex() const {
+ return (current_library_);
+ }
+
+ /// @brief Set current library index
+ ///
+ /// Sets the current library index. This has the following valid values:
+ ///
+ /// - -1: invalidate current index.
+ /// - 0: pre-user library callout.
+ /// - 1 - numlib: user-library callout (where "numlib" is the number of
+ /// libraries loaded in the system, this figure being passed to this
+ /// object at construction time).
+ /// - INT_MAX: post-user library callout.
+ ///
+ /// @param library_index New library index.
+ ///
+ /// @throw NoSuchLibrary if the index is not valid.
+ void setLibraryIndex(int library_index) {
+ checkLibraryIndex(library_index);
+ current_library_ = library_index;
+ }
+
+ /// @defgroup calloutManagerLibraryHandles Callout manager library handles
+ ///
+ /// The CalloutManager offers three library handles:
+ ///
+ /// - a "standard" one, used to register and deregister callouts for
+ /// the library index that is marked as current in the CalloutManager.
+ /// When a callout is called, it is passed this one.
+ /// - a pre-library callout handle, used by the server to register
+ // callouts to run prior to user-library callouts.
+ /// - a post-library callout handle, used by the server to register
+ /// callouts to run after the user-library callouts.
+ //@{
+
+ /// @brief Return library handle
+ ///
+ /// The library handle is available to the user callout via the callout
+ /// handle object. It provides a cut-down view of the CalloutManager,
+ /// allowing the callout to register and deregister callouts in the
+ /// library of which it is part, whilst denying access to anything that
+ /// may affect other libraries.
+ ///
+ /// @return Reference to library handle for this manager
+ LibraryHandle& getLibraryHandle() {
+ return (library_handle_);
+ }
+
+ /// @brief Return pre-user callouts library handle
+ ///
+ /// The LibraryHandle to affect callouts that will run before the
+ /// user-library callouts.
+ ///
+ /// @return Reference to pre-user library handle for this manager
+ LibraryHandle& getPreLibraryHandle() {
+ return (pre_library_handle_);
+ }
+
+ /// @brief Return post-user callouts library handle
+ ///
+ /// The LibraryHandle to affect callouts that will run before the
+ /// user-library callouts.
+ ///
+ /// @return Reference to post-user library handle for this manager
+ LibraryHandle& getPostLibraryHandle() {
+ return (post_library_handle_);
+ }
+
+ //@}
+
+private:
+ /// @brief Check library index
+ ///
+ /// Ensures that the current library index is valid. This is called by
+ /// the hook registration functions.
+ ///
+ /// @param library_index Value to check for validity as a library index.
+ /// Valid values are 0 - numlib+1 and -1: see @ref setLibraryIndex
+ /// for the meaning of the various values.
+ ///
+ /// @throw NoSuchLibrary Library index is not valid.
+ void checkLibraryIndex(int library_index) const;
+
+ /// @brief Compare two callout entries for library equality
+ ///
+ /// This is used in callout removal code when all callouts on a hook for a
+ /// given library are being removed. It checks whether two callout entries
+ /// have the same library index.
+ ///
+ /// @param ent1 First callout entry to check
+ /// @param ent2 Second callout entry to check
+ ///
+ /// @return bool true if the library entries are the same
+ class CalloutLibraryEqual :
+ public std::binary_function<CalloutEntry, CalloutEntry, bool> {
+ public:
+ bool operator()(const CalloutEntry& ent1,
+ const CalloutEntry& ent2) const {
+ return (ent1.first == ent2.first);
+ }
+ };
+
+ /// Current hook. When a call is made to callCallouts, this holds the
+ /// index of the current hook. It is set to an invalid value (-1)
+ /// otherwise.
+ int current_hook_;
+
+ /// Current library index. When a call is made to any of the callout
+ /// registration methods, this variable indicates the index of the user
+ /// library that should be associated with the call.
+ int current_library_;
+
+ /// Vector of callout vectors. There is one entry in this outer vector for
+ /// each hook. Each element is itself a vector, with one entry for each
+ /// callout registered for that hook.
+ std::vector<CalloutVector> hook_vector_;
+
+ /// LibraryHandle object user by the callout to access the callout
+ /// registration methods on this CalloutManager object. The object is set
+ /// such that the index of the library associated with any operation is
+ /// whatever is currently set in the CalloutManager.
+ LibraryHandle library_handle_;
+
+ /// LibraryHandle for callouts to be registered as being called before
+ /// the user-registered callouts.
+ LibraryHandle pre_library_handle_;
+
+ /// LibraryHandle for callouts to be registered as being called after
+ /// the user-registered callouts.
+ LibraryHandle post_library_handle_;
+
+ /// Number of libraries.
+ int num_libraries_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // CALLOUT_MANAGER_H
diff --git a/src/lib/hooks/hooks.h b/src/lib/hooks/hooks.h
new file mode 100644
index 0000000..a059d79
--- /dev/null
+++ b/src/lib/hooks/hooks.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 HOOKS_H
+#define HOOKS_H
+
+#include <hooks/callout_handle.h>
+#include <hooks/library_handle.h>
+
+namespace {
+
+// Version 1 of the hooks framework.
+const int BIND10_HOOKS_VERSION = 1;
+
+// Names of the framework functions.
+const char* const LOAD_FUNCTION_NAME = "load";
+const char* const UNLOAD_FUNCTION_NAME = "unload";
+const char* const VERSION_FUNCTION_NAME = "version";
+
+// Typedefs for pointers to the framework functions.
+typedef int (*version_function_ptr)();
+typedef int (*load_function_ptr)(isc::hooks::LibraryHandle&);
+typedef int (*unload_function_ptr)();
+
+} // Anonymous namespace
+
+#endif // HOOKS_H
diff --git a/src/lib/hooks/hooks_component_developer.dox b/src/lib/hooks/hooks_component_developer.dox
new file mode 100644
index 0000000..777ae2e
--- /dev/null
+++ b/src/lib/hooks/hooks_component_developer.dox
@@ -0,0 +1,483 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/**
+ at page hooksComponentDeveloperGuide Guide to Hooks for the BIND 10 Component Developer
+
+ at section hooksComponentIntroduction Introduction
+
+The hooks framework is a BIND 10 system that simplifies the way that
+users can write code to modify the behavior of BIND 10. Instead of
+altering the BIND 10 source code, they write functions that are compiled
+and linked into a shared library. The library is specified in the BIND 10
+configuration database and run time, BIND 10 dynamically loads the library
+into its address space. At various points in the processing, the component
+"calls out" to functions in the library, passing to them the data is it
+currently working on. They can examine and modify the data as required.
+
+This guide is aimed at BIND 10 developers who want to write or modify a
+BIND 10 component to use hooks. It shows how the component should be written
+to load a shared library at run-time and how to call functions in it.
+
+For information about writing a hooks library containing functions called by BIND 10
+during its execution, see the document @ref hooksdgDevelopersGuide.
+
+ at subsection hooksComponentTerminology Terminology
+
+In the remainder of this guide, the following terminology is used:
+
+- Component - a BIND 10 process, e.g. the authoritative DNS server or the
+DHCPv4 server.
+
+- Hook/Hook Point - used interchageably, this is a point in the code at
+which a call to user-written functions is made. Each hook has a name and
+each hook can have any number (including 0) of user-written functions
+attached to it.
+
+- Callout - a user-written function called by the component at a hook
+point. This is so-named because the component "calls out" to the library
+to execute a user-written function.
+
+- User code/user library - non-BIND 10 code that is compiled into a
+shared library and loaded by BIND 10 into its address space. Multiple
+user libraries can be loaded at the same time, each containing callouts for
+the same hooks. The hooks framework calls these libraries one after the
+other. (See the document @ref hooksdgDevelopersGuide for more details.)
+
+ at subsection hooksComponentLanguages Languages
+
+The core of BIND 10 is written in C++ with some parts in Python. While it is
+the intention to provide the hooks framework for all languages, the initial
+version is for C++. All examples in this guide are in that language.
+
+ at section hooksComponentBasicIdeas Basic Ideas
+
+From the point of view of the component author, the basic ideas of the hooks
+framework are quite simple:
+
+- The location of hook points in the code need to be determined.
+
+- Name the hook points and register them.
+
+- At each hook point, the component needs to complete the following steps to
+ execute callouts registered by the user-library:
+ -# copy data into the object used to pass information to the callout.
+ -# call the callout.
+ -# copy data back from the object used to exchange information.
+ -# take action based on information returned.
+
+Of course, to set up the system the libraries need to be loaded in the first
+place. The component also needs to:
+
+- Define the configuration item that specifies the user libraries for this
+component.
+
+- Handle configuration changes and load/unload the user libraries.
+
+The following sections will describe these tasks in more detail.
+
+ at section hooksComponentDefinition Determing the Hook Points
+
+Before any other action takes place, the location of the hook points
+in the code need to be determined. This of course depends on the
+component but as a general guideline, hook locations should be chosen
+where a callout is able to obtain useful information from BIND 10 and/or
+affect processing. Typically this means at the start or end of a major
+step in the processing of a request, at a point where either useful
+information can be passed to a callout and/or the callout can affect
+the processing of the component. The latter is achieved in either or both
+of the following eays:
+
+- Setting the "skip" flag. This is a boolean flag that the callout can set
+ and is a quick way of passing information back to the component. It is used
+ to indicate that the component should skip the processing step associated with
+ the hook. The exact action is up to the component, but is likely to be one
+ of skipping the processing step (probably because the callout has
+ done its own processing for the action) or dropping the current packet
+ and starting on a new request.
+
+- Modifying data passed to it. The component should be prepared to continue
+ processing with the data returned by the callout. It is up to the component
+ author whether the data is validated before being used, but doing so will
+ have performance implications.
+
+ at section hooksComponentRegistration Naming and Registering the Hooks Points
+
+Once the location of the hook point has been determined, it should be
+given a name. This name should be unique amongst all hook points and is
+subject to certain restrictions (see below).
+
+Before the callouts at any hook point are called and any user libraries
+loaded - so typically during component initialization - the component must
+register the names of all the hooks. The registration is done using
+the static method isc::hooks::HooksManager::registerHook():
+
+ at code
+
+#include <hooks/hooks_manager.h>
+ :
+ int example_index = HooksManager::registerHook("lease_allocate");
+ at endcode
+
+The name of the hook is passed as the sole argument to the registerHook()
+method. The value returned is the index of that hook point and should
+be retained - it is needed to call the callouts attached to that hook.
+
+Note that a hook only needs to be registered once. There is no mechanism for
+unregistering a hook and there is no need to do so.
+
+ at subsection hooksComponentAutomaticRegistration Automatic Registration of Hooks
+
+In some components, it may be convenient to set up a single initialization
+function that registers all hooks. For others, it may be more convenient
+for each module within the component to perform its own initialization.
+Since the isc::hooks::HooksManager object is a singleton and is created when first
+accessed, a useful trick is to automatically register the hooks when
+the module is loaded.
+
+This technique involves declaring an object outside of any execution
+unit in the module. When the module is loaded, the object's constructor
+is run. By placing the hook registration calls in the constructor,
+the hooks in the module are defined at load time, before any function in
+the module is run. The code for such an initialization sequence would
+be similar to:
+
+ at code
+#include <hooks/hooks_manager.h>
+
+namespace {
+
+// Declare structure to perform initialization and store the hook indexes.
+//
+struct MyHooks {
+ int pkt_rcvd; // Index of "packet received" hook
+ int pkt_sent; // Index of "packet sent" hook
+
+ // Constructor
+ MyHooks() {
+ pkt_rcvd = HooksManager::registerHook("pkt_rcvd");
+ pkt_sent = HooksManager::registerHook("pkt_sent");
+ }
+};
+
+// Declare a "MyHooks" object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+MyHooks my_hooks;
+
+} // Anonymous namespace
+
+void Someclass::someFunction() {
+ :
+ // Check if any callouts are defined on the pkt_rcvd hook.
+ if (HooksManager::calloutPresent(my_hooks.pkt_rcvd)) {
+ :
+ }
+ :
+}
+ at endcode
+
+ at subsection hooksComponentHookNames Hook Names
+
+Hook names are strings and in principle, any string can be used as the
+name of a hook, even one containing spaces and non-printable characters.
+However, the following guidelines should be observed:
+
+- The names <b>context_create</b> and <b>context_destroy</b> are reserved to
+the hooks system and are automatically registered: an attempt to register
+one of these will lead to a isc::hooks::DuplicateHook exception being thrown.
+
+- The hook name should be a valid "C" function name. If a user gives a
+callout the same name as one of the hooks, the hooks framework will
+automatically load that callout and attach it to the hook: the user does not
+have to explicitly register it.
+
+- The hook name should not conflict with the name of a function in any of
+the system libraries (e.g. naming a hook "sqrt" could lead to the
+square-root function in the system's maths library being attached to the hook
+as a callout).
+
+- Although hook names can be in any case (including mixed case), the BIND 10
+convention is that they are lower-case.
+
+ at section hooksComponentCallingCallouts Calling Callouts on a Hook
+
+ at subsection hooksComponentArgument The Callout Handle
+
+Before describing how to call user code at a hook point, we must first consider
+how to pass data to it.
+
+Each user callout has the signature:
+ at code
+int callout_name(isc::hooks::CalloutHandle& handle);
+ at endcode
+
+The isc::hooks::CalloutHandle object is the object used to pass data to
+and from the callout. This holds the data as a set of name/value pairs,
+each pair being considered an argument to the callout. If there are
+multiple callouts attached to a hook, the CalloutHandle is passed to
+each in turn. Should a callout modify an argument, the updated data is
+passed subsequent callouts (each of which could also modify it) before
+being returned to the component.
+
+Two methods are provided to get and set the arguments passed to
+the callout called (naturally enough) getArgument and SetArgument.
+Their usage is illustrated by the following code snippets.
+
+ at code
+ int count = 10;
+ boost::shared_ptr<Pkt4> pktptr = ... // Set to appropriate value
+
+ // Assume that "handle_ptr" has been created and is a pointer to a
+ // CalloutHandle.
+ handle_ptr->setArgument("data_count", count);
+ handle_ptr->setArgument("inpacket", pktptr);
+
+ // Call the hook code. lease_assigned_index is the value returned from
+ // HooksManager::registerHook() when the hook was registered.
+ HooksManager::callCallouts(lease_assigned_index, *handle_ptr);
+
+ // Retrieve the modified values
+ handle_ptr->getArgument("data_count", count);
+ handle_ptr->getArgument("inpacket", pktptr);
+ at endcode
+
+As can be seen "getArgument" is used to retrieve data from the
+CalloutHandle, and "setArgument" used to put data into it. If a callout
+wishes to alter data and pass it back to the component, it should retrieve
+the data with getArgument, modify it, and call setArgument to send
+it back.
+
+There are a couple points to be aware of:
+
+- The data type of the variable in the call to getArgument must
+match the data type of the variable passed to the corresponding
+setArgument <B>exactly</B>: using what would normally be considered
+to be a "compatible" type is not enough. For example, if the callout
+passed an argument back to the component as an "int" and the component
+attempted to retrieve it as a "long", an exception would be thrown even
+though any value that can be stored in an "int" will fit into a "long".
+This restriction also applies the "const" attribute but only as applied to
+data pointed to by pointers, e.g. if an argument is defined as a "char*",
+an exception will be thrown if an attempt is made to retrieve it into
+a variable of type "const char*". (However, if an argument is set as a
+"const int", it can be retrieved into an "int".) The documentation of
+a hook point should detail the exact data type of each argument.
+
+- If a pointer to an object is passed to a callout (either a "raw"
+pointer, or a boost smart pointer (as in the example above), and the
+underlying object is altered through that pointer, the change will be
+reflected in the component even if the callout makes no call to setArgument.
+This can be avoided by passing a pointer to a "const" object.
+
+ at subsection hooksComponentSkipFlag The Skip Flag
+
+Although information is passed back to the component from callouts through
+CalloutHandle arguments, a common action for callouts is to inform the component
+that its flow of control should be altered. For example:
+
+- In the DHCP servers, there is a hook at the point at which a lease is
+ about to be assigned. Callouts attached to this hooks may handle the
+ lease assignment in special cases, in which case they set the skip flag
+ to indicate that the server should not perform lease assignment in this
+ case.
+- A server may define a hook just after a packet is received. A callout
+ attached to the hook might inspect the source address and compare it
+ against a blacklist. If the address is on the list, the callout could set
+ the skip flag to indicate to the server that the packet should be dropped.
+
+For ease of processing, the CalloutHandle contains
+two methods, isc::hooks::CalloutHandle::getSkip() and
+isc::hooks::CalloutHandle::setSkip(). It is only meaningful for the
+component to use the "get" method. The skip flag is cleared by the hooks
+framework when the component requests that callouts be executed, so any
+value set by the component is lost. Callouts can both inspect the flag (it
+might have been set by callouts earlier in the callout list for the hook)
+and set it. Note that the setting of the flag by a callout does not
+prevent callouts later in the list from being called: the skip flag is
+just a boolean flag - the only significance comes from its interpretation
+by the component.
+
+An example of use could be:
+ at code
+// Set up arguments for DHCP lease assignment.
+handle->setArgument("query", query);
+handle->setArgument("response", response);
+HooksManager::callCallouts(lease_hook_index, *handle_ptr);
+if (! handle_ptr->getSkip()) {
+ // Skip flag not set, do the address allocation
+ :
+}
+ at endcode
+
+
+ at subsection hooksComponentGettingHandle Getting the Callout Handle
+
+The CalloutHandle object is linked to the loaded libraries
+for lifetime reasons (described below). Components
+should retrieve a isc::hooks::CalloutHandle using
+isc::hooks::HooksManager::createCalloutHandle():
+ at code
+ CalloutHandlePtr handle_ptr = HooksManager::createCalloutHandle();
+ at endcode
+(isc::hooks::CalloutHandlePtr is a typedef for a Boost shared pointer to a
+CalloutHandle.) The CalloutHandle so retrieved may be used for as
+long as the libraries are loaded.
+
+The handle is deleted by resetting the pointer:
+ at code
+ handle_ptr.reset();
+ at endcode
+... or by letting the handle pointer go out of scope. The actual deletion
+occurs when the CallHandle's reference count goes to zero. (The
+current version of the hooks framework does not maintain any other
+pointer to the returned CalloutHandle, so it gets destroyed when the
+shared pointer to it is cleared or destroyed. However, this may change
+in a future version.)
+
+ at subsection hooksComponentCallingCallout Calling the Callout
+
+Calling the callout is a simple matter of executing the
+isc::hooks::HooksManager::callCallouts() method for the hook index in
+question. For example, with the hook index pkt_sent defined as above,
+the hook can be executed by:
+ at code
+ HooksManager::callCallouts(pkt_sent, *handle_ptr);
+ at endcode
+... where "*handle_ptr" is a reference (note: not a pointer) to the
+isc::hooks::CalloutHandle object holding the arguments. No status code
+is returned. If a component needs to get data returned (other than that
+provided by the "skip" flag), it should define an argument through which
+the callout can do so.
+
+ at subsubsection hooksComponentConditionalCallout Conditionally Calling Hook Callouts
+
+Most hooks in a component will not have callouts attached to them. To
+avoid the overhead of setting up arguments in the CalloutHandle, a
+component can check for callouts before doing that processing using
+isc::hooks::HooksManager::calloutsPresent(). Taking the index of a
+hook as its sole argument, the function returns true if there are any
+callouts attached to the hook and false otherwise.
+
+With this check, the code in the component for calling a hook would look
+something like:
+ at code
+if (HooksManager::calloutsPresent(lease_hook_index)) {
+ // Set up arguments for lease assignment
+ handle->setArgument("query", query);
+ handle->setArgument("response", response);
+ HooksManager::callCallouts(lease_hook_index, *handle);
+ if (! handle->getSkip()) {
+ // Skip flag not set, do the address allocation
+ :
+ }
+}
+ at endcode
+
+ at section hooksComponentLoadLibraries Loading the User Libraries
+
+Once hooks are defined, all the hooks code described above will
+work, even if no libraries are loaded (and even if the library
+loading method is not called). The CalloutHandle returned by
+isc::hooks::HooksManager::createCalloutHandle() will be valid,
+isc::hooks::HooksManager::calloutsPresent() will return false for every
+index, and isc::hooks::HooksManager::callCallouts() will be a no-op.
+
+However, if user libraries are specified in the BIND 10 configuration,
+the component should load them. (Note the term "libraries": the hooks
+framework allows multiple user libraries to be loaded.) This should take
+place after the component's configuration has been read, and is achieved
+by the isc::hooks::HooksManager::loadLibraries() method. The method is
+passed a vector of strings, each giving the full file specification of
+a user library:
+ at code
+ std::vector<std::string> libraries = ... // Get array of libraries
+ bool success = HooksManager::loadLibraries(libraries);
+ at endcode
+loadLibraries() returns a boolean status which is true if all libraries
+loaded successfully or false if one or more failed to load. Appropriate
+error messages will have been logged in the latter case, the status
+being more to allow the developer to decide whether the execution
+should proceed in such circumstances.
+
+If loadLibraries() is called a second or subsequent time (as a result
+of a reconfiguration), all existing libraries are unloaded and a new
+set loaded. Libraries can be explicitly unloaded either by calling
+isc::hooks::HooksManager::unloadLibraries() or by calling
+loadLibraries() with an empty vector as an argument.
+
+ at subsection hooksComponentUnloadIssues Unload and Reload Issues
+
+Unloading a shared library works by unmapping the part of the process's
+virtual address space in which the library lies. This may lead to
+problems if there are still references to that address space elsewhere
+in the process.
+
+In many operating systems, heap storage allowed by a shared library will
+lie in the virtual address allocated to the library. This has implications
+in the hooks framework because:
+
+- Argument information stored in a CalloutHandle by a callout in a library
+may lie in the library's address space.
+- Data modified in objects passed as arguments may lie in the address
+space. For example, it is common for a DHCP callout to add "options"
+to a packet: the memory allocated for those options will most likely
+lie in library address space.
+
+The problem really arises because of the extensive use by BIND 10 of boost
+smart pointers. When the pointer is destroyed, the pointed-to memory is
+deallocated. If the pointer points to address space that is unmapped because
+a library has been unloaded, the deletion causes a segmentation fault.
+
+The hooks framework addresses the issue for CalloutHandles by keeping in
+that object a shared pointer to the object controlling library unloading.
+Although a library can be unloaded at any time, it is only when all
+CalloutHandles that could possibly reference address space in the library
+have been deleted that the library will actually be unloaded and the
+address space unmapped.
+
+The hooks framework cannot solve the second issue as the objects in
+question are under control of the component. It is up to the component
+developer to ensure that all such objects have been destroyed before
+libraries are reloaded. In extreme cases this may mean the component
+suspending all processing of incoming requests until all currently
+executing requests have completed and data object destroyed, reloading
+the libraries, then resuming processing.
+
+ at section hooksComponentCallouts Component-Defined Callouts
+
+Previous sections have discussed callout registration by user libraries.
+It is possible for a component to register its own functions (i.e. within
+its own address space) as hook callouts. These functions are called
+in eactly the same way as user callouts, being passed their arguments
+though a CalloutHandle object. (Guidelines for writing callouts can be
+found in @ref hooksdgDevelopersGuide.)
+
+A component can associate with a hook callouts that run either before
+user-registered callouts or after them. Registration is done via a
+isc::hooks::LibraryHandle object, a reference to one being obtained
+through the methods isc::hooks::HooksManager::preCalloutLibraryHandle()
+(for a handle to register callouts to run before the user library
+callouts) or isc::hooks::HooksManager::postCalloutLibraryHandle() (for
+a handle to register callouts to run after the user callouts). Use of
+the LibraryHandle to register and deregister callouts is described in
+ at ref hooksdgLibraryHandle.
+
+Finally, it should be noted that callouts registered in this way only
+remain registered until the next call to isc::hooks::loadLibraries().
+It is up to the component to re-register the callouts after this
+method has been called.
+
+*/
diff --git a/src/lib/hooks/hooks_log.cc b/src/lib/hooks/hooks_log.cc
new file mode 100644
index 0000000..360394c
--- /dev/null
+++ b/src/lib/hooks/hooks_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 "hooks/hooks_log.h"
+
+namespace isc {
+namespace hooks {
+
+isc::log::Logger hooks_logger("hooks");
+
+} // namespace hooks
+} // namespace isc
+
diff --git a/src/lib/hooks/hooks_log.h b/src/lib/hooks/hooks_log.h
new file mode 100644
index 0000000..92d429a
--- /dev/null
+++ b/src/lib/hooks/hooks_log.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 HOOKS_LOG_H
+#define HOOKS_LOG_H
+
+#include <log/macros.h>
+#include <hooks/hooks_messages.h>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Hooks debug Logging levels
+///
+/// Defines the levels used to output debug messages in the Hooks framework.
+/// Note that higher numbers equate to more verbose (and detailed) output.
+
+// The first level traces normal operations,
+const int HOOKS_DBG_TRACE = DBGLVL_TRACE_BASIC;
+
+// The next level traces each call to hook code.
+const int HOOKS_DBG_CALLS = DBGLVL_TRACE_BASIC_DATA;
+
+// Additional information on the calls. Report each call to a callout (even
+// if there are multiple callouts on a hook) and each status return.
+const int HOOKS_DBG_EXTENDED_CALLS = DBGLVL_TRACE_DETAIL_DATA;
+
+
+/// @brief Hooks 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 hooks_logger;
+
+} // namespace hooks
+} // namespace isc
+
+#endif // HOOKS_LOG_H
diff --git a/src/lib/hooks/hooks_maintenance.dox b/src/lib/hooks/hooks_maintenance.dox
new file mode 100644
index 0000000..c3c4946
--- /dev/null
+++ b/src/lib/hooks/hooks_maintenance.dox
@@ -0,0 +1,274 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Note: the prefix "hooksmg" to all labels is an abbreviation for "Hooks
+// Maintenance Guide" and is used to prevent a clash with symbols in any
+// other Doxygen file.
+
+/**
+ @page hooksmgMaintenanceGuide Hooks Maintenance Guide
+
+ @section hooksmgIntroduction Introduction
+
+ This document is aimed at BIND 10 maintainers responsible for the hooks
+ system. It provides an overview of the classes that make up the hooks
+ framework and notes important aspects of processing. More detailed
+ information can be found in the source code.
+
+ It is assumed that the reader is familiar with the contents of the @ref
+ hooksdgDevelopersGuide and the @ref hooksComponentDeveloperGuide.
+
+ @section hooksmgObjects Hooks Framework Objects
+
+ The relationships between the various objects in the hooks framework
+ is shown below:
+
+ @image html HooksUml.png "High-Level Class Diagram of the Hooks Framework"
+
+ (To avoid clutter, the @ref hooksmgServerHooks object, used to pass
+ information about registered hooks to the components, is not shown on
+ the diagram.)
+
+ The hooks framework objects can be split into user-side objects and
+ server-side objects. The former are those objects used or referenced
+ by user-written hooks libraries. The latter are those objects used in
+ the hooks framework.
+
+ @subsection hooksmgUserObjects User-Side Objects
+
+ The user-side code is able to access two objects in the framework,
+ the @ref hooksmgCalloutHandle and the @ref hooksmgLibraryHandle.
+ The @ref hooksmgCalloutHandle is used to pass data between the BIND 10
+ component and the loaded library; the @ref hooksmgLibraryHandle is used
+ for registering callouts.
+
+ @subsubsection hooksmgCalloutHandle Callout Handle
+
+ The @ref isc::hooks::CalloutHandle has two functions: passing arguments
+ between the BIND 10 component and the user-written library, and storing
+ per-request context between library calls. In both cases the data is
+ stored in a std::map structure, keyed by argument (or context item) name.
+ The actual data is stored in a boost::any object, which allows any
+ data type to be stored, although a penalty for this flexibility is
+ the restriction (mentioned in the @ref hooksdgDevelopersGuide) that
+ the type of data retrieved must be identical (and not just compatible)
+ with that stored.
+
+ The storage of context data is slightly complex because there is
+ separate context for each user library. For this reason, the @ref
+ hooksmgCalloutHandle has multiple maps, one for each library loaded.
+ The maps are stored in another map, the appropriate map being identified
+ by the "current library index" (this index is explained further below).
+ The reason for the second map (rather than a structure such as a vector)
+ is to avoid creating individual context maps unless needed; given the
+ key to the map (in this case the current library index) accessing an
+ element in a map using the operator[] method returns the element in
+ question if it exists, or creates a new one (and stores it in the map)
+ if its doesn't.
+
+ @subsubsection hooksmgLibraryHandle Library Handle
+
+ Little more than a restricted interface to the @ref
+ hooksmgCalloutManager, the @ref isc::hooks::LibraryHandle allows a
+ callout to register and deregister callouts. However, there are some
+ quirks to callout registration which, although the processing involved
+ is in the @ref hooksmgCalloutManager, are best described here.
+
+ Firstly, a callout can be deregistered by a function within a user
+ library only if it was registered by a function within that library. That
+ is to say, if library A registers the callout A_func() on hook "alpha"
+ and library B registers B_func(), functions within library A are only
+ able to remove A_func() (and functions in library B remove B_func()).
+ The restriction - here to prevent one library interfering with the
+ callouts of another - is enforced by means of the current library index.
+ As described below, each entry in the vector of callouts associated with
+ a hook is a pair object, comprising a pointer to the callout and
+ the index of the library with which it is associated. A callout
+ can only modify entries in that vector where the current library index
+ matches the index element of the pair.
+
+ A second quirk is that when dynamically modifying the list of callouts,
+ the change only takes effect when the current call out from the server
+ completes. To clarify this, suppose that functions A_func(), B_func()
+ and C_func() are registered on a hook, and the server executes a callout
+ on the hook. Suppose also during this call, A_func() removes the callout
+ C_func() and that B_func() adds D_func(). As changes only take effect
+ when the current call out completes, the user callouts executed will be
+ A_func(), B_func() then C_func(). When the server calls the hook callouts
+ again, the functions executed will be A_func(), B_func() and D_func().
+
+ This restriction is down to implementation. When a set of callouts on a hook
+ is being called, the @ref hooksmgCalloutManager iterates through a
+ vector (the "callout vector") of (index, callout pointer) pairs. Since
+ registration or deregistration of a callout on that hook would change the
+ vector (and so potentially invalidate the iterators used to access the it),
+ a copy of the vector is taken before the iteration starts. The @ref
+ hooksmgCalloutManager iterates over this copy while any changes made
+ by the callout registration functions affect the relevant callout vector.
+ Such approach was chosen because of performance considerations.
+
+ @subsection hooksmgServerObjects Server-Side Objects
+
+ Those objects are not accessible by user libraries. Please do not
+ attempt to use them if you are developing user callouts.
+
+ @subsubsection hooksmgServerHooks Server Hooks
+
+ The singleton @ref isc::hooks::ServerHooks object is used to register
+ hooks. It is little more than a wrapper around a map of (hook index,
+ hook name), generating a unique number (the hook index) for each
+ hook registered. It also handles the registration of the pre-defined
+ context_create and context_destroy hooks.
+
+ In operation, the @ref hooksmgHooksManager provides a thin wrapper
+ around it, so that the BIND 10 component developer does not have to
+ worry about another object.
+
+ @subsubsection hooksmgLibraryManager Library Manager
+
+ An @ref isc::hooks::LibraryManager is created by the @ref
+ hooksmgHooksManager object for each shared library loaded. It
+ controls the loading and unloading of the library and in essence
+ represents the library in the hooks framework. It also handles the
+ registration of the standard callouts (functions in the library with
+ the same name as the hook name).
+
+ Of particular importance is the "library's index", a number associated
+ with the library. This is passed to the LibraryManager at creation
+ time and is used to tag the callout pointers. It is discussed
+ further below.
+
+ As the LibraryManager provides all the methods needed to manage the
+ shared library, it is the natural home for the static validateLibrary()
+ method. The function called the parsing of the BIND 10 configuration, when
+ the "hooks-libraries" element is processed. It checks that shared library
+ exists, that it can be opened, that it contains the "version()" function
+ and that that function returns a valid value. It then closes the shared
+ library and returns an appropriate indication as to the library status.
+
+ @subsubsection hooksmgLibraryManagerCollection Library Manager Collection
+
+ The hooks framework can handle multiple libraries and as
+ a result will create a @ref hooksmgLibraryManager for each
+ of them. The collection of LibraryManagers is managed by the
+ @ref isc::hooks::LibraryManagerCollection object which, in most
+ cases has a method corresponding to a @ref hooksmgLibraryManager
+ method, e.g. it has a loadLibraries() that corresponds to the @ref
+ hooksmgLibraryManager's loadLibrary() call. As would be expected, methods
+ on the LibraryManagerCollection iterate through all specified libraries,
+ calling the corresponding LibraryManager method for each library.
+
+ One point of note is that LibraryManagerCollection operates on an "all
+ or none" principle. When loadLibraries() is called, on exit either all
+ libraries have been successfully opened or none of them have. There
+ is no use-case in BIND 10 where, after a user has specified the shared
+ libraries they want to load, the system will operate with only some of
+ them loaded.
+
+ The LibraryManagerCollection is the place where each library's index is set.
+ Each library is assigned a number ranging from 1 through to the number
+ of libraries being loaded. As mentioned in the previous section, this
+ index is used to tag callout pointers, something that is discussed
+ in the next section.
+
+ (Whilst on the subject of library index numbers, two additional
+ numbers - 0 and INT_MAX - are also valid as "current library index".
+ For flexibility, the BIND 10 component is able to register its own
+ functions as hook callouts. It does this by obtaining a suitable @ref
+ hooksmgLibraryHandle from the @ref hooksmgHooksManager. A choice
+ of two is available: one @ref hooksmgLibraryHandle (with an index
+ of 0) can be used to register a callout on a hook to execute before
+ any user-supplied callouts. The second (with an index of INT_MAX)
+ is used to register a callout to run after user-specified callouts.
+ Apart from the index number, the hooks framework does not treat these
+ callouts any differently from user-supplied ones.)
+
+ @subsubsection hooksmgCalloutManager Callout Manager
+
+ The @ref isc::hooks::CalloutManager is the core of the framework insofar
+ as the registration and calling of callouts is concerned.
+
+ It maintains a "hook vector" - a vector with one element for
+ each registered hook. Each element in this vector is itself a
+ vector (the callout vector), each element of which is a pair of
+ (library index, callback pointer). When a callout is registered, the
+ CalloutManager's current library index is used to supply the "library
+ index" part of the pair. The library index is set explicitly by the
+ @ref hooksmgLibraryManager prior to calling the user library's load()
+ function (and prior to registering the standard callbacks).
+
+ The situation is slightly more complex when a callout is executing. In
+ order to execute a callout, the CalloutManager's callCallouts()
+ method must be called. This iterates through the callout vector for
+ a hook and for each element in the vector, uses the "library index"
+ part of the pair to set the "current library index" before calling the
+ callout function recorded in the second part of the pair. In most cases,
+ the setting of the library index has no effect on the callout. However,
+ if the callout wishes to dynamically register or deregister a callout,
+ the @ref hooksmgLibraryHandle (see above) calls a method on the
+ @ref hooksmgCalloutManager which in turn uses that information.
+
+ @subsubsection hooksmgHooksManager Hooks Manager
+
+ The @ref isc::hooks::HooksManager is the main object insofar as the
+ server is concerned. It controls the creation of the library-related
+ objects and provides the framework in which they interact. It also
+ provides a shell around objects such as @ref hooksmgServerHooks so that all
+ interaction with the hooks framework by the server is through the
+ HooksManager object. Apart from this, it supplies no functionality to
+ the hooks framework.
+
+ @section hooksmgOtherIssues Other Issues
+
+ @subsection hooksmgMemoryAllocation Memory Allocation
+
+ Unloading a shared library works by unmapping the part of the process's
+ virtual address space in which the library lies. This may lead to
+ problems if there are still references to that address space elsewhere
+ in the process.
+
+ In many operating systems, heap storage allowed by a shared library
+ will lie in the virtual address allocated to the library. This has
+ implications in the hooks framework because:
+
+ - Argument information stored in a @ref hooksmgCalloutHandle by a
+ callout in a library may lie in the library's address space.
+
+ - Data modified in objects passed as arguments may lie in the address
+ space. For example, it is common for a DHCP callout to add "options"
+ to a packet: the memory allocated for those options will most likely
+ lie in library address space.
+
+ The problem really arises because of the extensive use by BIND 10 of
+ boost smart pointers. When the pointer is destroyed, the pointed-to
+ memory is deallocated. If the pointer points to address space that is
+ unmapped because a library has been unloaded, the deletion causes a
+ segmentation fault.
+
+ The hooks framework addresses the issue for the @ref hooksmgCalloutHandle
+ by keeping in that object a shared pointer to the object controlling
+ library unloading (the @ref hooksmgLibraryManagerCollection). Although
+ the libraries can be unloaded at any time, it is only when every
+ @ref hooksmgCalloutHandle that could possibly reference address space in the
+ library have been deleted that the library will actually be unloaded
+ and the address space unmapped.
+
+ The hooks framework cannot solve the second issue as the objects in
+ question are under control of the BIND 10 server incorporating the
+ hooks. It is up to the server developer to ensure that all such objects
+ have been destroyed before libraries are reloaded. In extreme cases
+ this may mean the server suspending all processing of incoming requests
+ until all currently executing requests have completed and data object
+ destroyed, reloading the libraries, then resuming processing.
+*/
diff --git a/src/lib/hooks/hooks_manager.cc b/src/lib/hooks/hooks_manager.cc
new file mode 100644
index 0000000..117db61
--- /dev/null
+++ b/src/lib/hooks/hooks_manager.cc
@@ -0,0 +1,200 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/library_manager_collection.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+#include <vector>
+
+using namespace std;
+
+namespace isc {
+namespace hooks {
+
+// Constructor
+
+HooksManager::HooksManager() {
+}
+
+// Return reference to singleton hooks manager.
+
+HooksManager&
+HooksManager::getHooksManager() {
+ static HooksManager manager;
+ return (manager);
+}
+
+// Are callouts present?
+
+bool
+HooksManager::calloutsPresentInternal(int index) {
+ conditionallyInitialize();
+ return (callout_manager_->calloutsPresent(index));
+}
+
+bool
+HooksManager::calloutsPresent(int index) {
+ return (getHooksManager().calloutsPresentInternal(index));
+}
+
+// Call the callouts
+
+void
+HooksManager::callCalloutsInternal(int index, CalloutHandle& handle) {
+ conditionallyInitialize();
+ return (callout_manager_->callCallouts(index, handle));
+}
+
+void
+HooksManager::callCallouts(int index, CalloutHandle& handle) {
+ return (getHooksManager().callCalloutsInternal(index, handle));
+}
+
+// Load the libraries. This will delete the previously-loaded libraries
+// (if present) and load new ones.
+
+bool
+HooksManager::loadLibrariesInternal(const std::vector<std::string>& libraries) {
+ // Unload current set of libraries (if any are loaded).
+ unloadLibrariesInternal();
+
+ // Create the library manager and load the libraries.
+ lm_collection_.reset(new LibraryManagerCollection(libraries));
+ bool status = lm_collection_->loadLibraries();
+
+ if (status) {
+ // ... and obtain the callout manager for them if successful.
+ callout_manager_ = lm_collection_->getCalloutManager();
+ } else {
+ // Unable to load libraries, reset to state before this function was
+ // called.
+ lm_collection_.reset();
+ callout_manager_.reset();
+ }
+
+ return (status);
+}
+
+bool
+HooksManager::loadLibraries(const std::vector<std::string>& libraries) {
+ return (getHooksManager().loadLibrariesInternal(libraries));
+}
+
+// Unload the libraries. This just deletes all internal objects which will
+// cause the libraries to be unloaded.
+
+void
+HooksManager::unloadLibrariesInternal() {
+ // The order of deletion does not matter here, as each library manager
+ // holds its own pointer to the callout manager. However, we may as
+ // well delete the library managers first: if there are no other references
+ // to the callout manager, the second statement will delete it, which may
+ // ease debugging.
+ lm_collection_.reset();
+ callout_manager_.reset();
+}
+
+void HooksManager::unloadLibraries() {
+ getHooksManager().unloadLibrariesInternal();
+}
+
+// Create a callout handle
+
+boost::shared_ptr<CalloutHandle>
+HooksManager::createCalloutHandleInternal() {
+ conditionallyInitialize();
+ return (boost::shared_ptr<CalloutHandle>(
+ new CalloutHandle(callout_manager_, lm_collection_)));
+}
+
+boost::shared_ptr<CalloutHandle>
+HooksManager::createCalloutHandle() {
+ return (getHooksManager().createCalloutHandleInternal());
+}
+
+// Get the list of the names of loaded libraries.
+
+std::vector<std::string>
+HooksManager::getLibraryNamesInternal() const {
+ return (lm_collection_ ? lm_collection_->getLibraryNames()
+ : std::vector<std::string>());
+}
+
+std::vector<std::string>
+HooksManager::getLibraryNames() {
+ return (getHooksManager().getLibraryNamesInternal());
+}
+
+// Perform conditional initialization if nothing is loaded.
+
+void
+HooksManager::performConditionalInitialization() {
+
+ // Nothing present, so create the collection with any empty set of
+ // libraries, and get the CalloutManager.
+ vector<string> libraries;
+ lm_collection_.reset(new LibraryManagerCollection(libraries));
+ lm_collection_->loadLibraries();
+
+ callout_manager_ = lm_collection_->getCalloutManager();
+}
+
+// Shell around ServerHooks::registerHook()
+
+int
+HooksManager::registerHook(const std::string& name) {
+ return (ServerHooks::getServerHooks().registerHook(name));
+}
+
+// Return pre- and post- library handles.
+
+isc::hooks::LibraryHandle&
+HooksManager::preCalloutsLibraryHandleInternal() {
+ conditionallyInitialize();
+ return (callout_manager_->getPreLibraryHandle());
+}
+
+isc::hooks::LibraryHandle&
+HooksManager::preCalloutsLibraryHandle() {
+ return (getHooksManager().preCalloutsLibraryHandleInternal());
+}
+
+isc::hooks::LibraryHandle&
+HooksManager::postCalloutsLibraryHandleInternal() {
+ conditionallyInitialize();
+ return (callout_manager_->getPostLibraryHandle());
+}
+
+isc::hooks::LibraryHandle&
+HooksManager::postCalloutsLibraryHandle() {
+ return (getHooksManager().postCalloutsLibraryHandleInternal());
+}
+
+// Validate libraries
+
+std::vector<std::string>
+HooksManager::validateLibraries(const std::vector<std::string>& libraries) {
+ return (LibraryManagerCollection::validateLibraries(libraries));
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/hooks/hooks_manager.h b/src/lib/hooks/hooks_manager.h
new file mode 100644
index 0000000..53a2525
--- /dev/null
+++ b/src/lib/hooks/hooks_manager.h
@@ -0,0 +1,316 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 HOOKS_MANAGER_H
+#define HOOKS_MANAGER_H
+
+#include <hooks/server_hooks.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace hooks {
+
+// Forward declarations
+class CalloutHandle;
+class CalloutManager;
+class LibraryHandle;
+class LibraryManagerCollection;
+
+/// @brief Hooks Manager
+///
+/// This is the overall manager of the hooks framework and is the main class
+/// used by a BIND 10 module when handling hooks. It is responsible for the
+/// loading and unloading of user libraries, and for calling the callouts on
+/// each hook point.
+///
+/// The class is a singleton, the single instance of the object being accessed
+/// through the static getHooksManager() method.
+
+class HooksManager : boost::noncopyable {
+public:
+ /// @brief Get singleton hooks manager
+ ///
+ /// @return Reference to the singleton hooks manager.
+ static HooksManager& getHooksManager();
+
+ /// @brief Load and reload libraries
+ ///
+ /// Loads the list of libraries into the server address space. For each
+ /// library, the "standard" functions (ones with the same names as the
+ /// hook points) are configured and the libraries' "load" function
+ /// called.
+ ///
+ /// If libraries are already loaded, they are unloaded and the new
+ /// libraries loaded.
+ ///
+ /// If any library fails to load, an error message will be logged. The
+ /// remaining libraries will be loaded if possible.
+ ///
+ /// @param libraries List of libraries to be loaded. The order is
+ /// important, as it determines the order that callouts on the same
+ /// hook will be called.
+ ///
+ /// @return true if all libraries loaded without a problem, false if one or
+ /// more libraries failed to load. In the latter case, message will
+ /// be logged that give the reason.
+ static bool loadLibraries(const std::vector<std::string>& libraries);
+
+ /// @brief Unload libraries
+ ///
+ /// Unloads the loaded libraries and leaves the hooks subsystem in the
+ /// state it was after construction but before loadLibraries() is called.
+ ///
+ /// @note: This method should be used with caution - see the notes for
+ /// the class LibraryManager for pitfalls. In general, a server
+ /// should not call this method: library unloading will automatically
+ /// take place when new libraries are loaded, and when appropriate
+ /// objects are destroyed.
+ ///
+ /// @return true if all libraries unloaded successfully, false on an error.
+ /// In the latter case, an error message will have been output.
+ static void unloadLibraries();
+
+ /// @brief Are callouts present?
+ ///
+ /// Checks loaded libraries and returns true if at lease one callout
+ /// has been registered by them for the given hook.
+ ///
+ /// @param index Hooks index for which callouts are checked.
+ ///
+ /// @return true if callouts are present, false if not.
+ /// @throw NoSuchHook Given index does not correspond to a valid hook.
+ static bool calloutsPresent(int index);
+
+ /// @brief Calls the callouts for a given hook
+ ///
+ /// Iterates through the libray handles and calls the callouts associated
+ /// with the given hook index.
+ ///
+ /// @note This method invalidates the current library index set with
+ /// setLibraryIndex().
+ ///
+ /// @param index Index of the hook to call.
+ /// @param handle Reference to the CalloutHandle object for the current
+ /// object being processed.
+ static void callCallouts(int index, CalloutHandle& handle);
+
+ /// @brief Return pre-callouts library handle
+ ///
+ /// Returns a library handle that can be used by the server to register
+ /// callouts on a hook that are called _before_ any callouts belonging
+ /// to a library.
+ ///
+ /// @note Both the reference returned and the callouts registered with
+ /// this handle only remain valid until the next loadLibraries() or
+ /// unloadLibraries() call. If the callouts are to remain registered
+ /// after this time, a new handle will need to be obtained and the
+ /// callouts re-registered.
+ ///
+ /// @return Reference to library handle associated with pre-library callout
+ /// registration.
+ static LibraryHandle& preCalloutsLibraryHandle();
+
+ /// @brief Return post-callouts library handle
+ ///
+ /// Returns a library handle that can be used by the server to register
+ /// callouts on a hook that are called _after any callouts belonging
+ /// to a library.
+ ///
+ /// @note Both the reference returned and the callouts registered with
+ /// this handle only remain valid until the next loadLibraries() or
+ /// unloadLibraries() call. If the callouts are to remain registered
+ /// after this time, a new handle will need to be obtained and the
+ /// callouts re-registered.
+ ///
+ /// @return Reference to library handle associated with post-library callout
+ /// registration.
+ static LibraryHandle& postCalloutsLibraryHandle();
+
+ /// @brief Return callout handle
+ ///
+ /// Returns a callout handle to be associated with a request passed round
+ /// the system.
+ ///
+ /// @note This handle is valid only after a loadLibraries() call and then
+ /// only up to the next loadLibraries() call.
+ ///
+ /// @return Shared pointer to a CalloutHandle object.
+ static boost::shared_ptr<CalloutHandle> createCalloutHandle();
+
+ /// @brief Register Hook
+ ///
+ /// This is just a convenience shell around the ServerHooks::registerHook()
+ /// method. It - along with the definitions of the two hook indexes for
+ /// the context_create and context_destroy methods - means that server
+ /// authors only need to deal with HooksManager and CalloutHandle, and not
+ /// include any other hooks framework classes.
+ ///
+ /// @param name Name of the hook
+ ///
+ /// @return Index of the hook, to be used in subsequent hook-related calls.
+ /// This will be greater than or equal to zero (so allowing a
+ /// negative value to indicate an invalid index).
+ ///
+ /// @throws DuplicateHook A hook with the same name has already been
+ /// registered.
+ static int registerHook(const std::string& name);
+
+ /// @brief Return list of loaded libraries
+ ///
+ /// Returns the names of the loaded libraries.
+ ///
+ /// @return List of loaded library names.
+ static std::vector<std::string> getLibraryNames();
+
+ /// @brief Validate library list
+ ///
+ /// For each library passed to it, checks that the library can be opened
+ /// and that the "version" function is present and gives the right answer.
+ /// Each library is closed afterwards.
+ ///
+ /// This is used during the configuration parsing - when the list of hooks
+ /// libraries is changed, each of the new libraries is checked before the
+ /// change is committed.
+ ///
+ /// @param List of libraries to be validated.
+ ///
+ /// @return An empty vector if all libraries validated. Otherwise it
+ /// holds the names of the libraries that failed validation.
+ static std::vector<std::string> validateLibraries(
+ const std::vector<std::string>& libraries);
+
+ /// Index numbers for pre-defined hooks.
+ static const int CONTEXT_CREATE = ServerHooks::CONTEXT_CREATE;
+ static const int CONTEXT_DESTROY = ServerHooks::CONTEXT_DESTROY;
+
+private:
+
+ /// @brief Constructor
+ ///
+ /// This is private as the object is a singleton and can only be addessed
+ /// through the getHooksManager() static method.
+ HooksManager();
+
+ //@{
+ /// The following methods correspond to similarly-named static methods,
+ /// but actually do the work on the singleton instance of the HooksManager.
+ /// See the descriptions of the static methods for more details.
+
+ /// @brief Validate library list
+ ///
+ /// @param List of libraries to be validated.
+ ///
+ /// @return An empty string if all libraries validated. Otherwise it is
+ /// the name of the first library that failed validation. The
+ /// configuration code can return this to bindctl as an indication
+ /// of the problem.
+ std::string validateLibrariesInternal(
+ const std::vector<std::string>& libraries) const;
+
+ /// @brief Load and reload libraries
+ ///
+ /// @param libraries List of libraries to be loaded. The order is
+ /// important, as it determines the order that callouts on the same
+ /// hook will be called.
+ ///
+ /// @return true if all libraries loaded without a problem, false if one or
+ /// more libraries failed to load. In the latter case, message will
+ /// be logged that give the reason.
+ bool loadLibrariesInternal(const std::vector<std::string>& libraries);
+
+ /// @brief Unload libraries
+ void unloadLibrariesInternal();
+
+ /// @brief Are callouts present?
+ ///
+ /// @param index Hooks index for which callouts are checked.
+ ///
+ /// @return true if callouts are present, false if not.
+ /// @throw NoSuchHook Given index does not correspond to a valid hook.
+ bool calloutsPresentInternal(int index);
+
+ /// @brief Calls the callouts for a given hook
+ ///
+ /// @param index Index of the hook to call.
+ /// @param handle Reference to the CalloutHandle object for the current
+ /// object being processed.
+ void callCalloutsInternal(int index, CalloutHandle& handle);
+
+ /// @brief Return callout handle
+ ///
+ /// @return Shared pointer to a CalloutHandle object.
+ boost::shared_ptr<CalloutHandle> createCalloutHandleInternal();
+
+ /// @brief Return pre-callouts library handle
+ ///
+ /// @return Reference to library handle associated with pre-library callout
+ /// registration.
+ LibraryHandle& preCalloutsLibraryHandleInternal();
+
+ /// @brief Return post-callouts library handle
+ ///
+ /// @return Reference to library handle associated with post-library callout
+ /// registration.
+ LibraryHandle& postCalloutsLibraryHandleInternal();
+
+ /// @brief Return list of loaded libraries
+ ///
+ /// @return List of loaded library names.
+ std::vector<std::string> getLibraryNamesInternal() const;
+
+ //@}
+
+ /// @brief Initialization to No Libraries
+ ///
+ /// Initializes the hooks manager with an "empty set" of libraries. This
+ /// method is called if conditionallyInitialize() determines that such
+ /// initialization is needed.
+ void performConditionalInitialization();
+
+ /// @brief Conditional initialization of the hooks manager
+ ///
+ /// loadLibraries() performs the initialization of the HooksManager,
+ /// setting up the internal structures and loading libraries. However,
+ /// in some cases, server authors may not do that. This method is called
+ /// whenever any hooks execution function is invoked (checking callouts,
+ /// calling callouts or returning a callout handle). If the HooksManager
+ /// is unitialised, it will initialize it with an "empty set" of libraries.
+ ///
+ /// For speed, the test of whether initialization is required is done
+ /// in-line here. The actual initialization is performed in
+ /// performConditionalInitialization().
+ void conditionallyInitialize() {
+ if (!lm_collection_) {
+ performConditionalInitialization();
+ }
+ }
+
+ // Members
+
+ /// Set of library managers.
+ boost::shared_ptr<LibraryManagerCollection> lm_collection_;
+
+ /// Callout manager for the set of library managers.
+ boost::shared_ptr<CalloutManager> callout_manager_;
+};
+
+} // namespace util
+} // namespace hooks
+
+#endif // HOOKS_MANAGER_H
diff --git a/src/lib/hooks/hooks_messages.mes b/src/lib/hooks/hooks_messages.mes
new file mode 100644
index 0000000..ebaed41
--- /dev/null
+++ b/src/lib/hooks/hooks_messages.mes
@@ -0,0 +1,162 @@
+# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::hooks
+
+% HOOKS_ALL_CALLOUTS_DEREGISTERED hook library at index %1 removed all callouts on hook %2
+A debug message issued when all callouts on the specified hook registered
+by the library with the given index were removed. This is similar to
+the HOOKS_CALLOUTS_REMOVED message (and the two are likely to be seen
+together), but is issued at a lower-level in the hook framework.
+
+% HOOKS_CALLOUTS_REMOVED callouts removed from hook %1 for library %2
+This is a debug message issued during library unloading. It notes that
+one of more callouts registered by that library have been removed from
+the specified hook. This is similar to the HOOKS_DEREGISTER_ALL_CALLOUTS
+message (and the two are likely to be seen together), but is issued at a
+higher-level in the hook framework.
+
+% HOOKS_CALLOUT_CALLED hooks library with index %1 has called a callout on hook %2 that has address %3
+Only output at a high debugging level, this message indicates that
+a callout on the named hook registered by the library with the given
+index (in the list of loaded libraries) has been called and returned a
+success state. The address of the callout is given in the message
+
+% HOOKS_CALLOUT_DEREGISTERED hook library at index %1 deregistered a callout on hook %2
+A debug message issued when all instances of a particular callouts on
+the hook identified in the message that were registered by the library
+with the given index have been removed.
+
+% HOOKS_CALLOUT_ERROR error returned by callout on hook %1 registered by library with index %2 (callout address %3)
+If a callout returns an error status when called, this error message
+is issued. It identifies the hook to which the callout is attached, the
+index of the library (in the list of loaded libraries) that registered
+it and the address of the callout. The error is otherwise ignored.
+
+% HOOKS_CALLOUT_EXCEPTION exception thrown by callout on hook %1 registered by library with index %2 (callout address %3): %4
+If a callout throws an exception when called, this error message is
+issued. It identifies the hook to which the callout is attached, the
+index of the library (in the list of loaded libraries) that registered
+it and the address of the callout. The error is otherwise ignored.
+
+% HOOKS_CALLOUT_REGISTRATION hooks library with index %1 registering callout for hook '%2'
+This is a debug message, output when a library (whose index in the list
+of libraries (being) loaded is given) registers a callout.
+
+% HOOKS_CLOSE_ERROR failed to close hook library %1: %2
+BIND 10 has failed to close the named hook library for the stated reason.
+Although this is an error, this should not affect the running system
+other than as a loss of resources. If this error persists, you should
+restart BIND 10.
+
+% HOOKS_HOOK_LIST_RESET the list of hooks has been reset
+This is a message indicating that the list of hooks has been reset.
+While this is usual when running the BIND 10 test suite, it should not be
+seen when running BIND 10 in a producion environment. If this appears,
+please report a bug through the usual channels.
+
+% HOOKS_INCORRECT_VERSION hook library %1 is at version %2, require version %3
+BIND 10 has detected that the named hook library has been built against
+a version of BIND 10 that is incompatible with the version of BIND 10
+running on your system. It has not loaded the library.
+
+This is most likely due to the installation of a new version of BIND 10
+without rebuilding the hook library. A rebuild and re-install of the
+library should fix the problem in most cases.
+
+% HOOKS_LIBRARY_LOADED hooks library %1 successfully loaded
+This information message is issued when a user-supplied hooks library
+has been successfully loaded.
+
+% HOOKS_LIBRARY_LOADING loading hooks library %1
+This is a debug message output just before the specified library is loaded.
+If the action is successfully, it will be followed by the
+HOOKS_LIBRARY_LOADED informational message.
+
+% HOOKS_LIBRARY_UNLOADED hooks library %1 successfully unloaded
+This information message is issued when a user-supplied hooks library
+has been successfully unloaded.
+
+% HOOKS_LIBRARY_UNLOADING unloading library %1
+This is a debug message called when the specified library is
+being unloaded. If all is successful, it will be followed by the
+HOOKS_LIBRARY_UNLOADED informational message.
+
+% HOOKS_LIBRARY_VERSION hooks library %1 reports its version as %2
+A debug message issued when the version check on the hooks library
+has succeeded.
+
+% HOOKS_LOAD_ERROR 'load' function in hook library %1 returned error %2
+A "load" function was found in the library named in the message and
+was called. The function returned a non-zero status (also given in
+the message) which was interpreted as an error. The library has been
+unloaded and no callouts from it will be installed.
+
+% HOOKS_LOAD_EXCEPTION 'load' function in hook library %1 threw an exception
+A "load" function was found in the library named in the message and
+was called. The function threw an exception (an error indication)
+during execution, which is an error condition. The library has been
+unloaded and no callouts from it will be installed.
+
+% HOOKS_LOAD_SUCCESS 'load' function in hook library %1 returned success
+This is a debug message issued when the "load" function has been found
+in a hook library and has been successfully called.
+
+% HOOKS_NO_LOAD no 'load' function found in hook library %1
+This is a debug message saying that the specified library was loaded
+but no function called "load" was found in it. Providing the library
+contained some "standard" functions (i.e. functions with the names of
+the hooks for the given server), this is not an issue.
+
+% HOOKS_NO_UNLOAD no 'unload' function found in hook library %1
+This is a debug message issued when the library is being unloaded.
+It merely states that the library did not contain an "unload" function.
+
+% HOOKS_NO_VERSION no 'version' function found in hook library %1
+The shared library named in the message was found and successfully loaded,
+but BIND 10 did not find a function named "version" in it. This function
+is required and should return the version of BIND 10 against which the
+library was built. The value is used to check that the library was built
+against a compatible version of BIND 10. The library has not been loaded.
+
+% HOOKS_OPEN_ERROR failed to open hook library %1: %2
+BIND 10 failed to open the specified hook library for the stated
+reason. The library has not been loaded. BIND 10 will continue to
+function, but without the services offered by the library.
+
+% HOOKS_STD_CALLOUT_REGISTERED hooks library %1 registered standard callout for hook %2 at address %3
+This is a debug message, output when the library loading function has
+located a standard callout (a callout with the same name as a hook point)
+and registered it. The address of the callout is indicated.
+
+% HOOKS_UNLOAD_ERROR 'unload' function in hook library %1 returned error %2
+During the unloading of a library, an "unload" function was found.
+It was called, but returned an error (non-zero) status, resulting in
+the issuing of this message. The unload process continued after this
+message and the library has been unloaded.
+
+% HOOKS_UNLOAD_EXCEPTION 'unload' function in hook library %1 threw an exception
+During the unloading of a library, an "unload" function was found. It was
+called, but in the process generated an exception (an error indication).
+The unload process continued after this message and the library has
+been unloaded.
+
+% HOOKS_UNLOAD_SUCCESS 'unload' function in hook library %1 returned success
+This is a debug message issued when an "unload" function has been found
+in a hook library during the unload process, called, and returned success.
+
+% HOOKS_VERSION_EXCEPTION 'version' function in hook library %1 threw an exception
+This error message is issued if the version() function in the specified
+hooks library was called and generated an exception. The library is
+considered unusable and will not be loaded.
diff --git a/src/lib/hooks/hooks_user.dox b/src/lib/hooks/hooks_user.dox
new file mode 100644
index 0000000..8aa75c1
--- /dev/null
+++ b/src/lib/hooks/hooks_user.dox
@@ -0,0 +1,1031 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Note: the prefix "hooksdg" to all labels is an abbreviation for "Hooks
+// Developer's Guide" and is used to prevent a clash with symbols in any
+// other Doxygen file.
+
+/**
+ @page hooksdgDevelopersGuide Hooks Developer's Guide
+
+ @section hooksdgIntroduction Introduction
+
+Although the BIND 10 framework and its associated DNS and DHCP programs
+provide comprehensive functionality, there will be times when it does
+not quite do what you require: the processing has to be extended in some
+way to solve your problem.
+
+Since the BIND 10 source code is freely available (BIND 10 being an
+open-source project), one option is to modify it to do what
+you want. Whilst perfectly feasible, there are drawbacks:
+
+- Although well-documented, BIND 10 is a large program. Just
+understanding how it works will take a significant amount of time. In
+addition, despite the fact that its object-oriented design keeps the
+coupling between modules to a minimum, an inappropriate change to one
+part of the program during the extension could cause another to
+behave oddly or to stop working altogether.
+
+- The change may need to be re-applied or re-written with every new
+version of BIND 10. As new functionality is added or bugs are fixed,
+the code or algorithms in the core software may change - and may change
+significantly.
+
+To overcome these problems, BIND 10 provides the "Hooks" interface -
+a defined interface for third-party or user-written code. (For ease of
+reference in the rest of this document, all such code will be referred
+to as "user code".) At specific points in its processing
+("hook points") BIND 10 will make a call to this code. The call passes
+data that the user code can examine and, if required, modify.
+BIND 10 uses the modified data in the remainder of its processing.
+
+In order to minimise the interaction between BIND 10 and the user
+code, the latter is built independently of BIND 10 in the form of
+a shared library (or libraries). These are made known to BIND 10
+through its configuration mechanism, and BIND 10 loads the library at
+run time. Libraries can be unloaded and reloaded as needed while BIND
+10 is running.
+
+Use of a defined API and the BIND 10 configuration mechanism means that
+as new versions of BIND 10 are released, there is no need to modify
+the user code. Unless there is a major change in an interface
+(which will be clearly documented), all that will be required is a rebuild
+of the libraries.
+
+ at note Although the defined interface should not change, the internals
+of some of the classes and structures referenced by the user code may
+change between versions of BIND 10. These changes have to be reflected
+in the compiled version of the software, hence the need for a rebuild.
+
+ at subsection hooksdgLanguages Languages
+
+The core of BIND 10 is written in C++. While it is the intention to
+provide interfaces into user code written in other languages, the initial
+versions of the Hooks system requires that user code be written in C++.
+All examples in this guide are in that language.
+
+ at subsection hooksdgTerminology Terminology
+
+In the remainder of this guide, the following terminology is used:
+
+- Hook/Hook Point - used interchageably, this is a point in the code at
+which a call to user functions is made. Each hook has a name and
+each hook can have any number (including 0) of user functions
+attached to it.
+
+- Callout - a user function called by the server at a hook
+point. This is so-named because the server "calls out" to the library
+to execute a user function.
+
+- Framework function - the functions that a user library needs to
+supply in order for the hooks framework to load and unload the library.
+
+- User code/user library - non-BIND 10 code that is compiled into a
+shared library and loaded by BIND 10 into its address space.
+
+
+ at section hooksdgTutorial Tutorial
+
+To illustrate how to write code that integrates with BIND 10, we will
+use the following (rather contrived) example:
+
+The BIND 10 DHCPv4 server is used to allocate IPv4 addresses to clients
+(as well as to pass them other information such as the address of DNS
+servers). We will suppose that we need to classify clients requesting
+IPv4 addresses according to their hardware address, and want to log both
+the hardware address and allocated IP address for the clients of interest.
+
+The following sections describe how to implement these requirements.
+The code presented here is not efficient and there are better ways of
+doing the task. The aim however, is to illustrate the main features of
+user hook code not to provide an optimal solution.
+
+
+ at subsection hooksdgFrameworkFunctions Framework Functions
+
+Loading and initializing a library holding user code makes use
+of three (user-supplied) functions:
+
+- version - defines the version of BIND 10 code with which the user-library
+is built
+- load - called when the library is loaded by the server.
+- unload - called when the library is unloaded by the server.
+
+Of these, only "version" is mandatory, although in our example, all three
+are used.
+
+ at subsubsection hooksdgVersionFunction The "version" Function
+
+"version" is used by the hooks framework to check that the libraries
+it is loading are compatible with the version of BIND 10 being run.
+Although the hooks system allows BIND 10 and user code to interface
+through a defined API, the relationship is somewhat tight in that the
+user code will depend on the internal structures of BIND 10. If these
+change - as they can between BIND 10 releases - and BIND 10 is run with
+a version of user code built against an earlier version of BIND
+10, a program crash could result.
+
+To guard against this, the "version" function must be provided in every
+library. It returns a constant defined in header files of the version
+of BIND 10 against which it was built. The hooks framework checks this
+for compatibility with the running version of BIND 10 before loading
+the library.
+
+In this tutorial, we'll put "version" in its own file, version.cc. The
+contents are:
+
+ at code
+// version.cc
+
+#include <hooks/hooks.h>
+
+extern "C" {
+
+int version() {
+ return (BIND10_HOOKS_VERSION);
+}
+
+};
+ at endcode
+
+The file "hooks/hooks.h" is specified relative to the BIND 10 libraries
+source directory - this is covered later in the section @ref hooksdgBuild.
+It defines the symbol BIND10_HOOKS_VERSION, which has a value that changes
+on every release of BIND 10: this is the value that needs to be returned
+to the hooks framework.
+
+A final point to note is that the definition of "version" is enclosed
+within 'extern "C"' braces. All functions accessed by the hooks
+framework use C linkage, mainly to avoid the name mangling that
+accompanies use of the C++ compiler, but also to avoid issues related
+to namespaces.
+
+ at subsubsection hooksdgLoadUnloadFunctions The "load" and "unload" Functions
+
+As the names suggest, "load" is called when a library is loaded and
+"unload" called when it is unloaded. (It is always guaranteed that
+"load" is called: "unload" may not be called in some circumstances,
+e.g. if the system shuts down abnormally.) These functions are the
+places where any library-wide resources are allocated and deallocated.
+"load" is also the place where any callouts with non-standard names
+(names that are not hook point names) can be registered:
+this is covered further in the section @ref hooksdgCalloutRegistration.
+
+The example does not make any use callouts with non-standard names. However,
+as our design requires that the log file be open while BIND 10 is active
+and the library loaded, we'll open the file in the "load" function and close
+it in "unload".
+
+We create two files, one for the file handle declaration:
+
+ at code
+// library_common.h
+
+#ifndef LIBRARY_COMMON_H
+#define LIBRARY_COMMON_H
+
+#include <fstream>
+
+// "Interesting clients" log file handle declaration.
+extern std::fstream interesting;
+
+#endif // LIBRARY_COMMON_H
+ at endcode
+
+... and one to hold the "load" and "unload" functions:
+
+ at code
+// load_unload.cc
+
+#include <hooks/hooks.h>
+#include "library_common.h"
+
+// "Interesting clients" log file handle definition.
+std::fstream interesting;
+
+extern "C" {
+
+int load(LibraryHandle&) {
+ interesting.open("/data/clients/interesting.log",
+ std::fstream::out | std::fstream::app);
+ return (interesting ? 0 : 1);
+}
+
+int unload() {
+ if (interesting) {
+ interesting.close();
+ }
+ return (0);
+}
+
+};
+ at endcode
+
+Notes:
+- The file handle ("interesting") is declared in a header file and defined
+outside of any function. This means it can be accessed by any function
+within the user library. For convenience, the definition is in the
+load_unload.cc file.
+- "load" is called with a LibraryHandle argument, this being used in
+the registration of functions. As no functions are being registered
+in this example, the argument specification omits the variable name
+(whilst retaining the type) to avoid an "unused variable" compiler
+warning. (The LibraryHandle and its use is discussed in the section
+ at ref hooksdgLibraryHandle.)
+- In the current version of the hooks framework, it is not possible to pass
+any configuration information to the "load" function. The name of the log
+file must therefore be hard-coded as an absolute path name or communicated
+to the user code by some other means.
+- "load" must 0 on success and non-zero on error. The hooks framework
+will abandon the loading of the library if "load" returns an error status.
+(In this example, "interesting" can be tested as a boolean value,
+returning "true" if the file opened successfully.)
+- "unload" closes the log file if it is open and is a no-op otherwise. As
+with "load", a zero value must be returned on success and a non-zero value
+on an error. The hooks framework will record a non-zero status return
+as an error in the current BIND 10 log but otherwise ignore it.
+- As before, the function definitions are enclosed in 'extern "C"' braces.
+
+ at subsection hooksdgCallouts Callouts
+
+Having sorted out the framework, we now come to the functions that
+actually do something. These functions are known as "callouts" because
+the BIND 10 code "calls out" to them. Each BIND 10 server has a number of
+hooks to which callouts can be attached: server-specific documentation
+describes in detail the points in the server at which the hooks are
+present together with the data passed to callouts attached to them.
+
+Before we continue with the example, we'll discuss how arguments are
+passed to callouts and information is returned to the server. We will
+also discuss how information can be moved between callouts.
+
+ at subsubsection hooksdgCalloutSignature The Callout Signature
+
+All callouts are declared with the signature:
+ at code
+extern "C" {
+int callout(CalloutHandle& handle);
+};
+ at endcode
+
+(As before, the callout is declared with "C" linkage.) Information is passed
+between BIND 10 and the callout through name/value pairs in the CalloutHandle
+object. The object is also used to pass information between callouts on a
+per-request basis. (Both of these concepts are explained below.)
+
+A callout returns an "int" as a status return. A value of 0 indicates
+success, anything else signifies an error. The status return has no
+effect on server processing; the only difference between a success
+and error code is that if the latter is returned, the server will
+log an error, specifying both the library and hook that generated it.
+Effectively the return status provides a quick way for a callout to log
+error information to the BIND 10 logging system.
+
+ at subsubsection hooksdgArguments Callout Arguments
+
+The CalloutHandle object provides two methods to get and set the
+arguments passed to the callout. These methods are called (naturally
+enough) getArgument and SetArgument. Their usage is illustrated by the
+following code snippets.
+
+ at code
+ // Server-side code snippet to show the setting of arguments
+
+ int count = 10;
+ boost::shared_ptr<Pkt4> pktptr = ... // Set to appropriate value
+
+ // Assume that "handle" has been created
+ handle.setArgument("data_count", count);
+ handle.setArgument("inpacket", pktptr);
+
+ // Call the callouts attached to the hook
+ ...
+
+ // Retrieve the modified values
+ handle.getArgument("data_count", count);
+ handle.getArgument("inpacket", pktptr);
+ at endcode
+
+In the callout
+
+ at code
+ int number;
+ boost::shared_ptr<Pkt4> packet;
+
+ // Retrieve data set by the server.
+ handle.getArgument("data_count", number);
+ handle.getArgument("inpacket", packet);
+
+ // Modify "number"
+ number = ...;
+
+ // Update the arguments to send the value back to the server.
+ handle.setArgument("data_count", number);
+ at endcode
+
+As can be seen "getArgument" is used to retrieve data from the
+CalloutHandle, and setArgument used to put data into it. If a callout
+wishes to alter data and pass it back to the server, it should retrieve
+the data with getArgument, modify it, and call setArgument to send
+it back.
+
+There are several points to be aware of:
+
+- the data type of the variable in the call to getArgument must match
+the data type of the variable passed to the corresponding setArgument
+<B>exactly</B>: using what would normally be considered to be a
+"compatible" type is not enough. For example, if the server passed
+an argument as an "int" and the callout attempted to retrieve it as a
+"long", an exception would be thrown even though any value that can
+be stored in an "int" will fit into a "long". This restriction also
+applies the "const" attribute but only as applied to data pointed to by
+pointers, e.g. if an argument is defined as a "char*", an exception will
+be thrown if an attempt is made to retrieve it into a variable of type
+"const char*". (However, if an argument is set as a "const int", it can
+be retrieved into an "int".) The documentation of each hook point will
+detail the data type of each argument.
+- Although all arguments can be modified, some altered values may not
+be read by the server. (These would be ones that the server considers
+"read-only".) Consult the documentation of each hook to see whether an
+argument can be used to transfer data back to the server.
+- If a pointer to an object is passed to a callout (either a "raw"
+pointer, or a boost smart pointer (as in the example above), and the
+underlying object is altered through that pointer, the change will be
+reflected in the server even if no call is made to setArgument.
+
+In all cases, consult the documentation for the particular hook to see whether
+parameters can be modified. As a general rule:
+
+- Do not alter arguments unless you mean the change to be reflected in
+the server.
+- If you alter an argument, call CalloutHandle::setArgument to update the
+value in the CalloutHandle object.
+
+ at subsubsection hooksdgSkipFlag The "Skip" Flag
+
+When a to callouts attached to a hook returns, the server will usually continue
+its processing. However, a callout might have done something that means that
+the server should follow another path. Possible actions a server could take
+include:
+
+- Skip the next stage of processing because the callout has already
+done it. For example, a hook is located just before the DHCP server
+allocates an address to the client. A callout may decide to allocate
+special addresses for certain clients, in which case it needs to tell
+the server not to allocate an address in this case.
+- Drop the packet and continue with the next request. A possible scenario
+is a DNS server where a callout inspects the source address of an incoming
+packet and compares it against a black list; if the address is on it,
+the callout notifies the server to drop the packet.
+
+To handle these common cases, the CalloutHandle has a "skip" flag.
+This is set by a callout when it wishes the server to skip normal
+processing. It is set false by the hooks framework before callouts on a
+hook are called. If the flag is set on return, the server will take the
+"skip" action relevant for the hook.
+
+The methods to get and set the "skip" flag are getSkip and setSkip. Their
+usage is intuitive:
+
+ at code
+ // Get the current setting of the skip flag.
+ bool skip = handle.getSkip();
+
+ // Do some processing...
+ :
+ if (lease_allocated) {
+ // Flag the server to skip the next step of the processing as we
+ // already have an address.
+ handle.setSkip(true);
+ }
+ return;
+
+ at endcode
+
+Like arguments, the "skip" flag is passed to all callouts on a hook. Callouts
+later in the list are able to examine (and modify) the settings of earlier ones.
+
+ at subsubsection hooksdgCalloutContext Per-Request Context
+
+Although many of the BIND 10 modules can be characterised as handling
+singles packet - e.g. the DHCPv4 server receives a DISCOVER packet,
+processes it and responds with an OFFER, this is not true in all cases.
+The principal exception is the recursive DNS resolver: this receives a
+packet from a client but that packet may itself generate multiple packets
+being sent to upstream servers. To avoid possible confusion the rest of
+this section uses the term "request" to indicate a request by a client
+for some information or action.
+
+As well as argument information, the CalloutHandle object can be used by
+callouts to attach information to a request being handled by the server.
+This information (known as "context") is not used by the server: its purpose
+is to allow callouts to pass information between one another on a
+per-request basis.
+
+Context only exists only for the duration of the request: when a request
+is completed, the context is destroyed. A new request starts with no
+context information. Context is particularly useful in servers that may
+be processing multiple requests simultaneously: callouts can effectively
+attach data to a request that follows the request around the system.
+
+Context information is held as name/value pairs in the same way
+as arguments, being accessed by the pair of methods setContext and
+getContext. They have the same restrictions as the setArgument and
+getArgument methods - the type of data retrieved from context must
+<B>exactly</B> match the type of the data set.
+
+The example in the next section illustrates their use.
+
+ at subsection hooksdgExampleCallouts Example Callouts
+
+Continuing with the tutorial, the requirements need us to retrieve the
+hardware address of the incoming packet, classify it, and write it,
+together with the assigned IP address, to a log file. Although we could
+do this in one callout, for this example we'll use two:
+
+- pkt_rcvd - a callout on this hook is invoked when a packet has been
+received and has been parsed. It is passed a single argument, "query"
+which is an isc::dhcp::Pkt4 object (representing a DHCP v4 packet).
+We will do the classification here.
+
+- v4_lease_write_post - called when the lease (an assignment of an IPv4
+address to a client for a fixed period of time) has been written to the
+database. It is passed two arguments, the query ("query")
+and the response (called "reply"). This is the point at which the
+example code will write the hardware and IP addresses to the log file.
+
+The standard for naming callouts is to give them the same name as
+the hook. If this is done, the callouts will be automatically found
+by the Hooks system (this is discussed further in section @ref
+hooksdgCalloutRegistration). For our example, we will assume this is the
+case, so the code for the first callout (used to classify the client's
+hardware address) is:
+
+ at code
+// pkt_rcvd.cc
+
+#include <hooks/hooks.h>
+#include <dhcp/pkt4.h>
+#include "library_common.h"
+
+#include <string>
+
+using namespace isc::dhcp;
+using namespace std;
+
+extern "C" {
+
+// This callout is called at the "pkt_rcvd" hook.
+int pkt_rcvd(CalloutHandle& handle) {
+
+ // A pointer to the packet is passed to the callout via a "boost" smart
+ // pointer. The include file "pkt4.h" typedefs a pointer to the Pkt4
+ // object as Pkt4Ptr. Retrieve a pointer to the object.
+ Pkt4Ptr query_ptr;
+ handle.getArgument("query", query_ptr);
+
+ // Point to the hardware address.
+ HwAddrPtr hwaddr_ptr = query_ptr->getHWAddr();
+
+ // The hardware address is held in a public member variable. We'll classify
+ // it as interesting if the sum of all the bytes in it is divisible by 4.
+ // (This is a contrived example after all!)
+ long sum = 0;
+ for (int i = 0; i < hwaddr_ptr->hwaddr_.size(); ++i) {
+ sum += hwaddr_ptr->hwadr_[i];
+ }
+
+ // Classify it.
+ if (sum % 4 == 0) {
+ // Store the text form of the hardware address in the context to pass
+ // to the next callout.
+ handle.setContext("hwaddr", hwaddr_ptr->hwaddr_.toText());
+ }
+
+ return (0);
+};
+ at endcode
+
+The pct_rcvd callout placed the hardware address of an interesting client in
+the "hwaddr" context for the packet. Turning now to the callout that will
+write this information to the log file:
+
+ at code
+// v4_lease_write.cc
+
+#include <hooks/hooks.h>
+#include <dhcp/pkt4.h>
+#include "library_common.h"
+
+#include <string>
+
+using namespace isc::dhcp;
+using namespace std;
+
+extern "C" {
+
+// This callout is called at the "v4_lease_write_post" hook.
+int v4_lease_write_post(CalloutHandle& handle) {
+
+ // Obtain the hardware address of the "interesting" client. We have to
+ // use a try...catch block here because if the client was not interesting,
+ // no information would be set and getArgument would thrown an exception.
+ string hwaddr;
+ try (handle.getArgument("hwaddr", hwaddr) {
+
+ // getArgument didn't throw so the client is interesting. Get a pointer
+ // to the reply. Note that the argument list for this hook also
+ // contains a pointer to the query: we don't need to access that in this
+ // example.
+ Pkt4Ptr reply;
+ handle.getArgument("reply", reply);
+
+ // Get the string form of the IP address.
+ string ipaddr = reply->getYiaddr().toText();
+
+ // Write the information to the log file.
+ interesting << hwaddr << " " << ipaddr << "\n";
+
+ // ... and to guard against a crash, we'll flush the output stream.
+ flush(interesting);
+
+ } catch (const NoSuchCalloutContext&) {
+
+ // No such element in the per-request context with the name
+ // "hwaddr". We will do nothing, so just dismiss the exception.
+
+ }
+
+ return (0);
+}
+
+};
+ at endcode
+
+ at subsection hooksdgBuild Building the Library
+
+Building the code requires building a shareable library. This requires
+the the code be compiled as positition-independent code (using the
+compiler's "-fpic" switch) and linked as a shared library (with the
+linker's "-shared" switch). The build command also needs to point to
+the BIND 10 include directory and link in the appropriate libraries.
+
+Assuming that BIND 10 has been installed in the default location, the
+command line needed to create the library using the Gnu C++ compiler on a
+Linux system is:
+
+ at code
+g++ -I /usr/include/bind10 -L /usr/lib/bind10 -fpic -shared -o example.so \
+ load_unload.cc pkt_rcvd.cc v4_lease_write.cc version.cc \
+ -lb10-dhcp++ -lb10-util -lb10-exceptions
+ at endcode
+
+Notes:
+- The compilation command and switches required may vary depending on
+your operating system and compiler - consult the relevant documentation
+for details.
+- The values for the "-I" and "-L" switches depend on where you have
+installed BIND 10.
+- The list of libraries that need to be included in the command line
+depends on the functionality used by the hook code and the module to
+which they are attached (e.g. hook code for DNS will need to link against
+the libb10-dns++ library). Depending on operating system, you may also need
+to explicitly list libraries on which the BIND 10 libraries depend.
+
+ at subsection hooksdgConfiguration Configuring the Hook Library
+
+The final step is to make the library known to BIND 10. All BIND 10 modules to
+which hooks can be added contain the "hook_library" element, and user
+libraries are added to this. (The BIND 10 hooks system can handle multiple libraries - this is discussed below.).
+
+To add the example library (assumed to be in /usr/local/lib) to the DHCPv4
+module, the following bindctl commands must be executed:
+
+ at code
+> config add Dhcp4/hook_libraries
+> config set Dhcp4/hook_libraries[0] "/usr/local/lib/example.so"
+> config commit
+ at endcode
+
+The DHCPv4 server will load the library and execute the callouts each time a
+request is received.
+
+ at section hooksdgAdvancedTopics Advanced Topics
+
+ at subsection hooksdgContextCreateDestroy Context Creation and Destruction
+
+As well as the hooks defined by the server, the hooks framework defines
+two hooks of its own, "context_create" and "context_destroy". The first
+is called when a request is created in the server, before any of the
+server-specific hooks gets called. It's purpose it to allow a library
+to initialize per-request context. The second is called after all
+server-defined hooks have been processed, and is to allow a library to
+tidy up.
+
+As an example, the v4_lease_write example above required that the code
+check for an exception being thrown when accessing the "hwaddr" context
+item in case it was not set. An alternative strategy would have been to
+provide a callout for the "context_create" hook and set the context item
+"hwaddr" to an empty string. Instead of needing to handle an exception,
+v4_lease_write would be guaranteed to get something when looking for
+the hwaddr item and so could write or not write the output depending on
+the value.
+
+In most cases, "context_destroy" is not needed as the Hooks system
+automatically deletes context. An example where it could be required
+is where memory has been allocated by a callout during the processing
+of a request and a raw pointer to it stored in the context object. On
+destruction of the context, that memory will not be automatically
+released. Freeing in the memory in the "context_destroy callout will solve
+that problem.
+
+Actually, when the context is destroyed, the destructor
+associated with any objects stored in it are run. Rather than point to
+allocated memory with a raw pointer, a better idea would be to point to
+it with a boost "smart" pointer and store that pointer in the context.
+When the context is destroyed, the smart pointer's destructor is run,
+which will automatically delete the pointed-to object.
+
+These approaches are illustrated in the following examples.
+Here it is assumed that the hooks library is performing some form of
+security checking on the packet and needs to maintain information in
+a user-specified "SecurityInformation" object. (The details of this
+fictitious object are of no concern here.) The object is created in
+the context_create callout and used in both the pkt4_rcvd and the
+v4_lease_write_post callouts.
+
+ at code
+// Storing information in a "raw" pointer. Assume that the
+
+#include <hooks/hooks.h>
+ :
+
+extern "C" {
+
+// context_create callout - called when the request is created.
+int context_create(CalloutHandle& handle) {
+ // Create the security information and store it in the context
+ // for this packet.
+ SecurityInformation* si = new SecurityInformation();
+ handle.setContext("security_information", si);
+}
+
+// Callouts that use the context
+int pktv_rcvd(CalloutHandle& handle) {
+ // Retrieve the pointer to the SecurityInformation object
+ SecurityInformation si;
+ handle.getContext("security_information", si);
+ :
+ :
+ // Set the security information
+ si->setSomething(...);
+
+ // The pointed-to information has been updated but the pointer has not been
+ // altered, so there is no need to call setContext() again.
+}
+
+int v4_lease_write_post(CalloutHandle& handle) {
+ // Retrieve the pointer to the SecurityInformation object
+ SecurityInformation si;
+ handle.getContext("security_information", si);
+ :
+ :
+ // Retrieve security information
+ bool active = si->getSomething(...);
+ :
+}
+
+// Context destruction. We need to delete the pointed-to SecurityInformation
+// object because we will lose the pointer to it when the CalloutHandle is
+// destroyed.
+int context_destroy(CalloutHandle& handle) {
+ // Retrieve the pointer to the SecurityInformation object
+ SecurityInformation si;
+ handle.getContext("security_information", si);
+
+ // Delete the pointed-to memory.
+ delete si;
+}
+ at endcode
+
+The requirement for the context_destroy callout can be eliminated if
+a Boost shared ptr is used to point to the allocated memory:
+
+ at code
+// Storing information in a "raw" pointer. Assume that the
+
+#include <hooks/hooks.h>
+#include <boost/shared_ptr.hpp>
+ :
+
+extern "C" {
+
+// context_create callout - called when the request is created.
+
+int context_create(CalloutHandle& handle) {
+ // Create the security information and store it in the context for this
+ // packet.
+ boost::shared_ptr<SecurityInformation> si(new SecurityInformation());
+ handle.setContext("security_information", si);
+}
+
+// Other than the data type, a shared pointer has similar semantics to a "raw"
+// pointer. Only the code from pkt_rcvd is shown here.
+
+int pktv_rcvd(CalloutHandle& handle) {
+ // Retrieve the pointer to the SecurityInformation object
+ boost::shared_ptr<SecurityInformation> si;
+ handle.setContext("security_information", si);
+ :
+ :
+ // Modify the security information
+ si->setSomething(...);
+
+ // The pointed-to information has been updated but the pointer has not
+ // altered, so theree is no need to reset the context.
+}
+
+// No context_destroy callout is needed to delete the allocated
+// SecurityInformation object. When the CalloutHandle is destroyed, the shared
+// pointer object will be destroyed. If that is the last shared pointer to the
+// allocated memory, then it too will be deleted.
+ at endcode
+
+(Note that a Boost shared pointer - rather than any other Boost smart pointer -
+should be used, as the pointer objects are copied within the hooks framework and
+only shared pointers have the correct behavior for the copy operation.)
+
+
+ at subsection hooksdgCalloutRegistration Registering Callouts
+
+As briefly mentioned in @ref hooksdgExampleCallouts, the standard is for
+callouts in the user library to have the same name as the name of the
+hook to which they are being attached. This convention was followed
+in the tutorial, e.g. the callout that needed to be attached to the
+"pkt_rcvd" hook was named pkt_rcvd.
+
+The reason for this convention is that when the library is loaded, the
+hook framework automatically searches the library for functions with
+the same names as the server hooks. When it finds one, it attaches it
+to the appropriate hook point. This simplifies the loading process and
+bookkeeping required to create a library of callouts.
+
+However, the hooks system is flexible in this area: callouts can have
+non-standard names, and multiple callouts can be registered on a hook.
+
+ at subsubsection hooksdgLibraryHandle The LibraryHandle Object
+
+The way into the part of the hooks framework that allows callout
+registration is through the LibraryHandle object. This was briefly
+introduced in the discussion of the framework functions, in that
+an object of this type is pass to the "load" function. A LibraryHandle
+can also be obtained from within a callout by calling the CalloutHandle's
+getLibraryHandle() method.
+
+The LibraryHandle provides three methods to manipulate callouts:
+
+- registerCallout - register a callout on a hook.
+- deregisterCallout - deregister a callout from a hook.
+- deregisterAllCallouts - deregister all callouts on a hook.
+
+The following sections cover some of the ways in which these can be used.
+
+ at subsubsection hooksdgNonstandardCalloutNames Non-Standard Callout Names
+
+The example in the tutorial used standard names for the callouts. As noted
+above, it is possible to use non-standard names. Suppose, instead of the
+callout names "pkt_rcvd" and "v4_lease_write", we had named our callouts
+"classify" and "write_data". The hooks framework would not have registered
+these callouts, so we would have needed to do it ourself. The place to
+do this is the "load" framework function, and its code would have had to
+been modified to:
+
+ at code
+int load(LibraryHandle& libhandle) {
+ // Register the callouts on the hooks. We assume that a header file
+ // declares the "classify" and "write_data" functions.
+ libhandle.registerCallout("pkt_rcvd", classify);
+ libhandle.registerCallout("v4_lease_write", write_data);
+
+ // Open the log file
+ interesting.open("/data/clients/interesting.log",
+ std::fstream::out | std::fstream::app);
+ return (interesting ? 0 : 1);
+}
+ at endcode
+
+It is possible for a library to contain callouts with both standard and
+non-standard names: ones with standard names will be registered automatically,
+ones with non-standard names need to be registered manually.
+
+ at subsubsection hooksdgMultipleCallouts Multiple Callouts on a Hook
+
+The BIND 10 hooks framework allows multiple callouts to be attached to
+a hook point. Although it is likely to be rare for user code to need to
+do this, there may be instances where it make sense.
+
+To register multiple callouts on a hook, just call
+LibraryHandle::registerCallout multiple times on the same hook, e.g.
+
+ at code
+ libhandle.registerCallout("pkt_rcvd", classify);
+ libhandle.registerCallout("pkt_rcvd", write_data);
+ at endcode
+
+The hooks framework will call the callouts in the order they are
+registered. The same CalloutHandle is passed between them, so any
+change made to the CalloutHandle's arguments, "skip" flag, or per-request
+context by the first is visible to the second.
+
+ at subsubsection hooksdgDynamicRegistration Dynamic Registration and Reregistration of Callouts
+
+The previous sections have dealt with callouts being registered during
+the call to "load". The hooks framework is more flexible than that
+in that callouts can be registered and deregistered within a callout.
+In fact, a callout is able to register or deregister itself, and a callout
+is able to be registered on a hook multiple times.
+
+Using our contrived example again, the DHCPv4 server processes one request
+to completion before it starts processing the next. With this knowledge,
+we could alter the logic of the code so that the callout attached to the
+"pkt_rcvd" hook registers the callout doing the logging when it detects
+an interesting packet, and the callout doing the logging deregisters
+itself in its execution. The relevant modifications to the code in
+the tutorial are shown below:
+
+ at code
+// pkt_rcvd.cc
+// :
+
+int pkt_rcvd(CalloutHandle& handle) {
+
+ :
+ :
+
+ // Classify it.
+ if (sum % 4 == 0) {
+ // Store the text form of the hardware address in the context to pass
+ // to the next callout.
+ handle.setContext("hwaddr", hwaddr_ptr->hwaddr_.toText());
+
+ // Register the callback to log the data.
+ handle.getLibraryHandle().registerCallout("v4_lease_write", write_data);
+ }
+
+ return (0);
+};
+ at endcode
+
+ at code
+// v4_lease_write.cc
+ :
+
+int write_data(CalloutHandle& handle) {
+
+ // Obtain the hardware address of the "interesting" client. As the
+ // callback is only registered when interesting data is present, we
+ // know that the context contains the hardware address so an exception
+ // will not be thrown when we call getArgument().
+ string hwaddr;
+ handle.getArgument("hwaddr", hwaddr);
+
+ // The pointer to the reply.
+ ConstPkt4Ptr reply;
+ handle.getArgument("reply", reply);
+
+ // Get the string form of the IP address.
+ string ipaddr = reply->getYiaddr().toText():
+
+ // Write the information to the log file and flush.
+ interesting << hwaddr << " " << ipaddr << "\n";
+ flush(interesting);
+
+ // We've logged the data, so deregister ourself. This callout will not
+ // be called again until it is registered by pkt_rcvd.
+
+ handle.getLibraryHandle().deregisterCallout("v4_lease_write", write_data);
+
+ return (0);
+}
+ at endcode
+
+Note that the above example used a non-standard name for the callout
+that wrote the data. Had the name been a standard one, it would have been
+registered when the library was loaded and called for the first request,
+regardless of whether that was defined as "interesting". (Although as
+callouts with standard names are always registered before "load" gets called,
+we could have got round that problem by deregistering that particular
+callout in the "load" function.)
+
+
+ at note Deregistration of a callout on the hook that is currently
+being called only takes effect when the server next calls the hook.
+To illustrate this, suppose the callouts attached to a hook are A, B and C
+(in that order), and during execution, A deregisters B and C and adds D.
+When callout A returns, B and C will still run. The next time the server
+calls the hook's callouts, A and D will run (in that order).
+
+ at subsection hooksdgMultipleLibraries Multiple User Libraries
+
+As alluded to in the section @ref hooksdgConfiguration, BIND 10 can load
+multiple libraries. The libraries are loaded in the order specified in
+the configuration, and the callouts attached to the hooks in the order
+presented by the libraries.
+
+The following picture illustrates this, and also illustrates the scope of
+data passed around the system.
+
+ at image html DataScopeArgument.png "Scope of Arguments"
+
+In this illustration, a server has three hook points, alpha, beta
+and gamma. Two libraries are configured, library 1 and library 2.
+Library 1 registers the callout "authorize" for hook alpha, "check" for
+hook beta and "add_option" for hook gamma. Library 2 registers "logpkt",
+"validate" and "putopt"
+
+The horizontal red lines represent arguments to callouts. When the server
+calls hook alpha, it creates an argument list and calls the
+first callout for the hook, "authorize". When that callout returns, the
+same (but possibly modified) argument list is passed to the next callout
+in the chain, "logpkt". Another, separate argument list is created for
+hook beta and passed to the callouts "check" and "validate" in
+that order. A similar sequence occurs for hook gamma.
+
+The next picture shows the scope of the context associated with a
+request.
+
+ at image html DataScopeContext.png "Illustration of per-library context"
+
+The vertical blue lines represent callout context. Context is
+per-packet but also per-library. When the server calls "authorize",
+the CalloutHandle's getContext and setContext methods access a context
+created purely for library 1. The next callout on the hook will access
+context created for library 2. These contexts are passed to the callouts
+associated with the next hook. So when "check" is called, it gets the
+context data that was set by "authorize", when "validate" is called,
+it gets the context data set by "logpkt".
+
+It is stressed that the context for callouts associated with different
+libraries is entirely separate. For example, suppose "authorize" sets
+the CalloutHandle's context item "foo" to 2 and "logpkt" sets an item of
+the same name to the string "bar". When "check" accesses the context
+item "foo", it gets a value of 2; when "validate" accesses an item of
+the same name, it gets the value "bar".
+
+It is also stressed that all this context exists only for the life of the
+request being processed. When that request is complete, all the
+context associated with that request - for all libraries - is destroyed,
+and new context created for the next request.
+
+This structure means that library authors can use per-request context
+without worrying about the presence of other libraries. Other libraries
+may be present, but will not affect the context values set by a library's
+callouts.
+
+ at subsection hooksdgInterLibraryData Passing Data Between Libraries
+
+In rare cases, it is possible that one library may want to pass
+data to another. This can be done in a limited way by means of the
+CalloutHandle's setArgument and getArgument calls. For example, in the
+above diagram, the callout "add_option" can pass a value to "putopt"
+by setting a name.value pair in the hook's argument list. "putopt"
+would be able to read this, but would not be able to return information
+back to "add_option".
+
+All argument names used by BIND 10 will be a combination of letters
+(both upper- and lower-case), digits, hyphens and underscores: no
+other characters will be used. As argument names are simple strings,
+it is suggested that if such a mechanism be used, the names of the data
+values passed between the libraries include a special character such as
+the dollar symbol or percent sign. In this way there is no danger that
+a name will conflict with any existing or future BIND 10 argument names.
+
+
+ at subsection hooksdgRegisterMultipleLibraries Dynamic Callout Registration and Multiple Libraries
+
+On a particular hook, callouts are called in the order the libraries appear
+in the configuration and, within a library, in the order the callouts
+are registered.
+
+This order applies to dynamically-registered callouts as well. As an
+example, consider the diagram above where for hook "beta", callout "check"
+is followed by callout "validate". Suppose that when "authorize" is run,
+it registers a new callout ("double_check") on hook "beta". That
+callout will be inserted at the end of the callouts registered by
+library 1 and before any registered by library 2. It would therefore
+appear between "check" and "validate". On the other hand, if it were
+"logpkt" that registered the new callout, "double_check" would appear
+after "validate".
+
+*/
diff --git a/src/lib/hooks/images/DataScopeArgument.dia b/src/lib/hooks/images/DataScopeArgument.dia
new file mode 100644
index 0000000..02a4f17
Binary files /dev/null and b/src/lib/hooks/images/DataScopeArgument.dia differ
diff --git a/src/lib/hooks/images/DataScopeArgument.png b/src/lib/hooks/images/DataScopeArgument.png
new file mode 100644
index 0000000..34a5bd1
Binary files /dev/null and b/src/lib/hooks/images/DataScopeArgument.png differ
diff --git a/src/lib/hooks/images/DataScopeContext.dia b/src/lib/hooks/images/DataScopeContext.dia
new file mode 100644
index 0000000..1e39f5b
Binary files /dev/null and b/src/lib/hooks/images/DataScopeContext.dia differ
diff --git a/src/lib/hooks/images/DataScopeContext.png b/src/lib/hooks/images/DataScopeContext.png
new file mode 100644
index 0000000..ba18875
Binary files /dev/null and b/src/lib/hooks/images/DataScopeContext.png differ
diff --git a/src/lib/hooks/images/HooksUml.dia b/src/lib/hooks/images/HooksUml.dia
new file mode 100644
index 0000000..0972fca
Binary files /dev/null and b/src/lib/hooks/images/HooksUml.dia differ
diff --git a/src/lib/hooks/images/HooksUml.png b/src/lib/hooks/images/HooksUml.png
new file mode 100644
index 0000000..3859e6a
Binary files /dev/null and b/src/lib/hooks/images/HooksUml.png differ
diff --git a/src/lib/hooks/library_handle.cc b/src/lib/hooks/library_handle.cc
new file mode 100644
index 0000000..7f43116
--- /dev/null
+++ b/src/lib/hooks/library_handle.cc
@@ -0,0 +1,76 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+
+namespace isc {
+namespace hooks {
+
+// Callout manipulation - all deferred to the CalloutManager.
+
+void
+LibraryHandle::registerCallout(const std::string& name, CalloutPtr callout) {
+ // Reset library index if required, saving the current value.
+ int saved_index = callout_manager_->getLibraryIndex();
+ if (index_ >= 0) {
+ callout_manager_->setLibraryIndex(index_);
+ }
+
+ // Register the callout.
+ callout_manager_->registerCallout(name, callout);
+
+ // Restore the library index if required. We know that the saved index
+ // is valid for the number of libraries (or is -1, which is an internal
+ // state indicating there is no current library index) as we obtained it
+ // from the callout manager.
+ if (index_ >= 0) {
+ callout_manager_->setLibraryIndex(saved_index);
+ }
+}
+
+bool
+LibraryHandle::deregisterCallout(const std::string& name, CalloutPtr callout) {
+ int saved_index = callout_manager_->getLibraryIndex();
+ if (index_ >= 0) {
+ callout_manager_->setLibraryIndex(index_);
+ }
+
+ bool status = callout_manager_->deregisterCallout(name, callout);
+
+ if (index_ >= 0) {
+ callout_manager_->setLibraryIndex(saved_index);
+ }
+
+ return (status);
+}
+
+bool
+LibraryHandle::deregisterAllCallouts(const std::string& name) {
+ int saved_index = callout_manager_->getLibraryIndex();
+ if (index_ >= 0) {
+ callout_manager_->setLibraryIndex(index_);
+ }
+
+ bool status = callout_manager_->deregisterAllCallouts(name);
+
+ if (index_ >= 0) {
+ callout_manager_->setLibraryIndex(saved_index);
+ }
+
+ return (status);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/hooks/library_handle.h b/src/lib/hooks/library_handle.h
new file mode 100644
index 0000000..4fe47cd
--- /dev/null
+++ b/src/lib/hooks/library_handle.h
@@ -0,0 +1,149 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 LIBRARY_HANDLE_H
+#define LIBRARY_HANDLE_H
+
+#include <string>
+
+namespace isc {
+namespace hooks {
+
+// Forward declarations
+class CalloutHandle;
+class CalloutManager;
+
+/// Typedef for a callout pointer. (Callouts must have "C" linkage.)
+extern "C" {
+ typedef int (*CalloutPtr)(CalloutHandle&);
+};
+
+/// @brief Library handle
+///
+/// This class is accessed by the user library when registering callouts,
+/// either by the library's load() function, or by one of the callouts
+/// themselves.
+///
+/// It is really little more than a shell around the CalloutManager. By
+/// presenting this object to the user-library callouts, callouts can manage
+/// the callout list for their own library, but cannot affect the callouts
+/// registered by other libraries.
+///
+/// (This restriction is achieved by the CalloutManager maintaining the concept
+/// of the "current library". When a callout is registered - either by the
+/// library's load() function, or by a callout in the library - the registration
+/// information includes the library active at the time. When that callout is
+/// called, the CalloutManager uses that information to set the "current
+/// library": the registration functions only operator on data whose
+/// associated library is equal to the "current library".)
+
+class LibraryHandle {
+public:
+
+ /// @brief Constructor
+ ///
+ /// @param manager Back pointer to the containing CalloutManager.
+ /// This pointer is used to access appropriate methods in that
+ /// object. Note that the "raw" pointer is safe - the only
+ /// instance of the LibraryHandle in the system is as a member of
+ /// the CalloutManager to which it points.
+ ///
+ /// @param index Index of the library to which the LibraryHandle applies.
+ /// If negative, the library index as set in the CalloutManager is
+ /// used. Otherwise the current library index is saved, this value
+ /// is set as the current index, the registration call is made, and
+ /// the CalloutManager's library index is reset. Note: although -1
+ /// is a valid argument value for
+ /// isc::hooks::CalloutManager::setLibraryIndex(), in this class is
+ /// is used as a sentinel to indicate that the library index in
+ /// isc::util::CalloutManager should not be set or reset.
+ LibraryHandle(CalloutManager* manager, int index = -1)
+ : callout_manager_(manager), index_(index)
+ {}
+
+ /// @brief Register a callout on a hook
+ ///
+ /// Registers a callout function with a given hook. The callout is added
+ /// to the end of the callouts for the current library that are associated
+ /// with that hook.
+ ///
+ /// @param name Name of the hook to which the callout is added.
+ /// @param callout Pointer to the callout function to be registered.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognised.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ void registerCallout(const std::string& name, CalloutPtr callout);
+
+ /// @brief De-Register a callout on a hook
+ ///
+ /// Searches through the functions registered by the current library with
+ /// the named hook and removes all entries matching the callout. It does
+ /// not affect callouts registered by other libraries.
+ ///
+ /// @param name Name of the hook from which the callout is removed.
+ /// @param callout Pointer to the callout function to be removed.
+ ///
+ /// @return true if a one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook The hook name is unrecognised.
+ /// @throw Unexpected The hook name is valid but an internal data structure
+ /// is of the wrong size.
+ bool deregisterCallout(const std::string& name, CalloutPtr callout);
+
+ /// @brief Removes all callouts on a hook
+ ///
+ /// Removes all callouts associated with a given hook that were registered.
+ /// by the current library. It does not affect callouts that were
+ /// registered by other libraries.
+ ///
+ /// @param name Name of the hook from which the callouts are removed.
+ ///
+ /// @return true if one or more callouts were deregistered.
+ ///
+ /// @throw NoSuchHook Thrown if the hook name is unrecognised.
+ bool deregisterAllCallouts(const std::string& name);
+
+private:
+ /// @brief Copy constructor
+ ///
+ /// Private (with no implementation) as it makes no sense to copy an object
+ /// of this type. All code receives a reference to an existing handle which
+ /// is tied to a particular CalloutManager. Creating a copy of that handle
+ /// runs the risk of a "dangling pointer" to the original handle's callout
+ /// manager.
+ ///
+ /// @param Unused - should be the object to copy.
+ LibraryHandle(const LibraryHandle&);
+
+ /// @brief Assignment operator
+ ///
+ /// Declared private like the copy constructor for the same reasons. It too
+ /// has no implementation.
+ ///
+ /// @param Unused - should be the object to copy.
+ LibraryHandle& operator=(const LibraryHandle&);
+
+ /// Back pointer to the collection object for the library
+ CalloutManager* callout_manager_;
+
+ /// Library index to which this handle applies. -1 indicates that it
+ /// applies to whatever index is current in the CalloutManager.
+ int index_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // LIBRARY_HANDLE_H
diff --git a/src/lib/hooks/library_manager.cc b/src/lib/hooks/library_manager.cc
new file mode 100644
index 0000000..70c76ba
--- /dev/null
+++ b/src/lib/hooks/library_manager.cc
@@ -0,0 +1,353 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <hooks/hooks.h>
+#include <hooks/hooks_log.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/library_manager.h>
+#include <hooks/pointer_converter.h>
+#include <hooks/server_hooks.h>
+
+#include <string>
+#include <vector>
+
+#include <dlfcn.h>
+
+using namespace std;
+
+namespace isc {
+namespace hooks {
+
+
+// Constructor (used by external agency)
+LibraryManager::LibraryManager(const std::string& name, int index,
+ const boost::shared_ptr<CalloutManager>& manager)
+ : dl_handle_(NULL), index_(index), manager_(manager),
+ library_name_(name)
+{
+ if (!manager) {
+ isc_throw(NoCalloutManager, "must specify a CalloutManager when "
+ "instantiating a LibraryManager object");
+ }
+}
+
+// Constructor (used by "validate" for library validation). Note that this
+// sets "manager_" to not point to anything, which means that methods such as
+// registerStandardCallout() will fail, probably with a segmentation fault.
+// There are no checks for this condition in those methods: this constructor
+// is declared "private", so can only be executed by a method in this class.
+// The only method to do so is "validateLibrary", which takes care not to call
+// methods requiring a non-NULL manager.
+LibraryManager::LibraryManager(const std::string& name)
+ : dl_handle_(NULL), index_(-1), manager_(), library_name_(name)
+{}
+
+// Destructor.
+LibraryManager::~LibraryManager() {
+ if (manager_) {
+ // LibraryManager instantiated to load a library, so ensure that
+ // it is unloaded before exiting.
+ static_cast<void>(unloadLibrary());
+ } else {
+ // LibraryManager instantiated to validate a library, so just ensure
+ // that it is closed before exiting.
+ static_cast<void>(closeLibrary());
+ }
+}
+
+// Open the library
+
+bool
+LibraryManager::openLibrary() {
+
+ // Open the library. We'll resolve names now, so that if there are any
+ // issues we don't bugcheck in the middle of apparently unrelated code.
+ dl_handle_ = dlopen(library_name_.c_str(), RTLD_NOW | RTLD_LOCAL);
+ if (dl_handle_ == NULL) {
+ LOG_ERROR(hooks_logger, HOOKS_OPEN_ERROR).arg(library_name_)
+ .arg(dlerror());
+ }
+
+ return (dl_handle_ != NULL);
+}
+
+// Close the library if not already open
+
+bool
+LibraryManager::closeLibrary() {
+
+ // Close the library if it is open. (If not, this is a no-op.)
+ int status = 0;
+ if (dl_handle_ != NULL) {
+ status = dlclose(dl_handle_);
+ dl_handle_ = NULL;
+ if (status != 0) {
+ LOG_ERROR(hooks_logger, HOOKS_CLOSE_ERROR).arg(library_name_)
+ .arg(dlerror());
+ }
+ }
+
+ return (status == 0);
+}
+
+// Check the version of the library
+
+bool
+LibraryManager::checkVersion() const {
+
+ // Get the pointer to the "version" function.
+ PointerConverter pc(dlsym(dl_handle_, VERSION_FUNCTION_NAME));
+ if (pc.versionPtr() != NULL) {
+ int version = BIND10_HOOKS_VERSION - 1; // This is an invalid value
+ try {
+ version = (*pc.versionPtr())();
+ } catch (...) {
+ LOG_ERROR(hooks_logger, HOOKS_VERSION_EXCEPTION).arg(library_name_);
+ return (false);
+ }
+
+ if (version == BIND10_HOOKS_VERSION) {
+ // All OK, version checks out
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_LIBRARY_VERSION)
+ .arg(library_name_).arg(version);
+ return (true);
+
+ } else {
+ LOG_ERROR(hooks_logger, HOOKS_INCORRECT_VERSION).arg(library_name_)
+ .arg(version).arg(BIND10_HOOKS_VERSION);
+ }
+ } else {
+ LOG_ERROR(hooks_logger, HOOKS_NO_VERSION).arg(library_name_);
+ }
+
+ return (false);
+}
+
+// Register the standard callouts
+
+void
+LibraryManager::registerStandardCallouts() {
+ // Set the library index for doing the registration. This is picked up
+ // when the library handle is created.
+ manager_->setLibraryIndex(index_);
+
+ // Iterate through the list of known hooks
+ vector<string> hook_names = ServerHooks::getServerHooks().getHookNames();
+ for (int i = 0; i < hook_names.size(); ++i) {
+
+ // Look up the symbol
+ void* dlsym_ptr = dlsym(dl_handle_, hook_names[i].c_str());
+ PointerConverter pc(dlsym_ptr);
+ if (pc.calloutPtr() != NULL) {
+ // Found a symbol, so register it.
+ manager_->getLibraryHandle().registerCallout(hook_names[i],
+ pc.calloutPtr());
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS,
+ HOOKS_STD_CALLOUT_REGISTERED).arg(library_name_)
+ .arg(hook_names[i]).arg(dlsym_ptr);
+
+ }
+ }
+}
+
+// Run the "load" function if present.
+
+bool
+LibraryManager::runLoad() {
+
+ // Get the pointer to the "load" function.
+ PointerConverter pc(dlsym(dl_handle_, LOAD_FUNCTION_NAME));
+ if (pc.loadPtr() != NULL) {
+
+ // Call the load() function with the library handle. We need to set
+ // the CalloutManager's index appropriately. We'll invalidate it
+ // afterwards.
+
+ int status = -1;
+ try {
+ manager_->setLibraryIndex(index_);
+ status = (*pc.loadPtr())(manager_->getLibraryHandle());
+ } catch (...) {
+ LOG_ERROR(hooks_logger, HOOKS_LOAD_EXCEPTION).arg(library_name_);
+ return (false);
+ }
+
+ if (status != 0) {
+ LOG_ERROR(hooks_logger, HOOKS_LOAD_ERROR).arg(library_name_)
+ .arg(status);
+ return (false);
+ } else {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LOAD_SUCCESS)
+ .arg(library_name_);
+ }
+
+ } else {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_NO_LOAD)
+ .arg(library_name_);
+ }
+
+ return (true);
+}
+
+
+// Run the "unload" function if present.
+
+bool
+LibraryManager::runUnload() {
+
+ // Get the pointer to the "load" function.
+ PointerConverter pc(dlsym(dl_handle_, UNLOAD_FUNCTION_NAME));
+ if (pc.unloadPtr() != NULL) {
+
+ // Call the load() function with the library handle. We need to set
+ // the CalloutManager's index appropriately. We'll invalidate it
+ // afterwards.
+ int status = -1;
+ try {
+ status = (*pc.unloadPtr())();
+ } catch (...) {
+ // Exception generated. Note a warning as the unload will occur
+ // anyway.
+ LOG_WARN(hooks_logger, HOOKS_UNLOAD_EXCEPTION).arg(library_name_);
+ return (false);
+ }
+
+ if (status != 0) {
+ LOG_ERROR(hooks_logger, HOOKS_UNLOAD_ERROR).arg(library_name_)
+ .arg(status);
+ return (false);
+ } else {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_UNLOAD_SUCCESS)
+ .arg(library_name_);
+ }
+ } else {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_NO_UNLOAD)
+ .arg(library_name_);
+ }
+
+ return (true);
+}
+
+// The main library loading function.
+
+bool
+LibraryManager::loadLibrary() {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LIBRARY_LOADING)
+ .arg(library_name_);
+
+ // In the following, if a method such as openLibrary() fails, it will
+ // have issued an error message so there is no need to issue another one
+ // here.
+
+ // Open the library (which is a check that it exists and is accessible).
+ if (openLibrary()) {
+
+ // Library opened OK, see if a version function is present and if so,
+ // check what value it returns.
+ if (checkVersion()) {
+
+ // Version OK, so now register the standard callouts and call the
+ // library's load() function if present.
+ registerStandardCallouts();
+ if (runLoad()) {
+
+ // Success - the library has been successfully loaded.
+ LOG_INFO(hooks_logger, HOOKS_LIBRARY_LOADED).arg(library_name_);
+ return (true);
+
+ } else {
+
+ // The load function failed, so back out. We can't just close
+ // the library as (a) we need to call the library's "unload"
+ // function (if present) in case "load" allocated resources that
+ // need to be freed and (b) we need to remove any callouts that
+ // have been installed.
+ static_cast<void>(unloadLibrary());
+ }
+ }
+
+ // Either the version check or call to load() failed, so close the
+ // library and free up resources. Ignore the status return here - we
+ // already know there's an error and will have output a message.
+ static_cast<void>(closeLibrary());
+ }
+
+ return (false);
+}
+
+// The library unloading function. Call the unload() function (if present),
+// remove callouts from the callout manager, then close the library. This is
+// only run if the library is still loaded and is a no-op if the library is
+// not open.
+
+bool
+LibraryManager::unloadLibrary() {
+ bool result = true;
+ if (dl_handle_ != NULL) {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LIBRARY_UNLOADING)
+ .arg(library_name_);
+
+ // Call the unload() function if present. Note that this is done first
+ // - operations take place in the reverse order to which they were done
+ // when the library was loaded.
+ result = runUnload();
+
+ // Regardless of status, remove all callouts associated with this
+ // library on all hooks.
+ vector<string> hooks = ServerHooks::getServerHooks().getHookNames();
+ manager_->setLibraryIndex(index_);
+ for (int i = 0; i < hooks.size(); ++i) {
+ bool removed = manager_->deregisterAllCallouts(hooks[i]);
+ if (removed) {
+ LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_REMOVED)
+ .arg(hooks[i]).arg(library_name_);
+ }
+ }
+
+ // ... and close the library.
+ result = closeLibrary() && result;
+ if (result) {
+
+ // Issue the informational message only if the library was unloaded
+ // with no problems. If there was an issue, an error message would
+ // have been issued.
+ LOG_INFO(hooks_logger, HOOKS_LIBRARY_UNLOADED).arg(library_name_);
+ }
+ }
+ return (result);
+}
+
+// Validate the library. We must be able to open it, and the version function
+// must both exist and return the right number. Note that this is a static
+// method.
+
+bool
+LibraryManager::validateLibrary(const std::string& name) {
+ // Instantiate a library manager for the validation. We use the private
+ // constructor as we don't supply a CalloutManager.
+ LibraryManager manager(name);
+
+ // Try to open it and, if we succeed, check the version.
+ bool validated = manager.openLibrary() && manager.checkVersion();
+
+ // Regardless of whether the version checked out, close the library. (This
+ // is a no-op if the library failed to open.)
+ static_cast<void>(manager.closeLibrary());
+
+ return (validated);
+}
+
+} // namespace hooks
+} // namespace isc
diff --git a/src/lib/hooks/library_manager.h b/src/lib/hooks/library_manager.h
new file mode 100644
index 0000000..dc7c5eb
--- /dev/null
+++ b/src/lib/hooks/library_manager.h
@@ -0,0 +1,231 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 LIBRARY_MANAGER_H
+#define LIBRARY_MANAGER_H
+
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+
+namespace isc {
+namespace hooks {
+
+/// @brief No Callout Manager
+///
+/// Thrown if a library manager is instantiated by an external agency without
+/// specifying a CalloutManager object.
+class NoCalloutManager : public Exception {
+public:
+ NoCalloutManager(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+class CalloutManager;
+class LibraryHandle;
+class LibraryManager;
+
+/// @brief Library manager
+///
+/// This class handles the loading and unloading of a specific library. It also
+/// provides a static method for checking that a library is valid (this is used
+/// in configuration parsing).
+///
+/// On loading, it opens the library using dlopen and checks the version (set
+/// with the "version" method. If all is OK, it iterates through the list of
+/// known hooks and locates their symbols, registering each callout as it does
+/// so. Finally it locates the "load" function (if present) and calls it.
+///
+/// On unload, it calls the "unload" method if present, clears the callouts on
+/// all hooks, and closes the library.
+///
+/// @note Caution needs to be exercised when using the unload method. During
+/// normal use, data will pass between the server and the library. In
+/// this process, the library may allocate memory and pass it back to the
+/// server. This could happen by the server setting arguments or context
+/// in the CalloutHandle object, or by the library modifying the content
+/// of pointed-to data. If the library is unloaded, this memory may lie
+/// in the virtual address space deleted in that process. (The word "may"
+/// is used, as this could be operating-system specific.) Should this
+/// happen, any reference to the memory will cause a segmentation fault.
+/// This can occur in a quite obscure place, for example in the middle of
+/// a destructor of an STL class when it is deleting memory allocated
+/// when the data structure was extended by a function in the library.
+///
+/// @note The only safe way to run the "unload" function is to ensure that all
+/// possible references to it are removed first. This means that all
+/// CalloutHandles must be destroyed, as must any data items that were
+/// passed to the callouts. In practice, it could mean that a server
+/// suspends processing of new requests until all existing ones have
+/// been serviced and all packet/context structures destroyed before
+/// reloading the libraries.
+///
+/// When validating a library, only the fact that the library can be opened and
+/// version() exists and returns the correct number is checked. The library
+/// is closed after the validation.
+
+class LibraryManager {
+public:
+ /// @brief Constructor
+ ///
+ /// This constructor is used by external agencies (i.e. the
+ /// LibraryManagerCollection) when instantiating a LibraryManager. It
+ /// stores the library name - the actual actual loading is done in
+ /// loadLibrary().
+ ///
+ /// @param name Name of the library to load. This should be an absolute
+ /// path name.
+ /// @param index Index of this library
+ /// @param manager CalloutManager object
+ ///
+ /// @throw NoCalloutManager Thrown if the manager argument is NULL.
+ LibraryManager(const std::string& name, int index,
+ const boost::shared_ptr<CalloutManager>& manager);
+
+ /// @brief Destructor
+ ///
+ /// If the library is open, closes it. This is principally a safety
+ /// feature to ensure closure in the case of an exception destroying this
+ /// object. However, see the caveat in the class header about when it is
+ /// safe to unload libraries.
+ ~LibraryManager();
+
+ /// @brief Validate library
+ ///
+ /// A static method that is used to validate a library. Validation checks
+ /// that the library can be opened, that "version" exists, and that it
+ /// returns the right number.
+ ///
+ /// @param name Name of the library to validate
+ ///
+ /// @return true if the library validated, false if not. If the library
+ /// fails to validate, the reason for the failure is logged.
+ static bool validateLibrary(const std::string& name);
+
+ /// @brief Loads a library
+ ///
+ /// Open the library and check the version. If all is OK, load all standard
+ /// symbols then call "load" if present.
+ ///
+ /// @return true if the library loaded successfully, false otherwise. In the
+ /// latter case, the library will be unloaded if possible.
+ bool loadLibrary();
+
+ /// @brief Unloads a library
+ ///
+ /// Calls the libraries "unload" function if present, the closes the
+ /// library.
+ ///
+ /// However, see the caveat in the class header about when it is safe to
+ /// unload libraries.
+ ///
+ /// @return true if the library unloaded successfully, false if an error
+ /// occurred in the process (most likely the unload() function
+ /// (if present) returned an error). Even if an error did occur,
+ /// the library is closed if possible.
+ bool unloadLibrary();
+
+ /// @brief Return library name
+ ///
+ /// @return Name of this library
+ std::string getName() const {
+ return (library_name_);
+ }
+
+protected:
+ // The following methods are protected as they are accessed in testing.
+
+ /// @brief Open library
+ ///
+ /// Opens the library associated with this LibraryManager. A message is
+ /// logged on an error.
+ ///
+ /// @return true if the library opened successfully, false otherwise.
+ bool openLibrary();
+
+ /// @brief Close library
+ ///
+ /// Closes the library associated with this LibraryManager. A message is
+ /// logged on an error.
+ ///
+ /// @return true if the library closed successfully, false otherwise. "true"
+ /// is also returned if the library were already closed when this
+ /// method was called.
+ bool closeLibrary();
+
+ /// @brief Check library version
+ ///
+ /// With the library open, accesses the "version()" function and, if
+ /// present, checks the returned value against the hooks version symbol
+ /// for the currently running BIND 10. The "version()" function is
+ /// mandatory and must be present (and return the correct value) for the
+ /// library to load.
+ ///
+ /// If there is no version() function, or if there is a mismatch in
+ /// version number, a message logged.
+ ///
+ /// @return bool true if the check succeeded
+ bool checkVersion() const;
+
+ /// @brief Register standard callouts
+ ///
+ /// Loops through the list of hook names and searches the library for
+ /// functions with those names. Any that are found are registered as
+ /// callouts for that hook.
+ void registerStandardCallouts();
+
+ /// @brief Run the load function if present
+ ///
+ /// Searches for the "load" framework function and, if present, runs it.
+ ///
+ /// @return bool true if not found or found and run successfully,
+ /// false on an error. In this case, an error message will
+ /// have been output.
+ bool runLoad();
+
+ /// @brief Run the unload function if present
+ ///
+ /// Searches for the "unload" framework function and, if present, runs it.
+ ///
+ /// @return bool true if not found or found and run successfully,
+ /// false on an error. In this case, an error message will
+ /// have been output.
+ bool runUnload();
+
+private:
+ /// @brief Validating constructor
+ ///
+ /// Constructor used when the LibraryManager is instantiated to validate
+ /// a library (i.e. by the "validateLibrary" static method).
+ ///
+ /// @param name Name of the library to load. This should be an absolute
+ /// path name.
+ LibraryManager(const std::string& name);
+
+ // Member variables
+
+ void* dl_handle_; ///< Handle returned by dlopen
+ int index_; ///< Index associated with this library
+ boost::shared_ptr<CalloutManager> manager_;
+ ///< Callout manager for registration
+ std::string library_name_; ///< Name of the library
+
+};
+
+} // namespace hooks
+} // namespace isc
+
+#endif // LIBRARY_MANAGER_H
diff --git a/src/lib/hooks/library_manager_collection.cc b/src/lib/hooks/library_manager_collection.cc
new file mode 100644
index 0000000..b9122e2
--- /dev/null
+++ b/src/lib/hooks/library_manager_collection.cc
@@ -0,0 +1,129 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <hooks/callout_manager.h>
+#include <hooks/library_manager.h>
+#include <hooks/library_manager_collection.h>
+
+namespace isc {
+namespace hooks {
+
+// Return callout manager for the loaded libraries. This call is only valid
+// after one has been created for the loaded libraries (which includes the
+// case of no loaded libraries).
+//
+// Note that there is no real connection between the callout manager and the
+// libraries, other than it knows the number of libraries so can do sanity
+// checks on values passed to it. However, this may change in the future,
+// so the hooks framework is written such that a callout manager is used only
+// with the LibraryManagerCollection that created it. It is also the reason
+// why each LibraryManager contains a pointer to this CalloutManager.
+
+boost::shared_ptr<CalloutManager>
+LibraryManagerCollection::getCalloutManager() const {
+
+ // Only return a pointer if we have a CalloutManager created.
+ if (! callout_manager_) {
+ isc_throw(LoadLibrariesNotCalled, "must load hooks libraries before "
+ "attempting to retrieve a CalloutManager for them");
+ }
+
+ return (callout_manager_);
+}
+
+// Load a set of libraries
+
+bool
+LibraryManagerCollection::loadLibraries() {
+
+ // Unload libraries if any are loaded.
+ static_cast<void>(unloadLibraries());
+
+ // Create the callout manager. A pointer to this is maintained by
+ // each library. Note that the callout manager does not hold any memory
+ // allocated by a library: although a library registers a callout (and so
+ // causes the creation of an entry in the CalloutManager's callout list),
+ // that creation is done by the CalloutManager itself. The CalloutManager
+ // is created within the server.
+ //
+ // The upshot of this is that it is therefore safe for the CalloutManager
+ // to be deleted after all associated libraries are deleted, hence this
+ // link (LibraryManager -> CalloutManager) is safe.
+ callout_manager_.reset(new CalloutManager(library_names_.size()));
+
+ // Now iterate through the libraries are load them one by one. We'll
+ for (int i = 0; i < library_names_.size(); ++i) {
+ // Create a pointer to the new library manager. The index of this
+ // library is determined by the number of library managers currently
+ // loaded: note that the library indexes run from 1 to (number of loaded
+ // libraries).
+ boost::shared_ptr<LibraryManager> manager(
+ new LibraryManager(library_names_[i], lib_managers_.size() + 1,
+ callout_manager_));
+
+ // Load the library. On success, add it to the list of loaded
+ // libraries. On failure, unload all currently loaded libraries,
+ // leaving the object in the state it was in before loadLibraries was
+ // called.
+ if (manager->loadLibrary()) {
+ lib_managers_.push_back(manager);
+ } else {
+ static_cast<void>(unloadLibraries());
+ return (false);
+ }
+ }
+
+ return (true);
+}
+
+// Unload the libraries.
+
+void
+LibraryManagerCollection::unloadLibraries() {
+
+ // Delete the library managers in the reverse order to which they were
+ // created, then clear the library manager vector.
+ for (int i = lib_managers_.size() - 1; i >= 0; --i) {
+ lib_managers_[i].reset();
+ }
+ lib_managers_.clear();
+
+ // Get rid of the callout manager. (The other member, the list of library
+ // names, was cleared when the libraries were loaded.)
+ callout_manager_.reset();
+}
+
+// Return number of loaded libraries.
+int
+LibraryManagerCollection::getLoadedLibraryCount() const {
+ return (lib_managers_.size());
+}
+
+// Validate the libraries.
+std::vector<std::string>
+LibraryManagerCollection::validateLibraries(
+ const std::vector<std::string>& libraries) {
+
+ std::vector<std::string> failures;
+ for (int i = 0; i < libraries.size(); ++i) {
+ if (!LibraryManager::validateLibrary(libraries[i])) {
+ failures.push_back(libraries[i]);
+ }
+ }
+
+ return (failures);
+}
+
+} // namespace hooks
+} // namespace isc
diff --git a/src/lib/hooks/library_manager_collection.h b/src/lib/hooks/library_manager_collection.h
new file mode 100644
index 0000000..0a255ba
--- /dev/null
+++ b/src/lib/hooks/library_manager_collection.h
@@ -0,0 +1,170 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 LIBRARY_MANAGER_COLLECTION_H
+#define LIBRARY_MANAGER_COLLECTION_H
+
+#include <exceptions/exceptions.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <vector>
+
+namespace isc {
+namespace hooks {
+
+/// @brief LoadLibraries not called
+///
+/// Thrown if an attempt is made get a CalloutManager before the libraries
+/// have been loaded.
+class LoadLibrariesNotCalled : public Exception {
+public:
+ LoadLibrariesNotCalled(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+// Forward declarations
+class CalloutManager;
+class LibraryManager;
+
+/// @brief Library manager collection
+///
+/// The LibraryManagerCollection class, as the name implies, is responsible for
+/// managing the collection of LibraryManager objects that describe the loaded
+/// libraries. As such, it converts a single operation (e.g load libraries)
+/// into multiple operations, one per library. However, the class does more
+/// than that - it provides a single object with which to manage lifetimes.
+///
+/// As described in the LibraryManager documentation, a CalloutHandle may end
+/// up with pointers to memory within the address space of a loaded library.
+/// If the library is unloaded before this address space is deleted, the
+/// deletion of the CalloutHandle may attempt to free memory into the newly-
+/// unmapped address space and cause a segmentation fault.
+///
+/// To prevent this, each CalloutHandle maintains a shared pointer to the
+/// LibraryManagerCollection current when it was created. In addition, the
+/// containing HooksManager object also maintains a shared pointer to it. A
+/// a LibraryManagerCollection is never explicitly deleted: when a new set
+/// of libraries is loaded, the HooksManager clears its pointer to the
+/// collection. The LibraryManagerCollection is only destroyed when all
+/// CallHandle objects referencing it are destroyed.
+///
+/// Note that this does not completely solve the problem - a hook function may
+/// have modified a packet being processed by the server and that packet may
+/// hold a pointer to memory in the library's virtual address space. To avoid
+/// a segmentation fault, that packet needs to free the memory before the
+/// LibraryManagerCollection is destroyed and this places demands on the server
+/// code. However, the link with the CalloutHandle does at least mean that
+/// authors of server code do not need to be so careful about when they destroy
+/// CalloutHandles.
+///
+/// The collection object also provides a utility function to validate a set
+/// of libraries. The function checks that each library exists, can be opened,
+/// that the "version" function exists and return the right number.
+
+class LibraryManagerCollection {
+public:
+ /// @brief Constructor
+ ///
+ /// @param libraries List of libraries that this collection will manage.
+ /// The order of the libraries is important.
+ LibraryManagerCollection(const std::vector<std::string>& libraries)
+ : library_names_(libraries)
+ {}
+
+ /// @brief Destructor
+ ///
+ /// Unloads all loaded libraries.
+ ~LibraryManagerCollection() {
+ static_cast<void>(unloadLibraries());
+ }
+
+ /// @brief Load libraries
+ ///
+ /// Loads the libraries. This creates the LibraryManager associated with
+ /// each library and calls its loadLibrary() method. If a library fails
+ /// to load, the loading is abandoned and all libraries loaded so far
+ /// are unloaded.
+ ///
+ /// @return true if all libraries loaded, false if one or more failed t
+ //// load.
+ bool loadLibraries();
+
+ /// @brief Get callout manager
+ ///
+ /// Returns a callout manager that can be used with this set of loaded
+ /// libraries (even if the number of loaded libraries is zero). This
+ /// method may only be caslled after loadLibraries() has been called.
+ ///
+ /// @return Pointer to a callout manager for this set of libraries.
+ ///
+ /// @throw LoadLibrariesNotCalled Thrown if this method is called between
+ /// construction and the time loadLibraries() is called.
+ boost::shared_ptr<CalloutManager> getCalloutManager() const;
+
+ /// @brief Get library names
+ ///
+ /// Returns the list of library names. If called before loadLibraries(),
+ /// the list is the list of names to be loaded; if called afterwards, it
+ /// is the list of libraries that have been loaded.
+ std::vector<std::string> getLibraryNames() const {
+ return (library_names_);
+ }
+
+ /// @brief Get number of loaded libraries
+ ///
+ /// Mainly for testing, this returns the number of libraries that are
+ /// loaded.
+ ///
+ /// @return Number of libraries that are loaded.
+ int getLoadedLibraryCount() const;
+
+ /// @brief Validate libraries
+ ///
+ /// Utility function to validate libraries. It checks that the libraries
+ /// exist, can be opened, that a "version" function is present in them, and
+ /// that it returns the right number. All errors are logged.
+ ///
+ /// @param libraries List of libraries to validate
+ ///
+ /// @return Vector of libraries that faled to validate, or an empty vector
+ /// if all validated.
+ static std::vector<std::string>
+ validateLibraries(const std::vector<std::string>& libraries);
+
+protected:
+ /// @brief Unload libraries
+ ///
+ /// Unloads and closes all loaded libraries. They are unloaded in the
+ /// reverse order to the order in which they were loaded.
+ void unloadLibraries();
+
+private:
+
+ /// Vector of library names
+ std::vector<std::string> library_names_;
+
+ /// Vector of library managers
+ std::vector<boost::shared_ptr<LibraryManager> > lib_managers_;
+
+ /// Callout manager to be associated with the libraries
+ boost::shared_ptr<CalloutManager> callout_manager_;
+};
+
+} // namespace hooks
+} // namespace isc
+
+
+#endif // LIBRARY_MANAGER_COLLECTION_H
diff --git a/src/lib/hooks/pointer_converter.h b/src/lib/hooks/pointer_converter.h
new file mode 100644
index 0000000..1fe15ac
--- /dev/null
+++ b/src/lib/hooks/pointer_converter.h
@@ -0,0 +1,121 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 POINTER_CONVERTER_H
+#define POINTER_CONVERTER_H
+
+#include <hooks/hooks.h>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Local class for conversion of void pointers to function pointers
+///
+/// Converting between void* and function pointers in C++ is fraught with
+/// difficulty and pitfalls, e.g. see
+/// https://groups.google.com/forum/?hl=en&fromgroups#!topic/comp.lang.c++/37o0l8rtEE0
+///
+/// The method given in that article - convert using a union is used here. A
+/// union is declared (and zeroed) and the appropriate member extracted when
+/// needed.
+
+class PointerConverter {
+public:
+ /// @brief Constructor
+ ///
+ /// Zeroes the union and stores the void* pointer we wish to convert (the
+ /// one returned by dlsym).
+ ///
+ /// @param dlsym_ptr void* pointer returned by call to dlsym()
+ PointerConverter(void* dlsym_ptr) {
+ memset(&pointers_, 0, sizeof(pointers_));
+ pointers_.dlsym_ptr = dlsym_ptr;
+ }
+
+ /// @brief Constructor
+ ///
+ /// Zeroes the union and stores the CalloutPtr pointer we wish to convert.
+ /// This constructor is used in debug messages; output of a pointer to
+ /// an object (including to a function) is, on some compilers, printed as
+ /// "1".
+ ///
+ /// @param callout_ptr Pointer to callout function
+ PointerConverter(CalloutPtr callout_ptr) {
+ memset(&pointers_, 0, sizeof(pointers_));
+ pointers_.callout_ptr = callout_ptr;
+ }
+
+ /// @name Pointer accessor functions
+ ///
+ /// It is up to the caller to ensure that the correct member is called so
+ /// that the correct type of pointer is returned.
+ ///
+ ///@{
+
+ /// @brief Return pointer returned by dlsym call
+ ///
+ /// @return void* pointer returned by the call to dlsym(). This can be
+ /// used in statements that print the hexadecimal value of the
+ /// symbol.
+ void* dlsymPtr() const {
+ return (pointers_.dlsym_ptr);
+ }
+
+ /// @brief Return pointer to callout function
+ ///
+ /// @return Pointer to the callout function
+ CalloutPtr calloutPtr() const {
+ return (pointers_.callout_ptr);
+ }
+
+ /// @brief Return pointer to load function
+ ///
+ /// @return Pointer to the load function
+ load_function_ptr loadPtr() const {
+ return (pointers_.load_ptr);
+ }
+
+ /// @brief Return pointer to unload function
+ ///
+ /// @return Pointer to the unload function
+ unload_function_ptr unloadPtr() const {
+ return (pointers_.unload_ptr);
+ }
+
+ /// @brief Return pointer to version function
+ ///
+ /// @return Pointer to the version function
+ version_function_ptr versionPtr() const {
+ return (pointers_.version_ptr);
+ }
+
+ ///@}
+
+private:
+
+ /// @brief Union linking void* and pointers to functions.
+ union {
+ void* dlsym_ptr; // void* returned by dlsym
+ CalloutPtr callout_ptr; // Pointer to callout
+ load_function_ptr load_ptr; // Pointer to load function
+ unload_function_ptr unload_ptr; // Pointer to unload function
+ version_function_ptr version_ptr; // Pointer to version function
+ } pointers_;
+};
+
+} // namespace hooks
+} // namespace isc
+
+
+#endif // POINTER_CONVERTER_H
diff --git a/src/lib/hooks/server_hooks.cc b/src/lib/hooks/server_hooks.cc
new file mode 100644
index 0000000..3057d25
--- /dev/null
+++ b/src/lib/hooks/server_hooks.cc
@@ -0,0 +1,159 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+#include <hooks/hooks_log.h>
+#include <hooks/server_hooks.h>
+
+#include <utility>
+#include <vector>
+
+using namespace std;
+using namespace isc;
+
+namespace isc {
+namespace hooks {
+
+// Constructor - register the pre-defined hooks and check that the indexes
+// assigned to them are as expected.
+//
+// Note that there are no logging messages here or in registerHooks(). The
+// recommended way to initialize hook names is to use static initialization.
+// Here, a static object is declared in a file outside of any function or
+// method. As a result, it is instantiated and its constructor run before the
+// program starts. By putting calls to ServerHooks::registerHook() in there,
+// hooks names are already registered when the program runs. However, at that
+// point, the logging system is not initialized, so messages are unable to
+// be output.
+
+ServerHooks::ServerHooks() {
+ initialize();
+}
+
+// Register a hook. The index assigned to the hook is the current number
+// of entries in the collection, so ensuring that hook indexes are unique
+// and non-negative.
+
+int
+ServerHooks::registerHook(const string& name) {
+
+ // Determine index for the new element and insert.
+ int index = hooks_.size();
+ pair<HookCollection::iterator, bool> result =
+ hooks_.insert(make_pair(name, index));
+
+ if (!result.second) {
+ // New element was not inserted because an element with the same name
+ // already existed.
+ isc_throw(DuplicateHook, "hook with name " << name <<
+ " is already registered");
+ }
+
+ // Element was inserted, so add to the inverse hooks collection.
+ inverse_hooks_[index] = name;
+
+ // ... and return numeric index.
+ return (index);
+}
+
+// Set ServerHooks object to initial state.
+
+void
+ServerHooks::initialize() {
+
+ // Clear out the name->index and index->name maps.
+ hooks_.clear();
+ inverse_hooks_.clear();
+
+ // Register the pre-defined hooks.
+ int create = registerHook("context_create");
+ int destroy = registerHook("context_destroy");
+
+ // Check registration went as expected.
+ if ((create != CONTEXT_CREATE) || (destroy != CONTEXT_DESTROY)) {
+ isc_throw(Unexpected, "pre-defined hook indexes are not as expected. "
+ "context_create: expected = " << CONTEXT_CREATE <<
+ ", actual = " << create <<
+ ". context_destroy: expected = " << CONTEXT_DESTROY <<
+ ", actual = " << destroy);
+ }
+}
+
+// Reset ServerHooks object to initial state.
+
+void
+ServerHooks::reset() {
+
+ // Clear all hooks then initialize the pre-defined ones.
+ initialize();
+
+ // Log a warning - although this is done during testing, it should never be
+ // seen in a production system.
+ LOG_WARN(hooks_logger, HOOKS_HOOK_LIST_RESET);
+}
+
+// Find the name associated with a hook index.
+
+std::string
+ServerHooks::getName(int index) const {
+
+ // Get iterator to matching element.
+ InverseHookCollection::const_iterator i = inverse_hooks_.find(index);
+ if (i == inverse_hooks_.end()) {
+ isc_throw(NoSuchHook, "hook index " << index << " is not recognised");
+ }
+
+ return (i->second);
+}
+
+// Find the index associated with a hook name.
+
+int
+ServerHooks::getIndex(const string& name) const {
+
+ // Get iterator to matching element.
+ HookCollection::const_iterator i = hooks_.find(name);
+ if (i == hooks_.end()) {
+ isc_throw(NoSuchHook, "hook name " << name << " is not recognised");
+ }
+
+ return (i->second);
+}
+
+// Return vector of hook names. The names are not sorted - it is up to the
+// caller to perform sorting if required.
+
+vector<string>
+ServerHooks::getHookNames() const {
+
+ vector<string> names;
+ HookCollection::const_iterator i;
+ for (i = hooks_.begin(); i != hooks_.end(); ++i) {
+ names.push_back(i->first);
+ }
+
+ return (names);
+}
+
+// Return global ServerHooks object
+
+ServerHooks&
+ServerHooks::getServerHooks() {
+ static ServerHooks hooks;
+ return (hooks);
+}
+
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/hooks/server_hooks.h b/src/lib/hooks/server_hooks.h
new file mode 100644
index 0000000..c4a7ae8
--- /dev/null
+++ b/src/lib/hooks/server_hooks.h
@@ -0,0 +1,183 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 SERVER_HOOKS_H
+#define SERVER_HOOKS_H
+
+#include <exceptions/exceptions.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace hooks {
+
+/// @brief Duplicate hook
+///
+/// Thrown if an attempt is made to register a hook with the same name as a
+/// previously-registered hook.
+class DuplicateHook : public Exception {
+public:
+ DuplicateHook(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief Invalid hook
+///
+/// Thrown if an attempt is made to get the index for an invalid hook.
+class NoSuchHook : public Exception {
+public:
+ NoSuchHook(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+
+/// @brief Server hook collection
+///
+/// This class is used by the server-side code to register hooks - points in the
+/// server processing at which libraries can register functions (callouts) that
+/// the server will call. These functions can modify data and so affect the
+/// processing of the server.
+///
+/// The ServerHooks class is little more than a wrapper around the std::map
+/// class. It stores a hook, assigning to it a unique index number. This
+/// number is then used by the server code to identify the hook being called.
+/// (Although it would be feasible to use a name as an index, using an integer
+/// will speed up the time taken to locate the callouts, which may make a
+/// difference in a frequently-executed piece of code.)
+///
+/// ServerHooks is a singleton object and is only accessible by the static
+/// method getServerHooks().
+
+class ServerHooks : public boost::noncopyable {
+public:
+
+ /// Index numbers for pre-defined hooks.
+ static const int CONTEXT_CREATE = 0;
+ static const int CONTEXT_DESTROY = 1;
+
+ /// @brief Reset to Initial State
+ ///
+ /// Resets the collection of hooks to the initial state, with just the
+ /// context_create and context_destroy hooks set. This used during
+ /// testing to reset the global ServerHooks object; it should never be
+ /// used in production.
+ ///
+ /// @throws isc::Unexpected if the registration of the pre-defined hooks
+ /// fails in some way.
+ void reset();
+
+ /// @brief Register a hook
+ ///
+ /// Registers a hook and returns the hook index.
+ ///
+ /// @param name Name of the hook
+ ///
+ /// @return Index of the hook, to be used in subsequent hook-related calls.
+ /// This will be greater than or equal to zero (so allowing a
+ /// negative value to indicate an invalid index).
+ ///
+ /// @throws DuplicateHook A hook with the same name has already been
+ /// registered.
+ int registerHook(const std::string& name);
+
+ /// @brief Get hook name
+ ///
+ /// Returns the name of a hook given the index. This is most likely to be
+ /// used in log messages.
+ ///
+ /// @param index Index of the hoold
+ ///
+ /// @return Name of the hook.
+ ///
+ /// @throw NoSuchHook if the hook index is invalid.
+ std::string getName(int index) const;
+
+ /// @brief Get hook index
+ ///
+ /// Returns the index of a hook.
+ ///
+ /// @param name Name of the hook
+ ///
+ /// @return Index of the hook, to be used in subsequent calls.
+ ///
+ /// @throw NoSuchHook if the hook name is unknown to the caller.
+ int getIndex(const std::string& name) const;
+
+ /// @brief Return number of hooks
+ ///
+ /// Returns the total number of hooks registered.
+ ///
+ /// @return Number of hooks registered.
+ int getCount() const {
+ return (hooks_.size());
+ }
+
+ /// @brief Get hook names
+ ///
+ /// Return list of hooks registered in the object.
+ ///
+ /// @return Vector of strings holding hook names.
+ std::vector<std::string> getHookNames() const;
+
+ /// @brief Return ServerHooks object
+ ///
+ /// Returns the global ServerHooks object.
+ ///
+ /// @return Reference to the global ServerHooks object.
+ static ServerHooks& getServerHooks();
+
+private:
+ /// @brief Constructor
+ ///
+ /// This pre-registers two hooks, context_create and context_destroy, which
+ /// are called by the server before processing a packet and after processing
+ /// for the packet has completed. They allow the server code to allocate
+ /// and destroy per-packet context.
+ ///
+ /// The constructor is declared private to enforce the singleton nature of
+ /// the object. A reference to the singleton is obtainable through the
+ /// getServerHooks() static method.
+ ///
+ /// @throws isc::Unexpected if the registration of the pre-defined hooks
+ /// fails in some way.
+ ServerHooks();
+
+ /// @brief Initialize hooks
+ ///
+ /// Sets the collection of hooks to the initial state, with just the
+ /// context_create and context_destroy hooks set. This is used during
+ /// construction.
+ ///
+ /// @throws isc::Unexpected if the registration of the pre-defined hooks
+ /// fails in some way.
+ void initialize();
+
+ /// Useful typedefs.
+ typedef std::map<std::string, int> HookCollection;
+ typedef std::map<int, std::string> InverseHookCollection;
+
+ /// Two maps, one for name->index, the other for index->name. (This is
+ /// simpler than using a multi-indexed container.)
+ HookCollection hooks_; ///< Hook name/index collection
+ InverseHookCollection inverse_hooks_; ///< Hook index/name collection
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // SERVER_HOOKS_H
diff --git a/src/lib/hooks/tests/.gitignore b/src/lib/hooks/tests/.gitignore
new file mode 100644
index 0000000..6fa0ec3
--- /dev/null
+++ b/src/lib/hooks/tests/.gitignore
@@ -0,0 +1,4 @@
+/marker_file.h
+/test_libraries.h
+
+/run_unittests
diff --git a/src/lib/hooks/tests/Makefile.am b/src/lib/hooks/tests/Makefile.am
new file mode 100644
index 0000000..37fe238
--- /dev/null
+++ b/src/lib/hooks/tests/Makefile.am
@@ -0,0 +1,104 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS = $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_CLANGPP
+# see ../Makefile.am
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
+
+# Files to clean include the file created by testing.
+CLEANFILES = *.gcno *.gcda $(builddir)/marker_file.dat
+
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+# Build shared libraries for testing.
+lib_LTLIBRARIES = libnvl.la libivl.la libfxl.la libbcl.la liblcl.la liblecl.la \
+ libucl.la libfcl.la
+
+# No version function
+libnvl_la_SOURCES = no_version_library.cc
+libnvl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libnvl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# Incorrect version function
+libivl_la_SOURCES = incorrect_version_library.cc
+libivl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libivl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# All framework functions throw an exception
+libfxl_la_SOURCES = framework_exception_library.cc
+libfxl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libfxl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# The basic callout library - contains standard callouts
+libbcl_la_SOURCES = basic_callout_library.cc
+libbcl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libbcl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# The load callout library - contains a load function
+liblcl_la_SOURCES = load_callout_library.cc
+liblcl_la_CXXFLAGS = $(AM_CXXFLAGS)
+liblcl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# The load error callout library - contains a load function that returns
+# an error.
+liblecl_la_SOURCES = load_error_callout_library.cc
+liblecl_la_CXXFLAGS = $(AM_CXXFLAGS)
+liblecl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# The unload callout library - contains an unload function that
+# creates a marker file.
+libucl_la_SOURCES = unload_callout_library.cc
+libucl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libucl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+# The full callout library - contains all three framework functions.
+libfcl_la_SOURCES = full_callout_library.cc
+libfcl_la_CXXFLAGS = $(AM_CXXFLAGS)
+libfcl_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += callout_handle_unittest.cc
+run_unittests_SOURCES += callout_manager_unittest.cc
+run_unittests_SOURCES += common_test_class.h
+run_unittests_SOURCES += handles_unittest.cc
+run_unittests_SOURCES += hooks_manager_unittest.cc
+run_unittests_SOURCES += library_manager_collection_unittest.cc
+run_unittests_SOURCES += library_manager_unittest.cc
+run_unittests_SOURCES += server_hooks_unittest.cc
+
+nodist_run_unittests_SOURCES = marker_file.h
+nodist_run_unittests_SOURCES += test_libraries.h
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+if USE_STATIC_LINK
+# If specified, only link unit tests static - the test libraries must be
+# build as shared libraries.
+run_unittests_LDFLAGS += -static
+endif
+
+run_unittests_LDADD = $(AM_LDADD) $(GTEST_LDADD)
+
+run_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+endif
+
+noinst_PROGRAMS = $(TESTS)
+
+EXTRA_DIST = marker_file.h.in test_libraries.h.in
diff --git a/src/lib/hooks/tests/basic_callout_library.cc b/src/lib/hooks/tests/basic_callout_library.cc
new file mode 100644
index 0000000..253de80
--- /dev/null
+++ b/src/lib/hooks/tests/basic_callout_library.cc
@@ -0,0 +1,115 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Basic callout library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - Only the "version" framework function is supplied.
+///
+/// - A context_create callout is supplied.
+///
+/// - Three "standard" callouts are supplied corresponding to the hooks
+/// "hookpt_one", "hookpt_two", "hookpt_three". All do some trivial calculations
+/// on the arguments supplied to it and the context variables, returning
+/// intermediate results through the "result" argument. The result of
+/// executing all four callouts in order is:
+///
+/// @f[ (10 + data_1) * data_2 - data_3 @f]
+///
+/// ...where data_1, data_2 and data_3 are the values passed in arguments of
+/// the same name to the three callouts (data_1 passed to hookpt_one, data_2 to
+/// hookpt_two etc.) and the result is returned in the argument "result".
+
+#include <hooks/hooks.h>
+#include <fstream>
+
+using namespace isc::hooks;
+using namespace std;
+
+extern "C" {
+
+// Callouts. All return their result through the "result" argument.
+
+int
+context_create(CalloutHandle& handle) {
+ handle.setContext("result", static_cast<int>(10));
+ handle.setArgument("result", static_cast<int>(10));
+ return (0);
+}
+
+// First callout adds the passed "data_1" argument to the initialized context
+// value of 10. (Note that the value set by context_create is accessed through
+// context and not the argument, so checking that context is correctly passed
+// between callouts in the same library.)
+
+int
+hookpt_one(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_1", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result += data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Second callout multiplies the current context value by the "data_2"
+// argument.
+
+int
+hookpt_two(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_2", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Final callout subtracts the result in "data_3".
+
+int
+hookpt_three(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_3", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result -= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Framework functions. Only version() is supplied here.
+
+int
+version() {
+ return (BIND10_HOOKS_VERSION);
+}
+
+};
+
diff --git a/src/lib/hooks/tests/callout_handle_unittest.cc b/src/lib/hooks/tests/callout_handle_unittest.cc
new file mode 100644
index 0000000..b24a4cf
--- /dev/null
+++ b/src/lib/hooks/tests/callout_handle_unittest.cc
@@ -0,0 +1,329 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+/// @file
+/// @brief Holds the CalloutHandle argument tests
+///
+/// Additional testing of the CalloutHandle - together with the interaction
+/// of the LibraryHandle - is done in the handles_unittests set of tests.
+
+class CalloutHandleTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor
+ ///
+ /// Sets up a callout manager to be referenced by the CalloutHandle in
+ /// these tests. (The "4" for the number of libraries in the
+ /// CalloutManager is arbitrary - it is not used in these tests.)
+ CalloutHandleTest() : manager_(new CalloutManager(4))
+ {}
+
+ /// Obtain hook manager
+ boost::shared_ptr<CalloutManager>& getCalloutManager() {
+ return (manager_);
+ }
+
+private:
+ /// Callout manager accessed by this CalloutHandle.
+ boost::shared_ptr<CalloutManager> manager_;
+};
+
+// *** Argument Tests ***
+//
+// The first set of tests check that the CalloutHandle can store and retrieve
+// arguments. These are very similar to the LibraryHandle context tests.
+
+// Test that we can store multiple values of the same type and that they
+// are distinct.
+
+TEST_F(CalloutHandleTest, ArgumentDistinctSimpleType) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Store and retrieve an int (random value).
+ int a = 42;
+ handle.setArgument("integer1", a);
+ EXPECT_EQ(42, a);
+
+ int b = 0;
+ handle.getArgument("integer1", b);
+ EXPECT_EQ(42, b);
+
+ // Add another integer (another random value).
+ int c = 142;
+ handle.setArgument("integer2", c);
+ EXPECT_EQ(142, c);
+
+ int d = 0;
+ handle.getArgument("integer2", d);
+ EXPECT_EQ(142, d);
+
+ // Add a short (random value).
+ short e = -81;
+ handle.setArgument("short", e);
+ EXPECT_EQ(-81, e);
+
+ short f = 0;
+ handle.getArgument("short", f);
+ EXPECT_EQ(-81, f);
+}
+
+// Test that trying to get an unknown argument throws an exception.
+
+TEST_F(CalloutHandleTest, ArgumentUnknownName) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Set an integer
+ int a = 42;
+ handle.setArgument("integer1", a);
+ EXPECT_EQ(42, a);
+
+ // Check we can retrieve it
+ int b = 0;
+ handle.getArgument("integer1", b);
+ EXPECT_EQ(42, b);
+
+ // Check that getting an unknown name throws an exception.
+ int c = 0;
+ EXPECT_THROW(handle.getArgument("unknown", c), NoSuchArgument);
+}
+
+// Test that trying to get an argument with an incorrect type throws an
+// exception.
+
+TEST_F(CalloutHandleTest, ArgumentIncorrectType) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Set an integer
+ int a = 42;
+ handle.setArgument("integer1", a);
+ EXPECT_EQ(42, a);
+
+ // Check we can retrieve it
+ long b = 0;
+ EXPECT_THROW(handle.getArgument("integer1", b), boost::bad_any_cast);
+}
+
+// Now try with some very complex types. The types cannot be defined within
+// the function and they should contain a copy constructor. For this reason,
+// a simple "struct" is used.
+
+struct Alpha {
+ int a;
+ int b;
+ Alpha(int first = 0, int second = 0) : a(first), b(second) {}
+};
+
+struct Beta {
+ int c;
+ int d;
+ Beta(int first = 0, int second = 0) : c(first), d(second) {}
+};
+
+TEST_F(CalloutHandleTest, ComplexTypes) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Declare two variables of different (complex) types. (Note as to the
+ // variable names: aleph and beth are the first two letters of the Hebrew
+ // alphabet.)
+ Alpha aleph(1, 2);
+ EXPECT_EQ(1, aleph.a);
+ EXPECT_EQ(2, aleph.b);
+ handle.setArgument("aleph", aleph);
+
+ Beta beth(11, 22);
+ EXPECT_EQ(11, beth.c);
+ EXPECT_EQ(22, beth.d);
+ handle.setArgument("beth", beth);
+
+ // Ensure we can extract the data correctly.
+ Alpha aleph2;
+ EXPECT_EQ(0, aleph2.a);
+ EXPECT_EQ(0, aleph2.b);
+ handle.getArgument("aleph", aleph2);
+ EXPECT_EQ(1, aleph2.a);
+ EXPECT_EQ(2, aleph2.b);
+
+ Beta beth2;
+ EXPECT_EQ(0, beth2.c);
+ EXPECT_EQ(0, beth2.d);
+ handle.getArgument("beth", beth2);
+ EXPECT_EQ(11, beth2.c);
+ EXPECT_EQ(22, beth2.d);
+
+ // Ensure that complex types also thrown an exception if we attempt to
+ // get a context element of the wrong type.
+ EXPECT_THROW(handle.getArgument("aleph", beth), boost::bad_any_cast);
+}
+
+// Check that the context can store pointers. And also check that it respects
+// that a "pointer to X" is not the same as a "pointer to const X".
+
+TEST_F(CalloutHandleTest, PointerTypes) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Declare a couple of variables, const and non-const.
+ Alpha aleph(5, 10);
+ const Beta beth(15, 20);
+
+ Alpha* pa = ℵ
+ const Beta* pcb = ℶ
+
+ // Check pointers can be set and retrieved OK.
+ handle.setArgument("non_const_pointer", pa);
+ handle.setArgument("const_pointer", pcb);
+
+ Alpha* pa2 = 0;
+ handle.getArgument("non_const_pointer", pa2);
+ EXPECT_TRUE(pa == pa2);
+
+ const Beta* pcb2 = 0;
+ handle.getArgument("const_pointer", pcb2);
+ EXPECT_TRUE(pcb == pcb2);
+
+ // Check that the "const" is protected in the context.
+ const Alpha* pca3;
+ EXPECT_THROW(handle.getArgument("non_const_pointer", pca3),
+ boost::bad_any_cast);
+
+ Beta* pb3;
+ EXPECT_THROW(handle.getArgument("const_pointer", pb3),
+ boost::bad_any_cast);
+}
+
+// Check that we can get the names of the arguments.
+
+TEST_F(CalloutHandleTest, ContextItemNames) {
+ CalloutHandle handle(getCalloutManager());
+
+ vector<string> expected_names;
+
+ expected_names.push_back("faith");
+ handle.setArgument("faith", 42);
+ expected_names.push_back("hope");
+ handle.setArgument("hope", 43);
+ expected_names.push_back("charity");
+ handle.setArgument("charity", 44);
+
+ // Get the names and check against the expected names. We'll sort
+ // both arrays to simplify the checking.
+ vector<string> actual_names = handle.getArgumentNames();
+
+ sort(actual_names.begin(), actual_names.end());
+ sort(expected_names.begin(), expected_names.end());
+ EXPECT_TRUE(expected_names == actual_names);
+}
+
+// Test that we can delete an argument.
+
+TEST_F(CalloutHandleTest, DeleteArgument) {
+ CalloutHandle handle(getCalloutManager());
+
+ int one = 1;
+ int two = 2;
+ int three = 3;
+ int four = 4;
+ int value; // Return value
+
+ handle.setArgument("one", one);
+ handle.setArgument("two", two);
+ handle.setArgument("three", three);
+ handle.setArgument("four", four);
+
+ // Delete "one".
+ handle.getArgument("one", value);
+ EXPECT_EQ(1, value);
+ handle.deleteArgument("one");
+
+ EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument);
+ handle.getArgument("two", value);
+ EXPECT_EQ(2, value);
+ handle.getArgument("three", value);
+ EXPECT_EQ(3, value);
+ handle.getArgument("four", value);
+ EXPECT_EQ(4, value);
+
+ // Delete "three".
+ handle.getArgument("three", value);
+ EXPECT_EQ(3, value);
+ handle.deleteArgument("three");
+
+ EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument);
+ handle.getArgument("two", value);
+ EXPECT_EQ(2, value);
+ EXPECT_THROW(handle.getArgument("three", value), NoSuchArgument);
+ handle.getArgument("four", value);
+ EXPECT_EQ(4, value);
+}
+
+// Test that we can delete all arguments.
+
+TEST_F(CalloutHandleTest, DeleteAllArguments) {
+ CalloutHandle handle(getCalloutManager());
+
+ int one = 1;
+ int two = 2;
+ int three = 3;
+ int four = 4;
+ int value; // Return value
+
+ // Set the arguments. The previous test verifies that this works.
+ handle.setArgument("one", one);
+ handle.setArgument("two", two);
+ handle.setArgument("three", three);
+ handle.setArgument("four", four);
+
+ // Delete all arguments...
+ handle.deleteAllArguments();
+
+ // ... and check that none are left.
+ EXPECT_THROW(handle.getArgument("one", value), NoSuchArgument);
+ EXPECT_THROW(handle.getArgument("two", value), NoSuchArgument);
+ EXPECT_THROW(handle.getArgument("three", value), NoSuchArgument);
+ EXPECT_THROW(handle.getArgument("four", value), NoSuchArgument);
+}
+
+// Test the "skip" flag.
+
+TEST_F(CalloutHandleTest, SkipFlag) {
+ CalloutHandle handle(getCalloutManager());
+
+ // Should be false on construction.
+ EXPECT_FALSE(handle.getSkip());
+
+ handle.setSkip(true);
+ EXPECT_TRUE(handle.getSkip());
+
+ handle.setSkip(false);
+ EXPECT_FALSE(handle.getSkip());
+}
+
+// Further tests of the "skip" flag and tests of getting the name of the
+// hook to which the current callout is attached is in the "handles_unittest"
+// module.
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/callout_manager_unittest.cc b/src/lib/hooks/tests/callout_manager_unittest.cc
new file mode 100644
index 0000000..c3f3b1d
--- /dev/null
+++ b/src/lib/hooks/tests/callout_manager_unittest.cc
@@ -0,0 +1,882 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <climits>
+#include <string>
+#include <vector>
+
+/// @file
+/// @brief CalloutManager and LibraryHandle tests
+///
+/// These set of tests check the CalloutManager and LibraryHandle. They are
+/// together in the same file because the LibraryHandle is little more than a
+/// restricted interface to the CalloutManager, and a lot of the support
+/// structure for the tests is common.
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+class CalloutManagerTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ /// Sets up a collection of three LibraryHandle objects to use in the test.
+ CalloutManagerTest() {
+
+ // Set up the server hooks. There is sone singleton for all tests,
+ // so reset it and explicitly set up the hooks for the test.
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+ alpha_index_ = hooks.registerHook("alpha");
+ beta_index_ = hooks.registerHook("beta");
+ gamma_index_ = hooks.registerHook("gamma");
+ delta_index_ = hooks.registerHook("delta");
+
+ // Set up the callout manager with these hooks. Assume a maximum of
+ // four libraries.
+ callout_manager_.reset(new CalloutManager(10));
+
+ // Set up the callout handle.
+ callout_handle_.reset(new CalloutHandle(callout_manager_));
+
+ // Initialize the static variable.
+ callout_value_ = 0;
+ }
+
+ /// @brief Return the callout handle
+ CalloutHandle& getCalloutHandle() {
+ return (*callout_handle_);
+ }
+
+ /// @brief Return the callout manager
+ boost::shared_ptr<CalloutManager> getCalloutManager() {
+ return (callout_manager_);
+ }
+
+ /// Static variable used for accumulating information
+ static int callout_value_;
+
+ /// Hook indexes. These are somewhat ubiquitous, so are made public for
+ /// ease of reference instead of being accessible by a function.
+ int alpha_index_;
+ int beta_index_;
+ int gamma_index_;
+ int delta_index_;
+
+private:
+ /// Callout handle used in calls
+ boost::shared_ptr<CalloutHandle> callout_handle_;
+
+ /// Callout manager used for the test
+ boost::shared_ptr<CalloutManager> callout_manager_;
+};
+
+// Definition of the static variable.
+int CalloutManagerTest::callout_value_ = 0;
+
+// Callout definitions
+//
+// The callouts defined here are structured in such a way that it is possible
+// to determine the order in which they are called and whether they are called
+// at all. The method used is simple - after a sequence of callouts, the digits
+// in the value, reading left to right, determines the order of the callouts
+// called. For example, callout one followed by two followed by three followed
+// by two followed by one results in a value of 12321.
+//
+// Functions return a zero to indicate success.
+
+extern "C" {
+int callout_general(int number) {
+ CalloutManagerTest::callout_value_ =
+ 10 * CalloutManagerTest::callout_value_ + number;
+ return (0);
+}
+
+int callout_one(CalloutHandle&) {
+ return (callout_general(1));
+}
+
+int callout_two(CalloutHandle&) {
+ return (callout_general(2));
+}
+
+int callout_three(CalloutHandle&) {
+ return (callout_general(3));
+}
+
+int callout_four(CalloutHandle&) {
+ return (callout_general(4));
+}
+
+int callout_five(CalloutHandle&) {
+ return (callout_general(5));
+}
+
+int callout_six(CalloutHandle&) {
+ return (callout_general(6));
+}
+
+int callout_seven(CalloutHandle&) {
+ return (callout_general(7));
+}
+
+// The next functions are duplicates of some of the above, but return an error.
+
+int callout_one_error(CalloutHandle& handle) {
+ (void) callout_one(handle);
+ return (1);
+}
+
+int callout_two_error(CalloutHandle& handle) {
+ (void) callout_two(handle);
+ return (1);
+}
+
+int callout_three_error(CalloutHandle& handle) {
+ (void) callout_three(handle);
+ return (1);
+}
+
+int callout_four_error(CalloutHandle& handle) {
+ (void) callout_four(handle);
+ return (1);
+}
+
+}; // extern "C"
+
+// *** Callout Tests ***
+//
+// The next set of tests check that callouts can be called.
+
+// Constructor - check that we trap bad parameters.
+
+TEST_F(CalloutManagerTest, BadConstructorParameters) {
+ boost::scoped_ptr<CalloutManager> cm;
+
+ // Invalid number of libraries
+ EXPECT_THROW(cm.reset(new CalloutManager(-1)), BadValue);
+}
+
+// Check the number of libraries is reported successfully.
+
+TEST_F(CalloutManagerTest, NumberOfLibraries) {
+ boost::scoped_ptr<CalloutManager> cm;
+
+ // Check two valid values of number of libraries to ensure that the
+ // GetNumLibraries() returns the value set.
+ EXPECT_NO_THROW(cm.reset(new CalloutManager()));
+ EXPECT_EQ(0, cm->getNumLibraries());
+
+ EXPECT_NO_THROW(cm.reset(new CalloutManager(0)));
+ EXPECT_EQ(0, cm->getNumLibraries());
+
+ EXPECT_NO_THROW(cm.reset(new CalloutManager(4)));
+ EXPECT_EQ(4, cm->getNumLibraries());
+
+ EXPECT_NO_THROW(cm.reset(new CalloutManager(42)));
+ EXPECT_EQ(42, cm->getNumLibraries());
+}
+
+// Check that we can only set the current library index to the correct values.
+
+TEST_F(CalloutManagerTest, CheckLibraryIndex) {
+ // Check valid indexes. As the callout manager is sized for 10 libraries,
+ // we expect:
+ //
+ // -1 to be valid as it is the standard "invalid" value.
+ // 0 to be valid for the pre-user library callouts
+ // 1-10 to be valid for the user-library callouts
+ // INT_MAX to be valid for the post-user library callouts
+ //
+ // All other values to be invalid.
+ for (int i = -1; i < 11; ++i) {
+ EXPECT_NO_THROW(getCalloutManager()->setLibraryIndex(i));
+ EXPECT_EQ(i, getCalloutManager()->getLibraryIndex());
+ }
+ EXPECT_NO_THROW(getCalloutManager()->setLibraryIndex(INT_MAX));
+ EXPECT_EQ(INT_MAX, getCalloutManager()->getLibraryIndex());
+
+ // Check invalid ones
+ EXPECT_THROW(getCalloutManager()->setLibraryIndex(-2), NoSuchLibrary);
+ EXPECT_THROW(getCalloutManager()->setLibraryIndex(11), NoSuchLibrary);
+}
+
+// Check that we can only register callouts on valid hook names.
+
+TEST_F(CalloutManagerTest, ValidHookNames) {
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_NO_THROW(getCalloutManager()->registerCallout("alpha", callout_one));
+ EXPECT_THROW(getCalloutManager()->registerCallout("unknown", callout_one),
+ NoSuchHook);
+}
+
+
+// Check we can register callouts appropriately.
+
+TEST_F(CalloutManagerTest, RegisterCallout) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+
+ // Set up so that hooks "alpha" and "beta" have callouts attached from a
+ // different libraries.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("beta", callout_two);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected. (This is also a
+ // test of the callCallouts method.)
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1, callout_value_);
+
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(2, callout_value_);
+
+ // Register some more callouts from different libraries on hook "alpha".
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("alpha", callout_five);
+
+ // Check it is as expected.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1345, callout_value_);
+
+ // ... and check the additional callouts were not registered on the "beta"
+ // hook.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(2, callout_value_);
+
+ // Add another callout to hook "alpha" from library index 2 - this should
+ // appear at the end of the callout list for that library.
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_six);
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(13465, callout_value_);
+
+ // Add a callout from library index 1 - this should appear between the
+ // callouts from library index 0 and linrary index 2.
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_seven);
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(173465, callout_value_);
+}
+
+// Check the "calloutsPresent()" method.
+
+TEST_F(CalloutManagerTest, CalloutsPresent) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Set up so that hooks "alpha", "beta" and "delta" have callouts attached
+ // to them, and callout "gamma" does not. (In the statements below, the
+ // exact callouts attached to a hook are not relevant - only the fact
+ // that some callouts are). Chose the libraries for which the callouts
+ // are registered randomly.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->registerCallout("beta", callout_two);
+
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("delta", callout_four);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check we fail on an invalid hook index.
+ EXPECT_THROW(getCalloutManager()->calloutsPresent(42), NoSuchHook);
+ EXPECT_THROW(getCalloutManager()->calloutsPresent(-1), NoSuchHook);
+}
+
+// Test that calling a hook with no callouts on it returns success.
+
+TEST_F(CalloutManagerTest, CallNoCallouts) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Call the callouts on an arbitrary hook and ensure that nothing happens.
+ callout_value_ = 475;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(475, callout_value_); // Unchanged
+}
+
+// Test that the callouts are called in the correct order (i.e. the callouts
+// from the first library in the order they were registered, then the callouts
+// from the second library in the order they were registered etc.)
+
+TEST_F(CalloutManagerTest, CallCalloutsSuccess) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributes one callout on hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Do a random selection of callouts on hook "beta".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("beta", callout_one);
+ getCalloutManager()->registerCallout("beta", callout_three);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("beta", callout_two);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("beta", callout_four);
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(1324, callout_value_);
+
+ // Ensure that calling the callouts on a hook with no callouts works.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(gamma_index_, getCalloutHandle());
+ EXPECT_EQ(0, callout_value_);
+}
+
+// Test that the callouts are called in order, but that callouts occurring
+// after a callout that returns an error are not called.
+//
+// (Note: in this test, the callouts that return an error set the value of
+// callout_value_ before they return the error code.)
+
+TEST_F(CalloutManagerTest, CallCalloutsError) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributing one callout on hook "alpha". The first callout
+ // returns an error (after adding its value to the result).
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one_error);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Each library contributing multiple callouts on hook "beta". The last
+ // callout on the first library returns an error.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("beta", callout_one);
+ getCalloutManager()->registerCallout("beta", callout_one_error);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("beta", callout_two);
+ getCalloutManager()->registerCallout("beta", callout_two);
+ getCalloutManager()->registerCallout("beta", callout_three);
+ getCalloutManager()->registerCallout("beta", callout_three);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("beta", callout_four);
+ getCalloutManager()->registerCallout("beta", callout_four);
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(11223344, callout_value_);
+
+ // A callout in a random position in the callout list returns an error.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("gamma", callout_one);
+ getCalloutManager()->registerCallout("gamma", callout_one);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("gamma", callout_two);
+ getCalloutManager()->registerCallout("gamma", callout_two);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("gamma", callout_four_error);
+ getCalloutManager()->registerCallout("gamma", callout_four);
+ getCalloutManager()->callCallouts(gamma_index_, getCalloutHandle());
+ EXPECT_EQ(112244, callout_value_);
+
+ // The last callout on a hook returns an error.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("delta", callout_one);
+ getCalloutManager()->registerCallout("delta", callout_one);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("delta", callout_two);
+ getCalloutManager()->registerCallout("delta", callout_two);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("delta", callout_three);
+ getCalloutManager()->registerCallout("delta", callout_three);
+ getCalloutManager()->setLibraryIndex(3);
+ getCalloutManager()->registerCallout("delta", callout_four);
+ getCalloutManager()->registerCallout("delta", callout_four_error);
+ getCalloutManager()->callCallouts(delta_index_, getCalloutHandle());
+ EXPECT_EQ(11223344, callout_value_);
+}
+
+// Now test that we can deregister a single callout on a hook.
+
+TEST_F(CalloutManagerTest, DeregisterSingleCallout) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Add a callout to hook "alpha" and check it is added correctly.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(2, callout_value_);
+
+ // Remove it and check that the no callouts are present. We have to reset
+ // the current library index here as it was invalidated by the call
+ // to callCallouts().
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+}
+
+// Now test that we can deregister a single callout on a hook that has multiple
+// callouts from the same library.
+
+TEST_F(CalloutManagerTest, DeregisterSingleCalloutSameLibrary) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Add multiple callouts to hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Remove the callout_two callout. We have to reset the current library
+ // index here as it was invalidated by the call to callCallouts().
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(134, callout_value_);
+
+ // Try removing it again.
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_FALSE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(134, callout_value_);
+
+}
+
+// Check we can deregister multiple callouts from the same library.
+
+TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsSameLibrary) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributes one callout on hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(12123434, callout_value_);
+
+ // Remove the callout_two callouts. We have to reset the current library
+ // index here as it was invalidated by the call to callCallouts().
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(113434, callout_value_);
+
+ // Try removing multiple callouts that includes one at the end of the
+ // list of callouts.
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_four));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1133, callout_value_);
+
+ // ... and from the start.
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_one));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(33, callout_value_);
+
+ // ... and the remaining callouts.
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_three));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(0, callout_value_);
+
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+}
+
+// Check we can deregister multiple callouts from multiple libraries.
+
+TEST_F(CalloutManagerTest, DeregisterMultipleCalloutsMultipleLibraries) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Each library contributes two callouts to hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_five);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+
+ // Remove the callout_two callout from library 0. It should not affect
+ // the second callout_two callout registered by library 2.
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(13452, callout_value_);
+}
+
+// Check we can deregister all callouts from a single library.
+
+TEST_F(CalloutManagerTest, DeregisterAllCallouts) {
+ // Ensure that no callouts are attached to hook one.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+
+ // Each library contributes two callouts to hook "alpha".
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_five);
+ getCalloutManager()->registerCallout("alpha", callout_six);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123456, callout_value_);
+
+ // Remove all callouts from library index 1.
+ getCalloutManager()->setLibraryIndex(1);
+ EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha"));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1256, callout_value_);
+
+ // Remove all callouts from library index 2.
+ getCalloutManager()->setLibraryIndex(2);
+ EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha"));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(12, callout_value_);
+}
+
+// Check that we can register/deregister callouts on different libraries
+// and different hooks, and that the callout instances are regarded as
+// unique and do not affect one another.
+
+TEST_F(CalloutManagerTest, MultipleCalloutsLibrariesHooks) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Register callouts on the alpha hook.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_one);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout_three);
+ getCalloutManager()->registerCallout("alpha", callout_four);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout_five);
+ getCalloutManager()->registerCallout("alpha", callout_two);
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+
+ // Register the same callouts on the beta hook, and check that those
+ // on the alpha hook are not affected.
+ callout_value_ = 0;
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("beta", callout_five);
+ getCalloutManager()->registerCallout("beta", callout_one);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("beta", callout_four);
+ getCalloutManager()->registerCallout("beta", callout_three);
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(5143, callout_value_);
+
+ // Check that the order of callouts on the alpha hook has not been affected.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+
+ // Remove callout four from beta and check that alpha is not affected.
+ getCalloutManager()->setLibraryIndex(2);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("beta", callout_four));
+
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(beta_index_, getCalloutHandle());
+ EXPECT_EQ(513, callout_value_);
+
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(123452, callout_value_);
+}
+
+// Library handle tests. As by inspection the LibraryHandle can be seen to be
+// little more than shell around CalloutManager, only a basic set of tests
+// is done concerning registration and deregistration of functions.
+//
+// More extensive tests (i.e. checking that when a callout is called it can
+// only register and deregister callouts within its library) require that
+// the CalloutHandle object pass the appropriate LibraryHandle to the
+// callout. These tests are done in the handles_unittest tests.
+
+TEST_F(CalloutManagerTest, LibraryHandleRegistration) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+
+ // Set up so that hooks "alpha" and "beta" have callouts attached from a
+ // different libraries.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha",
+ callout_one);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha",
+ callout_two);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha",
+ callout_three);
+ getCalloutManager()->getLibraryHandle().registerCallout("alpha",
+ callout_four);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected. (This is also a
+ // test of the callCallouts method.)
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Deregister a callout on library index 0 (after we check we can't
+ // deregister it through library index 1).
+ getCalloutManager()->setLibraryIndex(1);
+ EXPECT_FALSE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ getCalloutManager()->setLibraryIndex(0);
+ EXPECT_TRUE(getCalloutManager()->deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(134, callout_value_);
+
+ // Deregister all callouts on library index 1.
+ getCalloutManager()->setLibraryIndex(1);
+ EXPECT_TRUE(getCalloutManager()->deregisterAllCallouts("alpha"));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1, callout_value_);
+}
+
+// A repeat of the test above, but using the alternate constructor for the
+// LibraryHandle.
+TEST_F(CalloutManagerTest, LibraryHandleAlternateConstructor) {
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(alpha_index_));
+
+ // Set up so that hooks "alpha" and "beta" have callouts attached from a
+ // different libraries.
+ LibraryHandle lh0(getCalloutManager().get(), 0);
+ lh0.registerCallout("alpha", callout_one);
+ lh0.registerCallout("alpha", callout_two);
+
+ LibraryHandle lh1(getCalloutManager().get(), 1);
+ lh1.registerCallout("alpha", callout_three);
+ lh1.registerCallout("alpha", callout_four);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected. (This is also a
+ // test of the callCallouts method.)
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ // Deregister a callout on library index 0 (after we check we can't
+ // deregister it through library index 1).
+ EXPECT_FALSE(lh1.deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1234, callout_value_);
+
+ EXPECT_TRUE(lh0.deregisterCallout("alpha", callout_two));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(134, callout_value_);
+
+ // Deregister all callouts on library index 1.
+ EXPECT_TRUE(lh1.deregisterAllCallouts("alpha"));
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(1, callout_value_);
+}
+
+// Check that the pre- and post- user callout library handles work
+// appropriately with no user libraries.
+
+TEST_F(CalloutManagerTest, LibraryHandlePrePostNoLibraries) {
+ // Create a local callout manager and callout handle to reflect no libraries
+ // being loaded.
+ boost::shared_ptr<CalloutManager> manager(new CalloutManager(0));
+ CalloutHandle handle(manager);
+
+ // Ensure that no callouts are attached to any of the hooks.
+ EXPECT_FALSE(manager->calloutsPresent(alpha_index_));
+
+ // Setup the pre-and post callouts.
+ manager->getPostLibraryHandle().registerCallout("alpha", callout_four);
+ manager->getPreLibraryHandle().registerCallout("alpha", callout_one);
+ // Check all is as expected.
+ EXPECT_TRUE(manager->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(manager->calloutsPresent(beta_index_));
+ EXPECT_FALSE(manager->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(manager->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected.
+ callout_value_ = 0;
+ manager->callCallouts(alpha_index_, handle);
+ EXPECT_EQ(14, callout_value_);
+
+ // Deregister the pre- library callout.
+ EXPECT_TRUE(manager->getPreLibraryHandle().deregisterAllCallouts("alpha"));
+ callout_value_ = 0;
+ manager->callCallouts(alpha_index_, handle);
+ EXPECT_EQ(4, callout_value_);
+}
+
+// Repeat the tests with one user library.
+
+TEST_F(CalloutManagerTest, LibraryHandlePrePostUserLibrary) {
+
+ // Setup the pre-, library and post callouts.
+ getCalloutManager()->getPostLibraryHandle().registerCallout("alpha",
+ callout_four);
+ getCalloutManager()->getPreLibraryHandle().registerCallout("alpha",
+ callout_one);
+
+ // ... and set up a callout in between, on library number 2.
+ LibraryHandle lh1(getCalloutManager().get(), 2);
+ lh1.registerCallout("alpha", callout_five);
+
+ // Check all is as expected.
+ EXPECT_TRUE(getCalloutManager()->calloutsPresent(alpha_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(beta_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(gamma_index_));
+ EXPECT_FALSE(getCalloutManager()->calloutsPresent(delta_index_));
+
+ // Check that calling the callouts returns as expected.
+ callout_value_ = 0;
+ getCalloutManager()->callCallouts(alpha_index_, getCalloutHandle());
+ EXPECT_EQ(154, callout_value_);
+}
+
+// The setting of the hook index is checked in the handles_unittest
+// set of tests, as access restrictions mean it is not easily tested
+// on its own.
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/common_test_class.h b/src/lib/hooks/tests/common_test_class.h
new file mode 100644
index 0000000..803e25c
--- /dev/null
+++ b/src/lib/hooks/tests/common_test_class.h
@@ -0,0 +1,142 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 COMMON_HOOKS_TEST_CLASS_H
+#define COMMON_HOOKS_TEST_CLASS_H
+
+#include <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/server_hooks.h>
+#include <hooks/tests/marker_file.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+/// @brief Common hooks test class
+///
+/// This class is a shared parent of the test fixture class in the tests of the
+/// higher-level hooks classes (LibraryManager, LibraryManagerCollection and
+/// HooksManager). It
+///
+/// - sets the the ServerHooks object with three hooks and stores their
+/// indexes.
+/// - executes the callouts (which are assumed to perform a calculation)
+/// and checks the results.
+
+class HooksCommonTestClass {
+public:
+ /// @brief Constructor
+ HooksCommonTestClass() {
+
+ // Set up the server hooks. ServerHooks is a singleton, so we reset it
+ // between each test.
+ isc::hooks::ServerHooks& hooks =
+ isc::hooks::ServerHooks::getServerHooks();
+ hooks.reset();
+ hookpt_one_index_ = hooks.registerHook("hookpt_one");
+ hookpt_two_index_ = hooks.registerHook("hookpt_two");
+ hookpt_three_index_ = hooks.registerHook("hookpt_three");
+ }
+
+ /// @brief Call callouts test
+ ///
+ /// All of the loaded libraries for which callouts are called register four
+ /// callouts: a context_create callout and three callouts that are attached
+ /// to hooks hookpt_one, hookpt_two and hookpt_three. These four callouts,
+ /// executed in sequence, perform a series of calculations. Data is passed
+ /// between callouts in the argument list, in a variable named "result".
+ ///
+ /// context_create initializes the calculation by setting a seed
+ /// value, called r0 here. This value is dependent on the library being
+ /// loaded. Prior to that, the argument "result" is initialized to -1,
+ /// the purpose being to avoid exceptions when running this test with no
+ /// libraries loaded.
+ ///
+ /// Callout hookpt_one is passed a value d1 and performs a simple arithmetic
+ /// operation on it and r0 yielding a result r1. Hence we can say that
+ /// @f[ r1 = hookpt_one(r0, d1) @f]
+ ///
+ /// Callout hookpt_two is passed a value d2 and peforms another simple
+ /// arithmetic operation on it and d2, yielding r2, i.e.
+ /// @f[ r2 = hookpt_two(d1, d2) @f]
+ ///
+ /// hookpt_three does a similar operation giving
+ /// @f[ r3 = hookpt_three(r2, d3) @f].
+ ///
+ /// The details of the operations hookpt_one, hookpt_two and hookpt_three
+ /// depend on the library, so the results obtained not only depend on
+ /// the data, but also on the library loaded. This method is passed both
+ /// data and expected results. It executes the three callouts in sequence,
+ /// checking the intermediate and final results. Only if the expected
+ /// library has been loaded correctly and the callouts in it registered
+ /// correctly will be the results be as expected.
+ ///
+ /// It is assumed that callout_manager_ has been set up appropriately.
+ ///
+ /// @note The CalloutHandle used in the calls is declared locally here.
+ /// The advantage of this (apart from scope reduction) is that on
+ /// exit, it is destroyed. This removes any references to memory
+ /// allocated by loaded libraries while they are still loaded.
+ ///
+ /// @param manager CalloutManager to use for the test
+ /// @param r0...r3, d1..d3 Data (dN) and expected results (rN) - both
+ /// intermediate and final. The arguments are ordered so that they
+ /// appear in the argument list in the order they are used.
+ void executeCallCallouts(
+ const boost::shared_ptr<isc::hooks::CalloutManager>& manager,
+ int r0, int d1, int r1, int d2, int r2, int d3, int r3) {
+ static const char* COMMON_TEXT = " callout returned the wong value";
+ static const char* RESULT = "result";
+
+ int result;
+
+ // Set up a callout handle for the calls.
+ isc::hooks::CalloutHandle handle(manager);
+
+ // Initialize the argument RESULT. This simplifies testing by
+ // eliminating the generation of an exception when we try the unload
+ // test. In that case, RESULT is unchanged.
+ handle.setArgument(RESULT, -1);
+
+ // Seed the calculation.
+ manager->callCallouts(isc::hooks::ServerHooks::CONTEXT_CREATE, handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(r0, result) << "context_create" << COMMON_TEXT;
+
+ // Perform the first calculation.
+ handle.setArgument("data_1", d1);
+ manager->callCallouts(hookpt_one_index_, handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(r1, result) << "hookpt_one" << COMMON_TEXT;
+
+ // ... the second ...
+ handle.setArgument("data_2", d2);
+ manager->callCallouts(hookpt_two_index_, handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(r2, result) << "hookpt_two" << COMMON_TEXT;
+
+ // ... and the third.
+ handle.setArgument("data_3", d3);
+ manager->callCallouts(hookpt_three_index_, handle);
+ handle.getArgument(RESULT, result);
+ EXPECT_EQ(r3, result) << "hookpt_three" << COMMON_TEXT;
+ }
+
+ /// Hook indexes. These are are made public for ease of reference.
+ int hookpt_one_index_;
+ int hookpt_two_index_;
+ int hookpt_three_index_;
+};
+
+#endif // COMMON_HOOKS_TEST_CLASS_H
diff --git a/src/lib/hooks/tests/framework_exception_library.cc b/src/lib/hooks/tests/framework_exception_library.cc
new file mode 100644
index 0000000..e90fd36
--- /dev/null
+++ b/src/lib/hooks/tests/framework_exception_library.cc
@@ -0,0 +1,47 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Framework exception library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - All three framework functions are supplied (version(), load() and
+/// unload()) and all generate an exception.
+
+#include <hooks/hooks.h>
+
+#include <exception>
+
+extern "C" {
+
+int
+version() {
+ throw std::exception();
+}
+
+int
+load(isc::hooks::LibraryHandle& handle) {
+ throw std::exception();
+}
+
+int
+unload() {
+ throw std::exception();
+}
+
+};
+
diff --git a/src/lib/hooks/tests/full_callout_library.cc b/src/lib/hooks/tests/full_callout_library.cc
new file mode 100644
index 0000000..33d5660
--- /dev/null
+++ b/src/lib/hooks/tests/full_callout_library.cc
@@ -0,0 +1,137 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Full callout library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// The characteristics of this library are:
+///
+/// - All three framework functions are supplied (version(), load() and
+/// unload()), with unload() creating a marker file. The test code checks
+/// for the presence of this file, so verifying that unload() has been run.
+///
+/// - One standard and two non-standard callouts are supplied, with the latter
+/// being registered by the load() function.
+///
+/// All callouts do trivial calculations, the result of all being called in
+/// sequence being
+///
+/// @f[ ((7 * data_1) - data_2) * data_3 @f]
+///
+/// ...where data_1, data_2 and data_3 are the values passed in arguments of
+/// the same name to the three callouts (data_1 passed to hookpt_one, data_2 to
+/// hookpt_two etc.) and the result is returned in the argument "result".
+
+#include <hooks/hooks.h>
+#include <hooks/tests/marker_file.h>
+
+#include <fstream>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Callouts
+
+int
+context_create(CalloutHandle& handle) {
+ handle.setContext("result", static_cast<int>(7));
+ handle.setArgument("result", static_cast<int>(7));
+ return (0);
+}
+
+// First callout adds the passed "data_1" argument to the initialized context
+// value of 7. (Note that the value set by context_create is accessed through
+// context and not the argument, so checking that context is correctly passed
+// between callouts in the same library.)
+
+int
+hookpt_one(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_1", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Second callout subtracts the passed value of data_2 from the current
+// running total.
+
+static int
+hook_nonstandard_two(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_2", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result -= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Final callout multplies the current running total by data_3.
+
+static int
+hook_nonstandard_three(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_3", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Framework functions
+
+int
+version() {
+ return (BIND10_HOOKS_VERSION);
+}
+
+int
+load(LibraryHandle& handle) {
+ // Register the non-standard functions
+ handle.registerCallout("hookpt_two", hook_nonstandard_two);
+ handle.registerCallout("hookpt_three", hook_nonstandard_three);
+
+ return (0);
+}
+
+int
+unload() {
+ // Create the marker file.
+ std::fstream marker;
+ marker.open(MARKER_FILE, std::fstream::out);
+ marker.close();
+
+ return (0);
+}
+
+};
+
diff --git a/src/lib/hooks/tests/handles_unittest.cc b/src/lib/hooks/tests/handles_unittest.cc
new file mode 100644
index 0000000..c19dff2
--- /dev/null
+++ b/src/lib/hooks/tests/handles_unittest.cc
@@ -0,0 +1,974 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_handle.h>
+#include <hooks/server_hooks.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+
+/// @file
+/// CalloutHandle/LibraryHandle interaction tests
+///
+/// This file holds unit tests checking the interaction between the
+/// CalloutHandle/LibraryHandle and CalloutManager classes. In particular,
+/// they check that:
+///
+/// - A CalloutHandle's context is shared between callouts from the same
+/// library, but there is a separate context for each library.
+///
+/// - The various methods manipulating the items in the CalloutHandle's context
+/// work correctly.
+///
+/// - An active callout can only modify the registration of callouts registered
+/// by its own library.
+
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+class HandlesTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ /// Sets up the various elements used in each test.
+ HandlesTest() {
+ // Set up four hooks, although through gamma
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+ alpha_index_ = hooks.registerHook("alpha");
+ beta_index_ = hooks.registerHook("beta");
+ gamma_index_ = hooks.registerHook("gamma");
+ delta_index_ = hooks.registerHook("delta");
+
+ // Set up for three libraries.
+ manager_.reset(new CalloutManager(3));
+
+ // Initialize remaining variables.
+ common_string_ = "";
+ }
+
+ /// @brief Return callout manager
+ boost::shared_ptr<CalloutManager> getCalloutManager() {
+ return (manager_);
+ }
+
+ /// Hook indexes - these are frequently accessed, so are accessed directly.
+ int alpha_index_;
+ int beta_index_;
+ int gamma_index_;
+ int delta_index_;
+
+ /// String accessible by all callouts whatever the library
+ static std::string common_string_;
+
+private:
+ /// Callout manager. Declared static so that the callout functions can
+ /// access it.
+ boost::shared_ptr<CalloutManager> manager_;
+};
+
+/// Define the common string
+std::string HandlesTest::common_string_;
+
+
+// The next set of functions define the callouts used by the tests. They
+// manipulate the data in such a way that callouts called - and the order in
+// which they were called - can be determined. The functions also check that
+// the "callout context" data areas are separate.
+//
+// Three libraries are assumed, and each supplies four callouts. All callouts
+// manipulate two context elements the CalloutHandle, the elements being called
+// "string" and "int" (which describe the type of data manipulated).
+//
+// For the string item, each callout shifts data to the left and inserts its own
+// data. The data is a string of the form "nmc", where "n" is the number of
+// the library, "m" is the callout number and "y" is the indication of what
+// callout handle was passed as an argument ("1" or "2": "0" is used when no
+// identification has been set in the callout handle).
+//
+// For simplicity, and to cut down the number of functions actually written,
+// the callout indicator ("1" or "2") ) used in the in the CalloutHandle
+// functions is passed via a CalloutArgument. The argument is named "string":
+// use of a name the same as that of one of the context elements serves as a
+// check that the argument name space and argument context space are separate.
+//
+// For integer data, the value starts at zero and an increment is added on each
+// call. This increment is equal to:
+//
+// 100 * library number + 10 * callout number + callout handle
+//
+// Although this gives less information than the string value, the reasons for
+// using it are:
+//
+// - It is a separate item in the context, so checks that the context can
+// handle multiple items.
+// - It provides an item that can be deleted by the context deletion
+// methods.
+
+
+// Values set in the CalloutHandle context. There are three libraries, so
+// there are three contexts for the callout, one for each library.
+
+std::string& resultCalloutString(int index) {
+ static std::string result_callout_string[3];
+ return (result_callout_string[index]);
+}
+
+int& resultCalloutInt(int index) {
+ static int result_callout_int[3];
+ return (result_callout_int[index]);
+}
+
+// A simple function to zero the results.
+
+static void zero_results() {
+ for (int i = 0; i < 3; ++i) {
+ resultCalloutString(i) = "";
+ resultCalloutInt(i) = 0;
+ }
+}
+
+
+// Library callouts.
+
+// Common code for setting the callout context values.
+
+int
+execute(CalloutHandle& callout_handle, int library_num, int callout_num) {
+
+ // Obtain the callout handle number
+ int handle_num = 0;
+ try {
+ callout_handle.getArgument("handle_num", handle_num);
+ } catch (const NoSuchArgument&) {
+ // handle_num argument not set: this is the case in the tests where
+ // the context_create hook check is tested.
+ handle_num = 0;
+ }
+
+ // Create the basic data to be appended to the context value.
+ int idata = 100 * library_num + 10 * callout_num + handle_num;
+ string sdata = boost::lexical_cast<string>(idata);
+
+ // Get the context data. As before, this will not exist for the first
+ // callout called. (In real life, the library should create it when the
+ // "context_create" hook gets called before any packet processing takes
+ // place.)
+ int int_value = 0;
+ try {
+ callout_handle.getContext("int", int_value);
+ } catch (const NoSuchCalloutContext&) {
+ int_value = 0;
+ }
+
+ string string_value = "";
+ try {
+ callout_handle.getContext("string", string_value);
+ } catch (const NoSuchCalloutContext&) {
+ string_value = "";
+ }
+
+ // Update the values and set them back in the callout context.
+ int_value += idata;
+ callout_handle.setContext("int", int_value);
+
+ string_value += sdata;
+ callout_handle.setContext("string", string_value);
+
+ return (0);
+}
+
+// The following functions are the actual callouts - the name is of the
+// form "callout_<library number>_<callout number>"
+
+int
+callout11(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 1, 1));
+}
+
+int
+callout12(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 1, 2));
+}
+
+int
+callout13(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 1, 3));
+}
+
+int
+callout21(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 2, 1));
+}
+
+int
+callout22(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 2, 2));
+}
+
+int
+callout23(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 2, 3));
+}
+
+int
+callout31(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 3, 1));
+}
+
+int
+callout32(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 3, 2));
+}
+
+int
+callout33(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 3, 3));
+}
+
+// Common callout code for the fourth hook (which makes the data available for
+// checking). It copies the library and callout context data to the global
+// variables.
+
+int printExecute(CalloutHandle& callout_handle, int library_num) {
+ callout_handle.getContext("string", resultCalloutString(library_num - 1));
+ callout_handle.getContext("int", resultCalloutInt(library_num - 1));
+
+ return (0);
+}
+
+// These are the actual callouts.
+
+int
+print1(CalloutHandle& callout_handle) {
+ return (printExecute(callout_handle, 1));
+}
+
+int
+print2(CalloutHandle& callout_handle) {
+ return (printExecute(callout_handle, 2));
+}
+
+int
+print3(CalloutHandle& callout_handle) {
+ return (printExecute(callout_handle, 3));
+}
+
+// This test checks the many-faced nature of the context for the CalloutContext.
+
+TEST_F(HandlesTest, ContextAccessCheck) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ // Library 0.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("beta", callout12);
+ getCalloutManager()->registerCallout("gamma", callout13);
+ getCalloutManager()->registerCallout("delta", print1);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout21);
+ getCalloutManager()->registerCallout("beta", callout22);
+ getCalloutManager()->registerCallout("gamma", callout23);
+ getCalloutManager()->registerCallout("delta", print2);
+
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout31);
+ getCalloutManager()->registerCallout("beta", callout32);
+ getCalloutManager()->registerCallout("gamma", callout33);
+ getCalloutManager()->registerCallout("delta", print3);
+
+ // Create the callout handles and distinguish them by setting the
+ // "handle_num" argument.
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+
+ // Now call the callouts attached to the first three hooks. Each hook is
+ // called twice (once for each callout handle) before the next hook is
+ // called.
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_2);
+
+ // Get the results for each callout (the callout on hook "delta" copies
+ // the context values into a location the test can access). Explicitly
+ // zero the variables before getting the results so we are certain that
+ // the values are the results of the callouts.
+
+ zero_results();
+
+ // To explain the expected callout context results.
+ //
+ // Each callout handle maintains a separate context for each library. When
+ // the first call to callCallouts() is made, "111" gets appended to
+ // the context for library 1 maintained by the first callout handle, "211"
+ // gets appended to the context maintained for library 2, and "311" to
+ // the context maintained for library 3. In each case, the first digit
+ // corresponds to the library number, the second to the callout number and
+ // the third to the "handle_num" of the callout handle. For the first call
+ // to callCallouts, handle 1 is used, so the last digit is always 1.
+ //
+ // The next call to callCallouts() calls the same callouts but for the
+ // second callout handle. It also maintains three contexts (one for
+ // each library) and they will get "112", "212", "312" appended to
+ // them. The explanation for the digits is the same as before, except that
+ // in this case, the callout handle is number 2, so the third digit is
+ // always 2. These additions don't affect the contexts maintained by
+ // callout handle 1.
+ //
+ // The process is then repeated for hooks "beta" and "gamma" which, for
+ // callout handle 1, append "121", "221" and "321" for hook "beta" and
+ // "311", "321" and "331" for hook "gamma".
+ //
+ // The expected integer values can be found by summing up the values
+ // corresponding to the elements of the strings.
+
+ // At this point, we have only called the "print" function for callout
+ // handle "1", so the following results are checking the context values
+ // maintained in that callout handle.
+
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111121131", resultCalloutString(0));
+ EXPECT_EQ("211221231", resultCalloutString(1));
+ EXPECT_EQ("311321331", resultCalloutString(2));
+
+ EXPECT_EQ((111 + 121 + 131), resultCalloutInt(0));
+ EXPECT_EQ((211 + 221 + 231), resultCalloutInt(1));
+ EXPECT_EQ((311 + 321 + 331), resultCalloutInt(2));
+
+ // Repeat the checks for callout 2.
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+
+ EXPECT_EQ((112 + 122 + 132), resultCalloutInt(0));
+ EXPECT_EQ((212 + 222 + 232), resultCalloutInt(1));
+ EXPECT_EQ((312 + 322 + 332), resultCalloutInt(2));
+
+ EXPECT_EQ("112122132", resultCalloutString(0));
+ EXPECT_EQ("212222232", resultCalloutString(1));
+ EXPECT_EQ("312322332", resultCalloutString(2));
+}
+
+// Now repeat the test, but add a deletion callout to the list. The "beta"
+// hook of library 2 will have an additional callout to delete the "int"
+// element: the same hook for library 3 will delete both elements. In
+// addition, the names of context elements for the libraries at this point
+// will be printed.
+
+// List of context item names.
+
+vector<string>&
+getItemNames(int index) {
+ static vector<string> context_items[3];
+ return (context_items[index]);
+}
+
+// Context item deletion functions.
+
+int
+deleteIntContextItem(CalloutHandle& handle) {
+ handle.deleteContext("int");
+ return (0);
+}
+
+int
+deleteAllContextItems(CalloutHandle& handle) {
+ handle.deleteAllContext();
+ return (0);
+}
+
+// Generic print function - copy names in sorted order.
+
+int
+printContextNamesExecute(CalloutHandle& handle, int library_num) {
+ const int index = library_num - 1;
+ getItemNames(index) = handle.getContextNames();
+ sort(getItemNames(index).begin(), getItemNames(index).end());
+ return (0);
+}
+
+int
+printContextNames1(CalloutHandle& handle) {
+ return (printContextNamesExecute(handle, 1));
+}
+
+int
+printContextNames2(CalloutHandle& handle) {
+ return (printContextNamesExecute(handle, 2));
+}
+
+int
+printContextNames3(CalloutHandle& handle) {
+ return (printContextNamesExecute(handle, 3));
+}
+
+// Perform the test including deletion of context items.
+
+TEST_F(HandlesTest, ContextDeletionCheck) {
+
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("beta", callout12);
+ getCalloutManager()->registerCallout("beta", printContextNames1);
+ getCalloutManager()->registerCallout("gamma", callout13);
+ getCalloutManager()->registerCallout("delta", print1);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout21);
+ getCalloutManager()->registerCallout("beta", callout22);
+ getCalloutManager()->registerCallout("beta", deleteIntContextItem);
+ getCalloutManager()->registerCallout("beta", printContextNames2);
+ getCalloutManager()->registerCallout("gamma", callout23);
+ getCalloutManager()->registerCallout("delta", print2);
+
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout31);
+ getCalloutManager()->registerCallout("beta", callout32);
+ getCalloutManager()->registerCallout("beta", deleteAllContextItems);
+ getCalloutManager()->registerCallout("beta", printContextNames3);
+ getCalloutManager()->registerCallout("gamma", callout33);
+ getCalloutManager()->registerCallout("delta", print3);
+
+ // Create the callout handles and distinguish them by setting the "long"
+ // argument.
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+
+ // Now call the callouts attached to the first three hooks. Each hook is
+ // called twice (once for each callout handle) before the next hook is
+ // called.
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_2);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_1);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle_2);
+
+ // Get the results for each callout. Explicitly zero the variables before
+ // getting the results so we are certain that the values are the results
+ // of the callouts.
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+
+ // The logic by which the expected results are arrived at is described
+ // in the ContextAccessCheck test. The results here are different
+ // because context items have been modified along the way.
+
+ EXPECT_EQ((111 + 121 + 131), resultCalloutInt(0));
+ EXPECT_EQ(( 231), resultCalloutInt(1));
+ EXPECT_EQ(( 331), resultCalloutInt(2));
+
+ EXPECT_EQ("111121131", resultCalloutString(0));
+ EXPECT_EQ("211221231", resultCalloutString(1));
+ EXPECT_EQ( "331", resultCalloutString(2));
+
+ // Repeat the checks for callout handle 2.
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+
+ EXPECT_EQ((112 + 122 + 132), resultCalloutInt(0));
+ EXPECT_EQ(( 232), resultCalloutInt(1));
+ EXPECT_EQ(( 332), resultCalloutInt(2));
+
+ EXPECT_EQ("112122132", resultCalloutString(0));
+ EXPECT_EQ("212222232", resultCalloutString(1));
+ EXPECT_EQ( "332", resultCalloutString(2));
+
+ // ... and check what the names of the context items are after the callouts
+ // for hook "beta". We know they are in sorted order.
+
+ EXPECT_EQ(2, getItemNames(0).size());
+ EXPECT_EQ(string("int"), getItemNames(0)[0]);
+ EXPECT_EQ(string("string"), getItemNames(0)[1]);
+
+ EXPECT_EQ(1, getItemNames(1).size());
+ EXPECT_EQ(string("string"), getItemNames(1)[0]);
+
+ EXPECT_EQ(0, getItemNames(2).size());
+}
+
+// Tests that the CalloutHandle's constructor and destructor call the
+// context_create and context_destroy callbacks (if registered). For
+// simplicity, we'll use the same callout functions as used above.
+
+TEST_F(HandlesTest, ConstructionDestructionCallouts) {
+
+ // Register context callouts.
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("context_create", callout11);
+ getCalloutManager()->registerCallout("context_create", print1);
+ getCalloutManager()->registerCallout("context_destroy", callout12);
+ getCalloutManager()->registerCallout("context_destroy", print1);
+
+ // Create the CalloutHandle and check that the constructor callout
+ // has run.
+ zero_results();
+ boost::scoped_ptr<CalloutHandle>
+ callout_handle(new CalloutHandle(getCalloutManager()));
+ EXPECT_EQ("110", resultCalloutString(0));
+ EXPECT_EQ(110, resultCalloutInt(0));
+
+ // Check that the destructor callout runs. Note that the "print1" callout
+ // didn't destroy the library context - it only copied it to where it
+ // could be examined. As a result, the destructor callout appends its
+ // elements to the constructor's values and the result is printed.
+ zero_results();
+ callout_handle.reset();
+
+ EXPECT_EQ("110120", resultCalloutString(0));
+ EXPECT_EQ((110 + 120), resultCalloutInt(0));
+}
+
+// Dynamic callout registration and deregistration.
+// The following are the dynamic registration/deregistration callouts.
+
+
+// Add callout_78_alpha - adds a callout to hook alpha that appends "78x"
+// (where "x" is the callout handle) to the current output.
+
+int
+callout78(CalloutHandle& callout_handle) {
+ return (execute(callout_handle, 7, 8));
+}
+
+int
+add_callout78_alpha(CalloutHandle& callout_handle) {
+ callout_handle.getLibraryHandle().registerCallout("alpha", callout78);
+ return (0);
+}
+
+int
+delete_callout78_alpha(CalloutHandle& callout_handle) {
+ static_cast<void>(
+ callout_handle.getLibraryHandle().deregisterCallout("alpha",
+ callout78));
+ return (0);
+}
+
+// Check that a callout can register another callout on a different hook.
+
+TEST_F(HandlesTest, DynamicRegistrationAnotherHook) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ // Set up callouts on "alpha".
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("delta", print1);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout21);
+ getCalloutManager()->registerCallout("delta", print2);
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", callout31);
+ getCalloutManager()->registerCallout("delta", print3);
+
+ // ... and on "beta", set up the function to add a hook to alpha (but only
+ // for library 1).
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("beta", add_callout78_alpha);
+
+ // See what we get for calling the callouts on alpha first.
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111", resultCalloutString(0));
+ EXPECT_EQ("211", resultCalloutString(1));
+ EXPECT_EQ("311", resultCalloutString(2));
+
+ // All as expected, now call the callouts on beta. This should add a
+ // callout to the list of callouts for alpha, which we should see when
+ // we run the test again.
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+
+ // Use a new callout handle so as to get fresh callout context.
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+ EXPECT_EQ("112", resultCalloutString(0));
+ EXPECT_EQ("212782", resultCalloutString(1));
+ EXPECT_EQ("312", resultCalloutString(2));
+}
+
+// Check that a callout can register another callout on the same hook.
+// Note that the registration only applies to a subsequent invocation of
+// callCallouts, not to the current one. In other words, if
+//
+// * the callout list for a library is "A then B then C"
+// * when callCallouts is executed "B" adds "D" to that list,
+//
+// ... the current execution of callCallouts only executes A, B and C. A
+// subsequent invocation will execute A, B, C then D.
+
+TEST_F(HandlesTest, DynamicRegistrationSameHook) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ // Set up callouts on "alpha".
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("alpha", add_callout78_alpha);
+ getCalloutManager()->registerCallout("delta", print1);
+
+ // See what we get for calling the callouts on alpha first.
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111", resultCalloutString(0));
+
+ // Run it again - we should have added something to this hook.
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+ EXPECT_EQ("112782", resultCalloutString(0));
+
+ // And a third time...
+ CalloutHandle callout_handle_3(getCalloutManager());
+ callout_handle_3.setArgument("handle_num", static_cast<int>(3));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_3);
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_3);
+ EXPECT_EQ("113783783", resultCalloutString(0));
+}
+
+// Deregistration of a callout from a different hook
+
+TEST_F(HandlesTest, DynamicDeregistrationDifferentHook) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ // Set up callouts on "alpha".
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("alpha", callout78);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("delta", print1);
+
+ getCalloutManager()->registerCallout("beta", delete_callout78_alpha);
+
+ // Call the callouts on alpha
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111781111", resultCalloutString(0));
+
+ // Run the callouts on hook beta to remove the callout on alpha.
+ getCalloutManager()->callCallouts(beta_index_, callout_handle_1);
+
+ // The run of the callouts should have altered the callout list on the
+ // first library for hook alpha, so call again to make sure.
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+ EXPECT_EQ("112112", resultCalloutString(0));
+}
+
+// Deregistration of a callout from the same hook
+
+TEST_F(HandlesTest, DynamicDeregistrationSameHook) {
+ // Register callouts for the different libraries.
+ CalloutHandle handle(getCalloutManager());
+
+ // Set up callouts on "alpha".
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout11);
+ getCalloutManager()->registerCallout("alpha", delete_callout78_alpha);
+ getCalloutManager()->registerCallout("alpha", callout78);
+ getCalloutManager()->registerCallout("delta", print1);
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", callout21);
+ getCalloutManager()->registerCallout("alpha", callout78);
+ getCalloutManager()->registerCallout("delta", print2);
+
+ // Call the callouts on alpha
+ CalloutHandle callout_handle_1(getCalloutManager());
+ callout_handle_1.setArgument("handle_num", static_cast<int>(1));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_1);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_1);
+ EXPECT_EQ("111781", resultCalloutString(0));
+ EXPECT_EQ("211781", resultCalloutString(1));
+
+ // The run of the callouts should have altered the callout list on the
+ // first library for hook alpha, so call again to make sure.
+ CalloutHandle callout_handle_2(getCalloutManager());
+ callout_handle_2.setArgument("handle_num", static_cast<int>(2));
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle_2);
+
+ zero_results();
+ getCalloutManager()->callCallouts(delta_index_, callout_handle_2);
+ EXPECT_EQ("112", resultCalloutString(0));
+ EXPECT_EQ("212782", resultCalloutString(1));
+}
+
+// Testing the operation of the "skip" flag. Callouts print the value
+// they see in the flag and either leave it unchanged, set it or clear it.
+
+int
+calloutPrintSkip(CalloutHandle& handle) {
+ static const std::string YES("Y");
+ static const std::string NO("N");
+
+ HandlesTest::common_string_ = HandlesTest::common_string_ +
+ (handle.getSkip() ? YES : NO);
+ return (0);
+}
+
+int
+calloutSetSkip(CalloutHandle& handle) {
+ static_cast<void>(calloutPrintSkip(handle));
+ handle.setSkip(true);
+ return (0);
+}
+
+int
+calloutClearSkip(CalloutHandle& handle) {
+ static_cast<void>(calloutPrintSkip(handle));
+ handle.setSkip(false);
+ return (0);
+}
+
+// Do a series of tests, returning with the skip flag set "true".
+
+TEST_F(HandlesTest, ReturnSkipSet) {
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+
+ CalloutHandle callout_handle(getCalloutManager());
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+
+ // Check result. For each of visual checking, the expected string is
+ // divided into sections corresponding to the blocks of callouts above.
+ EXPECT_EQ(std::string("NNYY" "NNYYN" "NNYN"), common_string_);
+
+ // ... and check that the skip flag on exit from callCallouts is set.
+ EXPECT_TRUE(callout_handle.getSkip());
+}
+
+// Repeat the test, returning with the skip flag clear.
+TEST_F(HandlesTest, ReturnSkipClear) {
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+ getCalloutManager()->registerCallout("alpha", calloutPrintSkip);
+ getCalloutManager()->registerCallout("alpha", calloutSetSkip);
+ getCalloutManager()->registerCallout("alpha", calloutClearSkip);
+
+ CalloutHandle callout_handle(getCalloutManager());
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+
+ // Check result. For each of visual checking, the expected string is
+ // divided into sections corresponding to the blocks of callouts above.
+ EXPECT_EQ(std::string("NYY" "NNYNYN" "NNNY"), common_string_);
+
+ // ... and check that the skip flag on exit from callCallouts is set.
+ EXPECT_FALSE(callout_handle.getSkip());
+}
+
+// Check that the skip flag is cleared when callouts are called - even if
+// there are no callouts.
+
+TEST_F(HandlesTest, NoCalloutsSkipTest) {
+ // Note - no callouts are registered on any hook.
+ CalloutHandle callout_handle(getCalloutManager());
+
+ // Clear the skip flag and call a hook with no callouts.
+ callout_handle.setSkip(false);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+ EXPECT_FALSE(callout_handle.getSkip());
+
+ // Set the skip flag and call a hook with no callouts.
+ callout_handle.setSkip(true);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+ EXPECT_FALSE(callout_handle.getSkip());
+}
+
+// The next set of callouts do a similar thing to the above "skip" tests,
+// but alter the value of a string argument. This is for testing that the
+// a callout is able to change an argument and return it to the caller.
+
+const char* MODIFIED_ARG = "modified_arg";
+
+int
+calloutSetArgumentCommon(CalloutHandle& handle, const char* what) {
+ std::string modified_arg = "";
+
+ handle.getArgument(MODIFIED_ARG, modified_arg);
+ modified_arg = modified_arg + std::string(what);
+ handle.setArgument(MODIFIED_ARG, modified_arg);
+ return (0);
+}
+
+int
+calloutSetArgumentYes(CalloutHandle& handle) {
+ return (calloutSetArgumentCommon(handle, "Y"));
+}
+
+int
+calloutSetArgumentNo(CalloutHandle& handle) {
+ return (calloutSetArgumentCommon(handle, "N"));
+}
+
+// ... and a callout to just copy the argument to the "common_string_" variable
+// but otherwise not alter it.
+
+int
+calloutPrintArgument(CalloutHandle& handle) {
+ handle.getArgument(MODIFIED_ARG, HandlesTest::common_string_);
+ return (0);
+}
+
+TEST_F(HandlesTest, CheckModifiedArgument) {
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
+
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
+ getCalloutManager()->registerCallout("alpha", calloutPrintArgument);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
+
+ getCalloutManager()->setLibraryIndex(2);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentNo);
+ getCalloutManager()->registerCallout("alpha", calloutSetArgumentYes);
+
+ // Create the argument with an initial empty string value. Then call the
+ // sequence of callouts above.
+ CalloutHandle callout_handle(getCalloutManager());
+ std::string modified_arg = "";
+ callout_handle.setArgument(MODIFIED_ARG, modified_arg);
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+
+ // Check the intermediate and results. For visual checking, the expected
+ // string is divided into sections corresponding to the blocks of callouts
+ // above.
+ EXPECT_EQ(std::string("YNN" "YY"), common_string_);
+
+ callout_handle.getArgument(MODIFIED_ARG, modified_arg);
+ EXPECT_EQ(std::string("YNN" "YYNN" "YNY"), modified_arg);
+}
+
+// Test that the CalloutHandle provides the name of the hook to which the
+// callout is attached.
+
+int
+callout_hook_name(CalloutHandle& callout_handle) {
+ HandlesTest::common_string_ = callout_handle.getHookName();
+ return (0);
+}
+
+int
+callout_hook_dummy(CalloutHandle&) {
+ return (0);
+}
+
+TEST_F(HandlesTest, HookName) {
+ getCalloutManager()->setLibraryIndex(0);
+ getCalloutManager()->registerCallout("alpha", callout_hook_name);
+ getCalloutManager()->registerCallout("beta", callout_hook_name);
+
+ // Call alpha and beta callouts and check the hook to which they belong.
+ CalloutHandle callout_handle(getCalloutManager());
+
+ EXPECT_EQ(std::string(""), HandlesTest::common_string_);
+
+ getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+ EXPECT_EQ(std::string("alpha"), HandlesTest::common_string_);
+
+ getCalloutManager()->callCallouts(beta_index_, callout_handle);
+ EXPECT_EQ(std::string("beta"), HandlesTest::common_string_);
+
+ // Make sure that the callout accesses the name even if it is not the
+ // only callout in the list.
+ getCalloutManager()->setLibraryIndex(1);
+ getCalloutManager()->registerCallout("gamma", callout_hook_dummy);
+ getCalloutManager()->registerCallout("gamma", callout_hook_name);
+ getCalloutManager()->registerCallout("gamma", callout_hook_dummy);
+
+ EXPECT_EQ(std::string("beta"), HandlesTest::common_string_);
+ getCalloutManager()->callCallouts(gamma_index_, callout_handle);
+ EXPECT_EQ(std::string("gamma"), HandlesTest::common_string_);
+}
+
+} // Anonymous namespace
+
diff --git a/src/lib/hooks/tests/hooks_manager_unittest.cc b/src/lib/hooks/tests/hooks_manager_unittest.cc
new file mode 100644
index 0000000..136eeae
--- /dev/null
+++ b/src/lib/hooks/tests/hooks_manager_unittest.cc
@@ -0,0 +1,524 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <hooks/callout_handle.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/server_hooks.h>
+
+#include <hooks/tests/common_test_class.h>
+#include <hooks/tests/test_libraries.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+/// @brief Hooks manager collection test class
+
+class HooksManagerTest : public ::testing::Test,
+ public HooksCommonTestClass {
+public:
+ /// @brief Constructor
+ ///
+ /// Reset the hooks manager. The hooks manager is a singleton, so needs
+ /// to be reset for each test.
+ HooksManagerTest() {
+ HooksManager::unloadLibraries();
+ }
+
+ /// @brief Destructor
+ ///
+ /// Unload all libraries.
+ ~HooksManagerTest() {
+ HooksManager::unloadLibraries();
+ }
+
+
+ /// @brief Call callouts test
+ ///
+ /// See the header for HooksCommonTestClass::execute for details.
+ ///
+ /// @param r0...r3, d1..d3 Values and intermediate values expected. They
+ /// are ordered so that the variables appear in the argument list in
+ /// the order they are used.
+ void executeCallCallouts(int r0, int d1, int r1, int d2, int r2, int d3,
+ int r3) {
+ static const char* COMMON_TEXT = " callout returned the wong value";
+ static const char* RESULT = "result";
+
+ // Get a CalloutHandle for the calculation.
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+
+ // Initialize the argument RESULT. This simplifies testing by
+ // eliminating the generation of an exception when we try the unload
+ // test. In that case, RESULT is unchanged.
+ int result = -1;
+ handle->setArgument(RESULT, result);
+
+ // Seed the calculation.
+ HooksManager::callCallouts(isc::hooks::ServerHooks::CONTEXT_CREATE,
+ *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(r0, result) << "context_create" << COMMON_TEXT;
+
+ // Perform the first calculation.
+ handle->setArgument("data_1", d1);
+ HooksManager::callCallouts(hookpt_one_index_, *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(r1, result) << "hookpt_one" << COMMON_TEXT;
+
+ // ... the second ...
+ handle->setArgument("data_2", d2);
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(r2, result) << "hookpt_two" << COMMON_TEXT;
+
+ // ... and the third.
+ handle->setArgument("data_3", d3);
+ HooksManager::callCallouts(hookpt_three_index_, *handle);
+ handle->getArgument(RESULT, result);
+ EXPECT_EQ(r3, result) << "hookpt_three" << COMMON_TEXT;
+ }
+
+};
+
+// This is effectively the same test as for LibraryManager, but using the
+// HooksManager object.
+
+TEST_F(HooksManagerTest, LoadLibraries) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+ // Load the libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. The first library implements the calculation.
+ //
+ // r3 = (7 * d1 - d2) * d3
+ //
+ // The last-loaded library implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ //
+ // Putting the processing for each library together in the appropriate
+ // order, we get:
+ //
+ // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+ {
+ SCOPED_TRACE("Calculation with libraries loaded");
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+ }
+
+ // Try unloading the libraries.
+ EXPECT_NO_THROW(HooksManager::unloadLibraries());
+
+ // Re-execute the calculation - callouts can be called but as nothing
+ // happens, the result should always be -1.
+ {
+ SCOPED_TRACE("Calculation with libraries not loaded");
+ executeCallCallouts(-1, 3, -1, 22, -1, 83, -1);
+ }
+}
+
+// This is effectively the same test as above, but with a library generating
+// an error when loaded. It is expected that the failing library will not be
+// loaded, but others will be.
+
+TEST_F(HooksManagerTest, LoadLibrariesWithError) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ library_names.push_back(std::string(INCORRECT_VERSION_LIBRARY));
+ library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+ // Load the libraries. We expect a failure return because one of the
+ // libraries fails to load.
+ EXPECT_FALSE(HooksManager::loadLibraries(library_names));
+}
+
+// Test that we can unload a set of libraries while we have a CalloutHandle
+// created on them in existence, and can delete the handle afterwards.
+
+TEST_F(HooksManagerTest, CalloutHandleUnloadLibrary) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+
+ // Load the libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. Thiis library implements:
+ //
+ // r3 = (7 * d1 - d2) * d3
+ {
+ SCOPED_TRACE("Calculation with full callout library loaded");
+ executeCallCallouts(7, 4, 28, 8, 20, 2, 40);
+ }
+
+ // Get an outstanding callout handle on this library.
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+
+ // Execute once of the callouts again to ensure that the handle contains
+ // memory allocated by the library.
+ HooksManager::callCallouts(ServerHooks::CONTEXT_CREATE, *handle);
+
+ // Unload the libraries.
+ HooksManager::unloadLibraries();
+
+ // Deleting the callout handle should not cause a segmentation fault.
+ handle.reset();
+}
+
+// Test that we can load a new set of libraries while we have a CalloutHandle
+// created on them in existence, and can delete the handle afterwards.
+
+TEST_F(HooksManagerTest, CalloutHandleLoadLibrary) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+
+ // Load the libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. Thiis library implements:
+ //
+ // r3 = (7 * d1 - d2) * d3
+ {
+ SCOPED_TRACE("Calculation with full callout library loaded");
+ executeCallCallouts(7, 4, 28, 8, 20, 2, 40);
+ }
+
+ // Get an outstanding callout handle on this library and execute one of
+ // the callouts again to ensure that the handle contains memory allocated
+ // by the library.
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+ HooksManager::callCallouts(ServerHooks::CONTEXT_CREATE, *handle);
+
+ // Load a new library that implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ std::vector<std::string> new_library_names;
+ new_library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+ // Load the libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(new_library_names));
+
+ // Execute the calculation. Note that we still have the CalloutHandle
+ // for the old library: however, this should not affect the new calculation.
+ {
+ SCOPED_TRACE("Calculation with basic callout library loaded");
+ executeCallCallouts(10, 7, 17, 3, 51, 16, 35);
+ }
+
+ // Deleting the old callout handle should not cause a segmentation fault.
+ handle.reset();
+}
+
+// This is effectively the same test as the LoadLibraries test.
+
+TEST_F(HooksManagerTest, ReloadSameLibraries) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+ // Load the libraries.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. See the LoadLibraries test for an explanation of
+ // the calculation.
+ {
+ SCOPED_TRACE("Calculation with libraries loaded");
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+ }
+
+ // Try reloading the libraries and re-execute the calculation - we should
+ // get the same results.
+ EXPECT_NO_THROW(HooksManager::loadLibraries(library_names));
+ {
+ SCOPED_TRACE("Calculation with libraries reloaded");
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+ }
+}
+
+TEST_F(HooksManagerTest, ReloadLibrariesReverseOrder) {
+
+ // Set up the list of libraries to be loaded and load them.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Execute the callouts. The first library implements the calculation.
+ //
+ // r3 = (7 * d1 - d2) * d3
+ //
+ // The last-loaded library implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ //
+ // Putting the processing for each library together in the given order
+ // gives.
+ //
+ // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+ {
+ SCOPED_TRACE("Calculation with libraries loaded");
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+ }
+
+ // Reload the libraries in the reverse order.
+ std::reverse(library_names.begin(), library_names.end());
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // The calculation in the reverse order gives:
+ //
+ // r3 = ((((7 + d1) * d1) * d2 - d2) - d3) * d3
+ {
+ SCOPED_TRACE("Calculation with libraries loaded in reverse order");
+ executeCallCallouts(7, 3, 30, 3, 87, 7, 560);
+ }
+}
+
+// Local callouts for the test of server-registered callouts.
+
+namespace {
+
+ int
+testPreCallout(CalloutHandle& handle) {
+ handle.setArgument("result", static_cast<int>(1027));
+ return (0);
+}
+
+int
+testPostCallout(CalloutHandle& handle) {
+ int result;
+ handle.getArgument("result", result);
+ result *= 2;
+ handle.setArgument("result", result);
+ return (0);
+}
+
+}
+
+// The next test registers the pre and post- callouts above for hook hookpt_two,
+// and checks they are called.
+
+TEST_F(HooksManagerTest, PrePostCalloutTest) {
+
+ // Load a single library.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+
+ // Load the pre- and post- callouts.
+ HooksManager::preCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPreCallout);
+ HooksManager::postCalloutsLibraryHandle().registerCallout("hookpt_two",
+ testPostCallout);
+
+ // Execute the callouts. hookpt_two implements the calculation:
+ //
+ // "result - data_2"
+ //
+ // With the pre- and post- callouts above, the result expected is
+ //
+ // (1027 - data_2) * 2
+ CalloutHandlePtr handle = HooksManager::createCalloutHandle();
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ int result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(2024, result);
+
+ // ... and check that the pre- and post- callout functions don't survive a
+ // reload.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+ handle = HooksManager::createCalloutHandle();
+
+ handle->setArgument("result", static_cast<int>(0));
+ handle->setArgument("data_2", static_cast<int>(15));
+
+ HooksManager::callCallouts(hookpt_two_index_, *handle);
+
+ result = 0;
+ handle->getArgument("result", result);
+ EXPECT_EQ(-15, result);
+}
+
+// Check that everything works even with no libraries loaded. First that
+// calloutsPresent() always returns false.
+
+TEST_F(HooksManagerTest, NoLibrariesCalloutsPresent) {
+ // No callouts should be present on any hooks.
+ EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(HooksManager::calloutsPresent(hookpt_three_index_));
+}
+
+TEST_F(HooksManagerTest, NoLibrariesCallCallouts) {
+ executeCallCallouts(-1, 3, -1, 22, -1, 83, -1);
+}
+
+// Test the encapsulation of the ServerHooks::registerHook() method.
+
+TEST_F(HooksManagerTest, RegisterHooks) {
+ ServerHooks::getServerHooks().reset();
+ EXPECT_EQ(2, ServerHooks::getServerHooks().getCount());
+
+ // Check that the hook indexes are as expected. (Use temporary variables
+ // as it appears that Google test can't access the constants.)
+ int sh_cc = ServerHooks::CONTEXT_CREATE;
+ int hm_cc = HooksManager::CONTEXT_CREATE;
+ EXPECT_EQ(sh_cc, hm_cc);
+
+ int sh_cd = ServerHooks::CONTEXT_DESTROY;
+ int hm_cd = HooksManager::CONTEXT_DESTROY;
+ EXPECT_EQ(sh_cd, hm_cd);
+
+ // Register a few hooks and check we have the indexes as expected.
+ EXPECT_EQ(2, HooksManager::registerHook(string("alpha")));
+ EXPECT_EQ(3, HooksManager::registerHook(string("beta")));
+ EXPECT_EQ(4, HooksManager::registerHook(string("gamma")));
+ EXPECT_THROW(static_cast<void>(HooksManager::registerHook(string("alpha"))),
+ DuplicateHook);
+
+ // ... an check the hooks are as we expect.
+ EXPECT_EQ(5, ServerHooks::getServerHooks().getCount());
+ vector<string> names = ServerHooks::getServerHooks().getHookNames();
+ sort(names.begin(), names.end());
+
+ EXPECT_EQ(string("alpha"), names[0]);
+ EXPECT_EQ(string("beta"), names[1]);
+ EXPECT_EQ(string("context_create"), names[2]);
+ EXPECT_EQ(string("context_destroy"), names[3]);
+ EXPECT_EQ(string("gamma"), names[4]);
+}
+
+// Check that we can get the names of the libraries.
+
+TEST_F(HooksManagerTest, LibraryNames) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+ // Check the names before the libraries are loaded.
+ std::vector<std::string> loaded_names = HooksManager::getLibraryNames();
+ EXPECT_TRUE(loaded_names.empty());
+
+ // Load the libraries and check the names again.
+ EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+ loaded_names = HooksManager::getLibraryNames();
+ EXPECT_TRUE(library_names == loaded_names);
+
+ // Unload the libraries and check again.
+ EXPECT_NO_THROW(HooksManager::unloadLibraries());
+ loaded_names = HooksManager::getLibraryNames();
+ EXPECT_TRUE(loaded_names.empty());
+}
+
+// Test the library validation function.
+
+TEST_F(HooksManagerTest, validateLibraries) {
+ // Vector of libraries that failed validation
+ std::vector<std::string> failed;
+
+ // Test different vectors of libraries.
+
+ // No libraries should return a success.
+ std::vector<std::string> libraries;
+
+ failed = HooksManager::validateLibraries(libraries);
+ EXPECT_TRUE(failed.empty());
+
+ // Single valid library should validate.
+ libraries.clear();
+ libraries.push_back(BASIC_CALLOUT_LIBRARY);
+
+ failed = HooksManager::validateLibraries(libraries);
+ EXPECT_TRUE(failed.empty());
+
+ // Multiple valid libraries should succeed.
+ libraries.clear();
+ libraries.push_back(BASIC_CALLOUT_LIBRARY);
+ libraries.push_back(FULL_CALLOUT_LIBRARY);
+ libraries.push_back(UNLOAD_CALLOUT_LIBRARY);
+
+ failed = HooksManager::validateLibraries(libraries);
+ EXPECT_TRUE(failed.empty());
+
+ // Single invalid library should fail.
+ libraries.clear();
+ libraries.push_back(NOT_PRESENT_LIBRARY);
+
+ failed = HooksManager::validateLibraries(libraries);
+ EXPECT_TRUE(failed == libraries);
+
+ // Multiple invalid libraries should fail.
+ libraries.clear();
+ libraries.push_back(INCORRECT_VERSION_LIBRARY);
+ libraries.push_back(NO_VERSION_LIBRARY);
+ libraries.push_back(FRAMEWORK_EXCEPTION_LIBRARY);
+
+ failed = HooksManager::validateLibraries(libraries);
+ EXPECT_TRUE(failed == libraries);
+
+ // Combination of valid and invalid (first one valid) should fail.
+ libraries.clear();
+ libraries.push_back(FULL_CALLOUT_LIBRARY);
+ libraries.push_back(INCORRECT_VERSION_LIBRARY);
+ libraries.push_back(NO_VERSION_LIBRARY);
+
+ std::vector<std::string> expected_failures;
+ expected_failures.push_back(INCORRECT_VERSION_LIBRARY);
+ expected_failures.push_back(NO_VERSION_LIBRARY);
+
+ failed = HooksManager::validateLibraries(libraries);
+ EXPECT_TRUE(failed == expected_failures);
+
+ // Combination of valid and invalid (first one invalid) should fail.
+ libraries.clear();
+ libraries.push_back(NO_VERSION_LIBRARY);
+ libraries.push_back(FULL_CALLOUT_LIBRARY);
+ libraries.push_back(INCORRECT_VERSION_LIBRARY);
+
+ expected_failures.clear();
+ expected_failures.push_back(NO_VERSION_LIBRARY);
+ expected_failures.push_back(INCORRECT_VERSION_LIBRARY);
+
+ failed = HooksManager::validateLibraries(libraries);
+ EXPECT_TRUE(failed == expected_failures);
+}
+
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/incorrect_version_library.cc b/src/lib/hooks/tests/incorrect_version_library.cc
new file mode 100644
index 0000000..bb6eedf
--- /dev/null
+++ b/src/lib/hooks/tests/incorrect_version_library.cc
@@ -0,0 +1,33 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Incorrect version function test
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - It contains the version() framework function only, which returns an
+/// incorrect version number.
+
+#include <hooks/hooks.h>
+
+extern "C" {
+
+int version() {
+ return (BIND10_HOOKS_VERSION + 1);
+}
+
+};
diff --git a/src/lib/hooks/tests/library_manager_collection_unittest.cc b/src/lib/hooks/tests/library_manager_collection_unittest.cc
new file mode 100644
index 0000000..7fdbb7d
--- /dev/null
+++ b/src/lib/hooks/tests/library_manager_collection_unittest.cc
@@ -0,0 +1,251 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_manager.h>
+#include <hooks/library_manager_collection.h>
+
+#include <hooks/tests/common_test_class.h>
+#include <hooks/tests/test_libraries.h>
+
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+/// @brief Library manager collection test class
+
+class LibraryManagerCollectionTest : public ::testing::Test,
+ public HooksCommonTestClass {
+};
+
+/// @brief Public library manager collection class
+///
+/// This is an instance of the LibraryManagerCollection class but with the
+/// protected methods made public for test purposes.
+
+class PublicLibraryManagerCollection
+ : public isc::hooks::LibraryManagerCollection {
+public:
+ /// @brief Constructor
+ ///
+ /// @param List of libraries that this collection will manage. The order
+ /// of the libraries is important.
+ PublicLibraryManagerCollection(const std::vector<std::string>& libraries)
+ : LibraryManagerCollection(libraries)
+ {}
+
+ /// Public methods that call protected methods on the superclass.
+ using LibraryManagerCollection::unloadLibraries;
+};
+
+
+// This is effectively the same test as for LibraryManager, but using the
+// LibraryManagerCollection object.
+
+TEST_F(LibraryManagerCollectionTest, LoadLibraries) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+ // Set up the library manager collection and get the callout manager we'll
+ // be using.
+ PublicLibraryManagerCollection lm_collection(library_names);
+
+ // Load the libraries.
+ EXPECT_TRUE(lm_collection.loadLibraries());
+ EXPECT_EQ(2, lm_collection.getLoadedLibraryCount());
+
+ // Execute the callouts. The first library implements the calculation.
+ //
+ // r3 = (7 * d1 - d2) * d3
+ //
+ // The last-loaded library implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ //
+ // Putting the processing for each library together in the appropriate
+ // order, we get:
+ //
+ // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+ boost::shared_ptr<CalloutManager> manager =
+ lm_collection.getCalloutManager();
+ {
+ SCOPED_TRACE("Doing calculation with libraries loaded");
+ executeCallCallouts(manager, 10, 3, 33, 2, 62, 3, 183);
+ }
+
+ // Try unloading the libraries.
+ EXPECT_NO_THROW(lm_collection.unloadLibraries());
+ EXPECT_EQ(0, lm_collection.getLoadedLibraryCount());
+
+ // Re-execute the calculation - callouts can be called but as nothing
+ // happens, the result should always be -1.
+ {
+ SCOPED_TRACE("Doing calculation with libraries not loaded");
+ executeCallCallouts(manager, -1, 3, -1, 22, -1, 83, -1);
+ }
+}
+
+// This is effectively the same test as above, but with a library generating
+// an error when loaded. It is expected that no libraries will be loaded.
+
+TEST_F(LibraryManagerCollectionTest, LoadLibrariesWithError) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ library_names.push_back(std::string(INCORRECT_VERSION_LIBRARY));
+ library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+ // Set up the library manager collection and get the callout manager we'll
+ // be using.
+ PublicLibraryManagerCollection lm_collection(library_names);
+
+ // Load the libraries. We expect a failure status to be returned as
+ // one of the libraries failed to load.
+ EXPECT_FALSE(lm_collection.loadLibraries());
+
+ // Expect no libraries were loaded.
+ EXPECT_EQ(0, lm_collection.getLoadedLibraryCount());
+}
+
+// Check that everything works even with no libraries loaded.
+
+TEST_F(LibraryManagerCollectionTest, NoLibrariesLoaded) {
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+
+ // Set up the library manager collection and get the callout manager we'll
+ // be using.
+ LibraryManagerCollection lm_collection(library_names);
+ EXPECT_TRUE(lm_collection.loadLibraries());
+ EXPECT_EQ(0, lm_collection.getLoadedLibraryCount());
+ boost::shared_ptr<CalloutManager> manager =
+ lm_collection.getCalloutManager();
+
+ // Eecute the calculation - callouts can be called but as nothing
+ // happens, the result should always be -1.
+ executeCallCallouts(manager, -1, 3, -1, 22, -1, 83, -1);
+}
+
+// Check that we can get the names of the libraries.
+
+TEST_F(LibraryManagerCollectionTest, LibraryNames) {
+
+ // Set up the list of libraries to be loaded.
+ std::vector<std::string> library_names;
+ library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+ library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+ // Set up the library manager collection and get the callout manager we'll
+ // be using.
+ PublicLibraryManagerCollection lm_collection(library_names);
+
+ // Check the names before the libraries are loaded.
+ std::vector<std::string> collection_names = lm_collection.getLibraryNames();
+ EXPECT_TRUE(library_names == collection_names);
+
+ // Load the libraries and check the names again.
+ EXPECT_TRUE(lm_collection.loadLibraries());
+ EXPECT_EQ(2, lm_collection.getLoadedLibraryCount());
+ collection_names = lm_collection.getLibraryNames();
+ EXPECT_TRUE(library_names == collection_names);
+}
+
+// Test the library validation function.
+
+TEST_F(LibraryManagerCollectionTest, validateLibraries) {
+ // Vector of libraries that failed validation
+ std::vector<std::string> failed;
+
+ // Test different vectors of libraries.
+
+ // No libraries should return a success.
+ std::vector<std::string> libraries;
+
+ failed = LibraryManagerCollection::validateLibraries(libraries);
+ EXPECT_TRUE(failed.empty());
+
+ // Single valid library should validate.
+ libraries.clear();
+ libraries.push_back(BASIC_CALLOUT_LIBRARY);
+
+ failed = LibraryManagerCollection::validateLibraries(libraries);
+ EXPECT_TRUE(failed.empty());
+
+ // Multiple valid libraries should succeed.
+ libraries.clear();
+ libraries.push_back(BASIC_CALLOUT_LIBRARY);
+ libraries.push_back(FULL_CALLOUT_LIBRARY);
+ libraries.push_back(UNLOAD_CALLOUT_LIBRARY);
+
+ failed = LibraryManagerCollection::validateLibraries(libraries);
+ EXPECT_TRUE(failed.empty());
+
+ // Single invalid library should fail.
+ libraries.clear();
+ libraries.push_back(NOT_PRESENT_LIBRARY);
+
+ failed = LibraryManagerCollection::validateLibraries(libraries);
+ EXPECT_TRUE(failed == libraries);
+
+ // Multiple invalid libraries should fail.
+ libraries.clear();
+ libraries.push_back(INCORRECT_VERSION_LIBRARY);
+ libraries.push_back(NO_VERSION_LIBRARY);
+ libraries.push_back(FRAMEWORK_EXCEPTION_LIBRARY);
+
+ failed = LibraryManagerCollection::validateLibraries(libraries);
+ EXPECT_TRUE(failed == libraries);
+
+ // Combination of valid and invalid (first one valid) should fail.
+ libraries.clear();
+ libraries.push_back(FULL_CALLOUT_LIBRARY);
+ libraries.push_back(INCORRECT_VERSION_LIBRARY);
+ libraries.push_back(NO_VERSION_LIBRARY);
+
+ std::vector<std::string> expected_failures;
+ expected_failures.push_back(INCORRECT_VERSION_LIBRARY);
+ expected_failures.push_back(NO_VERSION_LIBRARY);
+
+ failed = LibraryManagerCollection::validateLibraries(libraries);
+ EXPECT_TRUE(failed == expected_failures);
+
+ // Combination of valid and invalid (first one invalid) should fail.
+ libraries.clear();
+ libraries.push_back(NO_VERSION_LIBRARY);
+ libraries.push_back(FULL_CALLOUT_LIBRARY);
+ libraries.push_back(INCORRECT_VERSION_LIBRARY);
+
+ expected_failures.clear();
+ expected_failures.push_back(NO_VERSION_LIBRARY);
+ expected_failures.push_back(INCORRECT_VERSION_LIBRARY);
+
+ failed = LibraryManagerCollection::validateLibraries(libraries);
+ EXPECT_TRUE(failed == expected_failures);
+}
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/library_manager_unittest.cc b/src/lib/hooks/tests/library_manager_unittest.cc
new file mode 100644
index 0000000..9336946
--- /dev/null
+++ b/src/lib/hooks/tests/library_manager_unittest.cc
@@ -0,0 +1,569 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <hooks/callout_handle.h>
+#include <hooks/callout_manager.h>
+#include <hooks/library_manager.h>
+#include <hooks/server_hooks.h>
+
+#include <hooks/tests/common_test_class.h>
+#include <hooks/tests/marker_file.h>
+#include <hooks/tests/test_libraries.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <fstream>
+#include <string>
+
+#include <unistd.h>
+
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+/// @brief Library manager test class
+
+class LibraryManagerTest : public ::testing::Test,
+ public HooksCommonTestClass {
+public:
+ /// @brief Constructor
+ ///
+ /// Initializes the CalloutManager object used in the tests. It sets it
+ /// up with the hooks initialized in the HooksCommonTestClass object and
+ /// with four libraries.
+ LibraryManagerTest() {
+ callout_manager_.reset(new CalloutManager(4));
+
+ // Ensure the marker file is not present at the start of a test.
+ static_cast<void>(unlink(MARKER_FILE));
+ }
+
+ /// @brief Destructor
+ ///
+ /// Ensures a marker file is removed after each test.
+ ~LibraryManagerTest() {
+ static_cast<void>(unlink(MARKER_FILE));
+ }
+
+ /// @brief Marker file present
+ ///
+ /// Convenience function to check whether a marker file is present. It
+ /// does this by opening the file.
+ ///
+ /// @return true if the marker file is present.
+ bool markerFilePresent() const {
+
+ // Try to open it.
+ std::fstream marker;
+ marker.open(MARKER_FILE, std::fstream::in);
+
+ // Check if it is open and close it if so.
+ bool exists = marker.is_open();
+ if (exists) {
+ marker.close();
+ }
+
+ return (exists);
+ }
+
+ /// @brief Call callouts test
+ ///
+ /// A wrapper around the method of the same name in the HooksCommonTestClass
+ /// object, this passes this class's CalloutManager to that method.
+ ///
+ /// @param r0...r3, d1..d3 Values and intermediate values expected. They
+ /// are ordered so that the variables appear in the argument list in
+ /// the order they are used. See HooksCommonTestClass::execute for
+ /// a full description. (rN is used to indicate an expected result,
+ /// dN is data to be passed to the calculation.)
+ void executeCallCallouts(int r0, int d1, int r1, int d2, int r2, int d3,
+ int r3) {
+ HooksCommonTestClass::executeCallCallouts(callout_manager_, r0, d1,
+ r1, d2, r2, d3, r3);
+ }
+
+ /// Callout manager used for the test.
+ boost::shared_ptr<CalloutManager> callout_manager_;
+};
+
+/// @brief Library manager class
+///
+/// This is an instance of the LibraryManager class but with the protected
+/// methods made public for test purposes.
+
+class PublicLibraryManager : public isc::hooks::LibraryManager {
+public:
+ /// @brief Constructor
+ ///
+ /// Stores the library name. The actual loading is done in loadLibrary().
+ ///
+ /// @param name Name of the library to load. This should be an absolute
+ /// path name.
+ /// @param index Index of this library. For all these tests, it will be
+ /// zero, as we are only using one library.
+ /// @param manager CalloutManager object
+ PublicLibraryManager(const std::string& name, int index,
+ const boost::shared_ptr<CalloutManager>& manager)
+ : LibraryManager(name, index, manager)
+ {}
+
+ /// Public methods that call protected methods on the superclass.
+ using LibraryManager::openLibrary;
+ using LibraryManager::closeLibrary;
+ using LibraryManager::checkVersion;
+ using LibraryManager::registerStandardCallouts;
+ using LibraryManager::runLoad;
+ using LibraryManager::runUnload;
+};
+
+
+// Check that openLibrary() reports an error when it can't find the specified
+// library.
+
+TEST_F(LibraryManagerTest, NoLibrary) {
+ PublicLibraryManager lib_manager(std::string(NOT_PRESENT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_FALSE(lib_manager.openLibrary());
+}
+
+// Check that the openLibrary() and closeLibrary() methods work.
+
+TEST_F(LibraryManagerTest, OpenClose) {
+ PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+
+ // Open and close the library
+ EXPECT_TRUE(lib_manager.openLibrary());
+ EXPECT_TRUE(lib_manager.closeLibrary());
+
+ // Check that a second close on an already closed library does not report
+ // an error.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the code handles the case of a library with no version function.
+
+TEST_F(LibraryManagerTest, NoVersion) {
+ PublicLibraryManager lib_manager(std::string(NO_VERSION_LIBRARY),
+ 0, callout_manager_);
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Version check should fail.
+ EXPECT_FALSE(lib_manager.checkVersion());
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the code handles the case of a library with a version function
+// that returns an incorrect version number.
+
+TEST_F(LibraryManagerTest, WrongVersion) {
+ PublicLibraryManager lib_manager(std::string(INCORRECT_VERSION_LIBRARY),
+ 0, callout_manager_);
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Version check should fail.
+ EXPECT_FALSE(lib_manager.checkVersion());
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the code handles the case of a library where the version function
+// throws an exception.
+
+TEST_F(LibraryManagerTest, VersionException) {
+ PublicLibraryManager lib_manager(std::string(FRAMEWORK_EXCEPTION_LIBRARY),
+ 0, callout_manager_);
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Version check should fail.
+ EXPECT_FALSE(lib_manager.checkVersion());
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Tests that checkVersion() function succeeds in the case of a library with a
+// version function that returns the correct version number.
+
+TEST_F(LibraryManagerTest, CorrectVersionReturned) {
+ PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ // Open should succeed.
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Version check should succeed.
+ EXPECT_TRUE(lib_manager.checkVersion());
+
+ // Tidy up.
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Checks the registration of standard callouts.
+
+TEST_F(LibraryManagerTest, RegisterStandardCallouts) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check the version of the library.
+ EXPECT_TRUE(lib_manager.checkVersion());
+
+ // Load the standard callouts
+ EXPECT_NO_THROW(lib_manager.registerStandardCallouts());
+
+ // Now execute the callouts in the order expected. The library performs
+ // the calculation:
+ //
+ // r3 = (10 + d1) * d2 - d3
+ executeCallCallouts(10, 5, 15, 7, 105, 17, 88);
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Test that the "load" function is called correctly.
+
+TEST_F(LibraryManagerTest, CheckLoadCalled) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(LOAD_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check the version of the library.
+ EXPECT_TRUE(lib_manager.checkVersion());
+
+ // Load the standard callouts
+ EXPECT_NO_THROW(lib_manager.registerStandardCallouts());
+
+ // Check that only context_create and hookpt_one have callouts registered.
+ EXPECT_TRUE(callout_manager_->calloutsPresent(
+ ServerHooks::CONTEXT_CREATE));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(
+ ServerHooks::CONTEXT_DESTROY));
+
+ // Call the runLoad() method to run the load() function.
+ EXPECT_TRUE(lib_manager.runLoad());
+ EXPECT_TRUE(callout_manager_->calloutsPresent(
+ ServerHooks::CONTEXT_CREATE));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_three_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(
+ ServerHooks::CONTEXT_DESTROY));
+
+ // Now execute the callouts in the order expected. The library performs
+ // the calculation:
+ //
+ // r3 = (5 * d1 + d2) * d3
+ executeCallCallouts(5, 5, 25, 7, 32, 10, 320);
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check handling of a "load" function that throws an exception
+
+TEST_F(LibraryManagerTest, CheckLoadException) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(FRAMEWORK_EXCEPTION_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Running the load function should fail.
+ EXPECT_FALSE(lib_manager.runLoad());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check handling of a "load" function that returns an error.
+
+TEST_F(LibraryManagerTest, CheckLoadError) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(LOAD_ERROR_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check that we catch a load error
+ EXPECT_FALSE(lib_manager.runLoad());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// No unload function
+
+TEST_F(LibraryManagerTest, CheckNoUnload) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(BASIC_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check that no unload function returns true.
+ EXPECT_TRUE(lib_manager.runUnload());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Unload function returns an error
+
+TEST_F(LibraryManagerTest, CheckUnloadError) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(LOAD_ERROR_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check that unload function returning an error returns false.
+ EXPECT_FALSE(lib_manager.runUnload());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Unload function throws an exception.
+
+TEST_F(LibraryManagerTest, CheckUnloadException) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(FRAMEWORK_EXCEPTION_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check that we detect that the unload function throws an exception.
+ EXPECT_FALSE(lib_manager.runUnload());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Check that the case of the library's unload() function returning a
+// success is handled correcty.
+
+TEST_F(LibraryManagerTest, CheckUnload) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(UNLOAD_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+
+ // Check that the marker file is not present (at least that the file
+ // open fails).
+ EXPECT_FALSE(markerFilePresent());
+
+ // Check that unload function runs and returns a success
+ EXPECT_TRUE(lib_manager.runUnload());
+
+ // Check that the marker file was created.
+ EXPECT_TRUE(markerFilePresent());
+
+ // Tidy up
+ EXPECT_TRUE(lib_manager.closeLibrary());
+}
+
+// Test the operation of unloadLibrary(). We load a library with a set
+// of callouts then unload it. We need to check that the callouts have been
+// removed. We'll also check that the library's unload() function was called
+// as well.
+
+TEST_F(LibraryManagerTest, LibUnload) {
+
+ // Load the only library, specifying the index of 0 as it's the only
+ // library. This should load all callouts.
+ PublicLibraryManager lib_manager(std::string(FULL_CALLOUT_LIBRARY),
+ 0, callout_manager_);
+ EXPECT_TRUE(lib_manager.openLibrary());
+
+ // Check the version of the library.
+ EXPECT_TRUE(lib_manager.checkVersion());
+
+ // No callouts should be registered at the moment.
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+
+ // Load the single standard callout and check it is registered correctly.
+ EXPECT_NO_THROW(lib_manager.registerStandardCallouts());
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+
+ // Call the load function to load the other callouts.
+ EXPECT_TRUE(lib_manager.runLoad());
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_TRUE(callout_manager_->calloutsPresent(hookpt_three_index_));
+
+ // Unload the library and check that the callouts have been removed from
+ // the CalloutManager.
+ lib_manager.unloadLibrary();
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+}
+
+// Now come the loadLibrary() tests that make use of all the methods tested
+// above. These tests are really to make sure that the methods have been
+// tied together correctly.
+
+// First test the basic error cases - no library, no version function, version
+// function returning an error.
+
+TEST_F(LibraryManagerTest, LoadLibraryNoLibrary) {
+ LibraryManager lib_manager(std::string(NOT_PRESENT_LIBRARY), 0,
+ callout_manager_);
+ EXPECT_FALSE(lib_manager.loadLibrary());
+}
+
+// Check that the code handles the case of a library with no version function.
+
+TEST_F(LibraryManagerTest, LoadLibraryNoVersion) {
+ LibraryManager lib_manager(std::string(NO_VERSION_LIBRARY), 0,
+ callout_manager_);
+ EXPECT_FALSE(lib_manager.loadLibrary());
+}
+
+// Check that the code handles the case of a library with a version function
+// that returns an incorrect version number.
+
+TEST_F(LibraryManagerTest, LoadLibraryWrongVersion) {
+ LibraryManager lib_manager(std::string(INCORRECT_VERSION_LIBRARY), 0,
+ callout_manager_);
+ EXPECT_FALSE(lib_manager.loadLibrary());
+}
+
+// Check that the full loadLibrary call works.
+
+TEST_F(LibraryManagerTest, LoadLibrary) {
+ LibraryManager lib_manager(std::string(FULL_CALLOUT_LIBRARY), 0,
+ callout_manager_);
+ EXPECT_TRUE(lib_manager.loadLibrary());
+
+ // Now execute the callouts in the order expected. The library performs
+ // the calculation:
+ //
+ // r3 = (7 * d1 - d2) * d3
+ executeCallCallouts(7, 5, 35, 9, 26, 3, 78);
+
+ EXPECT_TRUE(lib_manager.unloadLibrary());
+
+ // Check that the callouts have been removed from the callout manager.
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_one_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_two_index_));
+ EXPECT_FALSE(callout_manager_->calloutsPresent(hookpt_three_index_));
+}
+
+// Now test for multiple libraries. We'll load the full callout library
+// first, then load some of the libraries with missing framework functions.
+// This will check that when searching for framework functions, only the
+// specified library is checked, not other loaded libraries. We will
+// load a second library with suitable callouts and check that the callouts
+// are added correctly. Finally, we'll unload one of the libraries and
+// check that only the callouts belonging to that library were removed.
+
+TEST_F(LibraryManagerTest, LoadMultipleLibraries) {
+ // Load a library with all framework functions.
+ LibraryManager lib_manager_1(std::string(FULL_CALLOUT_LIBRARY), 0,
+ callout_manager_);
+ EXPECT_TRUE(lib_manager_1.loadLibrary());
+
+ // Attempt to load a library with no version() function. We should detect
+ // this and not end up calling the function from the already loaded
+ // library.
+ LibraryManager lib_manager_2(std::string(NO_VERSION_LIBRARY), 1,
+ callout_manager_);
+ EXPECT_FALSE(lib_manager_2.loadLibrary());
+
+ // Attempt to load the library with an incorrect version. This should
+ // be detected.
+ LibraryManager lib_manager_3(std::string(INCORRECT_VERSION_LIBRARY), 1,
+ callout_manager_);
+ EXPECT_FALSE(lib_manager_3.loadLibrary());
+
+ // Load the basic callout library. This only has standard callouts so,
+ // if the first library's load() function gets called, some callouts
+ // will be registered twice and lead to incorrect results.
+ LibraryManager lib_manager_4(std::string(BASIC_CALLOUT_LIBRARY), 1,
+ callout_manager_);
+ EXPECT_TRUE(lib_manager_4.loadLibrary());
+
+ // Execute the callouts. The first library implements the calculation.
+ //
+ // r3 = (7 * d1 - d2) * d3
+ //
+ // The last-loaded library implements the calculation
+ //
+ // r3 = (10 + d1) * d2 - d3
+ //
+ // Putting the processing for each library together in the appropriate
+ // order, we get:
+ //
+ // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+ executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
+
+ // All done, so unload the first library.
+ EXPECT_TRUE(lib_manager_1.unloadLibrary());
+
+ // Now execute the callouts again and check that the results are as
+ // expected for the new calculation.
+ executeCallCallouts(10, 5, 15, 7, 105, 17, 88);
+
+ // ... and tidy up.
+ EXPECT_TRUE(lib_manager_4.unloadLibrary());
+}
+
+// Check that libraries can be validated.
+
+TEST_F(LibraryManagerTest, validateLibraries) {
+ EXPECT_TRUE(LibraryManager::validateLibrary(BASIC_CALLOUT_LIBRARY));
+ EXPECT_TRUE(LibraryManager::validateLibrary(FULL_CALLOUT_LIBRARY));
+ EXPECT_FALSE(LibraryManager::validateLibrary(FRAMEWORK_EXCEPTION_LIBRARY));
+ EXPECT_FALSE(LibraryManager::validateLibrary(INCORRECT_VERSION_LIBRARY));
+ EXPECT_TRUE(LibraryManager::validateLibrary(LOAD_CALLOUT_LIBRARY));
+ EXPECT_TRUE(LibraryManager::validateLibrary(LOAD_ERROR_CALLOUT_LIBRARY));
+ EXPECT_FALSE(LibraryManager::validateLibrary(NOT_PRESENT_LIBRARY));
+ EXPECT_FALSE(LibraryManager::validateLibrary(NO_VERSION_LIBRARY));
+ EXPECT_TRUE(LibraryManager::validateLibrary(UNLOAD_CALLOUT_LIBRARY));
+}
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/load_callout_library.cc b/src/lib/hooks/tests/load_callout_library.cc
new file mode 100644
index 0000000..ae9f470
--- /dev/null
+++ b/src/lib/hooks/tests/load_callout_library.cc
@@ -0,0 +1,119 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Basic library with load() function
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - The "version" and "load" framework functions are supplied. One "standard"
+/// callout is supplied ("hookpt_one") and two non-standard ones which are
+/// registered during the call to "load" on the hooks "hookpt_two" and
+/// "hookpt_three".
+///
+/// All callouts do trivial calculations, the result of all being called in
+/// sequence being
+///
+/// @f[ ((5 * data_1) + data_2) * data_3 @f]
+///
+/// ...where data_1, data_2 and data_3 are the values passed in arguments of
+/// the same name to the three callouts (data_1 passed to hookpt_one, data_2 to
+/// hookpt_two etc.) and the result is returned in the argument "result".
+
+#include <hooks/hooks.h>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Callouts
+
+int
+context_create(CalloutHandle& handle) {
+ handle.setContext("result", static_cast<int>(5));
+ handle.setArgument("result", static_cast<int>(5));
+ return (0);
+}
+
+// First callout adds the passed "data_1" argument to the initialized context
+// value of 5. (Note that the value set by context_create is accessed through
+// context and not the argument, so checking that context is correctly passed
+// between callouts in the same library.)
+
+int
+hookpt_one(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_1", data);
+
+ int result;
+ handle.getContext("result", result);
+
+ result *= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Second callout multiplies the current context value by the "data_2"
+// argument.
+
+static int
+hook_nonstandard_two(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_2", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result += data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Final callout adds "data_3" to the result.
+
+static int
+hook_nonstandard_three(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("data_3", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result *= data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Framework functions
+
+int
+version() {
+ return (BIND10_HOOKS_VERSION);
+}
+
+int load(LibraryHandle& handle) {
+ // Register the non-standard functions
+ handle.registerCallout("hookpt_two", hook_nonstandard_two);
+ handle.registerCallout("hookpt_three", hook_nonstandard_three);
+
+ return (0);
+}
+
+};
+
diff --git a/src/lib/hooks/tests/load_error_callout_library.cc b/src/lib/hooks/tests/load_error_callout_library.cc
new file mode 100644
index 0000000..b861d7f
--- /dev/null
+++ b/src/lib/hooks/tests/load_error_callout_library.cc
@@ -0,0 +1,49 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Error load library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - All framework functions are supplied. "version" returns the correct
+/// value, but "load" and unload return an error.
+
+#include <hooks/hooks.h>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Framework functions
+
+int
+version() {
+ return (BIND10_HOOKS_VERSION);
+}
+
+int
+load(LibraryHandle&) {
+ return (1);
+}
+
+int
+unload() {
+ return (1);
+}
+
+};
+
diff --git a/src/lib/hooks/tests/marker_file.h.in b/src/lib/hooks/tests/marker_file.h.in
new file mode 100644
index 0000000..e032cdd
--- /dev/null
+++ b/src/lib/hooks/tests/marker_file.h.in
@@ -0,0 +1,27 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 MARKER_FILE_H
+#define MARKER_FILE_H
+
+/// @file
+/// Define a marker file that is used in tests to prove that an "unload"
+/// function has been called.
+
+namespace {
+const char* MARKER_FILE = "@abs_builddir@/marker_file.dat";
+}
+
+#endif // MARKER_FILE_H
+
diff --git a/src/lib/hooks/tests/no_version_library.cc b/src/lib/hooks/tests/no_version_library.cc
new file mode 100644
index 0000000..f5b5b9c
--- /dev/null
+++ b/src/lib/hooks/tests/no_version_library.cc
@@ -0,0 +1,30 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief No version function library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - No version() function is present.
+
+extern "C" {
+
+int no_version() {
+ return (0);
+}
+
+};
diff --git a/src/lib/hooks/tests/run_unittests.cc b/src/lib/hooks/tests/run_unittests.cc
new file mode 100644
index 0000000..f68a58d
--- /dev/null
+++ b/src/lib/hooks/tests/run_unittests.cc
@@ -0,0 +1,25 @@
+// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log/logger_support.h>
+#include <util/unittests/run_all.h>
+
+#include <gtest/gtest.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/hooks/tests/server_hooks_unittest.cc b/src/lib/hooks/tests/server_hooks_unittest.cc
new file mode 100644
index 0000000..ca9b6f0
--- /dev/null
+++ b/src/lib/hooks/tests/server_hooks_unittest.cc
@@ -0,0 +1,178 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <hooks/server_hooks.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+using namespace isc;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+// Checks the registration of hooks and the interrogation methods. As the
+// constructor registers two hooks, this is also a test of the constructor.
+
+TEST(ServerHooksTest, RegisterHooks) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ // There should be two hooks already registered, with indexes 0 and 1.
+ EXPECT_EQ(2, hooks.getCount());
+ EXPECT_EQ(0, hooks.getIndex("context_create"));
+ EXPECT_EQ(1, hooks.getIndex("context_destroy"));
+
+ // Check that the constants are as expected. (The intermediate variables
+ // are used because of problems with g++ 4.6.1/Ubuntu 11.10 when resolving
+ // the value of the ServerHooks constants when they appeared within the
+ // gtest macro.)
+ const int create_value = ServerHooks::CONTEXT_CREATE;
+ const int destroy_value = ServerHooks::CONTEXT_DESTROY;
+ EXPECT_EQ(0, create_value);
+ EXPECT_EQ(1, destroy_value);
+
+ // Register another couple of hooks. The test on returned index is based
+ // on knowledge that the hook indexes are assigned in ascending order.
+ int alpha = hooks.registerHook("alpha");
+ EXPECT_EQ(2, alpha);
+ EXPECT_EQ(2, hooks.getIndex("alpha"));
+
+ int beta = hooks.registerHook("beta");
+ EXPECT_EQ(3, beta);
+ EXPECT_EQ(3, hooks.getIndex("beta"));
+
+ // Should be four hooks now
+ EXPECT_EQ(4, hooks.getCount());
+}
+
+// Check that duplicate names cannot be registered.
+
+TEST(ServerHooksTest, DuplicateHooks) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ // Ensure we can't duplicate one of the existing names.
+ EXPECT_THROW(hooks.registerHook("context_create"), DuplicateHook);
+
+ // Check we can't duplicate a newly registered hook.
+ int gamma = hooks.registerHook("gamma");
+ EXPECT_EQ(2, gamma);
+ EXPECT_THROW(hooks.registerHook("gamma"), DuplicateHook);
+}
+
+// Checks that we can get the name of the hooks.
+
+TEST(ServerHooksTest, GetHookNames) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+ vector<string> expected_names;
+
+ // Add names into the hooks object and to the set of expected names.
+ expected_names.push_back("alpha");
+ expected_names.push_back("beta");
+ expected_names.push_back("gamma");
+ expected_names.push_back("delta");
+ for (int i = 0; i < expected_names.size(); ++i) {
+ hooks.registerHook(expected_names[i].c_str());
+ };
+
+ // Update the expected names to include the pre-defined hook names.
+ expected_names.push_back("context_create");
+ expected_names.push_back("context_destroy");
+
+ // Get the actual hook names
+ vector<string> actual_names = hooks.getHookNames();
+
+ // For comparison, sort the names into alphabetical order and do a straight
+ // vector comparison.
+ sort(expected_names.begin(), expected_names.end());
+ sort(actual_names.begin(), actual_names.end());
+
+ EXPECT_TRUE(expected_names == actual_names);
+}
+
+// Test the inverse hooks functionality (i.e. given an index, get the name).
+
+TEST(ServerHooksTest, GetHookIndexes) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ int alpha = hooks.registerHook("alpha");
+ int beta = hooks.registerHook("beta");
+ int gamma = hooks.registerHook("gamma");
+
+ EXPECT_EQ(std::string("context_create"),
+ hooks.getName(ServerHooks::CONTEXT_CREATE));
+ EXPECT_EQ(std::string("context_destroy"),
+ hooks.getName(ServerHooks::CONTEXT_DESTROY));
+ EXPECT_EQ(std::string("alpha"), hooks.getName(alpha));
+ EXPECT_EQ(std::string("beta"), hooks.getName(beta));
+ EXPECT_EQ(std::string("gamma"), hooks.getName(gamma));
+
+ // Check for an invalid index
+ EXPECT_THROW(hooks.getName(-1), NoSuchHook);
+ EXPECT_THROW(hooks.getName(42), NoSuchHook);
+}
+
+// Test the reset functionality.
+
+TEST(ServerHooksTest, Reset) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ int alpha = hooks.registerHook("alpha");
+ int beta = hooks.registerHook("beta");
+ int gamma = hooks.registerHook("gamma");
+
+ // Check the counts before and after a reset.
+ EXPECT_EQ(5, hooks.getCount());
+ hooks.reset();
+ EXPECT_EQ(2, hooks.getCount());
+
+ // ... and check that the hooks are as expected.
+ EXPECT_EQ(0, hooks.getIndex("context_create"));
+ EXPECT_EQ(1, hooks.getIndex("context_destroy"));
+}
+
+// Check that getting an unknown name throws an exception.
+
+TEST(ServerHooksTest, UnknownHookName) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ EXPECT_THROW(static_cast<void>(hooks.getIndex("unknown")), NoSuchHook);
+}
+
+// Check that the count of hooks is correct.
+
+TEST(ServerHooksTest, HookCount) {
+ ServerHooks& hooks = ServerHooks::getServerHooks();
+ hooks.reset();
+
+ // Insert the names into the hooks object
+ hooks.registerHook("alpha");
+ hooks.registerHook("beta");
+ hooks.registerHook("gamma");
+ hooks.registerHook("delta");
+
+ // Should be two more hooks that the number we have registered.
+ EXPECT_EQ(6, hooks.getCount());
+}
+
+} // Anonymous namespace
diff --git a/src/lib/hooks/tests/test_libraries.h.in b/src/lib/hooks/tests/test_libraries.h.in
new file mode 100644
index 0000000..bb6a24a
--- /dev/null
+++ b/src/lib/hooks/tests/test_libraries.h.in
@@ -0,0 +1,79 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TEST_LIBRARIES_H
+#define TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+
+// Take care of differences in DLL naming between operating systems.
+
+#ifdef OS_OSX
+#define DLL_SUFFIX ".dylib"
+
+#else
+#define DLL_SUFFIX ".so"
+
+#endif
+
+
+// Names of the libraries used in these tests. These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// .so file. Note that we access the .so file - libtool creates this as a
+// like to the real shared library.
+
+// Basic library with context_create and three "standard" callouts.
+static const char* BASIC_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libbcl"
+ DLL_SUFFIX;
+
+// Library with context_create and three "standard" callouts, as well as
+// load() and unload() functions.
+static const char* FULL_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libfcl"
+ DLL_SUFFIX;
+
+// Library where the all framework functions throw an exception
+static const char* FRAMEWORK_EXCEPTION_LIBRARY = "@abs_builddir@/.libs/libfxl"
+ DLL_SUFFIX;
+
+// Library where the version() function returns an incorrect result.
+static const char* INCORRECT_VERSION_LIBRARY = "@abs_builddir@/.libs/libivl"
+ DLL_SUFFIX;
+
+// Library where some of the callout registration is done with the load()
+// function.
+static const char* LOAD_CALLOUT_LIBRARY = "@abs_builddir@/.libs/liblcl"
+ DLL_SUFFIX;
+
+// Library where the load() function returns an error.
+static const char* LOAD_ERROR_CALLOUT_LIBRARY =
+ "@abs_builddir@/.libs/liblecl" DLL_SUFFIX;
+
+// Name of a library which is not present.
+static const char* NOT_PRESENT_LIBRARY = "@abs_builddir@/.libs/libnothere"
+ DLL_SUFFIX;
+
+// Library that does not include a version function.
+static const char* NO_VERSION_LIBRARY = "@abs_builddir@/.libs/libnvl"
+ DLL_SUFFIX;
+
+// Library where there is an unload() function.
+static const char* UNLOAD_CALLOUT_LIBRARY = "@abs_builddir@/.libs/libucl"
+ DLL_SUFFIX;
+} // anonymous namespace
+
+
+#endif // TEST_LIBRARIES_H
diff --git a/src/lib/hooks/tests/unload_callout_library.cc b/src/lib/hooks/tests/unload_callout_library.cc
new file mode 100644
index 0000000..9baa830
--- /dev/null
+++ b/src/lib/hooks/tests/unload_callout_library.cc
@@ -0,0 +1,52 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file
+/// @brief Basic unload library
+///
+/// This is source of a test library for various test (LibraryManager and
+/// HooksManager). The characteristics of the library produced from this
+/// file are:
+///
+/// - The "version" and "unload" framework functions are supplied. "version"
+/// returns a valid value and "unload" creates a marker file and returns
+/// success.
+
+#include <hooks/hooks.h>
+#include <hooks/tests/marker_file.h>
+
+#include <fstream>
+
+using namespace isc::hooks;
+
+extern "C" {
+
+// Framework functions
+
+int
+version() {
+ return (BIND10_HOOKS_VERSION);
+}
+
+int
+unload() {
+ // Create the marker file.
+ std::fstream marker;
+ marker.open(MARKER_FILE, std::fstream::out);
+ marker.close();
+
+ return (0);
+}
+
+};
diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am
index 56918e9..9febc95 100644
--- a/src/lib/log/Makefile.am
+++ b/src/lib/log/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = . compiler tests
+SUBDIRS = interprocess . compiler tests
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
@@ -8,7 +8,6 @@ CLEANFILES = *.gcno *.gcda
lib_LTLIBRARIES = libb10-log.la
libb10_log_la_SOURCES =
-libb10_log_la_SOURCES += dummylog.h dummylog.cc
libb10_log_la_SOURCES += logimpl_messages.cc logimpl_messages.h
libb10_log_la_SOURCES += log_dbglevels.h
libb10_log_la_SOURCES += log_formatter.h log_formatter.cc
@@ -48,5 +47,18 @@ if USE_CLANGPP
libb10_log_la_CXXFLAGS += -Wno-error
endif
libb10_log_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
-libb10_log_la_LIBADD = $(top_builddir)/src/lib/util/libb10-util.la $(LOG4CPLUS_LIBS)
+libb10_log_la_LIBADD = $(top_builddir)/src/lib/util/libb10-util.la
+libb10_log_la_LIBADD += interprocess/libb10-log_interprocess.la
+libb10_log_la_LIBADD += $(LOG4CPLUS_LIBS)
libb10_log_la_LDFLAGS = -no-undefined -version-info 1:0:0
+
+# Specify the headers for copying into the installation directory tree. User-
+# written libraries only need the definitions for logger.h and dependencies.
+libb10_log_includedir = $(pkgincludedir)/log
+libb10_log_include_HEADERS = \
+ log_formatter.h \
+ logger.h \
+ logger_level.h \
+ macros.h \
+ message_types.h
+
diff --git a/src/lib/log/dummylog.cc b/src/lib/log/dummylog.cc
deleted file mode 100644
index 5f025e1..0000000
--- a/src/lib/log/dummylog.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include "dummylog.h"
-
-#include <iostream>
-
-using namespace std;
-
-namespace isc {
-namespace log {
-
-bool denabled = false;
-string dprefix;
-
-void dlog(const string& message,bool error_flag) {
- if (denabled || error_flag) {
- if (!dprefix.empty()) {
- cerr << "[" << dprefix << "] ";
- }
- cerr << message << endl;
- }
-}
-
-}
-}
diff --git a/src/lib/log/dummylog.h b/src/lib/log/dummylog.h
deleted file mode 100644
index 6f6ae97..0000000
--- a/src/lib/log/dummylog.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef ISC_DUMMYLOG_H
-#define ISC_DUMMYLOG_H 1
-
-#include <string>
-
-namespace isc {
-namespace log {
-
-/// Are we doing logging?
-extern bool denabled;
-/**
- * \short Prefix into logs.
- *
- * The prefix is printed in front of every log message in square brackets.
- * The usual convention is to put the name of program here.
- */
-extern std::string dprefix;
-
-/**
- * \short Temporary interface to logging.
- *
- * This is a temporary function to do logging. It has wrong interface currently
- * and should be replaced by something else. Its main purpose now is to mark
- * places where logging should happen. When it is removed, compiler will do
- * our work of finding the places.
- *
- * The only thing it does is printing the program prefix, message and
- * a newline if denabled is true.
- *
- * There are no tests for this function, since it is only temporary and
- * trivial. Tests will be written for the real logging framework when it is
- * created.
- *
- * It has the d in front of the name so it is unlikely anyone will create
- * a real logging function with the same name and the place wouldn't be found
- * as a compilation error.
- *
- * @param message The message to log. The real interface will probably have
- * more parameters.
- * \param error_flag TODO
- */
-void dlog(const std::string& message, bool error_flag=false);
-
-}
-}
-
-#endif // ISC_DUMMYLOG_H
diff --git a/src/lib/log/interprocess/Makefile.am b/src/lib/log/interprocess/Makefile.am
new file mode 100644
index 0000000..567ff09
--- /dev/null
+++ b/src/lib/log/interprocess/Makefile.am
@@ -0,0 +1,21 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -DLOCKFILE_DIR=\"${localstatedir}/${PACKAGE_NAME}\"
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+noinst_LTLIBRARIES = libb10-log_interprocess.la
+
+libb10_log_interprocess_la_SOURCES = interprocess_sync.h
+libb10_log_interprocess_la_SOURCES += interprocess_sync_file.h
+libb10_log_interprocess_la_SOURCES += interprocess_sync_file.cc
+libb10_log_interprocess_la_SOURCES += interprocess_sync_null.h
+libb10_log_interprocess_la_SOURCES += interprocess_sync_null.cc
+
+libb10_log_interprocess_la_LIBADD = $(top_builddir)/src/lib/util/threads/libb10-threads.la
+
+EXTRA_DIST = README
diff --git a/src/lib/log/interprocess/README b/src/lib/log/interprocess/README
new file mode 100644
index 0000000..e910a3a
--- /dev/null
+++ b/src/lib/log/interprocess/README
@@ -0,0 +1,13 @@
+The files in this directory implement a helper sub-library of the
+inter process locking for the log library. We use our own locks
+because such locks are only available in relatively recent versions of
+log4cplus. Also (against our usual practice) we somehow re-invented
+an in-house version of such a general purose library rather than
+existing proven tools such as boost::interprocess. While we decided
+to go with the in-house version for the log library at least until we
+completely swith to log4cplus's native lock support, no other BIND 10
+module should use this; they should use existing external
+tools/libraries.
+
+This sub-library is therefore "hidden" here. As such, none of these
+files should be installed.
diff --git a/src/lib/log/interprocess/interprocess_sync.h b/src/lib/log/interprocess/interprocess_sync.h
new file mode 100644
index 0000000..10453cc
--- /dev/null
+++ b/src/lib/log/interprocess/interprocess_sync.h
@@ -0,0 +1,151 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef INTERPROCESS_SYNC_H
+#define INTERPROCESS_SYNC_H
+
+#include <string>
+
+namespace isc {
+namespace log {
+namespace interprocess {
+
+class InterprocessSyncLocker; // forward declaration
+
+/// \brief Interprocess Sync Class
+///
+/// This class specifies an interface for mutual exclusion among
+/// co-operating processes. This is an abstract class and a real
+/// implementation such as InterprocessSyncFile should be used
+/// in code. Usage is as follows:
+///
+/// 1. Client instantiates a sync object of an implementation (such as
+/// InterprocessSyncFile).
+/// 2. Client then creates an automatic (stack) object of
+/// InterprocessSyncLocker around the sync object. Such an object
+/// destroys itself and releases any acquired lock when it goes out of extent.
+/// 3. Client calls lock() method on the InterprocessSyncLocker.
+/// 4. Client performs task that needs mutual exclusion.
+/// 5. Client frees lock with unlock(), or simply returns from the basic
+/// block which forms the scope for the InterprocessSyncLocker.
+///
+/// NOTE: All implementations of InterprocessSync should keep the
+/// is_locked_ member variable updated whenever their
+/// lock()/tryLock()/unlock() implementations are called.
+class InterprocessSync {
+ // InterprocessSyncLocker is the only code outside this class that
+ // should be allowed to call the lock(), tryLock() and unlock()
+ // methods.
+ friend class InterprocessSyncLocker;
+
+public:
+ /// \brief Constructor
+ ///
+ /// Creates an interprocess synchronization object
+ ///
+ /// \param task_name Name of the synchronization task. This has to be
+ /// identical among the various processes that need to be
+ /// synchronized for the same task.
+ InterprocessSync(const std::string& task_name) :
+ task_name_(task_name), is_locked_(false)
+ {}
+
+ /// \brief Destructor
+ virtual ~InterprocessSync() {}
+
+protected:
+ /// \brief Acquire the lock (blocks if something else has acquired a
+ /// lock on the same task name)
+ ///
+ /// \return Returns true if the lock was acquired, false otherwise.
+ virtual bool lock() = 0;
+
+ /// \brief Try to acquire a lock (doesn't block)
+ ///
+ /// \return Returns true if the lock was acquired, false otherwise.
+ virtual bool tryLock() = 0;
+
+ /// \brief Release the lock
+ ///
+ /// \return Returns true if the lock was released, false otherwise.
+ virtual bool unlock() = 0;
+
+ const std::string task_name_; ///< The task name
+ bool is_locked_; ///< Is the lock taken?
+};
+
+/// \brief Interprocess Sync Locker Class
+///
+/// This class is used for making automatic stack objects to manage
+/// locks that are released automatically when the block is exited
+/// (RAII). It is meant to be used along with InterprocessSync objects. See
+/// the description of InterprocessSync.
+class InterprocessSyncLocker {
+public:
+ /// \brief Constructor
+ ///
+ /// Creates a lock manager around a interprocess synchronization object
+ ///
+ /// \param sync The sync object which has to be locked/unlocked by
+ /// this locker object.
+ InterprocessSyncLocker(InterprocessSync& sync) :
+ sync_(sync)
+ {}
+
+ /// \brief Destructor
+ ~InterprocessSyncLocker() {
+ if (isLocked())
+ unlock();
+ }
+
+ /// \brief Acquire the lock (blocks if something else has acquired a
+ /// lock on the same task name)
+ ///
+ /// \return Returns true if the lock was acquired, false otherwise.
+ bool lock() {
+ return (sync_.lock());
+ }
+
+ /// \brief Try to acquire a lock (doesn't block)
+ ///
+ /// \return Returns true if a new lock could be acquired, false
+ /// otherwise.
+ bool tryLock() {
+ return (sync_.tryLock());
+ }
+
+ /// \brief Check if the lock is taken
+ ///
+ /// \return Returns true if a lock is currently acquired, false
+ /// otherwise.
+ bool isLocked() const {
+ return (sync_.is_locked_);
+ }
+
+ /// \brief Release the lock
+ ///
+ /// \return Returns true if the lock was released, false otherwise.
+ bool unlock() {
+ return (sync_.unlock());
+ }
+
+protected:
+ InterprocessSync& sync_; ///< Ref to underlying sync object
+};
+
+} // namespace interprocess
+} // namespace log
+} // namespace isc
+
+#endif // INTERPROCESS_SYNC_H
diff --git a/src/lib/log/interprocess/interprocess_sync_file.cc b/src/lib/log/interprocess/interprocess_sync_file.cc
new file mode 100644
index 0000000..7f8fcb4
--- /dev/null
+++ b/src/lib/log/interprocess/interprocess_sync_file.cc
@@ -0,0 +1,134 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log/interprocess/interprocess_sync_file.h>
+
+#include <string>
+#include <cerrno>
+#include <cstring>
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+namespace isc {
+namespace log {
+namespace interprocess {
+
+InterprocessSyncFile::~InterprocessSyncFile() {
+ if (fd_ != -1) {
+ // This will also release any applied locks.
+ close(fd_);
+ // The lockfile will continue to exist, and we must not delete
+ // it.
+ }
+}
+
+bool
+InterprocessSyncFile::do_lock(int cmd, short l_type) {
+ // Open lock file only when necessary (i.e., here). This is so that
+ // if a default InterprocessSync object is replaced with another
+ // implementation, it doesn't attempt any opens.
+ if (fd_ == -1) {
+ std::string lockfile_path = LOCKFILE_DIR;
+
+ const char* const env = getenv("B10_FROM_BUILD");
+ if (env != NULL) {
+ lockfile_path = env;
+ }
+
+ const char* const env2 = getenv("B10_FROM_BUILD_LOCALSTATEDIR");
+ if (env2 != NULL) {
+ lockfile_path = env2;
+ }
+
+ const char* const env3 = getenv("B10_LOCKFILE_DIR_FROM_BUILD");
+ if (env3 != NULL) {
+ lockfile_path = env3;
+ }
+
+ lockfile_path += "/" + task_name_ + "_lockfile";
+
+ // Open the lockfile in the constructor so it doesn't do the access
+ // checks every time a message is logged.
+ const mode_t mode = umask(0111);
+ fd_ = open(lockfile_path.c_str(), O_CREAT | O_RDWR, 0660);
+ umask(mode);
+
+ if (fd_ == -1) {
+ isc_throw(InterprocessSyncFileError,
+ "Unable to use interprocess sync lockfile ("
+ << std::strerror(errno) << "): " << lockfile_path);
+ }
+ }
+
+ struct flock lock;
+
+ memset(&lock, 0, sizeof (lock));
+ lock.l_type = l_type;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 1;
+
+ return (fcntl(fd_, cmd, &lock) == 0);
+}
+
+bool
+InterprocessSyncFile::lock() {
+ if (is_locked_) {
+ return (true);
+ }
+
+ if (do_lock(F_SETLKW, F_WRLCK)) {
+ is_locked_ = true;
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+InterprocessSyncFile::tryLock() {
+ if (is_locked_) {
+ return (true);
+ }
+
+ if (do_lock(F_SETLK, F_WRLCK)) {
+ is_locked_ = true;
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+InterprocessSyncFile::unlock() {
+ if (!is_locked_) {
+ return (true);
+ }
+
+ if (do_lock(F_SETLKW, F_UNLCK)) {
+ is_locked_ = false;
+ return (true);
+ }
+
+ return (false);
+}
+
+} // namespace interprocess
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/interprocess/interprocess_sync_file.h b/src/lib/log/interprocess/interprocess_sync_file.h
new file mode 100644
index 0000000..cb07003
--- /dev/null
+++ b/src/lib/log/interprocess/interprocess_sync_file.h
@@ -0,0 +1,93 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef INTERPROCESS_SYNC_FILE_H
+#define INTERPROCESS_SYNC_FILE_H
+
+#include <log/interprocess/interprocess_sync.h>
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace log {
+namespace interprocess {
+
+/// \brief InterprocessSyncFileError
+///
+/// Exception that is thrown if it's not possible to open the
+/// lock file.
+class InterprocessSyncFileError : public Exception {
+public:
+ InterprocessSyncFileError(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief File-based Interprocess Sync Class
+///
+/// This class specifies a concrete implementation for a file-based
+/// interprocess synchronization mechanism. Please see the
+/// InterprocessSync class documentation for usage.
+///
+/// An InterprocessSyncFileError exception may be thrown if there is an
+/// issue opening the lock file.
+///
+/// Lock files are created typically in the local state directory
+/// (var). They are typically named like "<task_name>_lockfile".
+/// This implementation opens lock files lazily (only when
+/// necessary). It also leaves the lock files lying around as multiple
+/// processes may have locks on them.
+class InterprocessSyncFile : public InterprocessSync {
+public:
+ /// \brief Constructor
+ ///
+ /// Creates a file-based interprocess synchronization object
+ ///
+ /// \param name Name of the synchronization task. This has to be
+ /// identical among the various processes that need to be
+ /// synchronized for the same task.
+ InterprocessSyncFile(const std::string& task_name) :
+ InterprocessSync(task_name), fd_(-1)
+ {}
+
+ /// \brief Destructor
+ virtual ~InterprocessSyncFile();
+
+protected:
+ /// \brief Acquire the lock (blocks if something else has acquired a
+ /// lock on the same task name)
+ ///
+ /// \return Returns true if the lock was acquired, false otherwise.
+ bool lock();
+
+ /// \brief Try to acquire a lock (doesn't block)
+ ///
+ /// \return Returns true if the lock was acquired, false otherwise.
+ bool tryLock();
+
+ /// \brief Release the lock
+ ///
+ /// \return Returns true if the lock was released, false otherwise.
+ bool unlock();
+
+private:
+ bool do_lock(int cmd, short l_type);
+
+ int fd_; ///< The descriptor for the open file
+};
+
+} // namespace interprocess
+} // namespace log
+} // namespace isc
+
+#endif // INTERPROCESS_SYNC_FILE_H
diff --git a/src/lib/log/interprocess/interprocess_sync_null.cc b/src/lib/log/interprocess/interprocess_sync_null.cc
new file mode 100644
index 0000000..226f722
--- /dev/null
+++ b/src/lib/log/interprocess/interprocess_sync_null.cc
@@ -0,0 +1,44 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log/interprocess/interprocess_sync_null.h>
+
+namespace isc {
+namespace log {
+namespace interprocess {
+
+InterprocessSyncNull::~InterprocessSyncNull() {
+}
+
+bool
+InterprocessSyncNull::lock() {
+ is_locked_ = true;
+ return (true);
+}
+
+bool
+InterprocessSyncNull::tryLock() {
+ is_locked_ = true;
+ return (true);
+}
+
+bool
+InterprocessSyncNull::unlock() {
+ is_locked_ = false;
+ return (true);
+}
+
+} // namespace interprocess
+} // namespace log
+} // namespace isc
diff --git a/src/lib/log/interprocess/interprocess_sync_null.h b/src/lib/log/interprocess/interprocess_sync_null.h
new file mode 100644
index 0000000..41dab50
--- /dev/null
+++ b/src/lib/log/interprocess/interprocess_sync_null.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef INTERPROCESS_SYNC_NULL_H
+#define INTERPROCESS_SYNC_NULL_H
+
+#include <log/interprocess/interprocess_sync.h>
+
+namespace isc {
+namespace log {
+namespace interprocess {
+
+/// \brief Null Interprocess Sync Class
+///
+/// This class specifies a concrete implementation for a null (no effect)
+/// interprocess synchronization mechanism. Please see the
+/// InterprocessSync class documentation for usage.
+class InterprocessSyncNull : public InterprocessSync {
+public:
+ /// \brief Constructor
+ ///
+ /// Creates a null interprocess synchronization object
+ ///
+ /// \param name Name of the synchronization task. This has to be
+ /// identical among the various processes that need to be
+ /// synchronized for the same task.
+ InterprocessSyncNull(const std::string& task_name) :
+ InterprocessSync(task_name)
+ {}
+
+ /// \brief Destructor
+ virtual ~InterprocessSyncNull();
+
+protected:
+ /// \brief Acquire the lock (never blocks)
+ ///
+ /// \return Always returns true
+ bool lock();
+
+ /// \brief Try to acquire a lock (doesn't block)
+ ///
+ /// \return Always returns true
+ bool tryLock();
+
+ /// \brief Release the lock
+ ///
+ /// \return Always returns true
+ bool unlock();
+};
+
+} // namespace interprocess
+} // namespace log
+} // namespace isc
+
+#endif // INTERPROCESS_SYNC_NULL_H
diff --git a/src/lib/log/interprocess/tests/.gitignore b/src/lib/log/interprocess/tests/.gitignore
new file mode 100644
index 0000000..d6d1ec8
--- /dev/null
+++ b/src/lib/log/interprocess/tests/.gitignore
@@ -0,0 +1 @@
+/run_unittests
diff --git a/src/lib/log/interprocess/tests/Makefile.am b/src/lib/log/interprocess/tests/Makefile.am
new file mode 100644
index 0000000..3013f99
--- /dev/null
+++ b/src/lib/log/interprocess/tests/Makefile.am
@@ -0,0 +1,37 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+# XXX: we'll pollute the top builddir for creating a temporary test file
+# used to bind a UNIX domain socket so we can minimize the risk of exceeding
+# the limit of file name path size.
+AM_CPPFLAGS += -DTEST_DATA_TOPBUILDDIR=\"$(abs_top_builddir)\"
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += interprocess_sync_file_unittest.cc
+run_unittests_SOURCES += interprocess_sync_null_unittest.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+
+run_unittests_LDADD = ../libb10-log_interprocess.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+run_unittests_LDADD += $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/log/interprocess/tests/interprocess_sync_file_unittest.cc b/src/lib/log/interprocess/tests/interprocess_sync_file_unittest.cc
new file mode 100644
index 0000000..4df365e
--- /dev/null
+++ b/src/lib/log/interprocess/tests/interprocess_sync_file_unittest.cc
@@ -0,0 +1,151 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log/interprocess/interprocess_sync_file.h>
+
+#include <util/unittests/check_valgrind.h>
+#include <util/unittests/interprocess_util.h>
+#include <gtest/gtest.h>
+#include <unistd.h>
+
+using namespace std;
+using namespace isc::log::interprocess;
+using isc::util::unittests::parentReadState;
+
+namespace {
+TEST(InterprocessSyncFileTest, TestLock) {
+ InterprocessSyncFile sync("test");
+ InterprocessSyncLocker locker(sync);
+
+ EXPECT_FALSE(locker.isLocked());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.isLocked());
+
+ if (!isc::util::unittests::runningOnValgrind()) {
+
+ int fds[2];
+
+ // Here, we check that a lock has been taken by forking and
+ // checking from the child that a lock exists. This has to be
+ // done from a separate process as we test by trying to lock the
+ // range again on the lock file. The lock attempt would pass if
+ // done from the same process for the granted range. The lock
+ // attempt must fail to pass our check.
+
+ EXPECT_EQ(0, pipe(fds));
+
+ if (fork() == 0) {
+ unsigned char locked = 0;
+ // Child writes to pipe
+ close(fds[0]);
+
+ InterprocessSyncFile sync2("test");
+ InterprocessSyncLocker locker2(sync2);
+
+ if (!locker2.tryLock()) {
+ EXPECT_FALSE(locker2.isLocked());
+ locked = 1;
+ } else {
+ EXPECT_TRUE(locker2.isLocked());
+ }
+
+ ssize_t bytes_written = write(fds[1], &locked, sizeof(locked));
+ EXPECT_EQ(sizeof(locked), bytes_written);
+
+ close(fds[1]);
+ exit(0);
+ } else {
+ // Parent reads from pipe
+ close(fds[1]);
+
+ const unsigned char locked = parentReadState(fds[0]);
+
+ close(fds[0]);
+
+ EXPECT_EQ(1, locked);
+ }
+ }
+
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_FALSE(locker.isLocked());
+
+ EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test_lockfile"));
+}
+
+TEST(InterprocessSyncFileTest, TestMultipleFilesDirect) {
+ InterprocessSyncFile sync("test1");
+ InterprocessSyncLocker locker(sync);
+
+ EXPECT_TRUE(locker.lock());
+
+ InterprocessSyncFile sync2("test2");
+ InterprocessSyncLocker locker2(sync2);
+ EXPECT_TRUE(locker2.lock());
+ EXPECT_TRUE(locker2.unlock());
+
+ EXPECT_TRUE(locker.unlock());
+
+ EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test1_lockfile"));
+ EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test2_lockfile"));
+}
+
+TEST(InterprocessSyncFileTest, TestMultipleFilesForked) {
+ InterprocessSyncFile sync("test1");
+ InterprocessSyncLocker locker(sync);
+
+ EXPECT_TRUE(locker.lock());
+
+ if (!isc::util::unittests::runningOnValgrind()) {
+
+ int fds[2];
+
+ EXPECT_EQ(0, pipe(fds));
+
+ if (fork() == 0) {
+ unsigned char locked = 0xff;
+ // Child writes to pipe
+ close(fds[0]);
+
+ InterprocessSyncFile sync2("test2");
+ InterprocessSyncLocker locker2(sync2);
+
+ if (locker2.tryLock()) {
+ locked = 0;
+ }
+
+ ssize_t bytes_written = write(fds[1], &locked, sizeof(locked));
+ EXPECT_EQ(sizeof(locked), bytes_written);
+
+ close(fds[1]);
+ exit(0);
+ } else {
+ // Parent reads from pipe
+ close(fds[1]);
+
+ const unsigned char locked = parentReadState(fds[0]);
+
+ close(fds[0]);
+
+ EXPECT_EQ(0, locked);
+ }
+
+ EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test2_lockfile"));
+ }
+
+ EXPECT_TRUE(locker.unlock());
+
+ EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test1_lockfile"));
+}
+
+} // unnamed namespace
diff --git a/src/lib/log/interprocess/tests/interprocess_sync_null_unittest.cc b/src/lib/log/interprocess/tests/interprocess_sync_null_unittest.cc
new file mode 100644
index 0000000..cc9795c
--- /dev/null
+++ b/src/lib/log/interprocess/tests/interprocess_sync_null_unittest.cc
@@ -0,0 +1,75 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log/interprocess/interprocess_sync_null.h>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::log::interprocess;
+
+namespace {
+
+TEST(InterprocessSyncNullTest, TestNull) {
+ InterprocessSyncNull sync("test1");
+ InterprocessSyncLocker locker(sync);
+
+ // Check if the is_locked_ flag is set correctly during lock().
+ EXPECT_FALSE(locker.isLocked());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.isLocked());
+
+ // lock() must always return true (this is called 4 times, just an
+ // arbitrary number)
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.lock());
+
+ // Check if the is_locked_ flag is set correctly during unlock().
+ EXPECT_TRUE(locker.isLocked());
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_FALSE(locker.isLocked());
+
+ // unlock() must always return true (this is called 4 times, just an
+ // arbitrary number)
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.unlock());
+
+ // Check if the is_locked_ flag is set correctly during tryLock().
+ EXPECT_FALSE(locker.isLocked());
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.isLocked());
+
+ // tryLock() must always return true (this is called 4 times, just an
+ // arbitrary number)
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.tryLock());
+
+ // Random order (should all return true)
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.lock());
+ EXPECT_TRUE(locker.tryLock());
+ EXPECT_TRUE(locker.unlock());
+ EXPECT_TRUE(locker.unlock());
+}
+
+}
diff --git a/src/lib/log/interprocess/tests/run_unittests.cc b/src/lib/log/interprocess/tests/run_unittests.cc
new file mode 100644
index 0000000..03fb322
--- /dev/null
+++ b/src/lib/log/interprocess/tests/run_unittests.cc
@@ -0,0 +1,25 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <gtest/gtest.h>
+#include <util/unittests/run_all.h>
+#include <stdlib.h>
+
+int
+main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ setenv("B10_LOCKFILE_DIR_FROM_BUILD", TEST_DATA_TOPBUILDDIR, 1);
+ return (isc::util::unittests::run_all());
+}
diff --git a/src/lib/log/log_dbglevels.h b/src/lib/log/log_dbglevels.h
index a459bed..ccf89b9 100644
--- a/src/lib/log/log_dbglevels.h
+++ b/src/lib/log/log_dbglevels.h
@@ -42,7 +42,7 @@
/// libraries.
///
/// This file defines a set of standard debug levels for use across all loggers.
-/// In this way users can have some expection of what will be output when
+/// In this way users can have some expectation of what will be output when
/// enabling debugging. Symbols are prefixed DBGLVL so as not to clash with
/// DBG_ symbols in the various modules.
///
diff --git a/src/lib/log/logger.cc b/src/lib/log/logger.cc
index fef5627..a04267c 100644
--- a/src/lib/log/logger.cc
+++ b/src/lib/log/logger.cc
@@ -182,7 +182,7 @@ Logger::fatal(const isc::log::MessageID& ident) {
// Replace the interprocess synchronization object
void
-Logger::setInterprocessSync(isc::util::InterprocessSync* sync) {
+Logger::setInterprocessSync(isc::log::interprocess::InterprocessSync* sync) {
getLoggerPtr()->setInterprocessSync(sync);
}
diff --git a/src/lib/log/logger.h b/src/lib/log/logger.h
index e3ba163..de2b304 100644
--- a/src/lib/log/logger.h
+++ b/src/lib/log/logger.h
@@ -25,10 +25,13 @@
#include <log/message_types.h>
#include <log/log_formatter.h>
-#include <util/interprocess_sync.h>
-
namespace isc {
namespace log {
+namespace interprocess {
+// Forward declaration to hide implementation details from normal
+// applications.
+class InterprocessSync;
+}
/// \page LoggingApi Logging API
/// \section LoggingApiOverview Overview
@@ -254,11 +257,16 @@ public:
/// If this method is called with NULL as the argument, it throws a
/// BadInterprocessSync exception.
///
+ /// \note This method is intended to be used only within this log library
+ /// and its tests. Normal application shouldn't use it (in fact,
+ /// normal application shouldn't even be able to instantiate
+ /// InterprocessSync objects).
+ ///
/// \param sync The logger uses this synchronization object for
/// synchronizing output of log messages. It should be deletable and
/// the ownership is transferred to the logger. If NULL is passed,
/// a BadInterprocessSync exception is thrown.
- void setInterprocessSync(isc::util::InterprocessSync* sync);
+ void setInterprocessSync(isc::log::interprocess::InterprocessSync* sync);
/// \brief Equality
///
diff --git a/src/lib/log/logger_impl.cc b/src/lib/log/logger_impl.cc
index 689795d..96f021d 100644
--- a/src/lib/log/logger_impl.cc
+++ b/src/lib/log/logger_impl.cc
@@ -29,18 +29,18 @@
#include <log/logger_level.h>
#include <log/logger_level_impl.h>
#include <log/logger_name.h>
+#include <log/logger_manager.h>
#include <log/message_dictionary.h>
#include <log/message_types.h>
+#include <log/interprocess/interprocess_sync_file.h>
#include <util/strutil.h>
-#include <util/interprocess_sync_file.h>
// Note: as log4cplus and the BIND 10 logger have many concepts in common, and
// thus many similar names, to disambiguate types we don't "use" the log4cplus
// namespace: instead, all log4cplus types are explicitly qualified.
using namespace std;
-using namespace isc::util;
namespace isc {
namespace log {
@@ -53,7 +53,7 @@ namespace log {
LoggerImpl::LoggerImpl(const string& name) :
name_(expandLoggerName(name)),
logger_(log4cplus::Logger::getInstance(name_)),
- sync_(new InterprocessSyncFile("logger"))
+ sync_(new interprocess::InterprocessSyncFile("logger"))
{
}
@@ -111,7 +111,8 @@ LoggerImpl::lookupMessage(const MessageID& ident) {
// Replace the interprocess synchronization object
void
-LoggerImpl::setInterprocessSync(isc::util::InterprocessSync* sync) {
+LoggerImpl::setInterprocessSync(isc::log::interprocess::InterprocessSync* sync)
+{
if (sync == NULL) {
isc_throw(BadInterprocessSync,
"NULL was passed to setInterprocessSync()");
@@ -123,10 +124,13 @@ LoggerImpl::setInterprocessSync(isc::util::InterprocessSync* sync) {
void
LoggerImpl::outputRaw(const Severity& severity, const string& message) {
+ // Use a mutex locker for mutual exclusion from other threads in
+ // this process.
+ isc::util::thread::Mutex::Locker mutex_locker(LoggerManager::getMutex());
+
// Use an interprocess sync locker for mutual exclusion from other
// processes to avoid log messages getting interspersed.
-
- InterprocessSyncLocker locker(*sync_);
+ interprocess::InterprocessSyncLocker locker(*sync_);
if (!locker.lock()) {
LOG4CPLUS_ERROR(logger_, "Unable to lock logger lockfile");
diff --git a/src/lib/log/logger_impl.h b/src/lib/log/logger_impl.h
index 7280d5c..60f4c58 100644
--- a/src/lib/log/logger_impl.h
+++ b/src/lib/log/logger_impl.h
@@ -23,6 +23,7 @@
#include <string>
#include <map>
#include <utility>
+#include <boost/noncopyable.hpp>
// log4cplus logger header file
@@ -31,8 +32,7 @@
// BIND-10 logger files
#include <log/logger_level_impl.h>
#include <log/message_types.h>
-
-#include <util/interprocess_sync.h>
+#include <log/interprocess/interprocess_sync.h>
namespace isc {
namespace log {
@@ -62,7 +62,7 @@ namespace log {
/// b) The idea of debug levels is implemented. See logger_level.h and
/// logger_level_impl.h for more details on this.
-class LoggerImpl {
+class LoggerImpl : public boost::noncopyable {
public:
/// \brief Constructor
@@ -178,7 +178,7 @@ public:
/// synchronizing output of log messages. It should be deletable and
/// the ownership is transferred to the logger implementation.
/// If NULL is passed, a BadInterprocessSync exception is thrown.
- void setInterprocessSync(isc::util::InterprocessSync* sync);
+ void setInterprocessSync(isc::log::interprocess::InterprocessSync* sync);
/// \brief Equality
///
@@ -193,7 +193,7 @@ public:
private:
std::string name_; ///< Full name of this logger
log4cplus::Logger logger_; ///< Underlying log4cplus logger
- isc::util::InterprocessSync* sync_;
+ isc::log::interprocess::InterprocessSync* sync_;
};
} // namespace log
diff --git a/src/lib/log/logger_manager.cc b/src/lib/log/logger_manager.cc
index 77893d0..047c7dc 100644
--- a/src/lib/log/logger_manager.cc
+++ b/src/lib/log/logger_manager.cc
@@ -28,7 +28,7 @@
#include <log/message_initializer.h>
#include <log/message_reader.h>
#include <log/message_types.h>
-#include "util/interprocess_sync_null.h"
+#include <log/interprocess/interprocess_sync_null.h>
using namespace std;
@@ -121,25 +121,26 @@ LoggerManager::init(const std::string& root, isc::log::Severity severity,
// Check if there were any duplicate message IDs in the default dictionary
// and if so, log them. Log using the logging facility logger.
- vector<string>& duplicates = MessageInitializer::getDuplicates();
+ const vector<string>& duplicates = MessageInitializer::getDuplicates();
if (!duplicates.empty()) {
- // There are duplicates present. This will be listed in alphabetic
- // order of message ID, so they need to be sorted. This list itself may
- // contain duplicates; if so, the message ID is listed as many times as
+ // There are duplicates present. This list itself may contain
+ // duplicates; if so, the message ID is listed as many times as
// there are duplicates.
- sort(duplicates.begin(), duplicates.end());
- for (vector<string>::iterator i = duplicates.begin();
+ for (vector<string>::const_iterator i = duplicates.begin();
i != duplicates.end(); ++i) {
LOG_WARN(logger, LOG_DUPLICATE_MESSAGE_ID).arg(*i);
}
-
+ MessageInitializer::clearDuplicates();
}
// Replace any messages with local ones (if given)
if (file) {
readLocalMessageFile(file);
}
+
+ // Ensure that the mutex is constructed and ready at this point.
+ (void) getMutex();
}
@@ -156,7 +157,8 @@ LoggerManager::readLocalMessageFile(const char* file) {
// be used by standalone programs which may not have write access to
// the local state directory (to create lock files). So we switch to
// using a null interprocess sync object here.
- logger.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
+ logger.setInterprocessSync(
+ new isc::log::interprocess::InterprocessSyncNull("logger"));
try {
@@ -194,5 +196,12 @@ LoggerManager::reset() {
LoggerManagerImpl::reset(initSeverity(), initDebugLevel());
}
+isc::util::thread::Mutex&
+LoggerManager::getMutex() {
+ static isc::util::thread::Mutex mutex;
+
+ return (mutex);
+}
+
} // namespace log
} // namespace isc
diff --git a/src/lib/log/logger_manager.h b/src/lib/log/logger_manager.h
index da49ae4..0c49757 100644
--- a/src/lib/log/logger_manager.h
+++ b/src/lib/log/logger_manager.h
@@ -16,8 +16,11 @@
#define LOGGER_MANAGER_H
#include "exceptions/exceptions.h"
+#include <util/threads/sync.h>
#include <log/logger_specification.h>
+#include <boost/noncopyable.hpp>
+
// Generated if, when updating the logging specification, an unknown
// destination is encountered.
class UnknownLoggingDestination : public isc::Exception {
@@ -40,7 +43,7 @@ class LoggerManagerImpl;
/// To isolate the underlying implementation from basic processing, the
/// LoggerManager is implemented using the "pimpl" idiom.
-class LoggerManager {
+class LoggerManager : public boost::noncopyable {
public:
/// \brief Constructor
LoggerManager();
@@ -100,6 +103,9 @@ public:
/// an attempt is made to log a message before this is function is called,
/// the results will be dependent on the underlying logging package.
///
+ /// Any duplicate log IDs encountered are reported as warning, after which
+ /// the global duplicates vector is cleared
+ ///
/// \param root Name of the root logger. This should be set to the name of
/// the program.
/// \param severity Severity at which to log
@@ -129,6 +135,11 @@ public:
/// \param file Name of the local message file
static void readLocalMessageFile(const char* file);
+ /// \brief Return a process-global mutex that's used for mutual
+ /// exclusion among threads of a single process during logging
+ /// calls.
+ static isc::util::thread::Mutex& getMutex();
+
private:
/// \brief Initialize Processing
///
diff --git a/src/lib/log/logger_manager_impl.cc b/src/lib/log/logger_manager_impl.cc
index 711eae9..9fb4d57 100644
--- a/src/lib/log/logger_manager_impl.cc
+++ b/src/lib/log/logger_manager_impl.cc
@@ -35,7 +35,10 @@
#include <log/logger_specification.h>
#include <log/buffer_appender_impl.h>
+#include <boost/lexical_cast.hpp>
+
using namespace std;
+using boost::lexical_cast;
namespace isc {
namespace log {
@@ -121,21 +124,33 @@ LoggerManagerImpl::createConsoleAppender(log4cplus::Logger& logger,
// File appender. Depending on whether a maximum size is given, either
// a standard file appender or a rolling file appender will be created.
+// In the case of the latter, we set "UseLockFile" to true so that
+// log4cplus internally avoids race in rolling over the files by multiple
+// processes. This feature isn't supported in log4cplus 1.0.x, but setting
+// the property unconditionally is okay as unknown properties are simply
+// ignored.
void
LoggerManagerImpl::createFileAppender(log4cplus::Logger& logger,
- const OutputOption& opt)
+ const OutputOption& opt)
{
// Append to existing file
- std::ios::openmode mode = std::ios::app;
+ const std::ios::openmode mode = std::ios::app;
log4cplus::SharedAppenderPtr fileapp;
if (opt.maxsize == 0) {
fileapp = log4cplus::SharedAppenderPtr(new log4cplus::FileAppender(
opt.filename, mode, opt.flush));
} else {
+ log4cplus::helpers::Properties properties;
+ properties.setProperty("File", opt.filename);
+ properties.setProperty("MaxFileSize",
+ lexical_cast<string>(opt.maxsize));
+ properties.setProperty("MaxBackupIndex",
+ lexical_cast<string>(opt.maxver));
+ properties.setProperty("ImmediateFlush", opt.flush ? "true" : "false");
+ properties.setProperty("UseLockFile", "true");
fileapp = log4cplus::SharedAppenderPtr(
- new log4cplus::RollingFileAppender(opt.filename, opt.maxsize,
- opt.maxver, opt.flush));
+ new log4cplus::RollingFileAppender(properties));
}
// use the same console layout for the files.
diff --git a/src/lib/log/logger_manager_impl.h b/src/lib/log/logger_manager_impl.h
index bcb2fc7..7a49820 100644
--- a/src/lib/log/logger_manager_impl.h
+++ b/src/lib/log/logger_manager_impl.h
@@ -55,7 +55,7 @@ public:
/// \brief Initialize Processing
///
- /// This resets the hierachy of loggers back to their defaults. This means
+ /// This resets the hierarchy of loggers back to their defaults. This means
/// that all non-root loggers (if they exist) are set to NOT_SET, and the
/// root logger reset to logging informational messages.
void processInit();
diff --git a/src/lib/log/logger_unittest_support.cc b/src/lib/log/logger_unittest_support.cc
index 4f02b07..9dc90fd 100644
--- a/src/lib/log/logger_unittest_support.cc
+++ b/src/lib/log/logger_unittest_support.cc
@@ -43,7 +43,7 @@ b10LoggerSeverity(isc::log::Severity defseverity) {
return (defseverity);
}
-// Get the debug level. This is defined by the envornment variable
+// Get the debug level. This is defined by the environment variable
// B10_LOGGER_DBGLEVEL. If not defined, a default value passed to the function
// is returned.
int
diff --git a/src/lib/log/message_exception.h b/src/lib/log/message_exception.h
index 5f1ad12..7133cd8 100644
--- a/src/lib/log/message_exception.h
+++ b/src/lib/log/message_exception.h
@@ -62,7 +62,7 @@ public:
/// \param lineno Line number on which error occurred (if > 0).
MessageException(const char* file, size_t line, const char* what,
MessageID id, const std::string& arg1, int lineno)
- : isc::Exception(file, line, what), id_(id)
+ : isc::Exception(file, line, what), id_(id), lineno_(lineno)
{
if (lineno > 0) {
args_.push_back(boost::lexical_cast<std::string>(lineno));
@@ -82,7 +82,7 @@ public:
MessageException(const char* file, size_t line, const char *what,
MessageID id, const std::string& arg1,
const std::string& arg2, int lineno)
- : isc::Exception(file, line, what), id_(id)
+ : isc::Exception(file, line, what), id_(id), lineno_(lineno)
{
if (lineno > 0) {
args_.push_back(boost::lexical_cast<std::string>(lineno));
diff --git a/src/lib/log/message_initializer.cc b/src/lib/log/message_initializer.cc
index 3dd5da7..3277047 100644
--- a/src/lib/log/message_initializer.cc
+++ b/src/lib/log/message_initializer.cc
@@ -42,7 +42,13 @@ size_t& getIndex() {
return (index);
}
+// Return the duplicates singleton version (non-const for local use)
+std::vector<std::string>&
+getNonConstDuplicates() {
+ static std::vector<std::string> duplicates;
+ return (duplicates);
}
+} // end unnamed namespace
namespace isc {
@@ -67,7 +73,7 @@ MessageInitializer::getPendingCount() {
// into the global dictionary.
void
-MessageInitializer::loadDictionary() {
+MessageInitializer::loadDictionary(bool ignore_duplicates) {
MessageDictionary& global = MessageDictionary::globalDictionary();
for (size_t i = 0; i < getIndex(); ++i) {
@@ -75,8 +81,8 @@ MessageInitializer::loadDictionary() {
// Append the IDs in the list just loaded (the "repeats") to the
// global list of duplicate IDs.
- if (!repeats.empty()) {
- std::vector<std::string>& duplicates = getDuplicates();
+ if (!ignore_duplicates && !repeats.empty()) {
+ std::vector<std::string>& duplicates = getNonConstDuplicates();
duplicates.insert(duplicates.end(), repeats.begin(),
repeats.end());
}
@@ -88,11 +94,16 @@ MessageInitializer::loadDictionary() {
getIndex() = 0;
}
-// Return reference to duplicate array
+// Return reference to duplicates vector
+const std::vector<std::string>&
+MessageInitializer::getDuplicates() {
+ return (getNonConstDuplicates());
+}
-std::vector<std::string>& MessageInitializer::getDuplicates() {
- static std::vector<std::string> duplicates;
- return (duplicates);
+// Clear the duplicates vector
+void
+MessageInitializer::clearDuplicates() {
+ getNonConstDuplicates().clear();
}
} // namespace log
diff --git a/src/lib/log/message_initializer.h b/src/lib/log/message_initializer.h
index 3be973d..ae67484 100644
--- a/src/lib/log/message_initializer.h
+++ b/src/lib/log/message_initializer.h
@@ -43,6 +43,9 @@ namespace log {
/// All that needed is for the module containing the definitions to be
/// included in the execution unit.
///
+/// Dynamically loaded modules should call the initializer as well on the
+/// moment they are instantiated.
+///
/// To avoid static initialization fiasco problems, the initialization is
/// carried out in two stages:
/// - The constructor adds a pointer to the values array to a pre-defined array
@@ -93,7 +96,11 @@ public:
/// Loops through the internal array of pointers to message arrays
/// and adds the messages to the internal dictionary. This is called
/// during run-time initialization.
- static void loadDictionary();
+ ///
+ /// \param ignore_duplicates If true, duplicate IDs, and IDs already
+ /// loaded, are ignored instead of stored in the global duplicates
+ /// vector.
+ static void loadDictionary(bool ignore_duplicates = false);
/// \brief Return Duplicates
///
@@ -102,7 +109,12 @@ public:
///
/// \return List of duplicate message IDs when the global dictionary was
/// loaded. Note that the duplicates list itself may contain duplicates.
- static std::vector<std::string>& getDuplicates();
+ static const std::vector<std::string>& getDuplicates();
+
+ /// \brief Clear the static duplicates vector
+ ///
+ /// Empties the vector returned by getDuplicates()
+ static void clearDuplicates();
};
} // namespace log
diff --git a/src/lib/log/message_reader.cc b/src/lib/log/message_reader.cc
index b5a4d35..d54c41d 100644
--- a/src/lib/log/message_reader.cc
+++ b/src/lib/log/message_reader.cc
@@ -127,7 +127,7 @@ void
MessageReader::parsePrefix(const vector<string>& tokens) {
// Should not get here unless there is something in the tokens array.
- assert(tokens.size() > 0);
+ assert(!tokens.empty());
// 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
diff --git a/src/lib/log/tests/Makefile.am b/src/lib/log/tests/Makefile.am
index 5683842..92303e0 100644
--- a/src/lib/log/tests/Makefile.am
+++ b/src/lib/log/tests/Makefile.am
@@ -10,7 +10,7 @@ if USE_STATIC_LINK
AM_LDFLAGS += -static
endif
-CLEANFILES = *.gcno *.gcda
+CLEANFILES = *.gcno *.gcda *.lock
EXTRA_DIST = log_test_messages.mes
BUILT_SOURCES = log_test_messages.h log_test_messages.cc
@@ -46,6 +46,8 @@ buffer_logger_test_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
buffer_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
buffer_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
+# This test directly uses libb10-threads, and on some systems it seems to
+# require explicit LDADD (even if libb10-log has indirect dependencies)
noinst_PROGRAMS += logger_lock_test
logger_lock_test_SOURCES = logger_lock_test.cc
nodist_logger_lock_test_SOURCES = log_test_messages.cc log_test_messages.h
@@ -53,6 +55,7 @@ logger_lock_test_CPPFLAGS = $(AM_CPPFLAGS)
logger_lock_test_LDFLAGS = $(AM_LDFLAGS)
logger_lock_test_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
logger_lock_test_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+logger_lock_test_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
logger_lock_test_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
logger_lock_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
@@ -126,11 +129,17 @@ initializer_unittests_2_LDFLAGS = $(AM_LDFLAGS)
noinst_PROGRAMS += $(TESTS)
endif
+noinst_SCRIPTS = console_test.sh
+noinst_SCRIPTS += destination_test.sh
+noinst_SCRIPTS += init_logger_test.sh
+noinst_SCRIPTS += local_file_test.sh
+noinst_SCRIPTS += logger_lock_test.sh
+noinst_SCRIPTS += severity_test.sh
+
# Additional test using the shell. These are principally tests
# where the global logging environment is affected, and where the
# output needs to be compared with stored output (where "cut" and
# "diff" are useful utilities).
-
check-local:
$(SHELL) $(abs_builddir)/console_test.sh
$(SHELL) $(abs_builddir)/destination_test.sh
@@ -139,11 +148,3 @@ check-local:
$(SHELL) $(abs_builddir)/local_file_test.sh
$(SHELL) $(abs_builddir)/logger_lock_test.sh
$(SHELL) $(abs_builddir)/severity_test.sh
-
-noinst_SCRIPTS = console_test.sh
-noinst_SCRIPTS += destination_test.sh
-noinst_SCRIPTS += init_logger_test.sh
-noinst_SCRIPTS += buffer_logger_test.sh
-noinst_SCRIPTS += local_file_test.sh
-noinst_SCRIPTS += logger_lock_test.sh
-noinst_SCRIPTS += severity_test.sh
diff --git a/src/lib/log/tests/buffer_logger_test.cc b/src/lib/log/tests/buffer_logger_test.cc
index 8d1b3cf..d703e04 100644
--- a/src/lib/log/tests/buffer_logger_test.cc
+++ b/src/lib/log/tests/buffer_logger_test.cc
@@ -16,7 +16,7 @@
#include <log/logger_support.h>
#include <log/logger_manager.h>
#include <log/log_messages.h>
-#include <util/interprocess_sync_null.h>
+#include <log/interprocess/interprocess_sync_null.h>
using namespace isc::log;
@@ -58,7 +58,8 @@ main(int argc, char** argv) {
initLogger("buffertest", isc::log::INFO, 0, NULL, true);
Logger logger("log");
// No need for file interprocess locking in this test
- logger.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
+ logger.setInterprocessSync(
+ new isc::log::interprocess::InterprocessSyncNull("logger"));
LOG_INFO(logger, LOG_BAD_SEVERITY).arg("info");
LOG_DEBUG(logger, 50, LOG_BAD_DESTINATION).arg("debug-50");
LOG_INFO(logger, LOG_BAD_SEVERITY).arg("info");
diff --git a/src/lib/log/tests/logger_example.cc b/src/lib/log/tests/logger_example.cc
index 08c9084..daadb7c 100644
--- a/src/lib/log/tests/logger_example.cc
+++ b/src/lib/log/tests/logger_example.cc
@@ -41,11 +41,11 @@
// Include a set of message definitions.
#include <log/log_messages.h>
-#include "util/interprocess_sync_null.h"
+#include <log/interprocess/interprocess_sync_null.h>
using namespace isc::log;
using namespace std;
-
+using isc::log::interprocess::InterprocessSyncNull;
// Print usage information
@@ -134,7 +134,7 @@ int main(int argc, char** argv) {
// the parsing starts (console output for the BIND 10 root logger). This
// is included in the logging specifications UNLESS the first switch on
// the command line is a "-l" flag starting a new logger. To track this,
- // the "sw_found" flag is set when a switch is completey processed. The
+ // the "sw_found" flag is set when a switch is completely processed. The
// processing of "-l" will only add information for a previous logger to
// the list if this flag is set.
while ((option = getopt(argc, argv, "hc:d:f:l:m:s:y:z:")) != -1) {
@@ -286,11 +286,11 @@ int main(int argc, char** argv) {
// have write access to a local state directory to create
// lockfiles).
isc::log::Logger logger_ex(ROOT_NAME);
- logger_ex.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
+ logger_ex.setInterprocessSync(new InterprocessSyncNull("logger"));
isc::log::Logger logger_alpha("alpha");
- logger_alpha.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
+ logger_alpha.setInterprocessSync(new InterprocessSyncNull("logger"));
isc::log::Logger logger_beta("beta");
- logger_beta.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
+ logger_beta.setInterprocessSync(new InterprocessSyncNull("logger"));
LOG_FATAL(logger_ex, LOG_WRITE_ERROR).arg("test1").arg("42");
LOG_ERROR(logger_ex, LOG_READING_LOCAL_FILE).arg("dummy/file");
diff --git a/src/lib/log/tests/logger_lock_test.cc b/src/lib/log/tests/logger_lock_test.cc
index d63989c..9b9ee17 100644
--- a/src/lib/log/tests/logger_lock_test.cc
+++ b/src/lib/log/tests/logger_lock_test.cc
@@ -14,15 +14,19 @@
#include <log/macros.h>
#include <log/logger_support.h>
+#include <log/logger_manager.h>
#include <log/log_messages.h>
-#include "util/interprocess_sync.h"
+#include <log/interprocess/interprocess_sync.h>
#include "log_test_messages.h"
+
+#include <util/threads/sync.h>
#include <iostream>
using namespace std;
using namespace isc::log;
+using isc::util::thread::Mutex;
-class MockLoggingSync : public isc::util::InterprocessSync {
+class MockLoggingSync : public isc::log::interprocess::InterprocessSync {
public:
/// \brief Constructor
MockLoggingSync(const std::string& component_name) :
@@ -31,6 +35,15 @@ public:
protected:
virtual bool lock() {
+ // We first check if the logger acquired a lock on the
+ // LoggerManager mutex.
+ try {
+ // This lock attempt is non-blocking.
+ Mutex::Locker locker(LoggerManager::getMutex(), false);
+ } catch (Mutex::Locker::AlreadyLocked& e) {
+ cout << "FIELD1 FIELD2 LOGGER_LOCK_TEST: MUTEXLOCK\n";
+ }
+
cout << "FIELD1 FIELD2 LOGGER_LOCK_TEST: LOCK\n";
return (true);
}
diff --git a/src/lib/log/tests/logger_lock_test.sh.in b/src/lib/log/tests/logger_lock_test.sh.in
index c664c91..147998c 100755
--- a/src/lib/log/tests/logger_lock_test.sh.in
+++ b/src/lib/log/tests/logger_lock_test.sh.in
@@ -31,6 +31,7 @@ passfail() {
echo -n "Testing that logger acquires and releases locks correctly:"
cat > $tempfile << .
+LOGGER_LOCK_TEST: MUTEXLOCK
LOGGER_LOCK_TEST: LOCK
INFO [bind10.log] LOG_LOCK_TEST_MESSAGE this is a test message.
LOGGER_LOCK_TEST: UNLOCK
diff --git a/src/lib/log/tests/logger_manager_unittest.cc b/src/lib/log/tests/logger_manager_unittest.cc
index 584d0f5..e615c41 100644
--- a/src/lib/log/tests/logger_manager_unittest.cc
+++ b/src/lib/log/tests/logger_manager_unittest.cc
@@ -77,7 +77,11 @@ public:
// Destructor, remove the file. This is only a test, so ignore failures
~SpecificationForFileLogger() {
if (! name_.empty()) {
- (void) unlink(name_.c_str());
+ static_cast<void>(unlink(name_.c_str()));
+
+ // Depending on the log4cplus version, a lock file may also be
+ // created.
+ static_cast<void>(unlink((name_ + ".lock").c_str()));
}
}
diff --git a/src/lib/log/tests/logger_unittest.cc b/src/lib/log/tests/logger_unittest.cc
index 7b62d79..77a9d2a 100644
--- a/src/lib/log/tests/logger_unittest.cc
+++ b/src/lib/log/tests/logger_unittest.cc
@@ -20,10 +20,9 @@
#include <log/logger_manager.h>
#include <log/logger_name.h>
#include <log/log_messages.h>
+#include <log/interprocess/interprocess_sync_file.h>
#include "log/tests/log_test_messages.h"
-#include <util/interprocess_sync_file.h>
-
#include <iostream>
#include <string>
@@ -391,7 +390,7 @@ TEST_F(LoggerTest, setInterprocessSync) {
EXPECT_THROW(logger.setInterprocessSync(NULL), BadInterprocessSync);
}
-class MockSync : public isc::util::InterprocessSync {
+class MockSync : public isc::log::interprocess::InterprocessSync {
public:
/// \brief Constructor
MockSync(const std::string& component_name) :
diff --git a/src/lib/log/tests/message_dictionary_unittest.cc b/src/lib/log/tests/message_dictionary_unittest.cc
index b8bded3..065641a 100644
--- a/src/lib/log/tests/message_dictionary_unittest.cc
+++ b/src/lib/log/tests/message_dictionary_unittest.cc
@@ -189,9 +189,8 @@ TEST_F(MessageDictionaryTest, GlobalTest) {
// new symbol.
TEST_F(MessageDictionaryTest, GlobalLoadTest) {
- vector<string>& duplicates = MessageInitializer::getDuplicates();
- ASSERT_EQ(1, duplicates.size());
- EXPECT_EQ(string("LOG_DUPLICATE_NAMESPACE"), duplicates[0]);
+ // There were duplicates but the vector should be cleared in init() now
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
string text = MessageDictionary::globalDictionary().getText("NEWSYM");
EXPECT_EQ(string("new symbol added"), text);
diff --git a/src/lib/log/tests/message_initializer_1_unittest.cc b/src/lib/log/tests/message_initializer_1_unittest.cc
index 994174c..761231d 100644
--- a/src/lib/log/tests/message_initializer_1_unittest.cc
+++ b/src/lib/log/tests/message_initializer_1_unittest.cc
@@ -77,3 +77,37 @@ TEST(MessageInitializerTest1, MessageTest) {
EXPECT_EQ(string("global message five"), global.getText("GLOBAL5"));
EXPECT_EQ(string("global message six"), global.getText("GLOBAL6"));
}
+
+TEST(MessageInitializerTest1, Duplicates) {
+ // Original set should not have dupes
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
+
+ // This just defines 1, but we'll add it a number of times
+ const char* dupe[] = {
+ "DUPE", "dupe",
+ NULL
+ };
+ const MessageInitializer init_message_initializer_unittest_1(dupe);
+ const MessageInitializer init_message_initializer_unittest_2(dupe);
+
+ MessageInitializer::loadDictionary();
+ // Should be a dupe now
+ ASSERT_EQ(1, MessageInitializer::getDuplicates().size());
+
+ // clear them
+ MessageInitializer::clearDuplicates();
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
+
+ // Do it again to make sure, let's explicitly provide false now
+ const MessageInitializer init_message_initializer_unittest_3(dupe);
+ MessageInitializer::loadDictionary(false);
+ ASSERT_EQ(1, MessageInitializer::getDuplicates().size());
+
+ // Loading with ignore_duplicates=true should result in no (reported)
+ // dupes
+ MessageInitializer::clearDuplicates();
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
+ const MessageInitializer init_message_initializer_unittest_4(dupe);
+ MessageInitializer::loadDictionary(true);
+ ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
+}
diff --git a/src/lib/nsas/TODO b/src/lib/nsas/TODO
index 4a20690..92ca0bb 100644
--- a/src/lib/nsas/TODO
+++ b/src/lib/nsas/TODO
@@ -21,7 +21,7 @@ Long term:
tricky, though, because we need to be thread safe. There seems to be
solution to use weak_ptr inside the hash_table instead of shared_ptr and
catch the exception inside get() (and getOrAdd) and delete the dead pointer.
-* Better way to dispatch all calbacks in a list is needed. We take them out of
+* Better way to dispatch all callbacks in a list is needed. We take them out of
the list and dispatch them one by one. This is wrong because when an
exception happens inside the callback, we lose the ones not dispatched yet.
diff --git a/src/lib/nsas/address_request_callback.h b/src/lib/nsas/address_request_callback.h
index 457c587..e43dfe2 100644
--- a/src/lib/nsas/address_request_callback.h
+++ b/src/lib/nsas/address_request_callback.h
@@ -42,7 +42,7 @@ namespace nsas {
class AddressRequestCallback {
public:
- /// Default constructor, copy contructor and assignment operator
+ /// Default constructor, copy constructor and assignment operator
/// are implicitly present and are OK.
/// \brief Virtual Destructor
diff --git a/src/lib/nsas/nameserver_entry.cc b/src/lib/nsas/nameserver_entry.cc
index bca8f73..8bed10d 100644
--- a/src/lib/nsas/nameserver_entry.cc
+++ b/src/lib/nsas/nameserver_entry.cc
@@ -91,7 +91,7 @@ NameserverEntry::getAddresses(AddressVector& addresses,
if (!has_address_[family] && expect_address_[family]) {
return IN_PROGRESS;
}
- // If we do not expect the address, then fall trough to READY
+ // If we do not expect the address, then fall through to READY
case EXPIRED: // If expired_ok, we pretend to be ready
case READY:
if (!has_address_[family]) {
@@ -149,7 +149,7 @@ NameserverEntry::setAddressRTT(const IOAddress& address, uint32_t rtt) {
}
}
- // Hack. C++ does not allow ++ on enums, enumerating trough them is pain
+ // Hack. C++ does not allow ++ on enums, enumerating through them is pain
switch (family) {
case V4_ONLY: family = V6_ONLY; break;
default: return;
diff --git a/src/lib/nsas/nameserver_entry.h b/src/lib/nsas/nameserver_entry.h
index 77a640d..3ffdf10 100644
--- a/src/lib/nsas/nameserver_entry.h
+++ b/src/lib/nsas/nameserver_entry.h
@@ -168,8 +168,8 @@ public:
* \short Update RTT of an address.
*
* This is similar to updateAddressRTTAtIndex, but you pass the address,
- * not it's index. Passing the index might be unsafe, because the position
- * of the address or the cound of addresses may change in time.
+ * not its index. Passing the index might be unsafe, because the position
+ * of the address or the count of addresses may change in time.
*
* \param rtt Round-Trip Time
* \param address The address whose RTT should be updated.
@@ -235,7 +235,7 @@ public:
* \param resolver Who to ask.
* \param callback The callback.
* \param family Which addresses are interesting to the caller. This does
- * not change which adresses are requested, but the callback might
+ * not change which addresses are requested, but the callback might
* be executed when at last one requested type is available (eg. not
* waiting for the other one).
* \return The state the entry is currently in. It can return UNREACHABLE
diff --git a/src/lib/nsas/tests/hash_unittest.cc b/src/lib/nsas/tests/hash_unittest.cc
index 251e4b1..f71d4b3 100644
--- a/src/lib/nsas/tests/hash_unittest.cc
+++ b/src/lib/nsas/tests/hash_unittest.cc
@@ -76,7 +76,7 @@ TEST_F(HashTest, Algorithm) {
const int size = HASHTABLE_DEFAULT_SIZE; // Size of the hash table
Hash hash(size, 255, false);// Hashing algorithm object with seed
// randomisation disabled
- string base = "alphabeta"; // Base of the names to behashed
+ string base = "alphabeta"; // Base of the names to be hashed
vector<uint32_t> values; // Results stored here
// Generate hash values
diff --git a/src/lib/nsas/tests/nameserver_entry_unittest.cc b/src/lib/nsas/tests/nameserver_entry_unittest.cc
index 3aca08f..e0ec0ad 100644
--- a/src/lib/nsas/tests/nameserver_entry_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_entry_unittest.cc
@@ -59,10 +59,10 @@ protected:
};
private:
/**
- * \short Fills an rrset into the NameserverEntry trough resolver.
+ * \short Fills an rrset into the NameserverEntry through resolver.
*
* This function is used when we want to pass data to a NameserverEntry
- * trough the resolver.
+ * through the resolver.
* \param resolver The resolver used by the NameserverEntry
* \param index Index of the query in the resolver.
* \param set The answer. If the pointer is empty, it is taken
@@ -79,7 +79,7 @@ private:
}
}
protected:
- /// Fills the nameserver entry with data trough ask IP
+ /// Fills the nameserver entry with data through ask IP
void fillNSEntry(boost::shared_ptr<NameserverEntry> entry,
RRsetPtr rrv4, RRsetPtr rrv6)
{
diff --git a/src/lib/nsas/tests/zone_entry_unittest.cc b/src/lib/nsas/tests/zone_entry_unittest.cc
index 1982299..8754906 100644
--- a/src/lib/nsas/tests/zone_entry_unittest.cc
+++ b/src/lib/nsas/tests/zone_entry_unittest.cc
@@ -101,7 +101,7 @@ protected:
/**
* \short Function returning a new zone.
*
- * Convenience funcion, just creating a new zone, to shorten the code.
+ * Convenience function, just creating a new zone, to shorten the code.
*/
boost::shared_ptr<InheritedZoneEntry> getZone() {
return (boost::shared_ptr<InheritedZoneEntry>(new InheritedZoneEntry(
@@ -228,7 +228,7 @@ TEST_F(ZoneEntryTest, ChangedNS) {
EXPECT_EQ(Fetchable::IN_PROGRESS, zone->getState());
EXPECT_NO_THROW(resolver_->provideNS(0, rr_single_));
// It should not be answered yet, it should ask for the IP addresses
- // (trough the NameserverEntry there)
+ // (through the NameserverEntry there)
EXPECT_TRUE(callback_->successes_.empty());
EXPECT_EQ(0, callback_->unreachable_count_);
EXPECT_TRUE(resolver_->asksIPs(ns_name_, 1, 2));
@@ -372,7 +372,7 @@ TEST_F(ZoneEntryTest, CallbacksAOnly) {
EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[1].getAddress()));
EXPECT_EQ(1, callback_->unreachable_count_);
- // Everything arriwed, so we are ready
+ // Everything arrived, so we are ready
EXPECT_EQ(Fetchable::READY, zone->getState());
// Try asking something more and see it asks no more
zone->addCallback(callback_, V4_ONLY);
diff --git a/src/lib/nsas/zone_entry.cc b/src/lib/nsas/zone_entry.cc
index 8a72e5f..6d15533 100644
--- a/src/lib/nsas/zone_entry.cc
+++ b/src/lib/nsas/zone_entry.cc
@@ -93,7 +93,7 @@ class ZoneEntry::ResolverCallback :
* If there are in the hash table, it is used. If not, they are
* created. This might still fail, if the list is empty.
*
- * It then calls process, to go trough the list of nameservers,
+ * It then calls process, to go through the list of nameservers,
* examining them and seeing if some addresses are already there
* and to ask for the rest of them.
*/
@@ -389,7 +389,7 @@ class ZoneEntry::NameserverCallback : public NameserverEntry::Callback {
* \short Callback method.
*
* This is called by NameserverEntry when the change happens.
- * We just call process to go trough relevant nameservers and call
+ * We just call process to go through relevant nameservers and call
* any callbacks we can.
*/
virtual void operator()(NameserverPtr ns) {
@@ -451,8 +451,8 @@ ZoneEntry::process(AddressFamily family,
* one handle it when we return to it.
*
* If we didn't do it, one instance would call "resolve". If it
- * was from cache, it would imediatelly recurse back to another
- * process (trough the nameserver callback, etc), which would
+ * was from cache, it would immediately recurse back to another
+ * process (through the nameserver callback, etc), which would
* take that only one nameserver and trigger all callbacks.
* Only then would resolve terminate and we could ask for the
* second nameserver. This way, we first receive all the
diff --git a/src/lib/nsas/zone_entry.h b/src/lib/nsas/zone_entry.h
index 7e5ab5b..b0c26c3 100644
--- a/src/lib/nsas/zone_entry.h
+++ b/src/lib/nsas/zone_entry.h
@@ -125,7 +125,7 @@ protected:
// Which nameservers didn't have any of our callbacks yet
std::set<NameserverPtr> nameservers_not_asked_;
/*
- * Callbacks. For each fimily type one vector, so we can process
+ * Callbacks. For each family type one vector, so we can process
* them separately.
*/
std::vector<boost::shared_ptr<AddressRequestCallback> >
@@ -139,7 +139,7 @@ private:
/**
* \short Process all the callbacks that can be processed
*
- * The purpose of this funtion is to ask all nameservers for their IP
+ * The purpose of this function is to ask all nameservers for their IP
* addresses and execute all callbacks that can be executed. It is
* called whenever new callback appears and there's a chance it could
* be answered or when new information is available (list of nameservers,
diff --git a/src/lib/python/bind10_config.py.in b/src/lib/python/bind10_config.py.in
index 9cd8d66..e56e908 100644
--- a/src/lib/python/bind10_config.py.in
+++ b/src/lib/python/bind10_config.py.in
@@ -24,14 +24,16 @@ def reload():
global PLUGIN_PATHS
global PREFIX
global LIBEXECPATH
+ global SYSCONFPATH
BIND10_MSGQ_SOCKET_FILE = os.path.join("@localstatedir@",
"@PACKAGE_NAME@",
"msgq_socket").replace("${prefix}",
"@prefix@")
PREFIX = "@prefix@"
+ SYSCONFPATH="@sysconfdir@/@PACKAGE@".replace('${prefix}', PREFIX)
# B10_FROM_SOURCE is set in the environment for internal tests and
- # an experimental run without installagion. In that case we need to
+ # an experimental run without installation. In that case we need to
# specialize some configuration variables, generally so that they refer
# to somewhere in the source tree instead of the appropriate places
# after installation.
@@ -49,6 +51,9 @@ def reload():
# When "FROM_SOURCE", it lists the directories where the programs are
# built so that when BIND 10 is experimentally started on the source
# tree the programs in the tree (not installed ones) will be used.
+ # SYSCONFPATH: Path where the system-wide configuration files are
+ # stored (e.g. <prefix>/var/<package name>). This value is *not*
+ # overwritten if B10_FROM_SOURCE is specified.
#
# B10_FROM_SOURCE_LOCALSTATEDIR is specifically intended to be used for
# tests where we want to use various types of configuration within the test
diff --git a/src/lib/python/isc/Makefile.am b/src/lib/python/isc/Makefile.am
index 712843e..9d0a8ce 100644
--- a/src/lib/python/isc/Makefile.am
+++ b/src/lib/python/isc/Makefile.am
@@ -1,5 +1,9 @@
SUBDIRS = datasrc util cc config dns log net notify testutils acl bind10
SUBDIRS += xfrin log_messages server_common ddns sysinfo statistics
+if USE_SHARED_MEMORY
+# The memory manager is useless without shared memory support
+SUBDIRS += memmgr
+endif
python_PYTHON = __init__.py
diff --git a/src/lib/python/isc/acl/tests/Makefile.am b/src/lib/python/isc/acl/tests/Makefile.am
index e0a1895..efe3664 100644
--- a/src/lib/python/isc/acl/tests/Makefile.am
+++ b/src/lib/python/isc/acl/tests/Makefile.am
@@ -7,7 +7,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/bind10/component.py b/src/lib/python/isc/bind10/component.py
index 2efb376..4621fae 100644
--- a/src/lib/python/isc/bind10/component.py
+++ b/src/lib/python/isc/bind10/component.py
@@ -490,7 +490,7 @@ class Component(BaseComponent):
class Configurator:
"""
This thing keeps track of configuration changes and starts and stops
- components as it goes. It also handles the inital startup and final
+ components as it goes. It also handles the initial startup and final
shutdown.
Note that this will allow you to stop (by invoking reconfigure) a core
diff --git a/src/lib/python/isc/bind10/sockcreator.py b/src/lib/python/isc/bind10/sockcreator.py
index db9e6c5..bb4250a 100644
--- a/src/lib/python/isc/bind10/sockcreator.py
+++ b/src/lib/python/isc/bind10/sockcreator.py
@@ -26,7 +26,7 @@ from libutil_io_python import recv_fd
logger = isc.log.Logger("init")
"""
-Module that comunicates with the privileged socket creator (b10-sockcreator).
+Module that communicates with the privileged socket creator (b10-sockcreator).
"""
class CreatorError(Exception):
@@ -251,7 +251,7 @@ class Creator(Parser):
"""Function used before running a program that needs to run as a
different user."""
# Put us into a separate process group so we don't get
- # SIGINT signals on Ctrl-C (b10-init will shut everthing down by
+ # SIGINT signals on Ctrl-C (b10-init will shut everything down by
# other means).
os.setpgrp()
diff --git a/src/lib/python/isc/bind10/tests/Makefile.am b/src/lib/python/isc/bind10/tests/Makefile.am
index 196a8b9..12fb948 100644
--- a/src/lib/python/isc/bind10/tests/Makefile.am
+++ b/src/lib/python/isc/bind10/tests/Makefile.am
@@ -9,7 +9,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/bind10/tests/component_test.py b/src/lib/python/isc/bind10/tests/component_test.py
index adc035e..0fde13e 100644
--- a/src/lib/python/isc/bind10/tests/component_test.py
+++ b/src/lib/python/isc/bind10/tests/component_test.py
@@ -513,7 +513,7 @@ class ComponentTests(InitUtils, unittest.TestCase):
def test_pid_not_running(self):
"""
- Test that a componet that is not yet started doesn't have a PID.
+ Test that a component that is not yet started doesn't have a PID.
But it won't fail if asked for and return None.
"""
for component_type in [Component,
diff --git a/src/lib/python/isc/bind10/tests/sockcreator_test.py b/src/lib/python/isc/bind10/tests/sockcreator_test.py
index f67781c..e87387c 100644
--- a/src/lib/python/isc/bind10/tests/sockcreator_test.py
+++ b/src/lib/python/isc/bind10/tests/sockcreator_test.py
@@ -306,7 +306,7 @@ class WrapTests(unittest.TestCase):
p1.close()
p1 = socket.fromfd(t2.read_fd(), socket.AF_UNIX, socket.SOCK_STREAM)
- # Now, pass some data trough the socket
+ # Now, pass some data through the socket
p1.send(b'A')
data = p2.recv(1)
self.assertEqual(b'A', data)
diff --git a/src/lib/python/isc/bind10/tests/socket_cache_test.py b/src/lib/python/isc/bind10/tests/socket_cache_test.py
index bbbf776..6a53a27 100644
--- a/src/lib/python/isc/bind10/tests/socket_cache_test.py
+++ b/src/lib/python/isc/bind10/tests/socket_cache_test.py
@@ -58,7 +58,7 @@ class SocketTest(Test):
def test_init(self):
"""
- Checks the intrnals of the cache just after the creation.
+ Checks the internals of the cache just after the creation.
"""
self.assertEqual('UDP', self.__socket.protocol)
self.assertEqual(self.__address, self.__socket.address)
@@ -72,7 +72,7 @@ class SocketTest(Test):
"""
Check it closes the socket when removed.
"""
- # This should make the refcount 0 and call the descructor
+ # This should make the refcount 0 and call the destructor
# right away
self.__socket = None
self.assertEqual([42], self._closes)
@@ -357,7 +357,7 @@ class SocketCacheTest(Test):
}
self.__cache._live_tokens = set(['t1', 't2', 't3'])
self.assertEqual([], self._closes)
- # We cheat here little bit, the t3 doesn't exist enywhere else, but
+ # We cheat here little bit, the t3 doesn't exist anywhere else, but
# we need to check the app isn't removed too soon and it shouldn't
# matter anywhere else, so we just avoid the tiresome filling in
self.__cache._active_apps = {1: set(['t1', 't3']), 2: set(['t2'])}
diff --git a/src/lib/python/isc/cc/Makefile.am b/src/lib/python/isc/cc/Makefile.am
index f7c5b00..fe7d747 100644
--- a/src/lib/python/isc/cc/Makefile.am
+++ b/src/lib/python/isc/cc/Makefile.am
@@ -7,6 +7,7 @@ pylogmessagedir = $(pyexecdir)/isc/log_messages/
CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/pycc_messages.py
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/pycc_messages.pyc
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/pycc_messages.pyo
EXTRA_DIST = pycc_messages.mes proto_defs.py
diff --git a/src/lib/python/isc/cc/cc_generated/Makefile.am b/src/lib/python/isc/cc/cc_generated/Makefile.am
index 87e49c1..bc8d478 100644
--- a/src/lib/python/isc/cc/cc_generated/Makefile.am
+++ b/src/lib/python/isc/cc/cc_generated/Makefile.am
@@ -26,6 +26,7 @@ CLEANDIRS = __pycache__
CLEANFILES = proto_defs.py __init__.py
CLEANFILES += proto_defs.pyc __init__.pyc
+CLEANFILES += proto_defs.pyo __init__.pyo
clean-local:
rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/cc/data.py b/src/lib/python/isc/cc/data.py
index 636e9a9..3411411 100644
--- a/src/lib/python/isc/cc/data.py
+++ b/src/lib/python/isc/cc/data.py
@@ -217,7 +217,7 @@ def set(element, identifier, value):
id, list_indices = split_identifier_list_indices(id_parts[-1])
if list_indices is None:
- # value can be an empty list or dict, so check for None eplicitely
+ # value can be an empty list or dict, so check for None explicitly
if value is not None:
cur_el[id] = value
else:
@@ -231,7 +231,7 @@ def set(element, identifier, value):
if len(cur_el) <= list_index:
raise DataNotFoundError("List index at " + identifier + " out of range")
cur_el = cur_el[list_index]
- # value can be an empty list or dict, so check for None eplicitely
+ # value can be an empty list or dict, so check for None explicitly
list_index = list_indices[-1]
if type(cur_el) != list:
raise DataTypeError("Element at " + identifier + " is not a list")
diff --git a/src/lib/python/isc/cc/session.py b/src/lib/python/isc/cc/session.py
index 036c078..636dd08 100644
--- a/src/lib/python/isc/cc/session.py
+++ b/src/lib/python/isc/cc/session.py
@@ -57,17 +57,19 @@ class Session:
try:
self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self._socket.connect(self.socket_file)
- self.sendmsg({ "type": "getlname" })
+ self.sendmsg({ CC_HEADER_TYPE: CC_COMMAND_GET_LNAME })
env, msg = self.recvmsg(False)
if not env:
raise ProtocolError("Could not get local name")
- self._lname = msg["lname"]
+ self._lname = msg[CC_PAYLOAD_LNAME]
if not self._lname:
raise ProtocolError("Could not get local name")
logger.debug(logger.DBGLVL_TRACE_BASIC, PYCC_LNAME_RECEIVED,
self._lname)
except socket.error as se:
- raise SessionError(se)
+ if self._socket:
+ self._socket.close()
+ raise SessionError(se)
@property
def lname(self):
@@ -125,9 +127,10 @@ class Session:
if len(self._queue) > 0:
i = 0;
for env, msg in self._queue:
- if seq != None and "reply" in env and seq == env["reply"]:
+ if seq != None and CC_HEADER_REPLY in env and \
+ seq == env[CC_HEADER_REPLY]:
return self._queue.pop(i)
- elif seq == None and "reply" not in env:
+ elif seq == None and CC_HEADER_REPLY not in env:
return self._queue.pop(i)
else:
i = i + 1
@@ -141,7 +144,9 @@ class Session:
if data_length > 0:
env = isc.cc.message.from_wire(data[2:header_length+2])
msg = isc.cc.message.from_wire(data[header_length + 2:])
- if (seq == None and "reply" not in env) or (seq != None and "reply" in env and seq == env["reply"]):
+ if (seq == None and CC_HEADER_REPLY not in env) or \
+ (seq != None and CC_HEADER_REPLY in env and
+ seq == env[CC_HEADER_REPLY]):
return env, msg
else:
self._queue.append((env,msg))
@@ -248,18 +253,18 @@ class Session:
self._sequence += 1
return self._sequence
- def group_subscribe(self, group, instance = "*"):
+ def group_subscribe(self, group, instance=CC_INSTANCE_WILDCARD):
self.sendmsg({
- "type": "subscribe",
- "group": group,
- "instance": instance,
+ CC_HEADER_TYPE: CC_COMMAND_SUBSCRIBE,
+ CC_HEADER_GROUP: group,
+ CC_HEADER_INSTANCE: instance,
})
- def group_unsubscribe(self, group, instance = "*"):
+ def group_unsubscribe(self, group, instance=CC_INSTANCE_WILDCARD):
self.sendmsg({
- "type": "unsubscribe",
- "group": group,
- "instance": instance,
+ CC_HEADER_TYPE: CC_COMMAND_UNSUBSCRIBE,
+ CC_HEADER_GROUP: group,
+ CC_HEADER_INSTANCE: instance,
})
def group_sendmsg(self, msg, group, instance=CC_INSTANCE_WILDCARD,
@@ -308,13 +313,13 @@ class Session:
def group_reply(self, routing, msg):
seq = self._next_sequence()
self.sendmsg({
- "type": "send",
- "from": self._lname,
- "to": routing["from"],
- "group": routing["group"],
- "instance": routing["instance"],
- "seq": seq,
- "reply": routing["seq"],
+ CC_HEADER_TYPE: CC_COMMAND_SEND,
+ CC_HEADER_FROM: self._lname,
+ CC_HEADER_TO: routing[CC_HEADER_FROM],
+ CC_HEADER_GROUP: routing[CC_HEADER_GROUP],
+ CC_HEADER_INSTANCE: routing[CC_HEADER_INSTANCE],
+ CC_HEADER_SEQ: seq,
+ CC_HEADER_REPLY: routing[CC_HEADER_SEQ],
}, isc.cc.message.to_wire(msg))
return seq
diff --git a/src/lib/python/isc/cc/tests/Makefile.am b/src/lib/python/isc/cc/tests/Makefile.am
index 4c2acc0..7b4288c 100644
--- a/src/lib/python/isc/cc/tests/Makefile.am
+++ b/src/lib/python/isc/cc/tests/Makefile.am
@@ -10,7 +10,7 @@ EXTRA_DIST += test_session.py
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/cc/tests/session_test.py b/src/lib/python/isc/cc/tests/session_test.py
index 8de1e96..65afb6c 100644
--- a/src/lib/python/isc/cc/tests/session_test.py
+++ b/src/lib/python/isc/cc/tests/session_test.py
@@ -257,7 +257,7 @@ class testSession(unittest.TestCase):
self.assertEqual({"hello": "a"}, msg)
self.assertFalse(sess.has_queued_msgs())
- # ask for a differe sequence number reply (that doesn't exist)
+ # ask for a different sequence number reply (that doesn't exist)
# then ask for the one that is there
self.assertFalse(sess.has_queued_msgs())
sess._socket.addrecv({'to': 'someone', 'reply': 1}, {"hello": "a"})
@@ -270,7 +270,7 @@ class testSession(unittest.TestCase):
self.assertEqual({"hello": "a"}, msg)
self.assertFalse(sess.has_queued_msgs())
- # ask for a differe sequence number reply (that doesn't exist)
+ # ask for a different sequence number reply (that doesn't exist)
# then ask for any message
self.assertFalse(sess.has_queued_msgs())
sess._socket.addrecv({'to': 'someone', 'reply': 1}, {"hello": "a"})
@@ -285,7 +285,7 @@ class testSession(unittest.TestCase):
#print("sending message {'to': 'someone', 'reply': 1}, {'hello': 'a'}")
- # ask for a differe sequence number reply (that doesn't exist)
+ # ask for a different sequence number reply (that doesn't exist)
# send a new message, ask for specific message (get the first)
# then ask for any message (get the second)
self.assertFalse(sess.has_queued_msgs())
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index a5858a7..4dd73b7 100644
--- a/src/lib/python/isc/config/ccsession.py
+++ b/src/lib/python/isc/config/ccsession.py
@@ -82,22 +82,22 @@ def parse_answer(msg):
containing an error message"""
if type(msg) != dict:
raise ModuleCCSessionError("Answer message is not a dict: " + str(msg))
- if 'result' not in msg:
+ if CC_PAYLOAD_RESULT not in msg:
raise ModuleCCSessionError("answer message does not contain 'result' element")
- elif type(msg['result']) != list:
+ elif type(msg[CC_PAYLOAD_RESULT]) != list:
raise ModuleCCSessionError("wrong result type in answer message")
- elif len(msg['result']) < 1:
+ elif len(msg[CC_PAYLOAD_RESULT]) < 1:
raise ModuleCCSessionError("empty result list in answer message")
- elif type(msg['result'][0]) != int:
+ elif type(msg[CC_PAYLOAD_RESULT][0]) != int:
raise ModuleCCSessionError("wrong rcode type in answer message")
else:
- if len(msg['result']) > 1:
- if (msg['result'][0] != CC_REPLY_SUCCESS and
- type(msg['result'][1]) != str):
+ if len(msg[CC_PAYLOAD_RESULT]) > 1:
+ if (msg[CC_PAYLOAD_RESULT][0] != CC_REPLY_SUCCESS and
+ type(msg[CC_PAYLOAD_RESULT][1]) != str):
raise ModuleCCSessionError("rcode in answer message is non-zero, value is not a string")
- return msg['result'][0], msg['result'][1]
+ return msg[CC_PAYLOAD_RESULT][0], msg[CC_PAYLOAD_RESULT][1]
else:
- return msg['result'][0], None
+ return msg[CC_PAYLOAD_RESULT][0], None
def create_answer(rcode, arg = None):
"""Creates an answer packet for config&commands. rcode must be an
@@ -109,9 +109,9 @@ def create_answer(rcode, arg = None):
if rcode != CC_REPLY_SUCCESS and type(arg) != str:
raise ModuleCCSessionError("arg in create_answer for rcode != 0 must be a string describing the error")
if arg != None:
- return { 'result': [ rcode, arg ] }
+ return { CC_PAYLOAD_RESULT: [ rcode, arg ] }
else:
- return { 'result': [ rcode ] }
+ return { CC_PAYLOAD_RESULT: [ rcode ] }
# 'fixed' commands
"""Fixed names for command and configuration messages"""
@@ -133,7 +133,7 @@ def parse_command(msg):
string. If it is not, this function returns None, None"""
if type(msg) == dict and len(msg.items()) == 1:
cmd, value = msg.popitem()
- if cmd == "command" and type(value) == list:
+ if cmd == CC_PAYLOAD_COMMAND and type(value) == list:
if len(value) == 1 and type(value[0]) == str:
return value[0], None
elif len(value) > 1 and type(value[0]) == str:
@@ -150,7 +150,7 @@ def create_command(command_name, params = None):
cmd = [ command_name ]
if params:
cmd.append(params)
- msg = { 'command': cmd }
+ msg = { CC_PAYLOAD_COMMAND: cmd }
return msg
def default_logconfig_handler(new_config, config_data):
@@ -194,7 +194,7 @@ class ModuleCCSession(ConfigData):
it will read the system-wide Logging configuration and call
the logger manager to apply it. It will also inform the
logger manager when the logging configuration gets updated.
- The module does not need to do anything except intializing
+ The module does not need to do anything except initializing
its loggers, and provide log messages. Defaults to true.
socket_file: If cc_session was none, this optional argument
@@ -215,11 +215,14 @@ class ModuleCCSession(ConfigData):
self._session = Session(socket_file)
else:
self._session = cc_session
- self._session.group_subscribe(self._module_name, "*")
+ self._session.group_subscribe(self._module_name, CC_INSTANCE_WILDCARD)
self._remote_module_configs = {}
self._remote_module_callbacks = {}
+ self._notification_callbacks = {}
+ self._last_notif_id = 0
+
if handle_logging_config:
self.add_remote_config(path_search('logging.spec', bind10_config.PLUGIN_PATHS),
default_logconfig_handler)
@@ -228,7 +231,8 @@ class ModuleCCSession(ConfigData):
# If the CC Session obejct has been closed, it returns
# immediately.
if self._session._closed: return
- self._session.group_unsubscribe(self._module_name, "*")
+ self._session.group_unsubscribe(self._module_name,
+ CC_INSTANCE_WILDCARD)
for module_name in self._remote_module_configs:
self._session.group_unsubscribe(module_name)
@@ -293,11 +297,30 @@ class ModuleCCSession(ConfigData):
configuration update. Calls the corresponding handler
functions if present. Responds on the channel if the
handler returns a message."""
- # should we default to an answer? success-by-default? unhandled error?
- if msg is not None and not 'result' in msg:
+ if msg is None:
+ return
+ if CC_PAYLOAD_NOTIFICATION in msg:
+ group_s = env[CC_HEADER_GROUP].split('/', 1)
+ # What to do with these bogus inputs? We just ignore them for now.
+ if len(group_s) != 2:
+ return
+ [prefix, group] = group_s
+ if prefix + '/' != CC_GROUP_NOTIFICATION_PREFIX:
+ return
+ # Now, get the callbacks and call one by one
+ callbacks = self._notification_callbacks.get(group, {})
+ event = msg[CC_PAYLOAD_NOTIFICATION][0]
+ params = None
+ if len(msg[CC_PAYLOAD_NOTIFICATION]) > 1:
+ params = msg[CC_PAYLOAD_NOTIFICATION][1]
+ for key in sorted(callbacks.keys()):
+ callbacks[key](event, params)
+ elif not CC_PAYLOAD_RESULT in msg:
+ # should we default to an answer? success-by-default? unhandled
+ # error?
answer = None
try:
- module_name = env['group']
+ module_name = env[CC_HEADER_GROUP]
cmd, arg = isc.config.ccsession.parse_command(msg)
if cmd == COMMAND_CONFIG_UPDATE:
new_config = arg
@@ -529,7 +552,7 @@ class ModuleCCSession(ConfigData):
seq = self._session.group_sendmsg(cmd, group, instance=instance,
to=to, want_answer=True)
# For non-blocking, we'll have rpc_call_async (once the nonblock
- # actualy works)
+ # actually works)
reply, rheaders = self._session.group_recvmsg(nonblock=False, seq=seq)
code, value = parse_answer(reply)
if code == CC_REPLY_NO_RECPT:
@@ -538,6 +561,100 @@ class ModuleCCSession(ConfigData):
raise RPCError(code, value)
return value
+ def notify(self, notification_group, event_name, params=None):
+ """
+ Send a notification to subscribed users.
+
+ Send a notification message to all users subscribed to the given
+ notification group.
+
+ This method does not block.
+
+ See docs/design/ipc-high.txt for details about notifications
+ and the format of messages sent.
+
+ Throws:
+ - CCSessionError: for low-level communication errors.
+ Params:
+ - notification_group (string): This parameter (indirectly) signifies
+ what users should receive the notification. Only users that
+ subscribed to notifications on the same group receive it.
+ - event_name (string): The name of the event to notify about (for
+ example `new_group_member`).
+ - params: Other parameters that describe the event. This might be, for
+ example, the ID of the new member and the name of the group. This can
+ be any data that can be sent over the isc.cc.Session, but it is
+ common for it to be dict.
+ Returns: Nothing
+ """
+ notification = [event_name]
+ if params is not None:
+ notification.append(params)
+ self._session.group_sendmsg({CC_PAYLOAD_NOTIFICATION: notification},
+ CC_GROUP_NOTIFICATION_PREFIX +
+ notification_group,
+ instance=CC_INSTANCE_WILDCARD,
+ to=CC_TO_WILDCARD,
+ want_answer=False)
+
+ def subscribe_notification(self, notification_group, callback):
+ """
+ Subscribe to receive notifications in given notification group. When a
+ notification comes to the group, the callback is called with two
+ parameters, the name of the event (the value of `event_name` parameter
+ passed to `notify`) and the parameters of the event (the value
+ of `params` passed to `notify`).
+
+ This is a fast operation (there may be communication with the message
+ queue daemon, but it does not wait for any remote process).
+
+ The callback may get called multiple times (once for each notification).
+ It is possible to subscribe multiple callbacks for the same notification,
+ by multiple calls of this method, and they will be called in the order
+ of registration when the notification comes.
+
+ Throws:
+ - CCSessionError: for low-level communication errors.
+ Params:
+ - notification_group (string): Notification group to subscribe to.
+ Notification with the same value of the same parameter of `notify`
+ will be received.
+ - callback (callable): The callback to be called whenever the
+ notification comes.
+
+ The callback should not raise exceptions, such exceptions are
+ likely to propagate through the loop and terminate program.
+ Returns: Opaque id of the subscription. It can be used to cancel
+ the subscription by unsubscribe_notification.
+ """
+ self._last_notif_id += 1
+ my_id = self._last_notif_id
+ if notification_group in self._notification_callbacks:
+ self._notification_callbacks[notification_group][my_id] = callback
+ else:
+ self._session.group_subscribe(CC_GROUP_NOTIFICATION_PREFIX +
+ notification_group)
+ self._notification_callbacks[notification_group] = \
+ { my_id: callback }
+ return (notification_group, my_id)
+
+ def unsubscribe_notification(self, nid):
+ """
+ Remove previous subscription for notifications. Pass the id returned
+ from subscribe_notification.
+
+ Throws:
+ - CCSessionError: for low-level communication errors.
+ - KeyError: The id does not correspond to valid subscription.
+ """
+ (group, cid) = nid
+ del self._notification_callbacks[group][cid]
+ if not self._notification_callbacks[group]:
+ # Removed the last one
+ self._session.group_unsubscribe(CC_GROUP_NOTIFICATION_PREFIX +
+ group)
+ del self._notification_callbacks[group]
+
class UIModuleCCSession(MultiConfigData):
"""This class is used in a configuration user interface. It contains
specific functions for getting, displaying, and sending
diff --git a/src/lib/python/isc/config/cfgmgr.py b/src/lib/python/isc/config/cfgmgr.py
index bc24cbb..d100aa8 100644
--- a/src/lib/python/isc/config/cfgmgr.py
+++ b/src/lib/python/isc/config/cfgmgr.py
@@ -28,6 +28,7 @@ import tempfile
import json
import errno
from isc.cc import data
+from isc.cc.proto_defs import *
from isc.config import ccsession, config_data, module_spec
from isc.util.file import path_search
import bind10_config
@@ -221,7 +222,7 @@ class ConfigManagerData:
class ConfigManager:
"""Creates a configuration manager. The data_path is the path
- to the directory containing the configuraton file,
+ to the directory containing the configuration file,
database_filename points to the configuration file.
If session is set, this will be used as the communication
channel session. If not, a new session will be created.
@@ -441,9 +442,9 @@ class ConfigManager:
# polymorphism instead of branching. But it just might turn out it
# will get solved by itself when we move everything to virtual modules
# (which is possible solution to the offline configuration problem)
- # or when we solve the incorect behaviour here when a config is
+ # or when we solve the incorrect behaviour here when a config is
# rejected (spying modules don't know it was rejected and some modules
- # might have been commited already).
+ # might have been committed already).
if module_name in self.virtual_modules:
# The module is virtual, so call it to get the answer
try:
@@ -603,7 +604,7 @@ class ConfigManager:
# ignore 'None' value (even though they should not occur)
# and messages that are answers to questions we did
# not ask
- if msg is not None and not 'result' in msg:
+ if msg is not None and not CC_PAYLOAD_RESULT in msg:
answer = self.handle_msg(msg);
# Only respond if there actually is something to respond with
if answer is not None:
diff --git a/src/lib/python/isc/config/tests/Makefile.am b/src/lib/python/isc/config/tests/Makefile.am
index cb59e6f..8c79c84 100644
--- a/src/lib/python/isc/config/tests/Makefile.am
+++ b/src/lib/python/isc/config/tests/Makefile.am
@@ -8,7 +8,7 @@ EXTRA_DIST += unittest_fakesession.py
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/config/tests/ccsession_test.py b/src/lib/python/isc/config/tests/ccsession_test.py
index 3c1c57e..9c33ef6 100644
--- a/src/lib/python/isc/config/tests/ccsession_test.py
+++ b/src/lib/python/isc/config/tests/ccsession_test.py
@@ -350,6 +350,103 @@ class TestModuleCCSession(unittest.TestCase):
self.assertRaises(RPCRecipientMissing, self.rpc_check,
{"result": [-1, "Error"]})
+ def test_notify(self):
+ """
+ Test the sent notification has the right format.
+ """
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ mccs.notify("group", "event", {"param": True})
+ self.assertEqual([
+ ["notifications/group", "*", {"notification": ["event", {
+ "param": True
+ }]}, False]], fake_session.message_queue)
+
+ def test_notify_no_params(self):
+ """
+ Test the sent notification has the right format, this time
+ without passing parameters.
+ """
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ mccs.notify("group", "event")
+ self.assertEqual([
+ ["notifications/group", "*", {"notification": ["event"]},
+ False]
+ ],
+ fake_session.message_queue)
+
+ def test_notify_receive(self):
+ """
+ Test we can subscribe to notifications, receive them, unsubscribe, etc.
+ """
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ fake_session.group_sendmsg({"notification": ["event", {
+ "param": True
+ }]}, "notifications/group")
+ # Not subscribed to notifications -> not subscribed to
+ # 'notifications/group' -> message not eaten yet
+ mccs.check_command()
+ self.assertEqual(fake_session.message_queue, [['notifications/group',
+ None, {'notification': ['event', {'param': True}]},
+ False]])
+ # Place to log called notifications
+ notifications = []
+ def notified(tag, event, params):
+ notifications.append((tag, event, params))
+ # Subscribe to the notifications. Twice.
+ id1 = mccs.subscribe_notification('group',
+ lambda event, params:
+ notified("first", event, params))
+ id2 = mccs.subscribe_notification('group',
+ lambda event, params:
+ notified("second", event,
+ params))
+ # Now the message gets eaten because we are subscribed, and both
+ # callbacks are called.
+ mccs.check_command()
+ self.assertEqual(fake_session.message_queue, [])
+ self.assertEqual(notifications, [
+ ("first", "event", {'param': True}),
+ ("second", "event", {'param': True})
+ ])
+ del notifications[:]
+ # If a notification for different group comes, it is left untouched.
+ fake_session.group_sendmsg({"notification": ["event", {
+ "param": True
+ }]}, "notifications/other")
+ mccs.check_command()
+ self.assertEqual(notifications, [])
+ self.assertEqual(fake_session.message_queue, [['notifications/other',
+ None, {'notification': ['event', {'param': True}]},
+ False]])
+ del fake_session.message_queue[:]
+ # Unsubscribe one of the notifications and see that only the other
+ # is triggered.
+ mccs.unsubscribe_notification(id2)
+ fake_session.group_sendmsg({"notification": ["event", {
+ "param": True
+ }]}, "notifications/group")
+ mccs.check_command()
+ self.assertEqual(fake_session.message_queue, [])
+ self.assertEqual(notifications, [
+ ("first", "event", {'param': True})
+ ])
+ del notifications[:]
+ # If we try to unsubscribe again, it complains.
+ self.assertRaises(KeyError, mccs.unsubscribe_notification, id2)
+ # Unsubscribe the other one too. From now on, it doesn't eat the
+ # messages again.
+ mccs.unsubscribe_notification(id1)
+ fake_session.group_sendmsg({"notification": ["event", {
+ "param": True
+ }]}, "notifications/group")
+ mccs.check_command()
+ self.assertEqual(fake_session.message_queue, [['notifications/group',
+ None, {'notification': ['event', {'param': True}]},
+ False]])
+
def my_config_handler_ok(self, new_config):
return isc.config.ccsession.create_answer(0)
@@ -734,7 +831,7 @@ class TestModuleCCSession(unittest.TestCase):
mccs.check_command()
self.assertEqual(len(fake_session.message_queue), 0)
- def test_ignore_commant_remote_module(self):
+ def test_ignore_command_remote_module(self):
"""
Test that commands for remote modules aren't handled.
Remote module specified by the spec file name.
@@ -742,7 +839,7 @@ class TestModuleCCSession(unittest.TestCase):
self._common_remote_module_test(
self._internal_ignore_command_remote_module)
- def test_ignore_commant_remote_module_by_name(self):
+ def test_ignore_command_remote_module_by_name(self):
"""
Test that commands for remote modules aren't handled.
Remote module specified by its name.
diff --git a/src/lib/python/isc/config/tests/cfgmgr_test.py b/src/lib/python/isc/config/tests/cfgmgr_test.py
index d99fb86..ee6c98d 100644
--- a/src/lib/python/isc/config/tests/cfgmgr_test.py
+++ b/src/lib/python/isc/config/tests/cfgmgr_test.py
@@ -201,7 +201,7 @@ class TestConfigManager(unittest.TestCase):
def test_paths(self):
"""
- Test data_path and database filename is passed trough to
+ Test data_path and database filename is passed through to
underlying ConfigManagerData.
"""
cm = ConfigManager("datapath", "filename", self.fake_session)
@@ -527,7 +527,7 @@ class TestConfigManager(unittest.TestCase):
self.fake_session.get_message("Cmdctl", None))
# but if the 'stopping' module is either unknown or not running,
- # no followup message should be sent
+ # no follow-up message should be sent
self._handle_msg_helper({ "command":
[ "stopping",
{ "module_name": "NoSuchModule" } ] },
@@ -541,7 +541,7 @@ class TestConfigManager(unittest.TestCase):
# We run the same three times, with different return values
def single_test(value, returnFunc, expectedResult):
# Because closures can't assign to closed-in variables, we pass
- # it trough self
+ # it through self
self.called_with = None
def check_test(new_data):
self.called_with = new_data
diff --git a/src/lib/python/isc/datasrc/Makefile.am b/src/lib/python/isc/datasrc/Makefile.am
index 28c87ac..4278805 100644
--- a/src/lib/python/isc/datasrc/Makefile.am
+++ b/src/lib/python/isc/datasrc/Makefile.am
@@ -10,6 +10,7 @@ python_PYTHON = __init__.py sqlite3_ds.py
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += $(SQLITE_CFLAGS)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
python_LTLIBRARIES = datasrc.la
datasrc_la_SOURCES = datasrc.cc datasrc.h
@@ -21,6 +22,9 @@ datasrc_la_SOURCES += journal_reader_python.cc journal_reader_python.h
datasrc_la_SOURCES += configurableclientlist_python.cc
datasrc_la_SOURCES += configurableclientlist_python.h
datasrc_la_SOURCES += zone_loader_python.cc zone_loader_python.h
+datasrc_la_SOURCES += zonetable_accessor_python.cc zonetable_accessor_python.h
+datasrc_la_SOURCES += zonetable_iterator_python.cc zonetable_iterator_python.h
+datasrc_la_SOURCES += zonewriter_python.cc zonewriter_python.h
datasrc_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
datasrc_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
@@ -32,11 +36,13 @@ datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/python/libb10-pydnspp.la
datasrc_la_LIBADD += $(PYTHON_LIB)
EXTRA_DIST = client_inc.cc
+EXTRA_DIST += configurableclientlist_inc.cc
EXTRA_DIST += finder_inc.cc
EXTRA_DIST += iterator_inc.cc
EXTRA_DIST += updater_inc.cc
EXTRA_DIST += journal_reader_inc.cc
EXTRA_DIST += zone_loader_inc.cc
+EXTRA_DIST += zonewriter_inc.cc
CLEANDIRS = __pycache__
diff --git a/src/lib/python/isc/datasrc/client_inc.cc b/src/lib/python/isc/datasrc/client_inc.cc
index 7b6fac5..a59ff85 100644
--- a/src/lib/python/isc/datasrc/client_inc.cc
+++ b/src/lib/python/isc/datasrc/client_inc.cc
@@ -177,7 +177,7 @@ datasource, or when an internal error occurs.\n\
\n\
The default implementation throws isc.datasrc.NotImplemented. This allows for\n\
easy and fast deployment of minimal custom data sources, where the\n\
-user/implementator doesn't have to care about anything else but the\n\
+user/implementer doesn't have to care about anything else but the\n\
actual queries. Also, in some cases, it isn't possible to traverse the\n\
zone from logic point of view (eg. dynamically generated zone data).\n\
\n\
diff --git a/src/lib/python/isc/datasrc/client_python.cc b/src/lib/python/isc/datasrc/client_python.cc
index a30ae38..3eed80e 100644
--- a/src/lib/python/isc/datasrc/client_python.cc
+++ b/src/lib/python/isc/datasrc/client_python.cc
@@ -25,7 +25,7 @@
#include <datasrc/client.h>
#include <datasrc/factory.h>
#include <datasrc/database.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/zone_iterator.h>
#include <datasrc/client_list.h>
@@ -162,7 +162,7 @@ DataSourceClient_getIterator(PyObject* po_self, PyObject* args) {
try {
bool separate_rrs = false;
if (separate_rrs_obj != NULL) {
- // store result in local var so we can explicitely check for
+ // store result in local var so we can explicitly check for
// -1 error return value
int separate_rrs_true = PyObject_IsTrue(separate_rrs_obj);
if (separate_rrs_true == 1) {
diff --git a/src/lib/python/isc/datasrc/configurableclientlist_inc.cc b/src/lib/python/isc/datasrc/configurableclientlist_inc.cc
new file mode 100644
index 0000000..b16a6bb
--- /dev/null
+++ b/src/lib/python/isc/datasrc/configurableclientlist_inc.cc
@@ -0,0 +1,134 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+namespace {
+
+const char* const ConfigurableClientList_doc = "\
+The list of data source clients\n\
+\n\
+The purpose is to have several data source clients of the same class\
+and then be able to search through them to identify the one containing\
+a given zone.\n\
+\n\
+Unlike the C++ version, we don't have the abstract base class. Abstract\
+classes are not needed due to the duck typing nature of python.\
+";
+
+const char* const ConfigurableClientList_configure_doc = "\
+configure(configuration, allow_cache) -> None\n\
+\n\
+Wrapper around C++ ConfigurableClientList::configure\n\
+\n\
+This sets the active configuration. It fills the ConfigurableClientList with\
+corresponding data source clients.\n\
+\n\
+If any error is detected, an exception is raised and the previous\
+configuration preserved.\n\
+\n\
+Parameters:\n\
+ configuration The configuration, as a JSON encoded string.\
+ allow_cache If caching is allowed.\
+";
+
+const char* const ConfigurableClientList_reset_memory_segment_doc = "\
+reset_memory_segment(datasrc_name, mode, config_params) -> None\n\
+\n\
+This method resets the zone table segment for a datasource with a new\n\
+memory segment.\n\
+\n\
+Parameters:\n\
+ datasrc_name The name of the data source whose segment to reset.\n\
+ mode The open mode for the new memory segment.\n\
+ config_params The configuration for the new memory segment, as a JSON encoded string.\n\
+";
+
+const char* const ConfigurableClientList_get_zone_table_accessor_doc = "\
+get_zone_table_accessor(datasrc_name, use_cache) -> \
+isc.datasrc.ZoneTableAccessor\n\
+\n\
+Create a ZoneTableAccessor object for the specified data source.\n\
+\n\
+Parameters:\n\
+ datasrc_name If not empty, the name of the data source\n\
+ use_cache If true, create a zone table for in-memory cache.\n\
+";
+
+const char* const ConfigurableClientList_get_cached_zone_writer_doc = "\
+get_cached_zone_writer(zone, catch_load_error, datasrc_name) -> status, zone_writer\n\
+\n\
+This method returns a ZoneWriter that can be used to (re)load a zone.\n\
+\n\
+By default this method identifies the first data source in the list\n\
+that should serve the zone of the given name, and returns a ZoneWriter\n\
+object that can be used to load the content of the zone, in a specific\n\
+way for that data source.\n\
+\n\
+If the optional datasrc_name parameter is provided with a non empty\n\
+string, this method only tries to load the specified zone into or with\n\
+the data source which has the given name, regardless where in the list\n\
+that data source is placed. Even if the given name of zone doesn't\n\
+exist in the data source, other data sources are not searched and\n\
+this method simply returns ZONE_NOT_FOUND in the first element\n\
+of the pair.\n\
+\n\
+Two elements are returned. The first element is a status\n\
+indicating if it worked or not (and in case it didn't, also why). If the\n\
+status is ZONE_SUCCESS, the second element contains a ZoneWriter object. If\n\
+the status is anything else, the second element is None.\n\
+\n\
+Parameters:\n\
+ zone The origin of the zone to (re)load.\n\
+ catch_load_error Whether to catch load errors, or to raise them as exceptions.\n\
+ datasrc_name The name of the data source where the zone is to be loaded (optional).\n\
+";
+
+const char* const ConfigurableClientList_get_status_doc = "\
+get_status() -> list of tuples\n\
+\n\
+This method returns a list of tuples, with each tuple containing the\n\
+status of a data source client. If there are no data source clients\n\
+present, an empty list is returned.\n\
+\n\
+The tuples contain (name, segment_type, segment_state):\n\
+ name The name of the data source.\n\
+ segment_type A string indicating the type of memory segment in use.\n\
+ segment_state The state of the memory segment.\n\
+\n\
+If segment_state is SEGMENT_UNUSED, None is returned for the segment_type.\n\
+";
+
+const char* const ConfigurableClientList_find_doc = "\
+find(zone, want_exact_match=False, want_finder=True) -> datasrc_client,\
+zone_finder, exact_match\n\
+\n\
+Look for a data source containing the given zone.\n\
+\n\
+It searches through the contained data sources and returns a data source\
+containing the zone, the zone finder of the zone and a boolean if the answer\
+is an exact match.\n\
+\n\
+The first parameter is isc.dns.Name object of a name in the zone. If the\
+want_exact_match is True, only zone with this exact origin is returned.\
+If it is False, the best matching zone is returned.\n\
+\n\
+If the want_finder is False, the returned zone_finder might be None even\
+if the data source is identified (in such case, the datasrc_client is not\
+None). Setting it to false allows the client list some optimisations, if\
+you don't need it, but if you do need it, it is better to set it to True\
+instead of getting it from the datasrc_client later.\n\
+\n\
+If no answer is found, the datasrc_client and zone_finder are None.\
+";
+
+} // unnamed namespace
diff --git a/src/lib/python/isc/datasrc/configurableclientlist_python.cc b/src/lib/python/isc/datasrc/configurableclientlist_python.cc
index 81da7d8..a8c6d5f 100644
--- a/src/lib/python/isc/datasrc/configurableclientlist_python.cc
+++ b/src/lib/python/isc/datasrc/configurableclientlist_python.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -27,6 +27,7 @@
#include <dns/python/rrclass_python.h>
#include <dns/python/name_python.h>
+#include <dns/python/pydnspp_common.h>
#include <datasrc/client_list.h>
@@ -34,11 +35,18 @@
#include "datasrc.h"
#include "finder_python.h"
#include "client_python.h"
+#include "zonetable_accessor_python.h"
+#include "zonewriter_python.h"
+
+#include "configurableclientlist_inc.cc"
using namespace std;
using namespace isc::util::python;
using namespace isc::datasrc;
+using namespace isc::datasrc::memory;
using namespace isc::datasrc::python;
+using namespace isc::datasrc::memory::python;
+using namespace isc::dns::python;
//
// ConfigurableClientList
@@ -64,7 +72,8 @@ ConfigurableClientList_init(PyObject* po_self, PyObject* args, PyObject*) {
return (0);
}
} catch (const exception& ex) {
- const string ex_what = "Failed to construct ConfigurableClientList object: " +
+ const string ex_what =
+ "Failed to construct ConfigurableClientList object: " +
string(ex.what());
PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
return (-1);
@@ -116,6 +125,129 @@ ConfigurableClientList_configure(PyObject* po_self, PyObject* args) {
}
PyObject*
+ConfigurableClientList_resetMemorySegment(PyObject* po_self, PyObject* args) {
+ s_ConfigurableClientList* self =
+ static_cast<s_ConfigurableClientList*>(po_self);
+ try {
+ const char* datasrc_name_p;
+ int mode_int;
+ const char* config_p;
+ if (PyArg_ParseTuple(args, "sis", &datasrc_name_p, &mode_int,
+ &config_p)) {
+ const std::string datasrc_name(datasrc_name_p);
+ const isc::data::ConstElementPtr
+ config(isc::data::Element::fromJSON(std::string(config_p)));
+ ZoneTableSegment::MemorySegmentOpenMode mode =
+ static_cast<ZoneTableSegment::MemorySegmentOpenMode>
+ (mode_int);
+ self->cppobj->resetMemorySegment(datasrc_name, mode, config);
+ Py_RETURN_NONE;
+ }
+ } catch (const isc::data::JSONError& jse) {
+ const string ex_what(std::string("JSON parse error in memory segment"
+ " configuration: ") + jse.what());
+ PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unknown C++ exception");
+ }
+
+ return (NULL);
+}
+
+PyObject*
+ConfigurableClientList_getCachedZoneWriter(PyObject* po_self, PyObject* args) {
+ s_ConfigurableClientList* self =
+ static_cast<s_ConfigurableClientList*>(po_self);
+ try {
+ PyObject* name_obj;
+ int catch_load_error;
+ const char* datasrc_name_p = "";
+#if (PY_VERSION_HEX >= 0x030300f0)
+ // The 'p' specifier for predicate (boolean) is available from
+ // Python 3.3 (final) only.
+ if (PyArg_ParseTuple(args, "O!p|s", &isc::dns::python::name_type,
+ &name_obj, &catch_load_error, &datasrc_name_p)) {
+#else
+ if (PyArg_ParseTuple(args, "O!i|s", &isc::dns::python::name_type,
+ &name_obj, &catch_load_error, &datasrc_name_p)) {
+#endif
+ const isc::dns::Name&
+ name(isc::dns::python::PyName_ToName(name_obj));
+ const std::string datasrc_name(datasrc_name_p);
+
+ const ConfigurableClientList::ZoneWriterPair result =
+ self->cppobj->getCachedZoneWriter(name, catch_load_error,
+ datasrc_name);
+
+ PyObjectContainer writer;
+ if (!result.second) {
+ // Use the Py_BuildValue, as it takes care of the
+ // reference counts correctly.
+ writer.reset(Py_BuildValue(""));
+ } else {
+ // Make sure it keeps the writer alive.
+ writer.reset(createZoneWriterObject(result.second,
+ po_self));
+ }
+
+ return (Py_BuildValue("IO", result.first, writer.get()));
+ } else {
+ return (NULL);
+ }
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unknown C++ exception");
+ return (NULL);
+ }
+}
+
+PyObject*
+ConfigurableClientList_getStatus(PyObject* po_self, PyObject*) {
+ s_ConfigurableClientList* self =
+ static_cast<s_ConfigurableClientList*>(po_self);
+ try {
+ const std::vector<DataSourceStatus> status = self->cppobj->getStatus();
+
+ PyObjectContainer slist(PyList_New(status.size()));
+
+ for (size_t i = 0; i < status.size(); ++i) {
+ PyObjectContainer segment_type;
+
+ if (status[i].getSegmentState() != SEGMENT_UNUSED) {
+ segment_type.reset(Py_BuildValue(
+ "s", status[i].getSegmentType().c_str()));
+ } else {
+ Py_INCREF(Py_None);
+ segment_type.reset(Py_None);
+ }
+
+ PyObjectContainer tup(Py_BuildValue("(sOI)",
+ status[i].getName().c_str(),
+ segment_type.get(),
+ status[i].getSegmentState()));
+ // The following "steals" our reference on tup, so we must
+ // not decref.
+ PyList_SET_ITEM(slist.get(), i, tup.release());
+ }
+
+ return (slist.release());
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unknown C++ exception");
+ return (NULL);
+ }
+}
+
+PyObject*
ConfigurableClientList_find(PyObject* po_self, PyObject* args) {
s_ConfigurableClientList* self =
static_cast<s_ConfigurableClientList*>(po_self);
@@ -125,7 +257,7 @@ ConfigurableClientList_find(PyObject* po_self, PyObject* args) {
int want_finder = 1;
if (PyArg_ParseTuple(args, "O!|ii", &isc::dns::python::name_type,
&name_obj, &want_exact_match, &want_finder)) {
- const isc::dns::Name
+ const isc::dns::Name&
name(isc::dns::python::PyName_ToName(name_obj));
const ClientList::FindResult
result(self->cppobj->find(name, want_exact_match,
@@ -170,6 +302,37 @@ ConfigurableClientList_find(PyObject* po_self, PyObject* args) {
}
}
+PyObject*
+ConfigurableClientList_getZoneTableAccessor(PyObject* po_self, PyObject* args) {
+ s_ConfigurableClientList* self =
+ static_cast<s_ConfigurableClientList*>(po_self);
+ try {
+ const char* datasrc_name;
+ int use_cache;
+ if (PyArg_ParseTuple(args, "zi", &datasrc_name, &use_cache)) {
+ // python 'None' will be read as NULL, which we convert to an
+ // empty string, meaning "any data source"
+ const std::string name(datasrc_name ? datasrc_name : "");
+ const ConstZoneTableAccessorPtr
+ z(self->cppobj->getZoneTableAccessor(name, use_cache));
+ if (z == NULL) {
+ Py_RETURN_NONE;
+ } else {
+ return (createZoneTableAccessorObject(z, po_self));
+ }
+ } else {
+ return (NULL);
+ }
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unknown C++ exception");
+ return (NULL);
+ }
+}
+
// This list contains the actual set of functions we have in
// python. Each entry has
// 1. Python method name
@@ -177,55 +340,21 @@ ConfigurableClientList_find(PyObject* po_self, PyObject* args) {
// 3. Argument type
// 4. Documentation
PyMethodDef ConfigurableClientList_methods[] = {
- { "configure", ConfigurableClientList_configure, METH_VARARGS,
- "configure(configuration, allow_cache) -> None\n\
-\n\
-Wrapper around C++ ConfigurableClientList::configure\n\
-\n\
-This sets the active configuration. It fills the ConfigurableClientList with\
-corresponding data source clients.\n\
-\n\
-If any error is detected, an exception is raised and the previous\
-configuration preserved.\n\
-\n\
-Parameters:\n\
- configuration The configuration, as a JSON encoded string.\
- allow_cache If caching is allowed." },
- { "find", ConfigurableClientList_find, METH_VARARGS,
-"find(zone, want_exact_match=False, want_finder=True) -> datasrc_client,\
-zone_finder, exact_match\n\
-\n\
-Look for a data source containing the given zone.\n\
-\n\
-It searches through the contained data sources and returns a data source\
-containing the zone, the zone finder of the zone and a boolean if the answer\
-is an exact match.\n\
-\n\
-The first parameter is isc.dns.Name object of a name in the zone. If the\
-want_exact_match is True, only zone with this exact origin is returned.\
-If it is False, the best matching zone is returned.\n\
-\n\
-If the want_finder is False, the returned zone_finder might be None even\
-if the data source is identified (in such case, the datasrc_client is not\
-None). Setting it to false allows the client list some optimisations, if\
-you don't need it, but if you do need it, it is better to set it to True\
-instead of getting it from the datasrc_client later.\n\
-\n\
-If no answer is found, the datasrc_client and zone_finder are None." },
+ { "configure", ConfigurableClientList_configure,
+ METH_VARARGS, ConfigurableClientList_configure_doc },
+ { "reset_memory_segment", ConfigurableClientList_resetMemorySegment,
+ METH_VARARGS, ConfigurableClientList_reset_memory_segment_doc },
+ { "get_zone_table_accessor", ConfigurableClientList_getZoneTableAccessor,
+ METH_VARARGS, ConfigurableClientList_get_zone_table_accessor_doc },
+ { "get_cached_zone_writer", ConfigurableClientList_getCachedZoneWriter,
+ METH_VARARGS, ConfigurableClientList_get_cached_zone_writer_doc },
+ { "get_status", ConfigurableClientList_getStatus,
+ METH_NOARGS, ConfigurableClientList_get_status_doc },
+ { "find", ConfigurableClientList_find,
+ METH_VARARGS, ConfigurableClientList_find_doc },
{ NULL, NULL, 0, NULL }
};
-const char* const ConfigurableClientList_doc = "\
-The list of data source clients\n\
-\n\
-The purpose is to have several data source clients of the same class\
-and then be able to search through them to identify the one containing\
-a given zone.\n\
-\n\
-Unlike the C++ version, we don't have the abstract base class. Abstract\
-classes are not needed due to the duck typing nature of python.\
-";
-
} // end of unnamed namespace
namespace isc {
@@ -237,9 +366,9 @@ namespace python {
PyTypeObject configurableclientlist_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"datasrc.ConfigurableClientList",
- sizeof(s_ConfigurableClientList), // tp_basicsize
+ sizeof(s_ConfigurableClientList), // tp_basicsize
0, // tp_itemsize
- ConfigurableClientList_destroy, // tp_dealloc
+ ConfigurableClientList_destroy, // tp_dealloc
NULL, // tp_print
NULL, // tp_getattr
NULL, // tp_setattr
@@ -262,7 +391,7 @@ PyTypeObject configurableclientlist_type = {
0, // tp_weaklistoffset
NULL, // tp_iter
NULL, // tp_iternext
- ConfigurableClientList_methods, // tp_methods
+ ConfigurableClientList_methods, // tp_methods
NULL, // tp_members
NULL, // tp_getset
NULL, // tp_base
@@ -270,7 +399,7 @@ PyTypeObject configurableclientlist_type = {
NULL, // tp_descr_get
NULL, // tp_descr_set
0, // tp_dictoffset
- ConfigurableClientList_init, // tp_init
+ ConfigurableClientList_init, // tp_init
NULL, // tp_alloc
PyType_GenericNew, // tp_new
NULL, // tp_free
@@ -300,6 +429,66 @@ initModulePart_ConfigurableClientList(PyObject* mod) {
}
Py_INCREF(&configurableclientlist_type);
+ try {
+ // ConfigurableClientList::CacheStatus enum
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_CACHE_DISABLED",
+ Py_BuildValue("I", ConfigurableClientList::CACHE_DISABLED));
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_ZONE_NOT_CACHED",
+ Py_BuildValue("I", ConfigurableClientList::ZONE_NOT_CACHED));
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_ZONE_NOT_FOUND",
+ Py_BuildValue("I", ConfigurableClientList::ZONE_NOT_FOUND));
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_CACHE_NOT_WRITABLE",
+ Py_BuildValue("I", ConfigurableClientList::CACHE_NOT_WRITABLE));
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_DATASRC_NOT_FOUND",
+ Py_BuildValue("I", ConfigurableClientList::DATASRC_NOT_FOUND));
+ installClassVariable
+ (configurableclientlist_type,
+ "CACHE_STATUS_ZONE_SUCCESS",
+ Py_BuildValue("I", ConfigurableClientList::ZONE_SUCCESS));
+
+ // MemorySegmentState enum
+ installClassVariable(configurableclientlist_type,
+ "SEGMENT_UNUSED",
+ Py_BuildValue("I", SEGMENT_UNUSED));
+ installClassVariable(configurableclientlist_type,
+ "SEGMENT_WAITING",
+ Py_BuildValue("I", SEGMENT_WAITING));
+ installClassVariable(configurableclientlist_type,
+ "SEGMENT_INUSE",
+ Py_BuildValue("I", SEGMENT_INUSE));
+
+ // FIXME: These should eventually be moved to the
+ // ZoneTableSegment class when we add Python bindings for the
+ // memory data source specific bits. But for now, we add these
+ // enums here to support reloading a zone table segment.
+ installClassVariable(configurableclientlist_type, "CREATE",
+ Py_BuildValue("I", ZoneTableSegment::CREATE));
+ installClassVariable(configurableclientlist_type, "READ_WRITE",
+ Py_BuildValue("I", ZoneTableSegment::READ_WRITE));
+ installClassVariable(configurableclientlist_type, "READ_ONLY",
+ Py_BuildValue("I", ZoneTableSegment::READ_ONLY));
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in ConfigurableClientList initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in ConfigurableClientList initialization");
+ return (false);
+ }
+
return (true);
}
diff --git a/src/lib/python/isc/datasrc/datasrc.cc b/src/lib/python/isc/datasrc/datasrc.cc
index c183af9..11c3e7c 100644
--- a/src/lib/python/isc/datasrc/datasrc.cc
+++ b/src/lib/python/isc/datasrc/datasrc.cc
@@ -23,6 +23,8 @@
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/zone_loader.h>
+#include <log/message_initializer.h>
+
#include "datasrc.h"
#include "client_python.h"
#include "finder_python.h"
@@ -31,6 +33,9 @@
#include "journal_reader_python.h"
#include "configurableclientlist_python.h"
#include "zone_loader_python.h"
+#include "zonetable_accessor_python.h"
+#include "zonetable_iterator_python.h"
+#include "zonewriter_python.h"
#include <util/python/pycppwrapper_util.h>
#include <dns/python/pydnspp_common.h>
@@ -40,6 +45,7 @@
using namespace isc::datasrc;
using namespace isc::datasrc::python;
+using namespace isc::datasrc::memory::python;
using namespace isc::util::python;
using namespace isc::dns::python;
@@ -253,6 +259,42 @@ initModulePart_ZoneJournalReader(PyObject* mod) {
return (true);
}
+bool
+initModulePart_ZoneTableAccessor(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(&zonetableaccessor_type) < 0) {
+ return (false);
+ }
+ void* p = &zonetableaccessor_type;
+ if (PyModule_AddObject(mod, "ZoneTableAccessor",
+ static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&zonetableaccessor_type);
+
+ return (true);
+}
+
+bool
+initModulePart_ZoneTableIterator(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(&zonetableiterator_type) < 0) {
+ return (false);
+ }
+ void* p = &zonetableiterator_type;
+ if (PyModule_AddObject(mod, "ZoneTableIterator",
+ static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&zonetableiterator_type);
+
+ return (true);
+}
+
PyObject* po_DataSourceError;
PyObject* po_MasterFileError;
PyObject* po_NotImplemented;
@@ -276,6 +318,8 @@ PyModuleDef iscDataSrc = {
PyMODINIT_FUNC
PyInit_datasrc(void) {
+ isc::log::MessageInitializer::loadDictionary();
+
PyObject* mod = PyModule_Create(&iscDataSrc);
if (mod == NULL) {
return (NULL);
@@ -336,5 +380,20 @@ PyInit_datasrc(void) {
return (NULL);
}
+ if (!initModulePart_ZoneTableAccessor(mod)) {
+ Py_DECREF(mod);
+ return (NULL);
+ }
+
+ if (!initModulePart_ZoneTableIterator(mod)) {
+ Py_DECREF(mod);
+ return (NULL);
+ }
+
+ if (!initModulePart_ZoneWriter(mod)) {
+ Py_DECREF(mod);
+ return (NULL);
+ }
+
return (mod);
}
diff --git a/src/lib/python/isc/datasrc/finder_python.cc b/src/lib/python/isc/datasrc/finder_python.cc
index 05c44c9..5a8e84f 100644
--- a/src/lib/python/isc/datasrc/finder_python.cc
+++ b/src/lib/python/isc/datasrc/finder_python.cc
@@ -24,7 +24,7 @@
#include <datasrc/client.h>
#include <datasrc/database.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/zone_iterator.h>
#include <datasrc/zone.h>
@@ -64,7 +64,7 @@ getFindResultFlags(const ZoneFinder::Context& context) {
namespace isc_datasrc_internal {
// This is the shared code for the find() call in the finder and the updater
-// Is is intentionally not available through any header, nor at our standard
+// It is intentionally not available through any header, nor at our standard
// namespace, as it is not supposed to be called anywhere but from finder and
// updater
PyObject* ZoneFinder_helper(ZoneFinder* finder, PyObject* args) {
@@ -184,7 +184,7 @@ public:
ZoneFinderPtr cppobj;
// This is a reference to a base object; if the object of this class
// depends on another object to be in scope during its lifetime,
- // we use INCREF the base object upon creation, and DECREF it at
+ // we INCREF the base object upon creation, and DECREF it at
// the end of the destructor
// This is an optional argument to createXXX(). If NULL, it is ignored.
PyObject* base_obj;
diff --git a/src/lib/python/isc/datasrc/iterator_python.cc b/src/lib/python/isc/datasrc/iterator_python.cc
index 9757a3b..0b80f20 100644
--- a/src/lib/python/isc/datasrc/iterator_python.cc
+++ b/src/lib/python/isc/datasrc/iterator_python.cc
@@ -61,7 +61,7 @@ typedef CPPPyObjectContainer<s_ZoneIterator, ZoneIterator>
// General creation and destruction
int
-ZoneIterator_init(s_ZoneIterator* self, PyObject* args) {
+ZoneIterator_init(s_ZoneIterator*, PyObject*) {
// can't be called directly
PyErr_SetString(PyExc_TypeError,
"ZoneIterator cannot be constructed directly");
diff --git a/src/lib/python/isc/datasrc/tests/Makefile.am b/src/lib/python/isc/datasrc/tests/Makefile.am
index eb82972..256ca62 100644
--- a/src/lib/python/isc/datasrc/tests/Makefile.am
+++ b/src/lib/python/isc/datasrc/tests/Makefile.am
@@ -1,17 +1,10 @@
+SUBDIRS = testdata
+
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = datasrc_test.py sqlite3_ds_test.py
PYTESTS += clientlist_test.py zone_loader_test.py
EXTRA_DIST = $(PYTESTS)
-EXTRA_DIST += testdata/brokendb.sqlite3
-EXTRA_DIST += testdata/example.com.sqlite3
-EXTRA_DIST += testdata/example.com.source.sqlite3
-EXTRA_DIST += testdata/newschema.sqlite3
-EXTRA_DIST += testdata/oldschema.sqlite3
-EXTRA_DIST += testdata/new_minor_schema.sqlite3
-EXTRA_DIST += testdata/example.com
-EXTRA_DIST += testdata/example.com.ch
-EXTRA_DIST += testdata/static.zone
CLEANFILES = $(abs_builddir)/rwtest.sqlite3.copied
CLEANFILES += $(abs_builddir)/zoneloadertest.sqlite3
@@ -22,7 +15,7 @@ CLEANFILES += $(abs_builddir)/zoneloadertest.sqlite3
# of scope for this ticket
LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs:
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
else
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
@@ -35,13 +28,18 @@ if ENABLE_PYTHON_COVERAGE
rm -f .coverage
${LN_S} $(abs_top_srcdir)/.coverage .coverage
endif
+if USE_SHARED_MEMORY
+HAVE_SHARED_MEMORY=yes
+else
+HAVE_SHARED_MEMORY=no
+endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=:$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/python/isc/log:$(abs_top_builddir)/src/lib/python/isc/datasrc/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs \
TESTDATA_PATH=$(abs_srcdir)/testdata \
TESTDATA_WRITE_PATH=$(abs_builddir) \
- GLOBAL_TESTDATA_PATH=$(abs_top_srcdir)/src/lib/testutils/testdata \
+ HAVE_SHARED_MEMORY=$(HAVE_SHARED_MEMORY) \
B10_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/lib/python/isc/datasrc/tests/clientlist_test.py b/src/lib/python/isc/datasrc/tests/clientlist_test.py
index bdac69c..e8056a5 100644
--- a/src/lib/python/isc/datasrc/tests/clientlist_test.py
+++ b/src/lib/python/isc/datasrc/tests/clientlist_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 Internet Systems Consortium.
+# Copyright (C) 2012-2013 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
@@ -20,7 +20,8 @@ import unittest
import os
import sys
-TESTDATA_PATH = os.environ['GLOBAL_TESTDATA_PATH'] + os.sep
+TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
+MAPFILE_PATH = os.environ['TESTDATA_WRITE_PATH'] + os.sep + 'test.mapped'
class ClientListTest(unittest.TestCase):
"""
@@ -36,8 +37,18 @@ class ClientListTest(unittest.TestCase):
# last.
self.dsrc = None
self.finder = None
+
+ # If a test created a ZoneWriter with a mapped memory segment,
+ # the writer will hold a reference to the client list which will
+ # need the mapfile to exist until it's destroyed. So we'll make
+ # sure to destroy the writer (by resetting it) before removing
+ # the mapfile below.
+ self.__zone_writer = None
self.clist = None
+ if os.path.exists(MAPFILE_PATH):
+ os.unlink(MAPFILE_PATH)
+
def test_constructors(self):
"""
Test the constructor. It should accept an RRClass. Check it
@@ -54,32 +65,35 @@ class ClientListTest(unittest.TestCase):
self.assertRaises(TypeError, isc.datasrc.ConfigurableClientList,
isc.dns.RRClass.IN, isc.dns.RRClass.IN)
+ def configure_helper(self):
+ self.clist.configure('''[{
+ "type": "MasterFiles",
+ "params": {
+ "example.com": "''' + TESTDATA_PATH + '''example.com"
+ },
+ "cache-enable": true
+ }]''', True)
+
def test_configure(self):
"""
Test we can configure the client list. This tests if the valid
- ones are acceptend and invalid rejected. We check the changes
+ ones are accepted and invalid rejected. We check the changes
have effect.
"""
self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
# This should be NOP now
self.clist.configure("[]", True)
# Check the zone is not there yet
- dsrc, finder, exact = self.clist.find(isc.dns.Name("example.org"))
+ dsrc, finder, exact = self.clist.find(isc.dns.Name("example.com"))
self.assertIsNone(dsrc)
self.assertIsNone(finder)
self.assertFalse(exact)
# We can use this type, as it is not loaded dynamically.
- self.clist.configure('''[{
- "type": "MasterFiles",
- "params": {
- "example.org": "''' + TESTDATA_PATH + '''example.org.zone"
- },
- "cache-enable": true
- }]''', True)
+ self.configure_helper()
# Check the zone is there now. Proper tests of find are in other
# test methods.
self.dsrc, self.finder, exact = \
- self.clist.find(isc.dns.Name("example.org"))
+ self.clist.find(isc.dns.Name("example.com"))
self.assertIsNotNone(self.dsrc)
self.assertTrue(isinstance(self.dsrc, isc.datasrc.DataSourceClient))
self.assertIsNotNone(self.finder)
@@ -88,29 +102,14 @@ class ClientListTest(unittest.TestCase):
self.assertRaises(isc.datasrc.Error, self.clist.configure,
'"bad type"', True)
self.assertRaises(isc.datasrc.Error, self.clist.configure, '''[{
- "type": "bad type"
- }]''', True)
- self.assertRaises(isc.datasrc.Error, self.clist.configure, '''[{
bad JSON,
}]''', True)
self.assertRaises(TypeError, self.clist.configure, [], True)
self.assertRaises(TypeError, self.clist.configure, "[]")
self.assertRaises(TypeError, self.clist.configure, "[]", "true")
- def test_find(self):
- """
- Test the find accepts the right arguments, some of them can be omitted,
- etc.
- """
- self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
- self.clist.configure('''[{
- "type": "MasterFiles",
- "params": {
- "example.org": "''' + TESTDATA_PATH + '''example.org.zone"
- },
- "cache-enable": true
- }]''', True)
- dsrc, finder, exact = self.clist.find(isc.dns.Name("sub.example.org"))
+ def find_helper(self):
+ dsrc, finder, exact = self.clist.find(isc.dns.Name("sub.example.com"))
self.assertIsNotNone(dsrc)
self.assertTrue(isinstance(dsrc, isc.datasrc.DataSourceClient))
self.assertIsNotNone(finder)
@@ -124,33 +123,296 @@ class ClientListTest(unittest.TestCase):
# We check an exact match in test_configure already
self.assertFalse(exact)
self.dsrc, self.finder, exact = \
- self.clist.find(isc.dns.Name("sub.example.org"), False)
+ self.clist.find(isc.dns.Name("sub.example.com"), False)
self.assertIsNotNone(self.dsrc)
self.assertTrue(isinstance(self.dsrc, isc.datasrc.DataSourceClient))
self.assertIsNotNone(self.finder)
self.assertTrue(isinstance(self.finder, isc.datasrc.ZoneFinder))
self.assertFalse(exact)
self.dsrc, self.finder, exact = \
- self.clist.find(isc.dns.Name("sub.example.org"), True)
+ self.clist.find(isc.dns.Name("sub.example.com"), True)
self.assertIsNone(self.dsrc)
self.assertIsNone(self.finder)
self.assertFalse(exact)
self.dsrc, self.finder, exact = \
- self.clist.find(isc.dns.Name("sub.example.org"), False, False)
+ self.clist.find(isc.dns.Name("sub.example.com"), False, False)
self.assertIsNotNone(self.dsrc)
self.assertTrue(isinstance(self.dsrc, isc.datasrc.DataSourceClient))
self.assertIsNotNone(self.finder)
self.assertTrue(isinstance(self.finder, isc.datasrc.ZoneFinder))
self.assertFalse(exact)
self.dsrc, self.finder, exact = \
- self.clist.find(isc.dns.Name("sub.example.org"), True, False)
+ self.clist.find(isc.dns.Name("sub.example.com"), True, False)
self.assertIsNone(self.dsrc)
self.assertIsNone(self.finder)
self.assertFalse(exact)
# Some invalid inputs
- self.assertRaises(TypeError, self.clist.find, "example.org")
+ self.assertRaises(TypeError, self.clist.find, "example.com")
self.assertRaises(TypeError, self.clist.find)
+ def test_get_zone_table_accessor(self):
+ """
+ Test that we can get the zone table accessor and, thereby,
+ the zone table iterator.
+ """
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+
+ # null configuration
+ self.clist.configure("[]", True)
+ self.assertIsNone(self.clist.get_zone_table_accessor(None, True))
+
+ # empty configuration
+ self.clist.configure('''[{
+ "type": "MasterFiles",
+ "params": {},
+ "cache-enable": true
+ }]''', True)
+ # bogus datasrc
+ self.assertIsNone(self.clist.get_zone_table_accessor("bogus", True))
+ # first datasrc - empty zone table
+ table = self.clist.get_zone_table_accessor(None, True)
+ self.assertIsNotNone(table)
+ iterator = iter(table)
+ self.assertIsNotNone(iterator)
+ self.assertEqual(0, len(list(iterator)))
+
+ # normal configuration
+ self.configure_helper()
+ # !use_cache => NotImplemented
+ self.assertRaises(isc.datasrc.Error,
+ self.clist.get_zone_table_accessor, None, False)
+ # bogus datasrc
+ self.assertIsNone(self.clist.get_zone_table_accessor("bogus", True))
+
+ # first datasrc
+ table = self.clist.get_zone_table_accessor(None, True)
+ self.assertIsNotNone(table)
+ zonelist = list(table)
+ self.assertEqual(1, len(zonelist))
+ self.assertEqual(zonelist[0][1], isc.dns.Name("example.com"))
+
+ # named datasrc
+ table = self.clist.get_zone_table_accessor("MasterFiles", True)
+ self.assertEqual(zonelist, list(table))
+
+ # longer zone list for non-trivial iteration
+ self.clist.configure('''[{
+ "type": "MasterFiles",
+ "params": {
+ "example.org": "''' + TESTDATA_PATH + '''example.org.zone",
+ "example.com": "''' + TESTDATA_PATH + '''example.com.zone",
+ "example.net": "''' + TESTDATA_PATH + '''example.net.zone",
+ "example.biz": "''' + TESTDATA_PATH + '''example.biz.zone",
+ "example.edu": "''' + TESTDATA_PATH + '''example.edu.zone"
+ },
+ "cache-enable": true
+ }]''', True)
+ zonelist = list(self.clist.get_zone_table_accessor(None, True))
+ self.assertEqual(5, len(zonelist))
+ self.assertTrue((0, isc.dns.Name("example.net.")) in zonelist)
+
+ # ensure the iterator returns exactly and only the zones we expect
+ zonelist = [
+ isc.dns.Name("example.org"),
+ isc.dns.Name("example.com"),
+ isc.dns.Name("example.net"),
+ isc.dns.Name("example.biz"),
+ isc.dns.Name("example.edu")]
+ table = self.clist.get_zone_table_accessor("MasterFiles", True)
+ for index, zone in table:
+ self.assertTrue(zone in zonelist)
+ zonelist.remove(zone)
+ self.assertEqual(0, len(zonelist))
+
+ def test_find(self):
+ """
+ Test the find accepts the right arguments, some of them can be omitted,
+ etc.
+ """
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.configure_helper()
+ self.find_helper()
+
+ @unittest.skipIf(os.environ['HAVE_SHARED_MEMORY'] != 'yes',
+ 'shared memory is not available')
+ def test_find_mapped(self):
+ """
+ Test find on a mapped segment.
+ """
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.clist.configure('''[{
+ "type": "MasterFiles",
+ "params": {
+ "example.com": "''' + TESTDATA_PATH + '''example.com"
+ },
+ "cache-enable": true,
+ "cache-type": "mapped"
+ }]''', True)
+
+ map_params = '{"mapped-file": "' + MAPFILE_PATH + '"}'
+ self.clist.reset_memory_segment("MasterFiles",
+ isc.datasrc.ConfigurableClientList.CREATE,
+ map_params)
+ result, self.__zone_writer = \
+ self.clist.get_cached_zone_writer(isc.dns.Name("example.com"),
+ False)
+ self.assertEqual(isc.datasrc.ConfigurableClientList.CACHE_STATUS_ZONE_SUCCESS,
+ result)
+ err_msg = self.__zone_writer.load()
+ self.assertIsNone(err_msg)
+ self.__zone_writer.install()
+ self.__zone_writer.cleanup()
+
+ self.clist.reset_memory_segment("MasterFiles",
+ isc.datasrc.ConfigurableClientList.READ_ONLY,
+ map_params)
+ result, self.__zone_writer = \
+ self.clist.get_cached_zone_writer(isc.dns.Name("example.com"),
+ False)
+ self.assertEqual(isc.datasrc.ConfigurableClientList.CACHE_STATUS_CACHE_NOT_WRITABLE,
+ result)
+
+ # The segment is still in READ_ONLY mode.
+ self.find_helper()
+
+ def test_zone_writer_load_twice(self):
+ """
+ Test that the zone writer throws when load() is called more than
+ once.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.configure_helper()
+
+ result, self.__zone_writer = \
+ self.clist.get_cached_zone_writer(isc.dns.Name("example.com"),
+ False)
+ self.assertEqual(isc.datasrc.ConfigurableClientList.CACHE_STATUS_ZONE_SUCCESS,
+ result)
+ err_msg = self.__zone_writer.load()
+ self.assertIsNone(err_msg)
+ self.assertRaises(isc.datasrc.Error, self.__zone_writer.load)
+ self.__zone_writer.cleanup()
+
+ def test_zone_writer_load_without_raise(self):
+ """
+ Test that the zone writer does not throw when asked not to.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.clist.configure('''[{
+ "type": "MasterFiles",
+ "params": {
+ "example.com": "''' + TESTDATA_PATH + '''example.com-broken.zone"
+ },
+ "cache-enable": true
+ }]''', True)
+
+ result, self.__zone_writer = \
+ self.clist.get_cached_zone_writer(isc.dns.Name("example.com"),
+ True)
+ self.assertEqual(isc.datasrc.ConfigurableClientList.CACHE_STATUS_ZONE_SUCCESS,
+ result)
+ err_msg = self.__zone_writer.load()
+ self.assertIsNotNone(err_msg)
+ self.assertTrue('Errors found when validating zone' in err_msg)
+ self.__zone_writer.cleanup()
+
+ def test_zone_writer_install_without_load(self):
+ """
+ Test that the zone writer throws when install() is called
+ without calling load() first.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.configure_helper()
+
+ result, self.__zone_writer = \
+ self.clist.get_cached_zone_writer(isc.dns.Name("example.com"),
+ False)
+ self.assertEqual(isc.datasrc.ConfigurableClientList.CACHE_STATUS_ZONE_SUCCESS,
+ result)
+ self.assertRaises(isc.datasrc.Error, self.__zone_writer.install)
+ self.__zone_writer.cleanup()
+
+ def test_get_status(self):
+ """
+ Test getting status of various data sources.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+
+ status = self.clist.get_status()
+ self.assertIsNotNone(status)
+ self.assertTrue(isinstance(status, list))
+ self.assertEqual(0, len(status))
+
+ self.configure_helper()
+
+ status = self.clist.get_status()
+ self.assertIsNotNone(status)
+ self.assertTrue(isinstance(status, list))
+ self.assertEqual(1, len(status))
+ self.assertTrue(isinstance(status[0], tuple))
+ self.assertTupleEqual(('MasterFiles', 'local',
+ isc.datasrc.ConfigurableClientList.SEGMENT_INUSE),
+ status[0])
+
+ @unittest.skipIf(os.environ['HAVE_SHARED_MEMORY'] != 'yes',
+ 'shared memory is not available')
+ def test_get_status_unused(self):
+ """
+ Test getting status when segment type is mapped, but the cache
+ is disabled.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.clist.configure('''[{
+ "type": "sqlite3",
+ "params": {
+ "database_file": "''' + TESTDATA_PATH + '''example.com.sqlite3"
+ },
+ "cache-zones" : ["example.com"],
+ "cache-type": "mapped",
+ "cache-enable": false
+ }]''', True)
+
+ status = self.clist.get_status()
+ self.assertIsNotNone(status)
+ self.assertTrue(isinstance(status, list))
+ self.assertEqual(1, len(status))
+ self.assertTrue(isinstance(status[0], tuple))
+ self.assertTupleEqual(('sqlite3', None,
+ isc.datasrc.ConfigurableClientList.SEGMENT_UNUSED),
+ status[0])
+
+ @unittest.skipIf(os.environ['HAVE_SHARED_MEMORY'] != 'yes',
+ 'shared memory is not available')
+ def test_get_status_waiting(self):
+ """
+ Test getting status when segment type is mapped and it has not
+ been reset yet.
+ """
+
+ self.clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ self.clist.configure('''[{
+ "type": "MasterFiles",
+ "params": {
+ "example.com": "''' + TESTDATA_PATH + '''example.com"
+ },
+ "cache-enable": true,
+ "cache-type": "mapped"
+ }]''', True)
+
+ status = self.clist.get_status()
+ self.assertIsNotNone(status)
+ self.assertTrue(isinstance(status, list))
+ self.assertEqual(1, len(status))
+ self.assertTrue(isinstance(status[0], tuple))
+ self.assertTupleEqual(('MasterFiles', 'mapped',
+ isc.datasrc.ConfigurableClientList.SEGMENT_WAITING),
+ status[0])
+
if __name__ == "__main__":
isc.log.init("bind10")
isc.log.resetUnitTestRootLogger()
diff --git a/src/lib/python/isc/datasrc/tests/datasrc_test.py b/src/lib/python/isc/datasrc/tests/datasrc_test.py
index abc9739..1649fc1 100644
--- a/src/lib/python/isc/datasrc/tests/datasrc_test.py
+++ b/src/lib/python/isc/datasrc/tests/datasrc_test.py
@@ -33,8 +33,11 @@ WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "rwtest.sqlite3.copied"
READ_ZONE_DB_CONFIG = "{ \"database_file\": \"" + READ_ZONE_DB_FILE + "\" }"
WRITE_ZONE_DB_CONFIG = "{ \"database_file\": \"" + WRITE_ZONE_DB_FILE + "\"}"
-
-STATIC_ZONE_CONFIG = '"' + TESTDATA_PATH + "static.zone" + '"'
+# In-memory data source must be built via client list.
+INMEMORY_ZONE_CONFIG = '''[{
+ "type": "MasterFiles",
+ "cache-enable": true,
+ "params": {"example.com": "''' + TESTDATA_PATH + 'example.com"}}]'
def add_rrset(rrset_list, name, rrclass, rrtype, ttl, rdatas):
rrset_to_add = isc.dns.RRset(name, rrclass, rrtype, ttl)
@@ -261,7 +264,7 @@ class DataSrcClient(unittest.TestCase):
rrets = dsc.get_iterator(isc.dns.Name("example.com"))
# there are more than 80 RRs in this zone... let's just count them
# (already did a full check of the smaller zone above)
- # There are 40 non-RRSIG RRsets and 32 dinstinct RRSIGs.
+ # There are 40 non-RRSIG RRsets and 32 distinct RRSIGs.
self.assertEqual(72, len(list(rrets)))
# same test, but now with explicit False argument for separate_rrs
@@ -563,11 +566,10 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual(updater_refs, sys.getrefcount(updater))
def test_two_modules(self):
- # load two modules, and check if they don't interfere; as the
- # memory datasource module no longer exists, we check the static
- # datasource instead (as that uses the memory datasource
- # anyway).
- dsc_static = isc.datasrc.DataSourceClient("static", STATIC_ZONE_CONFIG)
+ # load two modules, and check if they don't interfere.
+ clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ clist.configure(INMEMORY_ZONE_CONFIG, True)
+ dsc_static = clist.find(isc.dns.Name("example.com"), True, False)[0]
dsc_sql = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
# check if exceptions are working
@@ -593,7 +595,7 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual(finder.NXDOMAIN, result)
def test_update_delete_abort(self):
- # we don't do enything with this one, just making sure loading two
+ # we don't do anything with this one, just making sure loading two
# datasources
dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
@@ -724,10 +726,9 @@ class DataSrcUpdater(unittest.TestCase):
self.assertTrue(dsc.delete_zone(zone_name))
def test_create_zone_not_implemented(self):
- # As the memory datasource module no longer exists, we check the
- # static datasource instead (as that uses the memory datasource
- # anyway).
- dsc = isc.datasrc.DataSourceClient("static", STATIC_ZONE_CONFIG)
+ clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ clist.configure(INMEMORY_ZONE_CONFIG, True)
+ dsc = clist.find(isc.dns.Name("example.com"), True, False)[0]
self.assertRaises(isc.datasrc.NotImplemented, dsc.create_zone,
isc.dns.Name("example.com"))
diff --git a/src/lib/python/isc/datasrc/tests/testdata/Makefile.am b/src/lib/python/isc/datasrc/tests/testdata/Makefile.am
new file mode 100644
index 0000000..8365a24
--- /dev/null
+++ b/src/lib/python/isc/datasrc/tests/testdata/Makefile.am
@@ -0,0 +1,12 @@
+EXTRA_DIST = \
+ brokendb.sqlite3 \
+ example.com \
+ example.com-broken.zone \
+ example.com.ch \
+ example.com.source.sqlite3 \
+ example.com.sqlite3 \
+ Makefile.am \
+ new_minor_schema.sqlite3 \
+ newschema.sqlite3 \
+ oldschema.sqlite3 \
+ static.zone
diff --git a/src/lib/python/isc/datasrc/tests/testdata/example.com-broken.zone b/src/lib/python/isc/datasrc/tests/testdata/example.com-broken.zone
new file mode 100644
index 0000000..079b400
--- /dev/null
+++ b/src/lib/python/isc/datasrc/tests/testdata/example.com-broken.zone
@@ -0,0 +1,6 @@
+; This zone is broken (contains no NS records).
+example.com. 1000 IN SOA a.dns.example.com. mail.example.com. 2 1 1 1 1
+a.dns.example.com. 1000 IN A 1.1.1.1
+b.dns.example.com. 1000 IN A 3.3.3.3
+b.dns.example.com. 1000 IN AAAA 4:4::4:4
+b.dns.example.com. 1000 IN AAAA 5:5::5:5
diff --git a/src/lib/python/isc/datasrc/tests/zone_loader_test.py b/src/lib/python/isc/datasrc/tests/zone_loader_test.py
index e437e36..4811aba 100644
--- a/src/lib/python/isc/datasrc/tests/zone_loader_test.py
+++ b/src/lib/python/isc/datasrc/tests/zone_loader_test.py
@@ -34,7 +34,11 @@ ORIG_DB_FILE = TESTDATA_PATH + '/example.com.sqlite3'
DB_FILE = TESTDATA_WRITE_PATH + '/zoneloadertest.sqlite3'
DB_CLIENT_CONFIG = '{ "database_file": "' + DB_FILE + '" }'
DB_SOURCE_CLIENT_CONFIG = '{ "database_file": "' + SOURCE_DB_FILE + '" }'
-STATIC_ZONE_CONFIG = '"' + TESTDATA_PATH + "/static.zone" + '"'
+# In-memory data source must be built via client list.
+INMEMORY_ZONE_CONFIG = '''[{
+ "type": "MasterFiles",
+ "cache-enable": true,
+ "params": {"example.com": "''' + TESTDATA_PATH + '/example.com"}}]'
ORIG_SOA_TXT = 'example.com. 3600 IN SOA master.example.com. ' +\
'admin.example.com. 1234 3600 1800 2419200 7200\n'
@@ -216,15 +220,11 @@ class ZoneLoaderTests(unittest.TestCase):
self.client, zone_name, self.source_client)
def test_no_ds_load_support(self):
- # As the memory datasource module no longer exists, we check the
- # static datasource instead (as that uses the memory datasource
- # anyway).
- #
- # This may change in the future, but ATM, the static ds does not
- # support the API the zone loader uses (it has direct load
- # calls).
- inmem_client = isc.datasrc.DataSourceClient('static',
- STATIC_ZONE_CONFIG);
+ # This may change in the future, but ATM, in-memory ds does not
+ # support the API the zone loader uses.
+ clist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.IN)
+ clist.configure(INMEMORY_ZONE_CONFIG, True)
+ inmem_client = clist.find(isc.dns.Name("example.com"), True, False)[0]
self.assertRaises(isc.datasrc.NotImplemented,
isc.datasrc.ZoneLoader,
inmem_client, self.test_name, self.test_file)
@@ -239,8 +239,10 @@ class ZoneLoaderTests(unittest.TestCase):
# For ds->ds loading, wrong class is detected upon construction
# Need a bit of the extended setup for CH source client
clientlist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.CH)
- clientlist.configure('[ { "type": "static", "params": "' +
- STATIC_ZONE_FILE +'" } ]', False)
+ clientlist.configure('[ { "type": "MasterFiles", ' +
+ '"cache-enable": true, ' +
+ '"params": {"BIND": "' +
+ STATIC_ZONE_FILE + '" }} ]', True)
self.source_client, _, _ = clientlist.find(isc.dns.Name("bind."),
False, False)
self.assertRaises(isc.dns.InvalidParameter, isc.datasrc.ZoneLoader,
diff --git a/src/lib/python/isc/datasrc/updater_python.cc b/src/lib/python/isc/datasrc/updater_python.cc
index e61db75..331eafa 100644
--- a/src/lib/python/isc/datasrc/updater_python.cc
+++ b/src/lib/python/isc/datasrc/updater_python.cc
@@ -24,7 +24,7 @@
#include <datasrc/client.h>
#include <datasrc/database.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/sqlite3_accessor.h>
#include <datasrc/zone.h>
diff --git a/src/lib/python/isc/datasrc/zonetable_accessor_python.cc b/src/lib/python/isc/datasrc/zonetable_accessor_python.cc
new file mode 100644
index 0000000..d0583b9
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zonetable_accessor_python.cc
@@ -0,0 +1,182 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <datasrc/zone_table_accessor.h>
+
+#include "datasrc.h"
+#include "zonetable_accessor_python.h"
+#include "zonetable_iterator_python.h"
+
+using namespace std;
+using namespace isc::datasrc;
+using namespace isc::datasrc::python;
+
+namespace {
+// The s_* Class simply covers one instantiation of the object
+class s_ZoneTableAccessor : public PyObject {
+public:
+ s_ZoneTableAccessor() :
+ cppobj(ConstZoneTableAccessorPtr()),
+ base_obj(NULL)
+ {}
+
+ ConstZoneTableAccessorPtr cppobj;
+ // This is a reference to a base object; if the object of this class
+ // depends on another object to be in scope during its lifetime,
+ // we use INCREF the base object upon creation, and DECREF it at
+ // the end of the destructor
+ // This is an optional argument to createXXX(). If NULL, it is ignored.
+ PyObject* base_obj;
+};
+
+int
+ZoneTableAccessor_init(PyObject*, PyObject*, PyObject*) {
+ // can't be called directly
+ PyErr_SetString(PyExc_TypeError,
+ "ZoneTableAccessor cannot be constructed directly");
+
+ return (-1);
+}
+
+void
+ZoneTableAccessor_destroy(PyObject* po_self) {
+ s_ZoneTableAccessor* const self =
+ static_cast<s_ZoneTableAccessor*>(po_self);
+ // cppobj is a shared ptr, but to make sure things are not destroyed in
+ // the wrong order, we reset it here.
+ self->cppobj.reset();
+ if (self->base_obj != NULL) {
+ Py_DECREF(self->base_obj);
+ }
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+ZoneTableAccessor_iter(PyObject* po_self) {
+ s_ZoneTableAccessor* const self =
+ static_cast<s_ZoneTableAccessor*>(po_self);
+ try {
+ return (createZoneTableIteratorObject(self->cppobj->getIterator(),
+ po_self));
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unexpected exception");
+ return (NULL);
+ }
+}
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef ZoneTableAccessor_methods[] = {
+ { NULL, NULL, 0, NULL }
+};
+
+const char* const ZoneTableAccessor_doc = "\
+An accessor to a zone table for a data source.\n\
+\n\
+This class object is intended to be used by applications that load zones\
+into memory, so that the application can get a list of zones to be loaded.";
+
+} // end anonymous namespace
+
+namespace isc {
+namespace datasrc {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_ZoneTableAccessor
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject zonetableaccessor_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "datasrc.ZoneTableAccessor",
+ sizeof(s_ZoneTableAccessor), // tp_basicsize
+ 0, // tp_itemsize
+ ZoneTableAccessor_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
+ ZoneTableAccessor_doc, // tp_doc
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ ZoneTableAccessor_iter, // tp_iter
+ NULL, // tp_iternext
+ ZoneTableAccessor_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
+ ZoneTableAccessor_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
+};
+
+PyObject*
+createZoneTableAccessorObject(isc::datasrc::ConstZoneTableAccessorPtr source,
+ PyObject* base_obj)
+{
+ s_ZoneTableAccessor* py_zt = static_cast<s_ZoneTableAccessor*>(
+ zonetableaccessor_type.tp_alloc(&zonetableaccessor_type, 0));
+ if (py_zt != NULL) {
+ py_zt->cppobj = source;
+ py_zt->base_obj = base_obj;
+ if (base_obj != NULL) {
+ Py_INCREF(base_obj);
+ }
+ }
+ return (py_zt);
+}
+
+} // namespace python
+} // namespace datasrc
+} // namespace isc
diff --git a/src/lib/python/isc/datasrc/zonetable_accessor_python.h b/src/lib/python/isc/datasrc/zonetable_accessor_python.h
new file mode 100644
index 0000000..6ebcd92
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zonetable_accessor_python.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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_ZONETABLE_ACCESSOR_H
+#define PYTHON_ZONETABLE_ACCESSOR_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace datasrc {
+namespace python {
+
+extern PyTypeObject zonetableaccessor_type;
+
+/// \brief Create a ZoneTableAccessor python object
+///
+/// \param source The zone iterator pointer to wrap
+/// \param base_obj An optional PyObject that this ZoneFinder depends on
+/// Its refcount is increased, and will be decreased when
+/// this zone iterator is destroyed, making sure that the
+/// base object is never destroyed before this zonefinder.
+PyObject* createZoneTableAccessorObject(
+ isc::datasrc::ConstZoneTableAccessorPtr source, PyObject* base_obj);
+
+} // namespace python
+} // namespace datasrc
+} // namespace isc
+#endif // PYTHON_ZONETABLE_ACCESSOR_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/python/isc/datasrc/zonetable_iterator_python.cc b/src/lib/python/isc/datasrc/zonetable_iterator_python.cc
new file mode 100644
index 0000000..fbf1ebf
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zonetable_iterator_python.cc
@@ -0,0 +1,197 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <datasrc/zone_table_accessor.h>
+#include <dns/python/name_python.h>
+
+#include "datasrc.h"
+#include "zonetable_iterator_python.h"
+
+using namespace std;
+using namespace isc::datasrc;
+using namespace isc::datasrc::python;
+
+namespace {
+// The s_* Class simply covers one instantiation of the object
+class s_ZoneTableIterator : public PyObject {
+public:
+ s_ZoneTableIterator() :
+ cppobj(ZoneTableAccessor::IteratorPtr()), base_obj(NULL)
+ {};
+
+ ZoneTableAccessor::IteratorPtr cppobj;
+ // This is a reference to a base object; if the object of this class
+ // depends on another object to be in scope during its lifetime,
+ // we use INCREF the base object upon creation, and DECREF it at
+ // the end of the destructor
+ // This is an optional argument to createXXX(). If NULL, it is ignored.
+ PyObject* base_obj;
+};
+
+// General creation and destruction
+int
+ZoneTableIterator_init(s_ZoneTableIterator*, PyObject*) {
+ // can't be called directly
+ PyErr_SetString(PyExc_TypeError,
+ "ZoneTableIterator cannot be constructed directly");
+
+ return (-1);
+}
+
+void
+ZoneTableIterator_destroy(s_ZoneTableIterator* const self) {
+ // cppobj is a shared ptr, but to make sure things are not destroyed in
+ // the wrong order, we reset it here.
+ self->cppobj.reset();
+ if (self->base_obj != NULL) {
+ Py_DECREF(self->base_obj);
+ }
+ Py_TYPE(self)->tp_free(self);
+}
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+PyObject*
+ZoneTableIterator_iter(PyObject *self) {
+ Py_INCREF(self);
+ return (self);
+}
+
+PyObject*
+ZoneTableIterator_next(PyObject* po_self) {
+ s_ZoneTableIterator* self = static_cast<s_ZoneTableIterator*>(po_self);
+ if (!self->cppobj || self->cppobj->isLast()) {
+ return (NULL);
+ }
+ try {
+ const isc::datasrc::ZoneSpec& zs = self->cppobj->getCurrent();
+ PyObject* result =
+ Py_BuildValue("iO", zs.index,
+ isc::dns::python::createNameObject(zs.origin));
+ self->cppobj->next();
+ return (result);
+ } catch (const std::exception& exc) {
+ // isc::InvalidOperation is thrown when we call getCurrent()
+ // when we are already done iterating ('iterating past end')
+ // We could also simply return None again
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unexpected exception");
+ return (NULL);
+ }
+}
+
+PyMethodDef ZoneTableIterator_methods[] = {
+ { NULL, NULL, 0, NULL }
+};
+
+const char* const ZoneTableIterator_doc = "\
+Read-only iterator to a zone table.\n\
+\n\
+You can get an instance of the ZoneTableIterator from the\
+ZoneTableAccessor.get_iterator() method.\n\
+\n\
+There's no way to start iterating from the beginning again or return.\n\
+\n\
+The ZoneTableIterator is a Python iterator, and can be iterated over\
+directly.\n\
+";
+
+} // end of unnamed namespace
+
+namespace isc {
+namespace datasrc {
+namespace python {
+PyTypeObject zonetableiterator_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "datasrc.ZoneTableIterator",
+ sizeof(s_ZoneTableIterator), // tp_basicsize
+ 0, // tp_itemsize
+ reinterpret_cast<destructor>(ZoneTableIterator_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
+ ZoneTableIterator_doc, // tp_doc
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ ZoneTableIterator_iter, // tp_iter
+ ZoneTableIterator_next, // tp_iternext
+ ZoneTableIterator_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>(ZoneTableIterator_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
+};
+
+PyObject*
+createZoneTableIteratorObject(ZoneTableAccessor::IteratorPtr source,
+ PyObject* base_obj)
+{
+ s_ZoneTableIterator* py_zi = static_cast<s_ZoneTableIterator*>(
+ zonetableiterator_type.tp_alloc(&zonetableiterator_type, 0));
+ if (py_zi != NULL) {
+ py_zi->cppobj = source;
+ py_zi->base_obj = base_obj;
+ if (base_obj != NULL) {
+ Py_INCREF(base_obj);
+ }
+ }
+ return (py_zi);
+}
+
+} // namespace python
+} // namespace datasrc
+} // namespace isc
+
diff --git a/src/lib/python/isc/datasrc/zonetable_iterator_python.h b/src/lib/python/isc/datasrc/zonetable_iterator_python.h
new file mode 100644
index 0000000..887861e
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zonetable_iterator_python.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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_ZONETABLE_ITERATOR_H
+#define PYTHON_ZONETABLE_ITERATOR_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace datasrc {
+namespace python {
+
+extern PyTypeObject zonetableiterator_type;
+
+/// \brief Create a ZoneTableIterator python object
+///
+/// \param source The zone table iterator pointer to wrap
+/// \param base_obj An optional PyObject that this ZoneIterator depends on
+/// Its refcount is increased, and will be decreased when
+/// this zone iterator is destroyed, making sure that the
+/// base object is never destroyed before this zone iterator.
+PyObject* createZoneTableIteratorObject(
+ isc::datasrc::ZoneTableAccessor::IteratorPtr source,
+ PyObject* base_obj = NULL);
+
+} // namespace python
+} // namespace datasrc
+} // namespace isc
+#endif // PYTHON_ZONETABLE_ITERATOR_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/python/isc/datasrc/zonewriter_inc.cc b/src/lib/python/isc/datasrc/zonewriter_inc.cc
new file mode 100644
index 0000000..1f10a9a
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zonewriter_inc.cc
@@ -0,0 +1,103 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+namespace {
+
+const char* const ZoneWriter_doc = "\
+Does an update to a zone.\n\
+\n\
+This represents the work of a (re)load of a zone. The work is divided\n\
+into three stages load(), install() and cleanup(). They should be\n\
+called in this order for the effect to take place.\n\
+\n\
+We divide them so the update of zone data can be done asynchronously,\n\
+in a different thread. The install() operation is the only one that\n\
+needs to be done in a critical section.\n\
+\n\
+This class provides strong exception guarantee for each public method.\n\
+That is, when any of the methods throws, the entire state stays the\n\
+same as before the call.\n\
+\n\
+ZoneWriter objects cannot be constructed directly. They have to be\n\
+obtained by using get_cached_zone_writer() on a ConfigurableClientList.\n\
+\n\
+";
+
+const char* const ZoneWriter_load_doc = "\
+load() -> err_msg\n\
+\n\
+Get the zone data into memory.\n\
+\n\
+This is the part that does the time-consuming loading into the memory.\n\
+This can be run in a separate thread, for example. It has no effect on\n\
+the data actually served, it only prepares them for future use.\n\
+\n\
+This is the first method you should call on the object. Never call it\n\
+multiple times.\n\
+\n\
+If the zone loads successfully, this method returns None. In the case of\n\
+a load error, if the ZoneWriter was constructed with the\n\
+catch_load_error option (see\n\
+ConfigurableClientList.get_cached_zone_writer()), this method will\n\
+return an error message string; if it was created without the\n\
+catch_load_error option, this method will raise a DataSourceError\n\
+exception.\n\
+\n\
+Exceptions:\n\
+ isc.InvalidOperation if called second time.\n\
+ DataSourceError load related error (not thrown if constructed not to).\n\
+\n\
+";
+
+const char* const ZoneWriter_install_doc = "\
+install() -> void\n\
+\n\
+Put the changes to effect.\n\
+\n\
+This replaces the old version of zone with the one previously prepared\n\
+by load(). It takes ownership of the old zone data, if any.\n\
+\n\
+You may call it only after successful load() and at most once. It\n\
+includes the case the writer is constructed to allow load errors,\n\
+and load() encountered and caught a DataSourceError exception.\n\
+In this case this method installs a special empty zone to\n\
+the table.\n\
+\n\
+The operation is expected to be fast and is meant to be used inside a\n\
+critical section.\n\
+\n\
+This may throw in rare cases. If it throws, you still need to call\n\
+cleanup().\n\
+\n\
+Exceptions:\n\
+ isc.InvalidOperation if called without previous load() or for the\n\
+ second time or cleanup() was called already.\n\
+\n\
+";
+
+const char* const ZoneWriter_cleanup_doc = "\
+cleanup() -> void\n\
+\n\
+Clean up resources.\n\
+\n\
+This releases all resources held by owned zone data. That means the\n\
+one loaded by load() in case install() was not called or was not\n\
+successful, or the one replaced in install().\n\
+\n\
+Exceptions:\n\
+ none\n\
+\n\
+";
+
+} // unnamed namespace
diff --git a/src/lib/python/isc/datasrc/zonewriter_python.cc b/src/lib/python/isc/datasrc/zonewriter_python.cc
new file mode 100644
index 0000000..b3783e8
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zonewriter_python.cc
@@ -0,0 +1,253 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <datasrc/memory/zone_writer.h>
+
+#include "zonewriter_python.h"
+#include "datasrc.h"
+
+#include "zonewriter_inc.cc"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::datasrc;
+using namespace isc::datasrc::memory;
+using namespace isc::datasrc::python;
+using namespace isc::datasrc::memory::python;
+
+//
+// ZoneWriter
+//
+
+namespace {
+
+// The s_* Class simply covers one instantiation of the object
+class s_ZoneWriter : public PyObject {
+public:
+ s_ZoneWriter() :
+ cppobj(ConfigurableClientList::ZoneWriterPtr()),
+ base_obj(NULL)
+ {}
+
+ ConfigurableClientList::ZoneWriterPtr cppobj;
+ // This is a reference to a base object; if the object of this class
+ // depends on another object to be in scope during its lifetime,
+ // we use INCREF the base object upon creation, and DECREF it at
+ // the end of the destructor
+ // This is an optional argument to createXXX(). If NULL, it is ignored.
+ PyObject* base_obj;
+};
+
+int
+ZoneWriter_init(PyObject*, PyObject*, PyObject*) {
+ // can't be called directly
+ PyErr_SetString(PyExc_TypeError,
+ "ZoneWriter cannot be constructed directly");
+
+ return (-1);
+}
+
+void
+ZoneWriter_destroy(PyObject* po_self) {
+ s_ZoneWriter* self = static_cast<s_ZoneWriter*>(po_self);
+ // cppobj is a shared ptr, but to make sure things are not destroyed in
+ // the wrong order, we reset it here.
+ self->cppobj.reset();
+ if (self->base_obj != NULL) {
+ Py_DECREF(self->base_obj);
+ }
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+ZoneWriter_load(PyObject* po_self, PyObject*) {
+ s_ZoneWriter* self = static_cast<s_ZoneWriter*>(po_self);
+ try {
+ std::string error_msg;
+ self->cppobj->load(&error_msg);
+ if (!error_msg.empty()) {
+ return (Py_BuildValue("s", error_msg.c_str()));
+ }
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unknown C++ exception");
+ return (NULL);
+ }
+
+ Py_RETURN_NONE;
+}
+
+PyObject*
+ZoneWriter_install(PyObject* po_self, PyObject*) {
+ s_ZoneWriter* self = static_cast<s_ZoneWriter*>(po_self);
+ try {
+ self->cppobj->install();
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unknown C++ exception");
+ return (NULL);
+ }
+
+ Py_RETURN_NONE;
+}
+
+PyObject*
+ZoneWriter_cleanup(PyObject* po_self, PyObject*) {
+ s_ZoneWriter* self = static_cast<s_ZoneWriter*>(po_self);
+ try {
+ self->cppobj->cleanup();
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unknown C++ exception");
+ return (NULL);
+ }
+
+ Py_RETURN_NONE;
+}
+
+// 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 ZoneWriter_methods[] = {
+ { "load", ZoneWriter_load, METH_NOARGS,
+ ZoneWriter_load_doc },
+ { "install", ZoneWriter_install, METH_NOARGS,
+ ZoneWriter_install_doc },
+ { "cleanup", ZoneWriter_cleanup, METH_NOARGS,
+ ZoneWriter_cleanup_doc },
+ { NULL, NULL, 0, NULL }
+};
+
+} // end of unnamed namespace
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_ZoneWriter
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject zonewriter_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "datasrc.ZoneWriter",
+ sizeof(s_ZoneWriter), // tp_basicsize
+ 0, // tp_itemsize
+ ZoneWriter_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
+ ZoneWriter_doc,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ ZoneWriter_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
+ ZoneWriter_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_ZoneWriter(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(&zonewriter_type) < 0) {
+ return (false);
+ }
+ void* p = &zonewriter_type;
+ if (PyModule_AddObject(mod, "ZoneWriter", static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&zonewriter_type);
+
+ return (true);
+}
+
+PyObject*
+createZoneWriterObject(ConfigurableClientList::ZoneWriterPtr source,
+ PyObject* base_obj)
+{
+ s_ZoneWriter* py_zf = static_cast<s_ZoneWriter*>(
+ zonewriter_type.tp_alloc(&zonewriter_type, 0));
+ if (py_zf != NULL) {
+ py_zf->cppobj = source;
+ py_zf->base_obj = base_obj;
+ if (base_obj != NULL) {
+ Py_INCREF(base_obj);
+ }
+ }
+ return (py_zf);
+}
+
+} // namespace python
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
diff --git a/src/lib/python/isc/datasrc/zonewriter_python.h b/src/lib/python/isc/datasrc/zonewriter_python.h
new file mode 100644
index 0000000..a7c97b3
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zonewriter_python.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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_ZONEWRITER_H
+#define PYTHON_ZONEWRITER_H 1
+
+#include <Python.h>
+#include <datasrc/client_list.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace python {
+
+extern PyTypeObject zonewriter_type;
+
+bool initModulePart_ZoneWriter(PyObject* mod);
+
+/// \brief Create a ZoneWriter python object
+///
+/// \param source The zone writer pointer to wrap
+/// \param base_obj An optional PyObject that this ZoneWriter depends on
+/// Its refcount is increased, and will be decreased when
+/// this zone iterator is destroyed, making sure that the
+/// base object is never destroyed before this ZoneWriter.
+PyObject* createZoneWriterObject(
+ isc::datasrc::ConfigurableClientList::ZoneWriterPtr source,
+ PyObject* base_obj = NULL);
+
+} // namespace python
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // PYTHON_ZONEWRITER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/python/isc/ddns/libddns_messages.mes b/src/lib/python/isc/ddns/libddns_messages.mes
index ab982ad..86af4c3 100644
--- a/src/lib/python/isc/ddns/libddns_messages.mes
+++ b/src/lib/python/isc/ddns/libddns_messages.mes
@@ -212,3 +212,13 @@ To make sure DDNS service is not interrupted, this problem is caught instead
of reraised; The update is aborted, and a SERVFAIL is sent back to the client.
This is most probably a bug in the DDNS code, but *could* be caused by
the data source.
+
+% LIBDDNS_ZONE_INVALID_ERROR Newly received zone data %1/%2 fails validation: %3
+The zone data was received successfully, but the zone when updated with
+this zone data fails validation.
+
+% LIBDDNS_ZONE_INVALID_WARN Newly received zone data %1/%2 has a problem: %3
+The zone data was received successfully, but when checking the zone when
+updated with this zone data, it was discovered there's some issue with
+it. It might be correct, but it should be checked and possibly
+fixed. The problem does not stop the zone from being used.
diff --git a/src/lib/python/isc/ddns/session.py b/src/lib/python/isc/ddns/session.py
index 3368523..6f2b7cf 100644
--- a/src/lib/python/isc/ddns/session.py
+++ b/src/lib/python/isc/ddns/session.py
@@ -656,7 +656,7 @@ class UpdateSession:
'''
# For a number of cases, we may need to remove data in the zone
# (note; SOA is handled separately by __do_update, so that one
- # is explicitely ignored here)
+ # is explicitly ignored here)
if rrset.get_type() == RRType.SOA:
return
result, orig_rrset, _ = self.__diff.find(rrset.get_name(),
@@ -812,6 +812,20 @@ class UpdateSession:
self.__diff.delete_data(old_soa)
self.__diff.add_data(new_soa)
+ def __validate_error(self, reason):
+ '''
+ Used as error callback below.
+ '''
+ logger.error(LIBDDNS_ZONE_INVALID_ERROR, self.__zname, self.__zclass,
+ reason)
+
+ def __validate_warning(self, reason):
+ '''
+ Used as warning callback below.
+ '''
+ logger.warn(LIBDDNS_ZONE_INVALID_WARN, self.__zname, self.__zclass,
+ reason)
+
def __do_update(self):
'''Scan, check, and execute the Update section in the
DDNS Update message.
@@ -849,8 +863,17 @@ class UpdateSession:
elif rrset.get_class() == RRClass.NONE:
self.__do_update_delete_rrs_from_rrset(rrset)
+ if not check_zone(self.__zname, self.__zclass,
+ self.__diff.get_rrset_collection(),
+ (self.__validate_error, self.__validate_warning)):
+ raise UpdateError('Validation of the new zone failed',
+ self.__zname, self.__zclass, Rcode.REFUSED)
self.__diff.commit()
return Rcode.NOERROR
+ except UpdateError:
+ # Propagate UpdateError exceptions (don't catch them in the
+ # blocks below)
+ raise
except isc.datasrc.Error as dse:
logger.info(LIBDDNS_UPDATE_DATASRC_COMMIT_FAILED, dse)
return Rcode.SERVFAIL
diff --git a/src/lib/python/isc/ddns/tests/Makefile.am b/src/lib/python/isc/ddns/tests/Makefile.am
index 4235a2b..9374dde 100644
--- a/src/lib/python/isc/ddns/tests/Makefile.am
+++ b/src/lib/python/isc/ddns/tests/Makefile.am
@@ -6,7 +6,7 @@ CLEANFILES = $(builddir)/rwtest.sqlite3.copied
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/ddns/tests/session_tests.py b/src/lib/python/isc/ddns/tests/session_tests.py
index bc25310..49bf672 100644
--- a/src/lib/python/isc/ddns/tests/session_tests.py
+++ b/src/lib/python/isc/ddns/tests/session_tests.py
@@ -209,7 +209,7 @@ class SessionTestBase(unittest.TestCase):
def tearDown(self):
# With the Updater created in _get_update_zone, and tests
# doing all kinds of crazy stuff, one might get database locked
- # errors if it doesn't clean up explicitely after each test
+ # errors if it doesn't clean up explicitly after each test
self._session = None
def check_response(self, msg, expected_rcode):
@@ -1481,6 +1481,16 @@ class SessionTest(SessionTestBase):
self.assertEqual(Rcode.SERVFAIL.to_text(),
self._session._UpdateSession__do_update().to_text())
+ def test_check_zone_failure(self):
+ # ns3.example.org. is an NS and should not have a CNAME
+ # record. This would cause check_zone() to fail.
+ self.__initialize_update_rrsets()
+ new_cname = create_rrset("ns3.example.org", TEST_RRCLASS,
+ RRType.CNAME, 3600,
+ [ "cname.example.org." ])
+
+ self.check_full_handle_result(Rcode.REFUSED, [ new_cname ])
+
class SessionACLTest(SessionTestBase):
'''ACL related tests for update session.'''
def test_update_acl_check(self):
diff --git a/src/lib/python/isc/log/tests/Makefile.am b/src/lib/python/isc/log/tests/Makefile.am
index ec29b7a..14f10a2 100644
--- a/src/lib/python/isc/log/tests/Makefile.am
+++ b/src/lib/python/isc/log/tests/Makefile.am
@@ -8,7 +8,7 @@ EXTRA_DIST = console.out check_output.sh $(PYTESTS_NOGEN)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/log_messages/Makefile.am b/src/lib/python/isc/log_messages/Makefile.am
index c8b9c7a..3e265d7 100644
--- a/src/lib/python/isc/log_messages/Makefile.am
+++ b/src/lib/python/isc/log_messages/Makefile.am
@@ -4,6 +4,8 @@ EXTRA_DIST = __init__.py
EXTRA_DIST += init_messages.py
EXTRA_DIST += cmdctl_messages.py
EXTRA_DIST += ddns_messages.py
+EXTRA_DIST += libmemmgr_messages.py
+EXTRA_DIST += memmgr_messages.py
EXTRA_DIST += stats_messages.py
EXTRA_DIST += stats_httpd_messages.py
EXTRA_DIST += xfrin_messages.py
@@ -24,6 +26,8 @@ CLEANFILES = __init__.pyc
CLEANFILES += init_messages.pyc
CLEANFILES += cmdctl_messages.pyc
CLEANFILES += ddns_messages.pyc
+CLEANFILES += libmemmgr_messages.pyc
+CLEANFILES += memmgr_messages.pyc
CLEANFILES += stats_messages.pyc
CLEANFILES += stats_httpd_messages.pyc
CLEANFILES += xfrin_messages.pyc
diff --git a/src/lib/python/isc/log_messages/libmemmgr_messages.py b/src/lib/python/isc/log_messages/libmemmgr_messages.py
new file mode 100644
index 0000000..3aedc3f
--- /dev/null
+++ b/src/lib/python/isc/log_messages/libmemmgr_messages.py
@@ -0,0 +1 @@
+from work.libmemmgr_messages import *
diff --git a/src/lib/python/isc/log_messages/memmgr_messages.py b/src/lib/python/isc/log_messages/memmgr_messages.py
new file mode 100644
index 0000000..8c59cc9
--- /dev/null
+++ b/src/lib/python/isc/log_messages/memmgr_messages.py
@@ -0,0 +1 @@
+from work.memmgr_messages import *
diff --git a/src/lib/python/isc/memmgr/Makefile.am b/src/lib/python/isc/memmgr/Makefile.am
new file mode 100644
index 0000000..5529570
--- /dev/null
+++ b/src/lib/python/isc/memmgr/Makefile.am
@@ -0,0 +1,25 @@
+SUBDIRS = . tests
+
+python_PYTHON = __init__.py builder.py datasrc_info.py logger.py
+
+pythondir = $(pyexecdir)/isc/memmgr
+
+BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/libmemmgr_messages.py
+
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/libmemmgr_messages.py
+
+pylogmessagedir = $(pyexecdir)/isc/log_messages/
+
+CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/libmemmgr_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/libmemmgr_messages.pyc
+
+EXTRA_DIST = libmemmgr_messages.mes
+
+$(PYTHON_LOGMSGPKG_DIR)/work/libmemmgr_messages.py : libmemmgr_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message \
+ -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/libmemmgr_messages.mes
+
+CLEANDIRS = __pycache__
+
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/memmgr/__init__.py b/src/lib/python/isc/memmgr/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/lib/python/isc/memmgr/builder.py b/src/lib/python/isc/memmgr/builder.py
new file mode 100644
index 0000000..9c3738e
--- /dev/null
+++ b/src/lib/python/isc/memmgr/builder.py
@@ -0,0 +1,185 @@
+# Copyright (C) 2013 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 json
+from isc.datasrc import ConfigurableClientList
+from isc.memmgr.datasrc_info import SegmentInfo
+
+from isc.log_messages.libmemmgr_messages import *
+from isc.memmgr.logger import logger
+
+class MemorySegmentBuilder:
+ """The builder runs in a different thread in the memory manager. It
+ waits for commands from the memory manager, and then executes them
+ in the given order sequentially.
+ """
+
+ def __init__(self, sock, cv, command_queue, response_queue):
+ """ The constructor takes the following arguments:
+
+ sock: A socket using which this builder object notifies the
+ main thread that it has a response waiting for it.
+
+ cv: A condition variable object that is used by the main
+ thread to tell this builder object that new commands are
+ available to it. Note that this is also used for
+ synchronizing access to the queues, so code that uses
+ MemorySegmentBuilder must use this condition variable's
+ lock object to synchronize its access to the queues.
+
+ command_queue: A list of commands sent by the main thread to
+ this object. Commands should be executed
+ sequentially in the given order by this
+ object.
+
+ response_queue: A list of responses sent by this object to
+ the main thread. The format of this is
+ currently not strictly defined. Future
+ tickets will be able to define it based on
+ how it's used.
+ """
+
+ self._sock = sock
+ self._cv = cv
+ self._command_queue = command_queue
+ self._response_queue = response_queue
+ self._shutdown = False
+
+ def __handle_shutdown(self):
+ # This method is called when handling the 'shutdown' command. The
+ # following tuple is passed:
+ #
+ # ('shutdown',)
+ self._shutdown = True
+
+ def __handle_bad_command(self, bad_command):
+ # A bad command was received. Raising an exception is not useful
+ # in this case as we are likely running in a different thread
+ # from the main thread which would need to be notified. Instead
+ # return this in the response queue.
+ logger.error(LIBMEMMGR_BUILDER_BAD_COMMAND_ERROR, bad_command)
+ self._response_queue.append(('bad_command',))
+ self._shutdown = True
+
+ def __handle_load(self, zone_name, dsrc_info, rrclass, dsrc_name):
+ # This method is called when handling the 'load' command. The
+ # following tuple is passed:
+ #
+ # ('load', zone_name, dsrc_info, rrclass, dsrc_name)
+ #
+ # where:
+ #
+ # * zone_name is None or isc.dns.Name, specifying the zone name
+ # to load. If it's None, it means all zones to be cached in
+ # the specified data source (used for initialization).
+ #
+ # * dsrc_info is a DataSrcInfo object corresponding to the
+ # generation ID of the set of data sources for this loading.
+ #
+ # * rrclass is an isc.dns.RRClass object, the RR class of the
+ # data source.
+ #
+ # * dsrc_name is a string, specifying a data source name.
+
+ clist = dsrc_info.clients_map[rrclass]
+ sgmt_info = dsrc_info.segment_info_map[(rrclass, dsrc_name)]
+ params = json.dumps(sgmt_info.get_reset_param(SegmentInfo.WRITER))
+ clist.reset_memory_segment(dsrc_name,
+ ConfigurableClientList.READ_WRITE,
+ params)
+
+ if zone_name is not None:
+ zones = [(None, zone_name)]
+ else:
+ zones = clist.get_zone_table_accessor(dsrc_name, True)
+
+ for _, zone_name in zones:
+ catch_load_error = (zone_name is None) # install empty zone initially
+ result, writer = clist.get_cached_zone_writer(zone_name, catch_load_error,
+ dsrc_name)
+ if result != ConfigurableClientList.CACHE_STATUS_ZONE_SUCCESS:
+ logger.error(LIBMEMMGR_BUILDER_GET_ZONE_WRITER_ERROR, zone_name, dsrc_name)
+ continue
+
+ try:
+ error = writer.load()
+ if error is not None:
+ logger.error(LIBMEMMGR_BUILDER_ZONE_WRITER_LOAD_1_ERROR, zone_name, dsrc_name, error)
+ continue
+ except Exception as e:
+ logger.error(LIBMEMMGR_BUILDER_ZONE_WRITER_LOAD_2_ERROR, zone_name, dsrc_name, str(e))
+ continue
+ writer.install()
+ writer.cleanup()
+
+ # need to reset the segment so readers can read it (note: memmgr
+ # itself doesn't have to keep it open, but there's currently no
+ # public API to just clear the segment)
+ clist.reset_memory_segment(dsrc_name,
+ ConfigurableClientList.READ_ONLY,
+ params)
+
+ self._response_queue.append(('load-completed', dsrc_info, rrclass,
+ dsrc_name))
+
+ def run(self):
+ """ This is the method invoked when the builder thread is
+ started. In this thread, be careful when modifying
+ variables passed-by-reference in the constructor. If they
+ are reassigned, they will not refer to the main thread's
+ objects any longer. Any use of command_queue and
+ response_queue must be synchronized by acquiring the lock in
+ the condition variable. This method must normally terminate
+ only when the 'shutdown' command is sent to it.
+ """
+
+ # Acquire the condition variable while running the loop.
+ with self._cv:
+ while not self._shutdown:
+ while not self._command_queue:
+ self._cv.wait()
+ # Move the queue content to a local queue. Be careful of
+ # not making assignments to reference variables.
+ local_command_queue = self._command_queue[:]
+ del self._command_queue[:]
+
+ # Run commands passed in the command queue sequentially
+ # in the given order. For now, it only supports the
+ # "shutdown" command, which just exits the thread.
+ for command_tuple in local_command_queue:
+ command = command_tuple[0]
+ if command == 'load':
+ # See the comments for __handle_load() for
+ # details of the tuple passed to the "load"
+ # command.
+ _, zone_name, dsrc_info, rrclass, dsrc_name = command_tuple
+ self.__handle_load(zone_name, dsrc_info, rrclass, dsrc_name)
+ elif command == 'shutdown':
+ self.__handle_shutdown()
+ # When the shutdown command is received, we do
+ # not process any further commands.
+ break
+ else:
+ self.__handle_bad_command(command)
+ # When a bad command is received, we do not
+ # process any further commands.
+ break
+
+ # Notify (any main thread) on the socket about a
+ # response. Otherwise, the main thread may wait in its
+ # loop without knowing there was a problem.
+ if self._response_queue:
+ while self._sock.send(b'x') != 1:
+ continue
diff --git a/src/lib/python/isc/memmgr/datasrc_info.py b/src/lib/python/isc/memmgr/datasrc_info.py
new file mode 100644
index 0000000..db4a515
--- /dev/null
+++ b/src/lib/python/isc/memmgr/datasrc_info.py
@@ -0,0 +1,413 @@
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import os
+from collections import deque
+
+class SegmentInfoError(Exception):
+ """An exception raised for general errors in the SegmentInfo class."""
+ pass
+
+class SegmentInfo:
+ """A base class to maintain information about memory segments.
+
+ An instance of this class corresponds to the memory segment used
+ for in-memory cache of a specific single data source. It manages
+ information to set/reset the latest effective segment (such as
+ path to a memory mapped file) and sets of other modules using the
+ segment.
+
+ Since there can be several different types of memory segments,
+ the top level class provides abstract interfaces independent from
+ segment-type specific details. Such details are expected to be
+ delegated to subclasses corresponding to specific types of segments.
+
+ A summarized (and simplified) state transition diagram (for __state)
+ would be as follows:
+ +--sync_reader()/remove_reader()
+ | still have old readers
+ | |
+ UPDATING-----complete_--->SYNCHRONIZING<---+
+ ^ update() |
+ start_update()| | sync_reader()/remove_reader()
+ events | V no more old reader
+ exist READY<------complete_----------COPYING
+ update()
+
+ """
+ # Common constants of user type: reader or writer
+ READER = 0
+ WRITER = 1
+
+ # Enumerated values for state:
+ UPDATING = 0 # the segment is being updated (by the builder thread,
+ # although SegmentInfo won't care about this level of
+ # details).
+ SYNCHRONIZING = 1 # one pair of underlying segments has been
+ # updated, and readers are now migrating to the
+ # updated version of the segment.
+ COPYING = 2 # all readers that used the old version of segment have
+ # been migrated to the updated version, and the old
+ # segment is now being updated.
+ READY = 3 # both segments of the pair have been updated. it can now
+ # handle further updates (e.g., from xfrin).
+
+ def __init__(self):
+ # Holds the state of SegmentInfo. See the class description
+ # above for the state transition diagram.
+ self.__state = self.READY
+ # __readers is a set of 'reader_session_id' private to
+ # SegmentInfo. It consists of the (ID of) reader modules that
+ # are using the "current" reader version of the segment.
+ self.__readers = set()
+ # __old_readers is a set of 'reader_session_id' private to
+ # SegmentInfo for write (update), but publicly readable. It can
+ # be non empty only in the SYNCHRONIZING state, and consists of
+ # (ID of) reader modules that are using the old version of the
+ # segments (and have to migrate to the updated version).
+ self.__old_readers = set()
+ # __events is a FIFO queue of opaque data for pending update
+ # events. Update events can come at any time (e.g., after
+ # xfr-in), but can be only handled if SegmentInfo is in the
+ # READY state. This maintains such pending events in the order
+ # they arrived. SegmentInfo doesn't have to know the details of
+ # the stored data; it only matters for the memmgr.
+ self.__events = deque()
+
+ def get_state(self):
+ """Returns the state of SegmentInfo (UPDATING, SYNCHRONIZING,
+ COPYING or READY)."""
+ return self.__state
+
+ def get_readers(self):
+ """Returns a set of IDs of the reader modules that are using the
+ "current" reader version of the segment. This method is mainly
+ useful for testing purposes."""
+ return self.__readers
+
+ def get_old_readers(self):
+ """Returns a set of IDs of reader modules that are using the old
+ version of the segments and have to be migrated to the updated
+ version."""
+ return self.__old_readers
+
+ def get_events(self):
+ """Returns a list of pending events in the order they arrived."""
+ return list(self.__events)
+
+ # Helper method used in complete_update(), sync_reader() and
+ # remove_reader().
+ def __sync_reader_helper(self, new_state):
+ if not self.__old_readers:
+ self.__state = new_state
+ if self.__events:
+ return self.__events.popleft()
+
+ return None
+
+ def add_event(self, event_data):
+ """Add an event to the end of the pending events queue. The
+ event_data is not used internally by this class, and is returned
+ as-is by other methods. The format of event_data only matters in
+ the memmgr. This method must be called by memmgr when it
+ receives a request for reloading a zone. No state transition
+ happens."""
+ self.__events.append(event_data)
+
+ def add_reader(self, reader_session_id):
+ """Add the reader module ID to an internal set of reader modules
+ that are using the "current" reader version of the segment. It
+ must be called by memmgr when it first gets the pre-existing
+ readers or when it's notified of a new reader. No state
+ transition happens.
+
+ When the SegmentInfo is not in the READY state, if memmgr gets
+ notified of a new reader (such as b10-auth) subscribing to the
+ readers group and calls add_reader(), we assume the new reader
+ is using the new mapped file and not the old one. For making
+ sure there is no race, memmgr should make SegmentInfo updates in
+ the main thread itself (which also handles communications) and
+ only have the builder in a different thread."""
+ if reader_session_id in self.__readers:
+ raise SegmentInfoError('Reader session ID is already in readers set: ' +
+ str(reader_session_id))
+
+ self.__readers.add(reader_session_id)
+
+ def start_update(self):
+ """If the current state is READY and there are pending events,
+ it changes the state to UPDATING and returns the head (oldest)
+ event (without removing it from the pending events queue). This
+ tells the caller (memmgr) that it should initiate the update
+ process with the builder. In all other cases it returns None."""
+ if self.__state == self.READY:
+ if self.__events:
+ self.__state = self.UPDATING
+ return self.__events[0]
+ else:
+ return None
+
+ raise SegmentInfoError('start_update() called in ' +
+ 'incorrect state: ' + str(self.__state))
+
+ def complete_update(self):
+ """This method should be called when memmgr is notified by the
+ builder of the completion of segment update. It changes the
+ state from UPDATING to SYNCHRONIZING, and COPYING to READY. In
+ the former case, set of reader modules that are using the
+ "current" reader version of the segment are moved to the set
+ that are using an "old" version of segment. If there are no such
+ readers using the "old" version of segment, it pops the head
+ (oldest) event from the pending events queue and returns it. It
+ is an error if this method is called in other states than
+ UPDATING and COPYING."""
+ if self.__state == self.UPDATING:
+ self.__state = self.SYNCHRONIZING
+ self.__old_readers = self.__readers
+ self.__readers = set()
+ return self.__sync_reader_helper(self.READY)
+ elif self.__state == self.COPYING:
+ self.__state = self.READY
+ return None
+ else:
+ raise SegmentInfoError('complete_update() called in ' +
+ 'incorrect state: ' + str(self.__state))
+
+ def sync_reader(self, reader_session_id):
+ """This method must only be called in the SYNCHRONIZING
+ state. memmgr should call it when it receives the
+ "segment_update_ack" message from a reader module. It moves the
+ given ID from the set of reader modules that are using the "old"
+ version of the segment to the set of reader modules that are
+ using the "current" version of the segment, and if there are no
+ reader modules using the "old" version of the segment, the state
+ is changed to COPYING. If the state has changed to COPYING, it
+ pops the head (oldest) event from the pending events queue and
+ returns it; otherwise it returns None."""
+ if self.__state != self.SYNCHRONIZING:
+ raise SegmentInfoError('sync_reader() called in ' +
+ 'incorrect state: ' + str(self.__state))
+ if reader_session_id not in self.__old_readers:
+ raise SegmentInfoError('Reader session ID is not in old readers set: ' +
+ str(reader_session_id))
+ if reader_session_id in self.__readers:
+ raise SegmentInfoError('Reader session ID is already in readers set: ' +
+ str(reader_session_id))
+
+ self.__old_readers.remove(reader_session_id)
+ self.__readers.add(reader_session_id)
+
+ return self.__sync_reader_helper(self.COPYING)
+
+ def remove_reader(self, reader_session_id):
+ """This method must only be called in the SYNCHRONIZING
+ state. memmgr should call it when it's notified that an existing
+ reader has unsubscribed. It removes the given reader ID from
+ either the set of readers that use the "current" version of the
+ segment or the "old" version of the segment (wherever the reader
+ belonged), and in the latter case, if there are no reader
+ modules using the "old" version of the segment, the state is
+ changed to COPYING. If the state has changed to COPYING, it pops
+ the head (oldest) event from the pending events queue and
+ returns it; otherwise it returns None."""
+ if self.__state != self.SYNCHRONIZING:
+ raise SegmentInfoError('remove_reader() called in ' +
+ 'incorrect state: ' + str(self.__state))
+ if reader_session_id in self.__old_readers:
+ self.__old_readers.remove(reader_session_id)
+ return self.__sync_reader_helper(self.COPYING)
+ elif reader_session_id in self.__readers:
+ self.__readers.remove(reader_session_id)
+ return None
+ else:
+ raise SegmentInfoError('Reader session ID is not in current ' +
+ 'readers or old readers set: ' +
+ str(reader_session_id))
+
+ def create(type, genid, rrclass, datasrc_name, mgr_config):
+ """Factory of specific SegmentInfo subclass instance based on the
+ segment type.
+
+ This is specifically for the memmgr, and segments that are not of
+ its interest will be ignored. This method returns None in these
+ cases. At least 'local' type segments will be ignored this way.
+
+ If an unknown type of segment is specified, this method throws an
+ SegmentInfoError exception. The assumption is that this method
+ is called after the corresponding data source configuration has been
+ validated, at which point such unknown segments should have been
+ rejected.
+
+ Parameters:
+ type (str or None): The type of memory segment; None if the segment
+ isn't used.
+ genid (int): The generation ID of the corresponding data source
+ configuration.
+ rrclass (isc.dns.RRClass): The RR class of the data source.
+ datasrc_name (str): The name of the data source.
+ mgr_config (dict): memmgr configuration related to memory segment
+ information. The content of the dict is type specific; each
+ subclass is expected to know which key is necessary and the
+ semantics of its value.
+
+ """
+ if type == 'mapped':
+ return MappedSegmentInfo(genid, rrclass, datasrc_name, mgr_config)
+ elif type is None or type == 'local':
+ return None
+ raise SegmentInfoError('unknown segment type to create info: ' + type)
+
+ def get_reset_param(self, user_type):
+ """Return parameters to reset the zone table memory segment.
+
+ It returns a dict object that consists of parameter mappings
+ (string to parameter value) for the specified type of user to
+ reset a zone table segment with
+ isc.datasrc.ConfigurableClientList.reset_memory_segment(). It
+ can also be passed to the user module as part of command
+ parameters. Note that reset_memory_segment() takes a json
+ expression encoded as a string, so the return value of this method
+ will have to be converted with json.dumps().
+
+ Each subclass must implement this method.
+
+ Parameter:
+ user_type (READER or WRITER): specifies the type of user to reset
+ the segment.
+
+ """
+ raise SegmentInfoError('get_reset_param is not implemented')
+
+ def switch_versions(self):
+ """Switch internal information for the reader segment and writer
+ segment.
+
+ This method is expected to be called when the writer on one version
+ of memory segment completes updates and the memmgr is going to
+ have readers switch to the updated version. Details of the
+ information to be switched would depend on the segment type, and
+ are delegated to the specific subclass.
+
+ Each subclass must implement this method.
+
+ """
+ raise SegmentInfoError('switch_versions is not implemented')
+
+class MappedSegmentInfo(SegmentInfo):
+ """SegmentInfo implementation of 'mapped' type memory segments.
+
+ It maintains paths to mapped files both readers and the writer.
+
+ While objets of this class are expected to be shared by multiple
+ threads, it assumes operations are serialized through message passing,
+ so access to this class itself is not protected by any explicit
+ synchronization mechanism.
+
+ """
+ def __init__(self, genid, rrclass, datasrc_name, mgr_config):
+ super().__init__()
+
+ # Something like "/var/bind10/zone-IN-1-sqlite3-mapped"
+ self.__mapped_file_base = mgr_config['mapped_file_dir'] + os.sep + \
+ 'zone-' + str(rrclass) + '-' + str(genid) + '-' + datasrc_name + \
+ '-mapped'
+
+ # Current versions (suffix of the mapped files) for readers and the
+ # writer. In this initial implementation we assume that all possible
+ # readers are waiting for a new version (not using pre-existing one),
+ # and the writer is expected to build a new segment as version "0".
+ self.__reader_ver = None # => 0 => 1 => 0 => 1 ...
+ self.__writer_ver = 0 # => 1 => 0 => 1 => 0 ...
+
+ def get_reset_param(self, user_type):
+ ver = self.__reader_ver if user_type == self.READER else \
+ self.__writer_ver
+ if ver is None:
+ return None
+ mapped_file = self.__mapped_file_base + '.' + str(ver)
+ return {'mapped-file': mapped_file}
+
+ def switch_versions(self):
+ # Swith the versions as noted in the constructor.
+ self.__writer_ver = 1 - self.__writer_ver
+
+ if self.__reader_ver is None:
+ self.__reader_ver = 0
+ else:
+ self.__reader_ver = 1 - self.__reader_ver
+
+ # Versions should be different
+ assert(self.__reader_ver != self.__writer_ver)
+
+class DataSrcInfo:
+ """A container for datasrc.ConfigurableClientLists and associated
+ in-memory segment information corresponding to a given geration of
+ configuration.
+
+ This class maintains all datasrc.ConfigurableClientLists in a form
+ of dict from RR classes corresponding to a given single generation
+ of data source configuration, along with sets of memory segment
+ information that needs to be used by memmgr.
+
+ Once constructed, mappings do not change (different generation of
+ configuration will result in another DataSrcInfo objects). Status
+ of SegmentInfo objects stored in this class object may change over time.
+
+ Attributes: these are all constant and read only. For dict objects,
+ mapping shouldn't be modified either.
+ gen_id (int): The corresponding configuration generation ID.
+ clients_map (dict, isc.dns.RRClass=>isc.datasrc.ConfigurableClientList):
+ The configured client lists for all RR classes of the generation.
+ segment_info_map (dict, (isc.dns.RRClass, str)=>SegmentInfo):
+ SegmentInfo objects managed in the DataSrcInfo objects. Can be
+ retrieved by (RRClass, <data source name>).
+
+ """
+ def __init__(self, genid, clients_map, mgr_config):
+ """Constructor.
+
+ As long as given parameters are of valid type and have been
+ validated, this constructor shouldn't raise an exception.
+
+ Parameters:
+ genid (int): see gen_id attribute
+ clients_map (dict): see clients_map attribute
+ mgr_config (dict, str=>key-dependent-value): A copy of the current
+ memmgr configuration, in case it's needed to construct a specific
+ type of SegmentInfo. The specific SegmentInfo class is expected
+ to know the key-value mappings that it needs.
+
+ """
+ self.__gen_id = genid
+ self.__clients_map = clients_map
+ self.__segment_info_map = {}
+ for (rrclass, client_list) in clients_map.items():
+ for (name, sgmt_type, _) in client_list.get_status():
+ sgmt_info = SegmentInfo.create(sgmt_type, genid, rrclass, name,
+ mgr_config)
+ if sgmt_info is not None:
+ self.__segment_info_map[(rrclass, name)] = sgmt_info
+
+ @property
+ def gen_id(self):
+ return self.__gen_id
+
+ @property
+ def clients_map(self):
+ return self.__clients_map
+
+ @property
+ def segment_info_map(self):
+ return self.__segment_info_map
diff --git a/src/lib/python/isc/memmgr/libmemmgr_messages.mes b/src/lib/python/isc/memmgr/libmemmgr_messages.mes
new file mode 100644
index 0000000..c8fcf05
--- /dev/null
+++ b/src/lib/python/isc/memmgr/libmemmgr_messages.mes
@@ -0,0 +1,35 @@
+# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# No namespace declaration - these constants go in the global namespace
+# of the config_messages python module.
+
+% LIBMEMMGR_BUILDER_BAD_COMMAND_ERROR MemorySegmentBuilder received bad command '%1'
+The MemorySegmentBuilder has received a bad command in its input command
+queue. This is likely a programming error. If the builder runs in a
+separate thread, this would cause it to exit the thread.
+
+% LIBMEMMGR_BUILDER_GET_ZONE_WRITER_ERROR Unable to get zone writer for zone '%1', data source '%2'. Skipping.
+The MemorySegmentBuilder was unable to get a ZoneWriter for the
+specified zone when handling the load command. This zone will be
+skipped.
+
+% LIBMEMMGR_BUILDER_ZONE_WRITER_LOAD_1_ERROR Error loading zone '%1', data source '%2': '%3'
+The MemorySegmentBuilder failed to load the specified zone when handling
+the load command. This zone will be skipped.
+
+% LIBMEMMGR_BUILDER_ZONE_WRITER_LOAD_2_ERROR Error loading zone '%1', data source '%2': '%3'
+An exception occured when the MemorySegmentBuilder tried to load the
+specified zone when handling the load command. This zone will be
+skipped.
diff --git a/src/lib/python/isc/memmgr/logger.py b/src/lib/python/isc/memmgr/logger.py
new file mode 100644
index 0000000..eb324cf
--- /dev/null
+++ b/src/lib/python/isc/memmgr/logger.py
@@ -0,0 +1,20 @@
+# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+'''Common definitions regarding logging for the memmgr package.'''
+
+import isc.log
+
+logger = isc.log.Logger("libmemmgr")
diff --git a/src/lib/python/isc/memmgr/tests/Makefile.am b/src/lib/python/isc/memmgr/tests/Makefile.am
new file mode 100644
index 0000000..b171cb1
--- /dev/null
+++ b/src/lib/python/isc/memmgr/tests/Makefile.am
@@ -0,0 +1,36 @@
+SUBDIRS = testdata
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = builder_tests.py datasrc_info_tests.py
+EXTRA_DIST = $(PYTESTS)
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+endif
+
+# Some tests require backend shared memory support
+if USE_SHARED_MEMORY
+HAVE_SHARED_MEMORY=yes
+else
+HAVE_SHARED_MEMORY=no
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+# B10_FROM_BUILD is necessary to load data source backend from the build tree.
+check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ TESTDATA_PATH=$(abs_srcdir)/testdata \
+ TESTDATA_WRITE_PATH=$(builddir) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
+ HAVE_SHARED_MEMORY=$(HAVE_SHARED_MEMORY) \
+ PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
diff --git a/src/lib/python/isc/memmgr/tests/builder_tests.py b/src/lib/python/isc/memmgr/tests/builder_tests.py
new file mode 100644
index 0000000..b5122cb
--- /dev/null
+++ b/src/lib/python/isc/memmgr/tests/builder_tests.py
@@ -0,0 +1,240 @@
+# Copyright (C) 2013 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 os
+import socket
+import select
+import threading
+
+import isc.log
+from isc.dns import *
+import isc.datasrc
+from isc.memmgr.builder import *
+from isc.server_common.datasrc_clients_mgr import DataSrcClientsMgr
+from isc.memmgr.datasrc_info import *
+
+TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
+
+# Defined for easier tests with DataSrcClientsMgr.reconfigure(), which
+# only needs get_value() method
+class MockConfigData:
+ def __init__(self, data):
+ self.__data = data
+
+ def get_value(self, identifier):
+ return self.__data[identifier], False
+
+class TestMemorySegmentBuilder(unittest.TestCase):
+ def _create_builder_thread(self):
+ (self._master_sock, self._builder_sock) = \
+ socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+
+ self._builder_command_queue = []
+ self._builder_response_queue = []
+
+ self._builder_lock = threading.Lock()
+ self._builder_cv = threading.Condition(lock=self._builder_lock)
+
+ self._builder = MemorySegmentBuilder(self._builder_sock,
+ self._builder_cv,
+ self._builder_command_queue,
+ self._builder_response_queue)
+ self._builder_thread = threading.Thread(target=self._builder.run)
+
+ def setUp(self):
+ self._create_builder_thread()
+ self.__mapped_file_path = None
+
+ def tearDown(self):
+ # It's the tests' responsibility to stop and join the builder
+ # thread if they start it.
+ self.assertFalse(self._builder_thread.isAlive())
+
+ self._master_sock.close()
+ self._builder_sock.close()
+
+ if self.__mapped_file_path is not None:
+ if os.path.exists(self.__mapped_file_path):
+ os.unlink(self.__mapped_file_path)
+
+ def test_bad_command(self):
+ """Tests what happens when a bad command is passed to the
+ MemorySegmentBuilder.
+ """
+
+ self._builder_thread.start()
+
+ # Now that the builder thread is running, send it a bad
+ # command. The thread should exit its main loop and be joinable.
+ with self._builder_cv:
+ self._builder_command_queue.append(('bad_command',))
+ self._builder_cv.notify_all()
+
+ # Wait 5 seconds to receive a notification on the socket from
+ # the builder.
+ (reads, _, _) = select.select([self._master_sock], [], [], 5)
+ self.assertTrue(self._master_sock in reads)
+
+ # Reading 1 byte should not block us here, especially as the
+ # socket is ready to read. It's a hack, but this is just a
+ # testcase.
+ got = self._master_sock.recv(1)
+ self.assertEqual(got, b'x')
+
+ # Wait 5 seconds at most for the main loop of the builder to
+ # exit.
+ self._builder_thread.join(5)
+ self.assertFalse(self._builder_thread.isAlive())
+
+ # The command queue must be cleared, and the response queue must
+ # contain a response that a bad command was sent. The thread is
+ # no longer running, so we can use the queues without a lock.
+ self.assertEqual(len(self._builder_command_queue), 0)
+ self.assertEqual(len(self._builder_response_queue), 1)
+
+ response = self._builder_response_queue[0]
+ self.assertTrue(isinstance(response, tuple))
+ self.assertTupleEqual(response, ('bad_command',))
+ del self._builder_response_queue[:]
+
+ def test_shutdown(self):
+ """Tests that shutdown command exits the MemorySegmentBuilder
+ loop.
+ """
+
+ self._builder_thread.start()
+
+ # Now that the builder thread is running, send it the "shutdown"
+ # command. The thread should exit its main loop and be joinable.
+ with self._builder_cv:
+ self._builder_command_queue.append(('shutdown',))
+ # Commands after 'shutdown' must be ignored.
+ self._builder_command_queue.append(('bad_command_1',))
+ self._builder_command_queue.append(('bad_command_2',))
+ self._builder_cv.notify_all()
+
+ # Wait 5 seconds at most for the main loop of the builder to
+ # exit.
+ self._builder_thread.join(5)
+ self.assertFalse(self._builder_thread.isAlive())
+
+ # The command queue must be cleared, and the response queue must
+ # be untouched (we don't use it in this test). The thread is no
+ # longer running, so we can use the queues without a lock.
+ self.assertEqual(len(self._builder_command_queue), 0)
+ self.assertEqual(len(self._builder_response_queue), 0)
+
+ @unittest.skipIf(os.environ['HAVE_SHARED_MEMORY'] != 'yes',
+ 'shared memory is not available')
+ def test_load(self):
+ """
+ Test "load" command.
+ """
+
+ mapped_file_dir = os.environ['TESTDATA_WRITE_PATH']
+ mgr_config = {'mapped_file_dir': mapped_file_dir}
+
+ cfg_data = MockConfigData(
+ {"classes":
+ {"IN": [{"type": "MasterFiles",
+ "params": { "example.com": TESTDATA_PATH + "example.com.zone" },
+ "cache-enable": True,
+ "cache-type": "mapped"}]
+ }
+ })
+ cmgr = DataSrcClientsMgr(use_cache=True)
+ cmgr.reconfigure({}, cfg_data)
+
+ genid, clients_map = cmgr.get_clients_map()
+ datasrc_info = DataSrcInfo(genid, clients_map, mgr_config)
+
+ self.assertEqual(1, datasrc_info.gen_id)
+ self.assertEqual(clients_map, datasrc_info.clients_map)
+ self.assertEqual(1, len(datasrc_info.segment_info_map))
+ sgmt_info = datasrc_info.segment_info_map[(RRClass.IN, 'MasterFiles')]
+ self.assertIsNone(sgmt_info.get_reset_param(SegmentInfo.READER))
+ self.assertIsNotNone(sgmt_info.get_reset_param(SegmentInfo.WRITER))
+
+ param = sgmt_info.get_reset_param(SegmentInfo.WRITER)
+ self.__mapped_file_path = param['mapped-file']
+
+ self._builder_thread.start()
+
+ # Now that the builder thread is running, send it the "load"
+ # command. We should be notified when the load operation is
+ # complete.
+ with self._builder_cv:
+ self._builder_command_queue.append(('load',
+ isc.dns.Name("example.com"),
+ datasrc_info, RRClass.IN,
+ 'MasterFiles'))
+ self._builder_cv.notify_all()
+
+ # Wait 60 seconds to receive a notification on the socket from
+ # the builder.
+ (reads, _, _) = select.select([self._master_sock], [], [], 60)
+ self.assertTrue(self._master_sock in reads)
+
+ # Reading 1 byte should not block us here, especially as the
+ # socket is ready to read. It's a hack, but this is just a
+ # testcase.
+ got = self._master_sock.recv(1)
+ self.assertEqual(got, b'x')
+
+ with self._builder_lock:
+ # The command queue must be cleared, and the response queue
+ # must contain a response that a bad command was sent. The
+ # thread is no longer running, so we can use the queues
+ # without a lock.
+ self.assertEqual(len(self._builder_command_queue), 0)
+ self.assertEqual(len(self._builder_response_queue), 1)
+
+ response = self._builder_response_queue[0]
+ self.assertTrue(isinstance(response, tuple))
+ self.assertTupleEqual(response, ('load-completed', datasrc_info,
+ RRClass.IN, 'MasterFiles'))
+ del self._builder_response_queue[:]
+
+ # Now try looking for some loaded data
+ clist = datasrc_info.clients_map[RRClass.IN]
+ dsrc, finder, exact = clist.find(isc.dns.Name("example.com"))
+ self.assertIsNotNone(dsrc)
+ self.assertTrue(isinstance(dsrc, isc.datasrc.DataSourceClient))
+ self.assertIsNotNone(finder)
+ self.assertTrue(isinstance(finder, isc.datasrc.ZoneFinder))
+ self.assertTrue(exact)
+
+ # Send the builder thread the "shutdown" command. The thread
+ # should exit its main loop and be joinable.
+ with self._builder_cv:
+ self._builder_command_queue.append(('shutdown',))
+ self._builder_cv.notify_all()
+
+ # Wait 5 seconds at most for the main loop of the builder to
+ # exit.
+ self._builder_thread.join(5)
+ self.assertFalse(self._builder_thread.isAlive())
+
+ # The command queue must be cleared, and the response queue must
+ # be untouched (we don't use it in this test). The thread is no
+ # longer running, so we can use the queues without a lock.
+ self.assertEqual(len(self._builder_command_queue), 0)
+ self.assertEqual(len(self._builder_response_queue), 0)
+
+if __name__ == "__main__":
+ isc.log.init("bind10-test")
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/lib/python/isc/memmgr/tests/datasrc_info_tests.py b/src/lib/python/isc/memmgr/tests/datasrc_info_tests.py
new file mode 100644
index 0000000..538f375
--- /dev/null
+++ b/src/lib/python/isc/memmgr/tests/datasrc_info_tests.py
@@ -0,0 +1,469 @@
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import os
+import unittest
+
+from isc.dns import *
+import isc.config
+import isc.datasrc
+import isc.log
+from isc.server_common.datasrc_clients_mgr import DataSrcClientsMgr
+from isc.memmgr.datasrc_info import *
+
+# Defined for easier tests with DataSrcClientsMgr.reconfigure(), which
+# only needs get_value() method
+class MockConfigData:
+ def __init__(self, data):
+ self.__data = data
+
+ def get_value(self, identifier):
+ return self.__data[identifier], False
+
+class TestSegmentInfo(unittest.TestCase):
+ def setUp(self):
+ self.__mapped_file_dir = os.environ['TESTDATA_WRITE_PATH']
+ self.__sgmt_info = SegmentInfo.create('mapped', 0, RRClass.IN,
+ 'sqlite3',
+ {'mapped_file_dir':
+ self.__mapped_file_dir})
+
+ def __check_sgmt_reset_param(self, user_type, expected_ver):
+ """Common check on the return value of get_reset_param() for
+ MappedSegmentInfo.
+
+ Unless it's expected to return None, it should be a map that
+ maps "mapped-file" to the expected version of mapped-file.
+
+ """
+ if expected_ver is None:
+ self.assertIsNone(self.__sgmt_info.get_reset_param(user_type))
+ return
+ param = self.__sgmt_info.get_reset_param(user_type)
+ self.assertEqual(self.__mapped_file_dir +
+ '/zone-IN-0-sqlite3-mapped.' + str(expected_ver),
+ param['mapped-file'])
+
+ def test_initial_params(self):
+ self.__check_sgmt_reset_param(SegmentInfo.WRITER, 0)
+ self.__check_sgmt_reset_param(SegmentInfo.READER, None)
+
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+ self.assertEqual(len(self.__sgmt_info.get_readers()), 0)
+ self.assertEqual(len(self.__sgmt_info.get_old_readers()), 0)
+ self.assertEqual(len(self.__sgmt_info.get_events()), 0)
+
+ def __si_to_ready_state(self):
+ # Go to a default starting state
+ self.__sgmt_info = SegmentInfo.create('mapped', 0, RRClass.IN,
+ 'sqlite3',
+ {'mapped_file_dir':
+ self.__mapped_file_dir})
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+
+ def __si_to_updating_state(self):
+ self.__si_to_ready_state()
+ self.__sgmt_info.add_reader(3)
+ self.__sgmt_info.add_event((42,))
+ e = self.__sgmt_info.start_update()
+ self.assertTupleEqual(e, (42,))
+ self.assertSetEqual(self.__sgmt_info.get_readers(), {3})
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.UPDATING)
+
+ def __si_to_synchronizing_state(self):
+ self.__si_to_updating_state()
+ self.__sgmt_info.complete_update()
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+ def __si_to_copying_state(self):
+ self.__si_to_synchronizing_state()
+ self.__sgmt_info.sync_reader(3)
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.COPYING)
+
+ def test_add_event(self):
+ self.assertEqual(len(self.__sgmt_info.get_events()), 0)
+ self.__sgmt_info.add_event(None)
+ self.assertEqual(len(self.__sgmt_info.get_events()), 1)
+ self.assertListEqual(self.__sgmt_info.get_events(), [None])
+
+ def test_add_reader(self):
+ self.assertSetEqual(self.__sgmt_info.get_readers(), set())
+ self.assertSetEqual(self.__sgmt_info.get_old_readers(), set())
+ self.__sgmt_info.add_reader(1)
+ self.assertSetEqual(self.__sgmt_info.get_readers(), {1})
+ self.__sgmt_info.add_reader(3)
+ self.assertSetEqual(self.__sgmt_info.get_readers(), {1, 3})
+ self.__sgmt_info.add_reader(2)
+ self.assertSetEqual(self.__sgmt_info.get_readers(), {1, 2, 3})
+
+ # adding the same existing reader must throw
+ self.assertRaises(SegmentInfoError, self.__sgmt_info.add_reader, (1))
+ # but the existing readers must be untouched
+ self.assertSetEqual(self.__sgmt_info.get_readers(), {1, 3, 2})
+
+ # none of this touches the old readers
+ self.assertSetEqual(self.__sgmt_info.get_old_readers(), set())
+
+ def test_start_update(self):
+ # in READY state
+ # a) there are no events
+ self.__si_to_ready_state()
+ e = self.__sgmt_info.start_update()
+ self.assertIsNone(e)
+ # if there are no events, there is nothing to update
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+
+ # b) there are events. this is the same as calling
+ # self.__si_to_updating_state(), but let's try to be
+ # descriptive.
+ self.__si_to_ready_state()
+ self.__sgmt_info.add_event((42,))
+ e = self.__sgmt_info.start_update()
+ self.assertTupleEqual(e, (42,))
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.UPDATING)
+
+ # in UPDATING state, it should always raise an exception and not
+ # change state.
+ self.__si_to_updating_state()
+ self.assertRaises(SegmentInfoError, self.__sgmt_info.start_update)
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.UPDATING)
+
+ # in SYNCHRONIZING state, it should always raise an exception
+ # and not change state.
+ self.__si_to_synchronizing_state()
+ self.assertRaises(SegmentInfoError, self.__sgmt_info.start_update)
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+ # in COPYING state, it should always raise an exception and not
+ # change state.
+ self.__si_to_copying_state()
+ self.assertRaises(SegmentInfoError, self.__sgmt_info.start_update)
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.COPYING)
+
+ def test_complete_update(self):
+ # in READY state
+ self.__si_to_ready_state()
+ self.assertRaises(SegmentInfoError, self.__sgmt_info.complete_update)
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+
+ # in UPDATING state this is the same as calling
+ # self.__si_to_synchronizing_state(), but let's try to be
+ # descriptive.
+ #
+ # a) with no events
+ self.__si_to_updating_state()
+ e = self.__sgmt_info.complete_update()
+ self.assertIsNone(e)
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+ # b) with events
+ self.__si_to_updating_state()
+ self.__sgmt_info.add_event((81,))
+ e = self.__sgmt_info.complete_update()
+ self.assertIsNone(e) # old_readers is not empty
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+ # c) with no readers, complete_update() from UPDATING must go
+ # directly to READY state
+ self.__si_to_ready_state()
+ self.__sgmt_info.add_event((42,))
+ e = self.__sgmt_info.start_update()
+ self.assertTupleEqual(e, (42,))
+ self.assertSetEqual(self.__sgmt_info.get_readers(), set())
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.UPDATING)
+ e = self.__sgmt_info.complete_update()
+ self.assertTupleEqual(e, (42,))
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+
+ # in SYNCHRONIZING state
+ self.__si_to_synchronizing_state()
+ self.assertRaises(SegmentInfoError, self.__sgmt_info.complete_update)
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+ # in COPYING state
+ self.__si_to_copying_state()
+ e = self.__sgmt_info.complete_update()
+ self.assertIsNone(e)
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+
+ def test_sync_reader(self):
+ # in READY state, it must raise an exception
+ self.__si_to_ready_state()
+ self.assertRaises(SegmentInfoError, self.__sgmt_info.sync_reader, (0))
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+
+ # in UPDATING state, it must raise an exception
+ self.__si_to_updating_state()
+ self.assertRaises(SegmentInfoError, self.__sgmt_info.sync_reader, (0))
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.UPDATING)
+
+ # in COPYING state, it must raise an exception
+ self.__si_to_copying_state()
+ self.assertRaises(SegmentInfoError, self.__sgmt_info.sync_reader, (0))
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.COPYING)
+
+ # in SYNCHRONIZING state:
+ #
+ # a) ID is not in old readers set. The following call sets up ID 3
+ # to be in the old readers set.
+ self.__si_to_synchronizing_state()
+ self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3})
+ self.assertSetEqual(self.__sgmt_info.get_readers(), set())
+ self.assertRaises(SegmentInfoError, self.__sgmt_info.sync_reader, (1))
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+ # b) ID is in old readers set, but also in readers set.
+ self.__si_to_synchronizing_state()
+ self.__sgmt_info.add_reader(3)
+ self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3})
+ self.assertSetEqual(self.__sgmt_info.get_readers(), {3})
+ self.assertRaises(SegmentInfoError, self.__sgmt_info.sync_reader, (3))
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+ # c) ID is in old readers set, but not in readers set, and
+ # old_readers becomes empty.
+ self.__si_to_synchronizing_state()
+ self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3})
+ self.assertSetEqual(self.__sgmt_info.get_readers(), set())
+ self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+ e = self.__sgmt_info.sync_reader(3)
+ self.assertTupleEqual(e, (42,))
+ # the ID should be moved from old readers to readers set
+ self.assertSetEqual(self.__sgmt_info.get_old_readers(), set())
+ self.assertSetEqual(self.__sgmt_info.get_readers(), {3})
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.COPYING)
+
+ # d) ID is in old readers set, but not in readers set, and
+ # old_readers doesn't become empty.
+ self.__si_to_updating_state()
+ self.__sgmt_info.add_reader(4)
+ self.__sgmt_info.complete_update()
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+ self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3, 4})
+ self.assertSetEqual(self.__sgmt_info.get_readers(), set())
+ self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+ e = self.__sgmt_info.sync_reader(3)
+ self.assertIsNone(e)
+ # the ID should be moved from old readers to readers set
+ self.assertSetEqual(self.__sgmt_info.get_old_readers(), {4})
+ self.assertSetEqual(self.__sgmt_info.get_readers(), {3})
+ # we should be left in SYNCHRONIZING state
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+ def test_remove_reader(self):
+ # in READY state, it must raise an exception
+ self.__si_to_ready_state()
+ self.assertRaises(SegmentInfoError, self.__sgmt_info.remove_reader, (0))
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+
+ # in UPDATING state, it must raise an exception
+ self.__si_to_updating_state()
+ self.assertRaises(SegmentInfoError, self.__sgmt_info.remove_reader, (0))
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.UPDATING)
+
+ # in COPYING state, it must raise an exception
+ self.__si_to_copying_state()
+ self.assertRaises(SegmentInfoError, self.__sgmt_info.remove_reader, (0))
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.COPYING)
+
+ # in SYNCHRONIZING state:
+ #
+ # a) ID is not in old readers set or readers set.
+ self.__si_to_synchronizing_state()
+ self.__sgmt_info.add_reader(4)
+ self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3})
+ self.assertSetEqual(self.__sgmt_info.get_readers(), {4})
+ self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+ self.assertRaises(SegmentInfoError, self.__sgmt_info.remove_reader, (1))
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+ # b) ID is in readers set.
+ self.__si_to_synchronizing_state()
+ self.__sgmt_info.add_reader(4)
+ self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3})
+ self.assertSetEqual(self.__sgmt_info.get_readers(), {4})
+ self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+ e = self.__sgmt_info.remove_reader(4)
+ self.assertIsNone(e)
+ self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3})
+ self.assertSetEqual(self.__sgmt_info.get_readers(), set())
+ self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+ # we only change state if it was removed from old_readers
+ # specifically and it became empty.
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+ # c) ID is in old_readers set and it becomes empty.
+ self.__si_to_synchronizing_state()
+ self.__sgmt_info.add_reader(4)
+ self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3})
+ self.assertSetEqual(self.__sgmt_info.get_readers(), {4})
+ self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+ e = self.__sgmt_info.remove_reader(3)
+ self.assertTupleEqual(e, (42,))
+ self.assertSetEqual(self.__sgmt_info.get_old_readers(), set())
+ self.assertSetEqual(self.__sgmt_info.get_readers(), {4})
+ self.assertListEqual(self.__sgmt_info.get_events(), [])
+ # we only change state if it was removed from old_readers
+ # specifically and it became empty.
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.COPYING)
+
+ # d) ID is in old_readers set and it doesn't become empty.
+ self.__si_to_updating_state()
+ self.__sgmt_info.add_reader(4)
+ self.__sgmt_info.complete_update()
+ self.__sgmt_info.add_reader(5)
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+ self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3, 4})
+ self.assertSetEqual(self.__sgmt_info.get_readers(), {5})
+ self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+ e = self.__sgmt_info.remove_reader(3)
+ self.assertIsNone(e)
+ self.assertSetEqual(self.__sgmt_info.get_old_readers(), {4})
+ self.assertSetEqual(self.__sgmt_info.get_readers(), {5})
+ self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+ # we only change state if it was removed from old_readers
+ # specifically and it became empty.
+ self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+ def test_switch_versions(self):
+ self.__sgmt_info.switch_versions()
+ self.__check_sgmt_reset_param(SegmentInfo.WRITER, 1)
+ self.__check_sgmt_reset_param(SegmentInfo.READER, 0)
+
+ self.__sgmt_info.switch_versions()
+ self.__check_sgmt_reset_param(SegmentInfo.WRITER, 0)
+ self.__check_sgmt_reset_param(SegmentInfo.READER, 1)
+
+ def test_init_others(self):
+ # For local type of segment, information isn't needed and won't be
+ # created.
+ self.assertIsNone(SegmentInfo.create('local', 0, RRClass.IN,
+ 'sqlite3', {}))
+
+ # Unknown type of segment will result in an exception.
+ self.assertRaises(SegmentInfoError, SegmentInfo.create, 'unknown', 0,
+ RRClass.IN, 'sqlite3', {})
+
+ def test_missing_methods(self):
+ # Bad subclass of SegmentInfo that doesn't implement mandatory methods.
+ class TestSegmentInfo(SegmentInfo):
+ pass
+
+ self.assertRaises(SegmentInfoError,
+ TestSegmentInfo().get_reset_param,
+ SegmentInfo.WRITER)
+ self.assertRaises(SegmentInfoError, TestSegmentInfo().switch_versions)
+
+class MockClientList:
+ """A mock ConfigurableClientList class.
+
+ Just providing minimal shortcut interfaces needed for DataSrcInfo class.
+
+ """
+ def __init__(self, status_list):
+ self.__status_list = status_list
+
+ def get_status(self):
+ return self.__status_list
+
+class TestDataSrcInfo(unittest.TestCase):
+ def setUp(self):
+ self.__mapped_file_dir = os.environ['TESTDATA_WRITE_PATH']
+ self.__mgr_config = {'mapped_file_dir': self.__mapped_file_dir}
+ self.__sqlite3_dbfile = os.environ['TESTDATA_WRITE_PATH'] + '/' + 'zone.db'
+ self.__clients_map = {
+ # mixture of 'local' and 'mapped' and 'unused' (type =None)
+ # segments
+ RRClass.IN: MockClientList([('datasrc1', 'local', None),
+ ('datasrc2', 'mapped', None),
+ ('datasrc3', None, None)]),
+ RRClass.CH: MockClientList([('datasrc2', 'mapped', None),
+ ('datasrc1', 'local', None)]) }
+
+ def tearDown(self):
+ if os.path.exists(self.__sqlite3_dbfile):
+ os.unlink(self.__sqlite3_dbfile)
+
+ def __check_sgmt_reset_param(self, sgmt_info, writer_file):
+ # Check if the initial state of (mapped) segment info object has
+ # expected values.
+ self.assertIsNone(sgmt_info.get_reset_param(SegmentInfo.READER))
+ param = sgmt_info.get_reset_param(SegmentInfo.WRITER)
+ self.assertEqual(writer_file, param['mapped-file'])
+
+ def test_init(self):
+ """Check basic scenarios of constructing DataSrcInfo."""
+
+ # This checks that all data sources of all RR classes are covered,
+ # "local" segments are ignored, info objects for "mapped" segments
+ # are created and stored in segment_info_map.
+ datasrc_info = DataSrcInfo(42, self.__clients_map, self.__mgr_config)
+ self.assertEqual(42, datasrc_info.gen_id)
+ self.assertEqual(self.__clients_map, datasrc_info.clients_map)
+ self.assertEqual(2, len(datasrc_info.segment_info_map))
+ sgmt_info = datasrc_info.segment_info_map[(RRClass.IN, 'datasrc2')]
+ self.__check_sgmt_reset_param(sgmt_info, self.__mapped_file_dir +
+ '/zone-IN-42-datasrc2-mapped.0')
+ sgmt_info = datasrc_info.segment_info_map[(RRClass.CH, 'datasrc2')]
+ self.__check_sgmt_reset_param(sgmt_info, self.__mapped_file_dir +
+ '/zone-CH-42-datasrc2-mapped.0')
+
+ # A case where clist.get_status() returns an empty list; shouldn't
+ # cause disruption
+ self.__clients_map = { RRClass.IN: MockClientList([])}
+ datasrc_info = DataSrcInfo(42, self.__clients_map, self.__mgr_config)
+ self.assertEqual(42, datasrc_info.gen_id)
+ self.assertEqual(0, len(datasrc_info.segment_info_map))
+
+ # A case where clients_map is empty; shouldn't cause disruption
+ self.__clients_map = {}
+ datasrc_info = DataSrcInfo(42, self.__clients_map, self.__mgr_config)
+ self.assertEqual(42, datasrc_info.gen_id)
+ self.assertEqual(0, len(datasrc_info.segment_info_map))
+
+ # This test uses real "mmaped" segment and doesn't work without shared
+ # memory support.
+ @unittest.skipIf(os.environ['HAVE_SHARED_MEMORY'] != 'yes',
+ 'shared memory support is not available')
+ def test_production(self):
+ """Check the behavior closer to a production environment.
+
+ Instead of using a mock classes, just for confirming we didn't miss
+ something.
+
+ """
+ cfg_data = MockConfigData(
+ {"classes":
+ {"IN": [{"type": "sqlite3", "cache-enable": True,
+ "cache-type": "mapped", "cache-zones": [],
+ "params": {"database_file": self.__sqlite3_dbfile}}]
+ }
+ })
+ cmgr = DataSrcClientsMgr(use_cache=True)
+ cmgr.reconfigure({}, cfg_data)
+
+ genid, clients_map = cmgr.get_clients_map()
+ datasrc_info = DataSrcInfo(genid, clients_map, self.__mgr_config)
+
+ self.assertEqual(1, datasrc_info.gen_id)
+ self.assertEqual(clients_map, datasrc_info.clients_map)
+ self.assertEqual(1, len(datasrc_info.segment_info_map))
+ sgmt_info = datasrc_info.segment_info_map[(RRClass.IN, 'sqlite3')]
+ self.assertIsNone(sgmt_info.get_reset_param(SegmentInfo.READER))
+
+if __name__ == "__main__":
+ isc.log.init("bind10-test")
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/lib/python/isc/memmgr/tests/testdata/Makefile.am b/src/lib/python/isc/memmgr/tests/testdata/Makefile.am
new file mode 100644
index 0000000..22e7ce3
--- /dev/null
+++ b/src/lib/python/isc/memmgr/tests/testdata/Makefile.am
@@ -0,0 +1,2 @@
+EXTRA_DIST = \
+ example.com.zone
diff --git a/src/lib/python/isc/memmgr/tests/testdata/example.com.zone b/src/lib/python/isc/memmgr/tests/testdata/example.com.zone
new file mode 100644
index 0000000..24e22e1
--- /dev/null
+++ b/src/lib/python/isc/memmgr/tests/testdata/example.com.zone
@@ -0,0 +1,8 @@
+example.com. 1000 IN SOA a.dns.example.com. mail.example.com. 1 1 1 1 1
+example.com. 1000 IN NS a.dns.example.com.
+example.com. 1000 IN NS b.dns.example.com.
+example.com. 1000 IN NS c.dns.example.com.
+a.dns.example.com. 1000 IN A 1.1.1.1
+b.dns.example.com. 1000 IN A 3.3.3.3
+b.dns.example.com. 1000 IN AAAA 4:4::4:4
+b.dns.example.com. 1000 IN AAAA 5:5::5:5
diff --git a/src/lib/python/isc/net/tests/Makefile.am b/src/lib/python/isc/net/tests/Makefile.am
index dd94946..44f6063 100644
--- a/src/lib/python/isc/net/tests/Makefile.am
+++ b/src/lib/python/isc/net/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/notify/notify_out.py b/src/lib/python/isc/notify/notify_out.py
index d1ec2e9..f4de7b8 100644
--- a/src/lib/python/isc/notify/notify_out.py
+++ b/src/lib/python/isc/notify/notify_out.py
@@ -25,7 +25,8 @@ from isc.datasrc import DataSourceClient
from isc.net import addr
import isc
from isc.log_messages.notify_out_messages import *
-from isc.statistics import Counters
+from isc.statistics.dns import Counters
+from isc.util.address_formatter import AddressFormatter
logger = isc.log.Logger("notify_out")
@@ -40,7 +41,6 @@ ZONE_XFRIN_FAILED = 'zone_xfrin_failed'
_MAX_NOTIFY_NUM = 30
_MAX_NOTIFY_TRY_NUM = 5
-_EVENT_NONE = 0
_EVENT_READ = 1
_EVENT_TIMEOUT = 2
_NOTIFY_TIMEOUT = 1
@@ -128,7 +128,7 @@ class NotifyOut:
notify message to its slaves). notify service can be started by
calling dispatcher(), and it can be stopped by calling shutdown()
in another thread. '''
- def __init__(self, datasrc_file, verbose=True):
+ def __init__(self, datasrc_file, counters=None, verbose=True):
self._notify_infos = {} # key is (zone_name, zone_class)
self._waiting_zones = []
self._notifying_zones = []
@@ -143,7 +143,7 @@ class NotifyOut:
# Use nonblock event to eliminate busy loop
# If there are no notifying zones, clear the event bit and wait.
self._nonblock_event = threading.Event()
- self._counters = Counters()
+ self._counters = counters
def _init_notify_out(self, datasrc_file):
'''Get all the zones name and its notify target's address.
@@ -210,7 +210,8 @@ class NotifyOut:
for name_ in not_replied_zones:
if not_replied_zones[name_].notify_timeout <= time.time():
- self._zone_notify_handler(not_replied_zones[name_], _EVENT_TIMEOUT)
+ self._zone_notify_handler(not_replied_zones[name_],
+ _EVENT_TIMEOUT)
def dispatcher(self, daemon=False):
"""Spawns a thread that will handle notify related events.
@@ -420,31 +421,57 @@ class NotifyOut:
return replied_zones, not_replied_zones
def _zone_notify_handler(self, zone_notify_info, event_type):
- '''Notify handler for one zone. The first notify message is
- always triggered by the event "_EVENT_TIMEOUT" since when
- one zone prepares to notify its slaves, its notify_timeout
- is set to now, which is used to trigger sending notify
- message when dispatcher() scanning zones. '''
+ """Notify handler for one zone.
+
+ For the event type of _EVENT_READ, this method reads a new notify
+ response message from the corresponding socket. If it succeeds
+ and the response is the expected one, it will send another notify
+ to the next slave for the zone (if any) or the next zone (if any)
+ waiting for its turn of sending notifies.
+
+ In the case of _EVENT_TIMEOUT, or if the read fails or the response
+ is not an expected one in the case of _EVENT_READ, this method will
+ resend the notify request to the same slave up to _MAX_NOTIFY_TRY_NUM
+ times. If it reaches the max, it will swith to the next slave or
+ the next zone like the successful case above.
+
+ The first notify message is always triggered by the event
+ "_EVENT_TIMEOUT" since when one zone prepares to notify its slaves,
+ its notify_timeout is set to now, which is used to trigger sending
+ notify message when dispatcher() scanning zones.
+
+ Parameters:
+ zone_notify_info(ZoneNotifyInfo): the notify context for the event
+ event_type(int): either _EVENT_READ or _EVENT_TIMEOUT constant
+
+ """
tgt = zone_notify_info.get_current_notify_target()
if event_type == _EVENT_READ:
+ # Note: _get_notify_reply() should also check the response's
+ # source address (see #2924). When it's done the following code
+ # should also be adjusted a bit.
reply = self._get_notify_reply(zone_notify_info.get_socket(), tgt)
if reply is not None:
- if self._handle_notify_reply(zone_notify_info, reply, tgt):
+ if (self._handle_notify_reply(zone_notify_info, reply, tgt) ==
+ _REPLY_OK):
self._notify_next_target(zone_notify_info)
- elif event_type == _EVENT_TIMEOUT and zone_notify_info.notify_try_num > 0:
- logger.info(NOTIFY_OUT_TIMEOUT, tgt[0], tgt[1])
+ else:
+ assert event_type == _EVENT_TIMEOUT
+ if zone_notify_info.notify_try_num > 0:
+ logger.info(NOTIFY_OUT_TIMEOUT, AddressFormatter(tgt))
tgt = zone_notify_info.get_current_notify_target()
if tgt:
zone_notify_info.notify_try_num += 1
if zone_notify_info.notify_try_num > _MAX_NOTIFY_TRY_NUM:
- logger.warn(NOTIFY_OUT_RETRY_EXCEEDED, tgt[0], tgt[1],
+ logger.warn(NOTIFY_OUT_RETRY_EXCEEDED, AddressFormatter(tgt),
_MAX_NOTIFY_TRY_NUM)
self._notify_next_target(zone_notify_info)
else:
- # set exponential backoff according rfc1996 section 3.6
- retry_timeout = _NOTIFY_TIMEOUT * pow(2, zone_notify_info.notify_try_num)
+ # set exponential backoff according to rfc1996 section 3.6
+ retry_timeout = (_NOTIFY_TIMEOUT *
+ pow(2, zone_notify_info.notify_try_num))
zone_notify_info.notify_timeout = time.time() + retry_timeout
self._send_notify_message_udp(zone_notify_info, tgt)
@@ -481,21 +508,25 @@ class NotifyOut:
sock = zone_notify_info.create_socket(addrinfo[0])
sock.sendto(render.get_data(), 0, addrinfo)
# count notifying by IPv4 or IPv6 for statistics
- if zone_notify_info.get_socket().family == socket.AF_INET:
- self._counters.inc('zones', zone_notify_info.zone_name,
- 'notifyoutv4')
- elif zone_notify_info.get_socket().family == socket.AF_INET6:
- self._counters.inc('zones', zone_notify_info.zone_name,
- 'notifyoutv6')
- logger.info(NOTIFY_OUT_SENDING_NOTIFY, addrinfo[0],
- addrinfo[1])
+ if self._counters is not None:
+ if zone_notify_info.get_socket().family == socket.AF_INET:
+ self._counters.inc('zones',
+ zone_notify_info.zone_class,
+ zone_notify_info.zone_name,
+ 'notifyoutv4')
+ elif zone_notify_info.get_socket().family == socket.AF_INET6:
+ self._counters.inc('zones',
+ zone_notify_info.zone_class,
+ zone_notify_info.zone_name,
+ 'notifyoutv6')
+ logger.info(NOTIFY_OUT_SENDING_NOTIFY, AddressFormatter(addrinfo))
except (socket.error, addr.InvalidAddress) as err:
- logger.error(NOTIFY_OUT_SOCKET_ERROR, addrinfo[0],
- addrinfo[1], err)
+ logger.error(NOTIFY_OUT_SOCKET_ERROR, AddressFormatter(addrinfo),
+ err)
return False
except addr.InvalidAddress as iae:
- logger.error(NOTIFY_OUT_INVALID_ADDRESS, addrinfo[0],
- addrinfo[1], iae)
+ logger.error(NOTIFY_OUT_INVALID_ADDRESS,
+ AddressFormatter(addrinfo), iae)
return False
return True
@@ -537,47 +568,56 @@ class NotifyOut:
return soa_rrset
def _handle_notify_reply(self, zone_notify_info, msg_data, from_addr):
- '''Parse the notify reply message.
- rcode will not checked here, If we get the response
- from the slave, it means the slaves has got the notify.'''
+ """Parse the notify reply message.
+
+ rcode will not be checked here; if we get the response
+ from the slave, it means the slave got the notify.
+
+ """
msg = Message(Message.PARSE)
try:
msg.from_wire(msg_data)
if not msg.get_header_flag(Message.HEADERFLAG_QR):
- logger.warn(NOTIFY_OUT_REPLY_QR_NOT_SET, from_addr[0],
- from_addr[1])
+ logger.warn(NOTIFY_OUT_REPLY_QR_NOT_SET,
+ AddressFormatter(from_addr))
return _BAD_QR
if msg.get_qid() != zone_notify_info.notify_msg_id:
- logger.warn(NOTIFY_OUT_REPLY_BAD_QID, from_addr[0],
- from_addr[1], msg.get_qid(),
+ logger.warn(NOTIFY_OUT_REPLY_BAD_QID,
+ AddressFormatter(from_addr), msg.get_qid(),
zone_notify_info.notify_msg_id)
return _BAD_QUERY_ID
question = msg.get_question()[0]
if question.get_name() != Name(zone_notify_info.zone_name):
- logger.warn(NOTIFY_OUT_REPLY_BAD_QUERY_NAME, from_addr[0],
- from_addr[1], question.get_name().to_text(),
+ logger.warn(NOTIFY_OUT_REPLY_BAD_QUERY_NAME,
+ AddressFormatter(from_addr),
+ question.get_name().to_text(),
Name(zone_notify_info.zone_name).to_text())
return _BAD_QUERY_NAME
if msg.get_opcode() != Opcode.NOTIFY:
- logger.warn(NOTIFY_OUT_REPLY_BAD_OPCODE, from_addr[0],
- from_addr[1], msg.get_opcode().to_text())
+ logger.warn(NOTIFY_OUT_REPLY_BAD_OPCODE,
+ AddressFormatter(from_addr),
+ msg.get_opcode().to_text())
return _BAD_OPCODE
except Exception as err:
# We don't care what exception, just report it?
logger.error(NOTIFY_OUT_REPLY_UNCAUGHT_EXCEPTION, err)
return _BAD_REPLY_PACKET
+ logger.debug(logger.DBGLVL_TRACE_BASIC, NOTIFY_OUT_REPLY_RECEIVED,
+ zone_notify_info.zone_name, zone_notify_info.zone_class,
+ AddressFormatter(from_addr), msg.get_rcode())
+
return _REPLY_OK
def _get_notify_reply(self, sock, tgt_addr):
try:
msg, addr = sock.recvfrom(512)
except socket.error as err:
- logger.error(NOTIFY_OUT_SOCKET_RECV_ERROR, tgt_addr[0],
- tgt_addr[1], err)
+ logger.error(NOTIFY_OUT_SOCKET_RECV_ERROR,
+ AddressFormatter(tgt_addr), err)
return None
return msg
diff --git a/src/lib/python/isc/notify/notify_out_messages.mes b/src/lib/python/isc/notify/notify_out_messages.mes
index 3bc0f38..fd08f43 100644
--- a/src/lib/python/isc/notify/notify_out_messages.mes
+++ b/src/lib/python/isc/notify/notify_out_messages.mes
@@ -27,7 +27,7 @@ because notify_out first identifies a list of available zones before
this process. So this means some critical inconsistency in the data
source or software bug.
-% NOTIFY_OUT_INVALID_ADDRESS invalid address %1#%2: %3
+% NOTIFY_OUT_INVALID_ADDRESS invalid address %1: %2
The notify_out library tried to send a notify message to the given
address, but it appears to be an invalid address. The configuration
for secondary nameservers might contain a typographic error, or a
@@ -35,31 +35,36 @@ different BIND 10 module has forgotten to validate its data before
sending this module a notify command. As such, this should normally
not happen, and points to an oversight in a different module.
-% NOTIFY_OUT_REPLY_BAD_OPCODE bad opcode in notify reply from %1#%2: %3
+% NOTIFY_OUT_REPLY_BAD_OPCODE bad opcode in notify reply from %1: %2
The notify_out library sent a notify message to the nameserver at
the given address, but the response did not have the opcode set to
NOTIFY. The opcode in the response is printed. Since there was a
response, no more notifies will be sent to this server for this
notification event.
-% NOTIFY_OUT_REPLY_BAD_QID bad QID in notify reply from %1#%2: got %3, should be %4
+% NOTIFY_OUT_REPLY_BAD_QID bad QID in notify reply from %1: got %2, should be %3
The notify_out library sent a notify message to the nameserver at
the given address, but the query id in the response does not match
the one we sent. Since there was a response, no more notifies will
be sent to this server for this notification event.
-% NOTIFY_OUT_REPLY_BAD_QUERY_NAME bad query name in notify reply from %1#%2: got %3, should be %4
+% NOTIFY_OUT_REPLY_BAD_QUERY_NAME bad query name in notify reply from %1: got %2, should be %3
The notify_out library sent a notify message to the nameserver at
the given address, but the query name in the response does not match
the one we sent. Since there was a response, no more notifies will
be sent to this server for this notification event.
-% NOTIFY_OUT_REPLY_QR_NOT_SET QR flags set to 0 in reply to notify from %1#%2
+% NOTIFY_OUT_REPLY_QR_NOT_SET QR flags set to 0 in reply to notify from %1
The notify_out library sent a notify message to the namesever at the
given address, but the reply did not have the QR bit set to one.
Since there was a response, no more notifies will be sent to this
server for this notification event.
+% NOTIFY_OUT_REPLY_RECEIVED Zone %1/%2: notify response from %3: %4
+The notify_out library sent a notify message to the nameserver at
+the given address, and received a response. Its Rcode will be shown,
+too.
+
% NOTIFY_OUT_REPLY_UNCAUGHT_EXCEPTION uncaught exception: %1
There was an uncaught exception in the handling of a notify reply
message, either in the message parser, or while trying to extract data
@@ -70,27 +75,27 @@ explicitly. Please file a bug report. Since there was a response,
no more notifies will be sent to this server for this notification
event.
-% NOTIFY_OUT_RETRY_EXCEEDED notify to %1#%2: number of retries (%3) exceeded
+% NOTIFY_OUT_RETRY_EXCEEDED notify to %1: number of retries (%2) exceeded
The maximum number of retries for the notify target has been exceeded.
Either the address of the secondary nameserver is wrong, or it is not
responding.
-% NOTIFY_OUT_SENDING_NOTIFY sending notify to %1#%2
+% NOTIFY_OUT_SENDING_NOTIFY sending notify to %1
A notify message is sent to the secondary nameserver at the given
address.
-% NOTIFY_OUT_SOCKET_ERROR socket error sending notify to %1#%2: %3
+% NOTIFY_OUT_SOCKET_ERROR socket error sending notify to %1: %2
There was a network error while trying to send a notify message to
the given address. The address might be unreachable. The socket
error is printed and should provide more information.
-% NOTIFY_OUT_SOCKET_RECV_ERROR socket error reading notify reply from %1#%2: %3
+% NOTIFY_OUT_SOCKET_RECV_ERROR socket error reading notify reply from %1: %2
There was a network error while trying to read a notify reply
message from the given address. The socket error is printed and should
provide more information.
-% NOTIFY_OUT_TIMEOUT retry notify to %1#%2
-The notify message to the given address (noted as address#port) has
+% NOTIFY_OUT_TIMEOUT retry notify to %1
+The notify message to the given address (noted as address:port) has
timed out, and the message will be resent until the max retry limit
is reached.
diff --git a/src/lib/python/isc/notify/tests/Makefile.am b/src/lib/python/isc/notify/tests/Makefile.am
index 3af5991..52f2409 100644
--- a/src/lib/python/isc/notify/tests/Makefile.am
+++ b/src/lib/python/isc/notify/tests/Makefile.am
@@ -5,13 +5,13 @@ EXTRA_DIST += testdata/test.sqlite3 testdata/brokentest.sqlite3
# The rest of the files are actually not necessary, but added for reference
EXTRA_DIST += testdata/example.com testdata/example.net
EXTRA_DIST += testdata/nons.example testdata/nosoa.example
-EXTRA_DIST += testdata/multisoa.example
+EXTRA_DIST += testdata/multisoa.example testdata/test_spec1.spec
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
else
# Some systems need the ds path even if not all paths are necessary
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs
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 3b2324d..ec4df0d 100644
--- a/src/lib/python/isc/notify/tests/notify_out_test.py
+++ b/src/lib/python/isc/notify/tests/notify_out_test.py
@@ -22,13 +22,37 @@ import socket
from isc.notify import notify_out, SOCK_DATA
import isc.log
from isc.dns import *
+from isc.statistics.dns import Counters
TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
+SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec1.spec'
+
+def get_notify_msgdata(zone_name, qid=0):
+ """A helper function to generate a notify response in wire format.
+
+ Parameters:
+ zone_name(isc.dns.Name()) The zone name for the notify. Used as the
+ question name.
+ qid (int): The QID of the response. In most test cases a value of 0 is
+ expected.
+
+ """
+ m = Message(Message.RENDER)
+ m.set_opcode(Opcode.NOTIFY)
+ m.set_rcode(Rcode.NOERROR)
+ m.set_qid(qid)
+ m.set_header_flag(Message.HEADERFLAG_QR)
+ m.add_question(Question(zone_name, RRClass.IN, RRType.SOA))
+
+ renderer = MessageRenderer()
+ m.to_wire(renderer)
+ return renderer.get_data()
# our fake socket, where we can read and insert messages
class MockSocket():
def __init__(self):
self._local_sock, self._remote_sock = socket.socketpair()
+ self.__raise_on_recv = False # see set_raise_on_recv()
def connect(self, to):
pass
@@ -44,6 +68,8 @@ class MockSocket():
return self._local_sock.send(data)
def recvfrom(self, length):
+ if self.__raise_on_recv:
+ raise socket.error('fake error')
data = self._local_sock.recv(length)
return (data, None)
@@ -51,6 +77,14 @@ class MockSocket():
def remote_end(self):
return self._remote_sock
+ def set_raise_on_recv(self, on):
+ """A helper to force recvfrom() to raise an exception or cancel it.
+
+ The next call to recvfrom() will result in an exception iff parameter
+ 'on' (bool) is set to True.
+ """
+ self.__raise_on_recv = on
+
# We subclass the ZoneNotifyInfo class we're testing here, only
# to override the create_socket() method.
class MockZoneNotifyInfo(notify_out.ZoneNotifyInfo):
@@ -79,12 +113,12 @@ class TestZoneNotifyInfo(unittest.TestCase):
def test_set_next_notify_target(self):
self.info.notify_slaves.append(('127.0.0.1', 53))
- self.info.notify_slaves.append(('1.1.1.1', 5353))
+ self.info.notify_slaves.append(('192.0.2.1', 5353))
self.info.prepare_notify_out()
self.assertEqual(self.info.get_current_notify_target(), ('127.0.0.1', 53))
self.info.set_next_notify_target()
- self.assertEqual(self.info.get_current_notify_target(), ('1.1.1.1', 5353))
+ self.assertEqual(self.info.get_current_notify_target(), ('192.0.2.1', 5353))
self.info.set_next_notify_target()
self.assertIsNone(self.info.get_current_notify_target())
@@ -96,7 +130,7 @@ class TestZoneNotifyInfo(unittest.TestCase):
class TestNotifyOut(unittest.TestCase):
def setUp(self):
self._db_file = TESTDATA_SRCDIR + '/test.sqlite3'
- self._notify = notify_out.NotifyOut(self._db_file)
+ self._notify = notify_out.NotifyOut(self._db_file, counters=Counters(SPECFILE_LOCATION))
self._notify._notify_infos[('example.com.', 'IN')] = MockZoneNotifyInfo('example.com.', 'IN')
self._notify._notify_infos[('example.com.', 'CH')] = MockZoneNotifyInfo('example.com.', 'CH')
self._notify._notify_infos[('example.net.', 'IN')] = MockZoneNotifyInfo('example.net.', 'IN')
@@ -105,14 +139,18 @@ class TestNotifyOut(unittest.TestCase):
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))
+ net_info.notify_slaves.append(('192.0.2.1', 5353))
com_info = self._notify._notify_infos[('example.com.', 'IN')]
- com_info.notify_slaves.append(('1.1.1.1', 5353))
+ com_info.notify_slaves.append(('192.0.2.1', 5353))
com_ch_info = self._notify._notify_infos[('example.com.', 'CH')]
- com_ch_info.notify_slaves.append(('1.1.1.1', 5353))
+ com_ch_info.notify_slaves.append(('192.0.2.1', 5353))
+ # Keep the original library version in case a test case replaces it
+ self.__time_time_orig = notify_out.time.time
def tearDown(self):
self._notify._counters.clear_all()
+ # restore the original time.time() in case it was replaced.
+ notify_out.time.time = self.__time_time_orig
def test_send_notify(self):
notify_out._MAX_NOTIFY_NUM = 2
@@ -221,7 +259,7 @@ class TestNotifyOut(unittest.TestCase):
info = self._notify._notify_infos[('example.net.', 'IN')]
self._notify._notify_next_target(info)
self.assertEqual(0, info.notify_try_num)
- self.assertEqual(info.get_current_notify_target(), ('1.1.1.1', 5353))
+ self.assertEqual(info.get_current_notify_target(), ('192.0.2.1', 5353))
self.assertEqual(2, self._notify.notify_num)
self.assertEqual(1, len(self._notify._waiting_zones))
@@ -268,10 +306,10 @@ class TestNotifyOut(unittest.TestCase):
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv4')
+ 'zones', 'IN', 'example.net.', 'notifyoutv4')
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv6')
+ 'zones', 'IN', 'example.net.', 'notifyoutv6')
example_com_info.prepare_notify_out()
ret = self._notify._send_notify_message_udp(example_com_info,
@@ -279,38 +317,38 @@ class TestNotifyOut(unittest.TestCase):
self.assertTrue(ret)
self.assertEqual(socket.AF_INET, example_com_info.sock_family)
self.assertEqual(self._notify._counters.get(
- 'zones', 'example.net.', 'notifyoutv4'), 1)
+ 'zones', 'IN', 'example.net.', 'notifyoutv4'), 1)
self.assertEqual(self._notify._counters.get(
- 'zones', 'example.net.', 'notifyoutv6'), 0)
+ 'zones', 'IN', 'example.net.', 'notifyoutv6'), 0)
def test_send_notify_message_udp_ipv6(self):
example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv4')
+ 'zones', 'IN', 'example.net.', 'notifyoutv4')
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv6')
+ 'zones', 'IN', 'example.net.', 'notifyoutv6')
ret = self._notify._send_notify_message_udp(example_com_info,
('2001:db8::53', 53))
self.assertTrue(ret)
self.assertEqual(socket.AF_INET6, example_com_info.sock_family)
self.assertEqual(self._notify._counters.get(
- 'zones', 'example.net.', 'notifyoutv4'), 0)
+ 'zones', 'IN', 'example.net.', 'notifyoutv4'), 0)
self.assertEqual(self._notify._counters.get(
- 'zones', 'example.net.', 'notifyoutv6'), 1)
+ 'zones', 'IN', 'example.net.', 'notifyoutv6'), 1)
def test_send_notify_message_with_bogus_address(self):
example_com_info = self._notify._notify_infos[('example.net.', 'IN')]
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv4')
+ 'zones', 'IN', 'example.net.', 'notifyoutv4')
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv6')
+ 'zones', 'IN', 'example.net.', 'notifyoutv6')
# As long as the underlying data source validates RDATA this shouldn't
# happen, but right now it's not actually the case. Even if the
@@ -322,45 +360,92 @@ class TestNotifyOut(unittest.TestCase):
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv4')
+ 'zones', 'IN', 'example.net.', 'notifyoutv4')
self.assertRaises(isc.cc.data.DataNotFoundError,
self._notify._counters.get,
- 'zones', 'example.net.', 'notifyoutv4')
+ 'zones', 'IN', 'example.net.', 'notifyoutv4')
def test_zone_notify_handler(self):
- old_send_msg = self._notify._send_notify_message_udp
- def _fake_send_notify_message_udp(va1, va2):
+ sent_addrs = []
+ def _fake_send_notify_message_udp(notify_info, addrinfo):
+ sent_addrs.append(addrinfo)
pass
+ notify_out.time.time = lambda: 42
self._notify._send_notify_message_udp = _fake_send_notify_message_udp
self._notify.send_notify('example.net.')
- self._notify.send_notify('example.com.')
- notify_out._MAX_NOTIFY_NUM = 2
- self._notify.send_notify('example.org.')
example_net_info = self._notify._notify_infos[('example.net.', 'IN')]
- example_net_info.prepare_notify_out()
+ # On timeout, the request will be resent until try_num reaches the max
+ self.assertEqual([], sent_addrs)
example_net_info.notify_try_num = 2
- self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_TIMEOUT)
+ self._notify._zone_notify_handler(example_net_info,
+ notify_out._EVENT_TIMEOUT)
self.assertEqual(3, example_net_info.notify_try_num)
-
- time1 = example_net_info.notify_timeout
- self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_TIMEOUT)
- self.assertEqual(4, example_net_info.notify_try_num)
- self.assertGreater(example_net_info.notify_timeout, time1 + 2) # bigger than 2 seconds
-
+ self.assertEqual([('127.0.0.1', 53)], sent_addrs)
+ # the timeout time will be set to "current time(=42)"+2**(new try_num)
+ self.assertEqual(42 + 2**3, example_net_info.notify_timeout)
+
+ # If try num exceeds max, the next slave will be tried (and then
+ # next zone, but for this test it sufficies to check the former case)
+ example_net_info.notify_try_num = 5
+ self._notify._zone_notify_handler(example_net_info,
+ notify_out._EVENT_TIMEOUT)
+ self.assertEqual(0, example_net_info.notify_try_num) # should be reset
+ self.assertEqual(('192.0.2.1', 5353), example_net_info._notify_current)
+
+ # Possible event is "read" or "timeout".
cur_tgt = example_net_info._notify_current
example_net_info.notify_try_num = notify_out._MAX_NOTIFY_TRY_NUM
- self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_NONE)
- self.assertNotEqual(cur_tgt, example_net_info._notify_current)
+ self.assertRaises(AssertionError, self._notify._zone_notify_handler,
+ example_net_info, notify_out._EVENT_TIMEOUT + 1)
- cur_tgt = example_net_info._notify_current
+ def test_zone_notify_read_handler(self):
+ """Similar to the previous test, but focus on the READ events.
+
+ """
+ sent_addrs = []
+ def _fake_send_notify_message_udp(notify_info, addrinfo):
+ sent_addrs.append(addrinfo)
+ pass
+ self._notify._send_notify_message_udp = _fake_send_notify_message_udp
+ self._notify.send_notify('example.net.')
+
+ example_net_info = self._notify._notify_infos[('example.net.', 'IN')]
example_net_info.create_socket('127.0.0.1')
- # dns message, will result in bad_qid, but what we are testing
- # here is whether handle_notify_reply is called correctly
- example_net_info._sock.remote_end().send(b'\x2f\x18\xa0\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\03com\x00\x00\x06\x00\x01')
- self._notify._zone_notify_handler(example_net_info, notify_out._EVENT_READ)
- self.assertNotEqual(cur_tgt, example_net_info._notify_current)
+
+ # A successful case: an expected notify response is received, and
+ # another notify will be sent to the next slave immediately.
+ example_net_info._sock.remote_end().send(
+ get_notify_msgdata(Name('example.net')))
+ self._notify._zone_notify_handler(example_net_info,
+ notify_out._EVENT_READ)
+ self.assertEqual(1, example_net_info.notify_try_num)
+ expected_sent_addrs = [('192.0.2.1', 5353)]
+ self.assertEqual(expected_sent_addrs, sent_addrs)
+ self.assertEqual(('192.0.2.1', 5353), example_net_info._notify_current)
+
+ # response's QID doesn't match. the request will be resent.
+ example_net_info._sock.remote_end().send(
+ get_notify_msgdata(Name('example.net'), qid=1))
+ self._notify._zone_notify_handler(example_net_info,
+ notify_out._EVENT_READ)
+ self.assertEqual(2, example_net_info.notify_try_num)
+ expected_sent_addrs.append(('192.0.2.1', 5353))
+ self.assertEqual(expected_sent_addrs, sent_addrs)
+ self.assertEqual(('192.0.2.1', 5353), example_net_info._notify_current)
+
+ # emulate exception from socket.recvfrom(). It will have the same
+ # effect as a bad response.
+ example_net_info._sock.set_raise_on_recv(True)
+ example_net_info._sock.remote_end().send(
+ get_notify_msgdata(Name('example.net')))
+ self._notify._zone_notify_handler(example_net_info,
+ notify_out._EVENT_READ)
+ self.assertEqual(3, example_net_info.notify_try_num)
+ expected_sent_addrs.append(('192.0.2.1', 5353))
+ self.assertEqual(expected_sent_addrs, sent_addrs)
+ self.assertEqual(('192.0.2.1', 5353), example_net_info._notify_current)
def test_get_notify_slaves_from_ns(self):
records = self._notify._get_notify_slaves_from_ns(Name('example.net.'),
diff --git a/src/lib/python/isc/notify/tests/testdata/test_spec1.spec b/src/lib/python/isc/notify/tests/testdata/test_spec1.spec
new file mode 100644
index 0000000..7ac8013
--- /dev/null
+++ b/src/lib/python/isc/notify/tests/testdata/test_spec1.spec
@@ -0,0 +1,57 @@
+{
+ "module_spec": {
+ "module_name": "NotifyOutLike",
+ "module_description": "Test notifier",
+ "config_data": [],
+ "commands": [],
+ "statistics": [
+ {
+ "item_name": "zones",
+ "item_type": "named_set",
+ "item_optional": false,
+ "item_default": {
+ "_SERVER_" : {
+ "notifyoutv4" : 0,
+ "notifyoutv6" : 0
+ }
+ },
+ "item_title": "Zone names",
+ "item_description": "Zone names",
+ "named_set_item_spec": {
+ "item_name": "classname",
+ "item_type": "named_set",
+ "item_optional": false,
+ "item_default": {},
+ "item_title": "RR class name",
+ "item_description": "RR class name",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "Zone name",
+ "map_item_spec": [
+ {
+ "item_name": "notifyoutv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IPv4 notifies",
+ "item_description": "Number of IPv4 notifies per zone name sent out"
+ },
+ {
+ "item_name": "notifyoutv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IPv6 notifies",
+ "item_description": "Number of IPv6 notifies per zone name sent out"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+}
diff --git a/src/lib/python/isc/server_common/.gitignore b/src/lib/python/isc/server_common/.gitignore
new file mode 100644
index 0000000..63239b7
--- /dev/null
+++ b/src/lib/python/isc/server_common/.gitignore
@@ -0,0 +1 @@
+/bind10_server.py
diff --git a/src/lib/python/isc/server_common/Makefile.am b/src/lib/python/isc/server_common/Makefile.am
index d89df2f..54b2885 100644
--- a/src/lib/python/isc/server_common/Makefile.am
+++ b/src/lib/python/isc/server_common/Makefile.am
@@ -1,11 +1,14 @@
SUBDIRS = tests
python_PYTHON = __init__.py tsig_keyring.py auth_command.py dns_tcp.py
+python_PYTHON += datasrc_clients_mgr.py bind10_server.py
python_PYTHON += logger.py
pythondir = $(pyexecdir)/isc/server_common
BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.py
+BUILT_SOURCES += bind10_server.py
+
nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/server_common_messages.py
pylogmessagedir = $(pyexecdir)/isc/log_messages/
diff --git a/src/lib/python/isc/server_common/bind10_server.py.in b/src/lib/python/isc/server_common/bind10_server.py.in
new file mode 100644
index 0000000..5bbd341
--- /dev/null
+++ b/src/lib/python/isc/server_common/bind10_server.py.in
@@ -0,0 +1,275 @@
+# Copyright (C) 2013 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 errno
+import os
+import select
+import signal
+
+import isc.log
+import isc.config
+from isc.server_common.logger import logger
+from isc.log_messages.server_common_messages import *
+
+class BIND10ServerFatal(Exception):
+ """Exception raised when the server program encounters a fatal error."""
+ pass
+
+class BIND10Server:
+ """A mixin class for common BIND 10 server implementations.
+
+ It takes care of common initialization such as setting up a module CC
+ session, and running main event loop. It also handles the "shutdown"
+ command for its normal behavior. If a specific server class wants to
+ handle this command differently or if it does not support the command,
+ it should override the _command_handler method.
+
+ Specific modules can define module-specific class inheriting this class,
+ instantiate it, and call run() with the module name.
+
+ Methods to be implemented in the actual class:
+ _config_handler: config handler method as specified in ModuleCCSession.
+ must be exception free; errors should be signaled by
+ the return value.
+ _mod_command_handler: can be optionally defined to handle
+ module-specific commands. should conform to
+ command handlers as specified in ModuleCCSession.
+ must be exception free; errors should be signaled
+ by the return value.
+ _setup_module: can be optionally defined for module-specific
+ initialization. This is called after the module CC
+ session has started, and can be used for registering
+ interest on remote modules, etc. If it raises an
+ exception, the server will be immediately stopped.
+ Parameter: None, Return: None
+ _shutdown_module: can be optionally defined for module-specific
+ finalization. This is called right before the
+ module CC session is stopped. If it raises an
+ exception, the server will be immediately
+ stopped.
+ Parameter: None, Return: None
+
+ """
+ # Will be set to True when the server should stop and shut down.
+ # Can be read via accessor method 'shutdown', mainly for testing.
+ __shutdown = False
+
+ # ModuleCCSession used in the server. Defined as 'protectd' so tests
+ # can refer to it directly; others should access it via the
+ # 'mod_ccsession' accessor.
+ _mod_cc = None
+
+ # Will be set in run(). Define a tentative value so other methods can
+ # be tested directly.
+ __module_name = ''
+
+ # Basically constant, but allow tests to override it.
+ _select_fn = select.select
+
+ def __init__(self):
+ self._read_callbacks = {}
+ self._write_callbacks = {}
+ self._error_callbacks = {}
+
+ @property
+ def shutdown(self):
+ return self.__shutdown
+
+ @property
+ def mod_ccsession(self):
+ return self._mod_cc
+
+ def _setup_ccsession(self):
+ """Create and start module CC session.
+
+ This is essentially private, but allows tests to override it.
+
+ """
+ self._mod_cc = isc.config.ModuleCCSession(
+ self._get_specfile_location(), self._config_handler,
+ self._command_handler)
+ self._mod_cc.start()
+
+ def _get_specfile_location(self):
+ """Return the path to the module spec file following common convetion.
+
+ This method generates the path commonly used by most BIND 10 modules,
+ determined by a well known prefix and the module name.
+
+ A specific module can override this method if it uses a different
+ path for the spec file.
+
+ """
+ # First check if it's running under an 'in-source' environment,
+ # then try commonly used paths and file names. If found, use it.
+ for ev in ['B10_FROM_SOURCE', 'B10_FROM_BUILD']:
+ if ev in os.environ:
+ specfile = os.environ[ev] + '/src/bin/' + self.__module_name +\
+ '/' + self.__module_name + '.spec'
+ if os.path.exists(specfile):
+ return specfile
+ # Otherwise, just use the installed path, whether or not it really
+ # exists; leave error handling to the caller.
+ specfile_path = '${datarootdir}/bind10'\
+ .replace('${datarootdir}', '${prefix}/share')\
+ .replace('${prefix}', '/Users/jinmei/opt')
+ return specfile_path + '/' + self.__module_name + '.spec'
+
+ def _trigger_shutdown(self):
+ """Initiate a shutdown sequence.
+
+ This method is expected to be called in various ways including
+ in the middle of a signal handler, and is designed to be as simple
+ as possible to minimize side effects. Actual shutdown will take
+ place in a normal control flow.
+
+ This method is defined as 'protected'. User classes can use it
+ to shut down the server.
+
+ """
+ self.__shutdown = True
+
+ def _run_internal(self):
+ """Main event loop.
+
+ This method is essentially private, but allows tests to override it.
+
+ """
+
+ logger.info(PYSERVER_COMMON_SERVER_STARTED, self.__module_name)
+ cc_fileno = self._mod_cc.get_socket().fileno()
+ while not self.__shutdown:
+ try:
+ read_fds = list(self._read_callbacks.keys())
+ read_fds.append(cc_fileno)
+ write_fds = list(self._write_callbacks.keys())
+ error_fds = list(self._error_callbacks.keys())
+
+ (reads, writes, errors) = \
+ self._select_fn(read_fds, write_fds, error_fds)
+ except select.error as ex:
+ # ignore intterruption by signal; regard other select errors
+ # fatal.
+ if ex.args[0] == errno.EINTR:
+ continue
+ else:
+ raise
+
+ for fileno in reads:
+ if fileno in self._read_callbacks:
+ for callback in self._read_callbacks[fileno]:
+ callback()
+
+ for fileno in writes:
+ if fileno in self._write_callbacks:
+ for callback in self._write_callbacks[fileno]:
+ callback()
+
+ for fileno in errors:
+ if fileno in self._error_callbacks:
+ for callback in self._error_callbacks[fileno]:
+ callback()
+
+ if cc_fileno in reads:
+ # this shouldn't raise an exception (if it does, we'll
+ # propagate it)
+ self._mod_cc.check_command(True)
+
+ self._shutdown_module()
+ self._mod_cc.send_stopping()
+
+ def _command_handler(self, cmd, args):
+ logger.debug(logger.DBGLVL_TRACE_BASIC, PYSERVER_COMMON_COMMAND,
+ self.__module_name, cmd)
+ if cmd == 'shutdown':
+ self._trigger_shutdown()
+ answer = isc.config.create_answer(0)
+ else:
+ answer = self._mod_command_handler(cmd, args)
+
+ return answer
+
+ def _mod_command_handler(self, cmd, args):
+ """The default implementation of the module specific command handler"""
+ return isc.config.create_answer(1, "Unknown command: " + str(cmd))
+
+ def _setup_module(self):
+ """The default implementation of the module specific initialization"""
+ pass
+
+ def _shutdown_module(self):
+ """The default implementation of the module specific finalization"""
+ pass
+
+ def watch_fileno(self, fileno, rcallback=None, wcallback=None, \
+ xcallback=None):
+ """Register the fileno for the internal select() call.
+
+ *callback's are callable objects which would be called when
+ read, write, error events occur on the specified fileno.
+ """
+ if rcallback is not None:
+ if fileno in self._read_callbacks:
+ self._read_callbacks[fileno].append(rcallback)
+ else:
+ self._read_callbacks[fileno] = [rcallback]
+
+ if wcallback is not None:
+ if fileno in self._write_callbacks:
+ self._write_callbacks[fileno].append(wcallback)
+ else:
+ self._write_callbacks[fileno] = [wcallback]
+
+ if xcallback is not None:
+ if fileno in self._error_callbacks:
+ self._error_callbacks[fileno].append(xcallback)
+ else:
+ self._error_callbacks[fileno] = [xcallback]
+
+ def run(self, module_name):
+ """Start the server and let it run until it's told to stop.
+
+ Usually this must be the first method of this class that is called
+ from its user.
+
+ Parameter:
+ module_name (str): the Python module name for the actual server
+ implementation. Often identical to the directory name in which
+ the implementation files are placed.
+
+ Returns: values expected to be used as program's exit code.
+ 0: server has run and finished successfully.
+ 1: some error happens
+
+ """
+ try:
+ self.__module_name = module_name
+ shutdown_sighandler = \
+ lambda signal, frame: self._trigger_shutdown()
+ signal.signal(signal.SIGTERM, shutdown_sighandler)
+ signal.signal(signal.SIGINT, shutdown_sighandler)
+ self._setup_ccsession()
+ self._setup_module()
+ self._run_internal()
+ logger.info(PYSERVER_COMMON_SERVER_STOPPED, self.__module_name)
+ return 0
+ except BIND10ServerFatal as ex:
+ logger.error(PYSERVER_COMMON_SERVER_FATAL, self.__module_name,
+ ex)
+ except Exception as ex:
+ logger.error(PYSERVER_COMMON_UNCAUGHT_EXCEPTION, type(ex).__name__,
+ ex)
+
+ return 1
diff --git a/src/lib/python/isc/server_common/datasrc_clients_mgr.py b/src/lib/python/isc/server_common/datasrc_clients_mgr.py
new file mode 100644
index 0000000..4f565df
--- /dev/null
+++ b/src/lib/python/isc/server_common/datasrc_clients_mgr.py
@@ -0,0 +1,173 @@
+# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import isc.dns
+import isc.datasrc
+import threading
+import json
+
+class ConfigError(Exception):
+ """Exception class raised for data source configuration errors."""
+ pass
+
+class DataSrcClientsMgr:
+ """A container of data source client lists.
+
+ This class represents a set of isc.datasrc.ConfigurableClientList
+ objects (currently per RR class), and provides APIs to configure
+ the lists and access to a specific list in a thread safe manner.
+
+ It is intended to be used by applications that refer to the global
+ 'data_sources' module. The reconfigure() method can be called from
+ a configuration callback for the module of the application. The
+ get_client_list() method is a simple search method to get the configured
+ ConfigurableClientList object for a specified RR class (if any),
+ while still allowing a separate thread to reconfigure the entire lists.
+
+ """
+ def __init__(self, use_cache=False):
+ """Constructor.
+
+ In the initial implementation, most user applications of this class
+ are generally expected to NOT use in-memory cache; the only expected
+ exception is the memory (cache) manager, which, by definition,
+ needs to deal with in-memory data. In future, some more applications
+ such as outbound zone transfer may want to set it to True.
+
+ Parameter:
+ use_cache (bool): If set to True, enable in-memory cache on
+ (re)configuration.
+
+ """
+ self.__use_cache = use_cache
+
+ # Map from RRClass to ConfigurableClientList. Resetting this map
+ # is protected by __map_lock. Note that this lock doesn't protect
+ # "updates" of the map content (currently it's not a problem, but
+ # if and when we support more operations such as reloading
+ # particular zones in in-memory cache, remember that there will have
+ # to be an additional layer of protection).
+ self.__clients_map = {}
+ self.__map_lock = threading.Lock()
+
+ # The generation ID of the configuration corresponding to
+ # current __clinets_map. Until we support the concept of generations
+ # in the configuration framework, we tentatively maintain it within
+ # this class.
+ self.__gen_id = 0
+
+ def get_clients_map(self):
+ """Returns a dict from RR class to ConfigurableClientList with gen ID.
+
+ It corresponds to the generation of data source configuration at the
+ time of the call. It can be safely called while reconfigure() is
+ called from another thread.
+
+ The mapping of the dict should be considered "frozen"; the caller
+ shouldn't modify the mapping (it can use the mapped objects in a
+ way modifying its internal state).
+
+ Note: in a future version we may also need to return the
+ "generation ID" of the corresponding configuration so the caller
+ application can handle migration between generations gradually.
+
+ """
+ with self.__map_lock:
+ return (self.__gen_id, self.__clients_map)
+
+ def get_client_list(self, rrclass):
+ """Return the configured ConfigurableClientList for the RR class.
+
+ If no client list is configured for the specified RR class, it
+ returns None.
+
+ This method should not raise an exception as long as the parameter
+ is of valid type.
+
+ This method can be safely called from a thread even if a different
+ thread is calling reconfigure(). Also, it's safe for the caller
+ to use the returned list even if reconfigure() is called while or
+ after the call to this thread.
+
+ Note that this class does not protect further access to the returned
+ list from multiple threads; it's the caller's responsbility to make
+ such access thread safe. In general, the find() method on the list
+ and the use of ZoneFinder created by a DataSourceClient in the list
+ cannot be done by multiple threads without explicit synchronization.
+ On the other hand, multiple threads can create and use ZoneUpdater,
+ ZoneIterator, or ZoneJournalReader on a DataSourceClient in parallel.
+
+ Parameter:
+ rrclass (isc.dns.RRClass): the RR class of the ConfigurableClientList
+ to be returned.
+ """
+ with self.__map_lock:
+ client_list = self.__clients_map.get(rrclass)
+ return client_list
+
+ def reconfigure(self, new_config, config_data):
+ """(Re)configure the set of client lists.
+
+ This method takes a new set of data source configuration, builds
+ a new set of ConfigurableClientList objects corresponding to the
+ configuration, and replaces the internal set with the newly built
+ one. Its parameter is expected to be the "new configuration"
+ parameter of a configuration update callback for the global
+ "data_sources" module. It should match the configuration data
+ of the module spec (see the datasrc.spec file).
+
+ Any error in reconfiguration is converted to a ConfigError
+ exception and is raised from the method. This method guarantees
+ strong exception safety: unless building a new set for the new
+ configuration is fully completed, the old set is intact.
+
+ This method can be called from a thread while some other thread
+ is calling get_client_list() and using the result (see
+ the description of get_client_list()). In general, however,
+ only one thread can call this method at one time; while data
+ integrity will still be preserved, the ordering of the change
+ will not be guaranteed if multiple threads call this method
+ at the same time.
+
+ Parameter:
+ new_config (dict): configuration data for the data_sources module
+ (actually unused in this method).
+ config_data (isc.config.ConfigData): the latest full config data
+ for the data_sources module. Usually the second parameter of
+ the (remote) configuration update callback for the module.
+
+ """
+ try:
+ new_map = {}
+ # We only refer to config_data, not new_config (diff from the
+ # previous). the latter may be empty for the initial default
+ # configuration while the former works for all cases.
+ for rrclass_cfg, class_cfg in \
+ config_data.get_value('classes')[0].items():
+ rrclass = isc.dns.RRClass(rrclass_cfg)
+ new_client_list = isc.datasrc.ConfigurableClientList(rrclass)
+ new_client_list.configure(json.dumps(class_cfg),
+ self.__use_cache)
+ new_map[rrclass] = new_client_list
+ with self.__map_lock:
+ self.__clients_map = new_map
+
+ # NOTE: when we support the concept of generations this should
+ # be retrieved from the configuration
+ self.__gen_id += 1
+ except Exception as ex:
+ # Catch all types of exceptions as a whole: there won't be much
+ # granularity for exceptions raised from the C++ module anyway.
+ raise ConfigError(ex)
diff --git a/src/lib/python/isc/server_common/dns_tcp.py b/src/lib/python/isc/server_common/dns_tcp.py
index 9ce94fe..2cbd47a 100644
--- a/src/lib/python/isc/server_common/dns_tcp.py
+++ b/src/lib/python/isc/server_common/dns_tcp.py
@@ -91,7 +91,7 @@ class DNSTCPSendBuffer:
A Python binary object that corresponds to a part of the TCP
DNS message data starting at the specified position. It may
or may not contain all remaining data from that position.
- If the given position is beyond the end of the enrire data,
+ If the given position is beyond the end of the entire data,
None will be returned.
'''
@@ -267,7 +267,7 @@ class DNSTCPContext:
when this object is deallocated, but Python seems to expect socket
objects should be explicitly closed before deallocation. So it's
generally advisable for the user of this object to call this method
- explictily when it doesn't need the context.
+ explicitly when it doesn't need the context.
This method can be called more than once or can be called after
other I/O related methods have returned CLOSED; it's compatible
@@ -277,4 +277,4 @@ class DNSTCPContext:
if self.__sock is None:
return
self.__sock.close()
- self.__sock = None # prevent furhter operation
+ self.__sock = None # prevent further operation
diff --git a/src/lib/python/isc/server_common/server_common_messages.mes b/src/lib/python/isc/server_common/server_common_messages.mes
index f22ce65..54602f9 100644
--- a/src/lib/python/isc/server_common/server_common_messages.mes
+++ b/src/lib/python/isc/server_common/server_common_messages.mes
@@ -21,6 +21,9 @@
# have that at this moment. So when adding a message, make sure that
# the name is not already used in src/lib/config/config_messages.mes
+% PYSERVER_COMMON_COMMAND %1 server has received '%2' command
+The server process received the shown name of command from other module.
+
% PYSERVER_COMMON_DNS_TCP_SEND_DONE completed sending TCP message to %1 (%2 bytes in total)
Debug message. A complete DNS message has been successfully
transmitted over a TCP connection, possibly after multiple send
@@ -44,6 +47,18 @@ The destination address and the total size of the message that has
been transmitted so far (including the 2-byte length field) are shown
in the log message.
+% PYSERVER_COMMON_SERVER_FATAL %1 server has encountered a fatal error: %2
+The BIND 10 server process encountered a fatal error (normally specific to
+the particular program), and is forcing itself to shut down.
+
+% PYSERVER_COMMON_SERVER_STARTED %1 server has started
+The server process has successfully started and is now ready to receive
+commands and configuration updates.
+
+% PYSERVER_COMMON_SERVER_STOPPED %1 server has started
+The server process has successfully stopped and is no longer listening for or
+handling commands. Normally the process will soon exit.
+
% PYSERVER_COMMON_TSIG_KEYRING_DEINIT Deinitializing global TSIG keyring
A debug message noting that the global TSIG keyring is being removed from
memory. Most programs don't do that, they just exit, which is OK.
@@ -57,3 +72,8 @@ to be loaded from configuration.
A debug message. The TSIG keyring is being (re)loaded from configuration.
This happens at startup or when the configuration changes. The old keyring
is removed and new one created with all the keys.
+
+% PYSERVER_COMMON_UNCAUGHT_EXCEPTION uncaught exception of type %1: %2
+The BIND 10 server process encountered an uncaught exception and will now shut
+down. This is indicative of a programming error and should not happen under
+normal circumstances. The exception type and message are printed.
diff --git a/src/lib/python/isc/server_common/tests/.gitignore b/src/lib/python/isc/server_common/tests/.gitignore
new file mode 100644
index 0000000..dfeb2e5
--- /dev/null
+++ b/src/lib/python/isc/server_common/tests/.gitignore
@@ -0,0 +1,2 @@
+/datasrc.spec
+/zone.sqlite3
diff --git a/src/lib/python/isc/server_common/tests/Makefile.am b/src/lib/python/isc/server_common/tests/Makefile.am
index 82cd854..bf6b26c 100644
--- a/src/lib/python/isc/server_common/tests/Makefile.am
+++ b/src/lib/python/isc/server_common/tests/Makefile.am
@@ -1,14 +1,23 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = tsig_keyring_test.py dns_tcp_test.py
+PYTESTS = tsig_keyring_test.py dns_tcp_test.py datasrc_clients_mgr_test.py
+PYTESTS += bind10_server_test.py
EXTRA_DIST = $(PYTESTS)
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
+# We use our own "default" datasrc.spec, tweaking some installation path,
+# so we can run the tests with something very close to the actual spec and
+# yet independent from installation environment.
+BUILT_SOURCES = datasrc.spec
+datasrc.spec: $(abs_top_builddir)/src/bin/cfgmgr/plugins/datasrc.spec.pre
+ $(SED) -e "s|@@STATIC_ZONE_FILE@@|$(abs_top_builddir)/src/lib/datasrc/static.zone|;s|@@SQLITE3_DATABASE_FILE@@|$(abs_builddir)/zone.sqlite3|" $(abs_top_builddir)/src/bin/cfgmgr/plugins/datasrc.spec.pre >$@
+CLEANFILES = datasrc.spec zone.sqlite3
+
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
if ENABLE_PYTHON_COVERAGE
@@ -21,5 +30,7 @@ endif
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
+ B10_FROM_SOURCE=$(abs_top_srcdir) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/lib/python/isc/server_common/tests/bind10_server_test.py b/src/lib/python/isc/server_common/tests/bind10_server_test.py
new file mode 100755
index 0000000..f93eed6
--- /dev/null
+++ b/src/lib/python/isc/server_common/tests/bind10_server_test.py
@@ -0,0 +1,294 @@
+# Copyright (C) 2013 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 errno
+import os
+import signal
+
+import isc.log
+import isc.config
+from isc.server_common.bind10_server import *
+from isc.testutils.ccsession_mock import MockModuleCCSession
+
+TEST_FILENO = 42 # arbitrarily chosen
+
+class TestException(Exception):
+ """A generic exception class specific in this test module."""
+ pass
+
+class MyCCSession(MockModuleCCSession, isc.config.ConfigData):
+ def __init__(self, specfile, config_handler, command_handler):
+ # record parameter for later inspection
+ self.specfile_param = specfile
+ self.config_handler_param = config_handler
+ self.command_handler_param = command_handler
+
+ self.check_command_param = None # used in check_command()
+
+ # Initialize some local attributes of MockModuleCCSession, including
+ # 'stopped'
+ MockModuleCCSession.__init__(self)
+
+ def start(self):
+ pass
+
+ def check_command(self, nonblock):
+ """Mock check_command(). Just record the param for later inspection."""
+ self.check_command_param = nonblock
+
+ def get_socket(self):
+ return self
+
+ def fileno(self):
+ """Pretending get_socket().fileno()
+
+ Returing an arbitrarily chosen constant.
+
+ """
+ return TEST_FILENO
+
+class MockServer(BIND10Server):
+ def __init__(self):
+ BIND10Server.__init__(self)
+ self._select_fn = self.select_wrapper
+
+ def _setup_ccsession(self):
+ orig_cls = isc.config.ModuleCCSession
+ isc.config.ModuleCCSession = MyCCSession
+ try:
+ super()._setup_ccsession()
+ except Exception:
+ raise
+ finally:
+ isc.config.ModuleCCSession = orig_cls
+
+ def _config_handler(self):
+ pass
+
+ def mod_command_handler(self, cmd, args):
+ """A sample _mod_command_handler implementation."""
+ self.command_handler_params = (cmd, args) # for inspection
+ return isc.config.create_answer(0)
+
+ def select_wrapper(self, reads, writes, errors):
+ self._trigger_shutdown() # make sure the loop will stop
+ self.select_params = (reads, writes, errors) # record for inspection
+ return [], [], []
+
+class TestBIND10Server(unittest.TestCase):
+ def setUp(self):
+ self.__server = MockServer()
+ self.__reads = 0
+ self.__writes = 0
+ self.__errors = 0
+
+ def test_init(self):
+ """Check initial conditions"""
+ self.assertFalse(self.__server.shutdown)
+
+ def test_trigger_shutdown(self):
+ self.__server._trigger_shutdown()
+ self.assertTrue(self.__server.shutdown)
+
+ def test_sigterm_handler(self):
+ """Check the signal handler behavior.
+
+ SIGTERM and SIGINT should be caught and should call memmgr's
+ _trigger_shutdown(). This test also indirectly confirms run() calls
+ run_internal().
+
+ """
+ def checker():
+ self.__shutdown_called = True
+
+ self.__server._run_internal = lambda: os.kill(os.getpid(),
+ signal.SIGTERM)
+ self.__server._trigger_shutdown = lambda: checker()
+ self.assertEqual(0, self.__server.run('test'))
+ self.assertTrue(self.__shutdown_called)
+
+ self.__shutdown_called = False
+ self.__server._run_internal = lambda: os.kill(os.getpid(),
+ signal.SIGINT)
+ self.assertEqual(0, self.__server.run('test'))
+ self.assertTrue(self.__shutdown_called)
+
+ def test_exception(self):
+ """Check exceptions are handled, not leaked."""
+ def exception_raiser(ex_cls):
+ raise ex_cls('test')
+
+ # Test all possible exceptions that are explicitly caught
+ for ex in [TestException, BIND10ServerFatal]:
+ self.__server._run_internal = lambda: exception_raiser(ex)
+ self.assertEqual(1, self.__server.run('test'))
+
+ def test_run(self):
+ """Check other behavior of run()"""
+ self.__server._run_internal = lambda: None # prevent looping
+ self.assertEqual(0, self.__server.run('test'))
+ # module CC session should have been setup.
+ # The exact path to the spec file can vary, so we simply check
+ # it works and it's the expected name stripping the path.
+ self.assertEqual(
+ self.__server.mod_ccsession.specfile_param.split('/')[-1],
+ 'test.spec')
+ self.assertEqual(self.__server.mod_ccsession.config_handler_param,
+ self.__server._config_handler)
+ self.assertEqual(self.__server.mod_ccsession.command_handler_param,
+ self.__server._command_handler)
+
+ def test_run_with_setup_module(self):
+ """Check run() with module specific setup method."""
+ self.setup_called = False
+ def check_called():
+ self.setup_called = True
+ self.__server._run_internal = lambda: None
+ self.__server._setup_module = check_called
+ self.assertEqual(0, self.__server.run('test'))
+ self.assertTrue(self.setup_called)
+
+ def test_shutdown_command(self):
+ answer = self.__server._command_handler('shutdown', None)
+ self.assertTrue(self.__server.shutdown)
+ self.assertEqual((0, None), isc.config.parse_answer(answer))
+
+ def test_run_with_shutdown_module(self):
+ """Check run() with module specific shutdown method."""
+ self.shutdown_called = False
+ def check_called():
+ self.shutdown_called = True
+ self.__server.__shutdown = True
+ self.__server._shutdown_module = check_called
+ self.assertEqual(0, self.__server.run('test'))
+ self.assertTrue(self.shutdown_called)
+
+ def test_other_command(self):
+ self.__server._mod_command_handler = self.__server.mod_command_handler
+ answer = self.__server._command_handler('other command', None)
+ # shouldn't be confused with shutdown
+ self.assertFalse(self.__server.shutdown)
+ self.assertEqual((0, None), isc.config.parse_answer(answer))
+ self.assertEqual(('other command', None),
+ self.__server.command_handler_params)
+
+ def test_other_command_nohandler(self):
+ """Similar to test_other_command, but without explicit handler"""
+ # In this case "unknown command" error should be returned.
+ answer = self.__server._command_handler('other command', None)
+ self.assertEqual(1, isc.config.parse_answer(answer)[0])
+
+ def test_run_internal(self):
+ self.__server._setup_ccsession()
+ self.__server._run_internal()
+ self.assertEqual(([TEST_FILENO], [], []), self.__server.select_params)
+
+ def select_wrapper(self, r, w, e, ex=None, ret=None):
+ """Mock select() function used some of the tests below.
+
+ If ex is not None and it's first call to this method, it raises ex
+ assuming it's an exception.
+
+ If ret is not None, it returns the given value; otherwise it returns
+ all empty lists.
+
+ """
+ self.select_params.append((r, w, e))
+ if ex is not None and len(self.select_params) == 1:
+ raise ex
+ else:
+ self.__server._trigger_shutdown()
+ if ret is not None:
+ return ret
+ return [], [], []
+
+ def test_select_for_command(self):
+ """A normal event iteration, handling one command."""
+ self.select_params = []
+ self.__server._select_fn = \
+ lambda r, w, e: self.select_wrapper(r, w, e,
+ ret=([TEST_FILENO], [], []))
+ self.__server._setup_ccsession()
+ self.__server._run_internal()
+ # select should be called only once.
+ self.assertEqual([([TEST_FILENO], [], [])], self.select_params)
+ # check_command should have been called.
+ self.assertTrue(self.__server.mod_ccsession.check_command_param)
+ # module CC session should have been stopped explicitly.
+ self.assertTrue(self.__server.mod_ccsession.stopped)
+
+ def test_select_interrupted(self):
+ """Emulating case select() raises EINTR."""
+ self.select_params = []
+ self.__server._select_fn = \
+ lambda r, w, e: self.select_wrapper(r, w, e,
+ ex=select.error(errno.EINTR))
+ self.__server._setup_ccsession()
+ self.__server._run_internal()
+ # EINTR will be ignored and select() will be called again.
+ self.assertEqual([([TEST_FILENO], [], []), ([TEST_FILENO], [], [])],
+ self.select_params)
+ # check_command() shouldn't have been called (select_wrapper returns
+ # empty lists by default).
+ self.assertIsNone(self.__server.mod_ccsession.check_command_param)
+ self.assertTrue(self.__server.mod_ccsession.stopped)
+
+ def test_select_other_exception(self):
+ """Emulating case select() raises other select error."""
+ self.select_params = []
+ self.__server._select_fn = \
+ lambda r, w, e: self.select_wrapper(r, w, e,
+ ex=select.error(errno.EBADF))
+ self.__server._setup_ccsession()
+ # the exception will be propagated.
+ self.assertRaises(select.error, self.__server._run_internal)
+ self.assertEqual([([TEST_FILENO], [], [])], self.select_params)
+ # in this case module CC session hasn't been stopped explicitly
+ # others will notice it due to connection reset.
+ self.assertFalse(self.__server.mod_ccsession.stopped)
+
+ def my_read_callback(self):
+ self.__reads += 1
+
+ def my_write_callback(self):
+ self.__writes += 1
+
+ def my_error_callback(self):
+ self.__errors += 1
+
+ def test_watch_fileno(self):
+ """Test watching for fileno."""
+ self.select_params = []
+ self.__server._select_fn = \
+ lambda r, w, e: self.select_wrapper(r, w, e,
+ ret=([10, 20, 42, TEST_FILENO], [], [30]))
+ self.__server._setup_ccsession()
+
+ self.__server.watch_fileno(10, rcallback=self.my_read_callback)
+ self.__server.watch_fileno(20, rcallback=self.my_read_callback, \
+ wcallback=self.my_write_callback)
+ self.__server.watch_fileno(30, xcallback=self.my_error_callback)
+
+ self.__server._run_internal()
+ self.assertEqual([([10, 20, TEST_FILENO], [20], [30])], self.select_params)
+ self.assertEqual(2, self.__reads)
+ self.assertEqual(0, self.__writes)
+ self.assertEqual(1, self.__errors)
+
+if __name__== "__main__":
+ isc.log.init("bind10_server_test")
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/lib/python/isc/server_common/tests/datasrc_clients_mgr_test.py b/src/lib/python/isc/server_common/tests/datasrc_clients_mgr_test.py
new file mode 100644
index 0000000..e717d65
--- /dev/null
+++ b/src/lib/python/isc/server_common/tests/datasrc_clients_mgr_test.py
@@ -0,0 +1,142 @@
+# Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import isc.log
+from isc.server_common.datasrc_clients_mgr import *
+from isc.dns import *
+import unittest
+import isc.config
+import os
+
+# A (slightly tweaked) local copy of the default data source spec
+DATASRC_SPECFILE = os.environ["B10_FROM_BUILD"] + \
+ "/src/lib/python/isc/server_common/tests/datasrc.spec"
+DEFAULT_CONFIG = \
+ isc.config.ConfigData(isc.config.module_spec_from_file(DATASRC_SPECFILE)).\
+ get_full_config()
+
+class DataSrcClientsMgrTest(unittest.TestCase):
+ def setUp(self):
+ # We construct the manager with enabling in-memory cache for easier
+ # tests. There should be no risk of inter-thread issues in the tests.
+ self.__mgr = DataSrcClientsMgr(use_cache=True)
+ self.__datasrc_cfg = isc.config.ConfigData(
+ isc.config.module_spec_from_file(DATASRC_SPECFILE))
+
+ def test_init(self):
+ """Check some initial state.
+
+ Initially there's no client list available, but get_client_list
+ doesn't cause disruption.
+ """
+ self.assertIsNone(self.__mgr.get_client_list(RRClass.IN))
+ self.assertIsNone(self.__mgr.get_client_list(RRClass.CH))
+
+ def test_reconfigure(self):
+ """Check configuration behavior.
+
+ First try the default configuration, and replace it with something
+ else.
+ """
+
+ # There should be at least in-memory only data for the static
+ # bind/CH zone. (We don't assume the existence of SQLite3 datasrc,
+ # so it'll still work if and when we make the default DB-independent).
+ self.__mgr.reconfigure({}, self.__datasrc_cfg)
+ clist = self.__mgr.get_client_list(RRClass.CH)
+ self.assertIsNotNone(clist)
+ self.assertTrue(clist.find(Name('bind'), True, False)[2])
+
+ # Reconfigure it with a simple new config: the list for CH will be
+ # gone, and and an empty list for IN will be installed.
+ self.__datasrc_cfg.set_local_config({"classes": {"IN": []}})
+ self.__mgr.reconfigure({}, self.__datasrc_cfg)
+ self.assertIsNone(self.__mgr.get_client_list(RRClass.CH))
+ self.assertIsNotNone(self.__mgr.get_client_list(RRClass.IN))
+
+ def test_reconfigure_error(self):
+ """Check reconfigure failure preserves the old config."""
+ # Configure it with the default
+ self.__mgr.reconfigure({}, self.__datasrc_cfg)
+ self.assertIsNotNone(self.__mgr.get_client_list(RRClass.CH))
+
+ # Then try invalid configuration
+ self.assertRaises(ConfigError, self.__mgr.reconfigure, {}, 42)
+ self.assertIsNotNone(self.__mgr.get_client_list(RRClass.CH))
+
+ # Another type of invalid configuration: exception would come from
+ # the C++ wrapper.
+ self.__datasrc_cfg.set_local_config({"classes": {"IN": 42}})
+ self.assertRaises(ConfigError,
+ self.__mgr.reconfigure, {}, self.__datasrc_cfg)
+ self.assertIsNotNone(self.__mgr.get_client_list(RRClass.CH))
+
+ def check_client_list_content(self, clist):
+ """Some set of checks on given data source client list.
+
+ Used by a couple of tests below.
+ """
+ datasrc_client, finder, exact = clist.find(Name('bind'))
+ self.assertTrue(exact)
+
+ # Reset the client list
+ clist = None
+
+ # Both finder and datasrc client should still work without causing
+ # disruption. We shouldn't have to inspect too much details of the
+ # returned values.
+ result, rrset, _ = finder.find(Name('bind'), RRType.SOA)
+ self.assertEqual(Name('bind'), rrset.get_name())
+ self.assertEqual(RRType.SOA, rrset.get_type())
+ self.assertEqual(RRClass.CH, rrset.get_class())
+ self.assertEqual(RRTTL(0), rrset.get_ttl())
+
+ # iterator should produce some non empty set of RRsets
+ rrsets = datasrc_client.get_iterator(Name('bind'))
+ self.assertNotEqual(0, len(list(rrsets)))
+
+ def test_reconfig_while_using_old(self):
+ """Check datasrc client and finder can work even after list is gone."""
+ self.__mgr.reconfigure({}, self.__datasrc_cfg)
+ clist = self.__mgr.get_client_list(RRClass.CH)
+
+ self.__datasrc_cfg.set_local_config({"classes": {"IN": []}})
+ self.__mgr.reconfigure({}, self.__datasrc_cfg)
+ self.check_client_list_content(clist)
+
+ def test_get_clients_map(self):
+ # This is basically a trivial getter, so it should be sufficient
+ # to check we can call it as we expect.
+
+ # Initially map iss empty, the generation ID is 0.
+ self.assertEqual((0, {}), self.__mgr.get_clients_map())
+
+ self.__mgr.reconfigure({}, self.__datasrc_cfg)
+ genid, clients_map = self.__mgr.get_clients_map()
+ self.assertEqual(1, genid)
+ self.assertEqual(2, len(clients_map)) # should contain 'IN' and 'CH'
+
+ # Check the retrieved map is usable even after further reconfig().
+ self.__datasrc_cfg.set_local_config({"classes": {"IN": []}})
+ self.__mgr.reconfigure({}, self.__datasrc_cfg)
+ self.check_client_list_content(clients_map[RRClass.CH])
+
+ # generation ID should be incremented again
+ self.assertEqual(2, self.__mgr.get_clients_map()[0])
+
+if __name__ == "__main__":
+ isc.log.init("bind10")
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/lib/python/isc/server_common/tests/tsig_keyring_test.py b/src/lib/python/isc/server_common/tests/tsig_keyring_test.py
index e9a2174..6b9b75c 100644
--- a/src/lib/python/isc/server_common/tests/tsig_keyring_test.py
+++ b/src/lib/python/isc/server_common/tests/tsig_keyring_test.py
@@ -65,7 +65,7 @@ class TSIGKeyRingTest(unittest.TestCase):
def test_initialization(self):
"""
- Test we can initialize and deintialize the keyring. It also
+ Test we can initialize and deinitialize the keyring. It also
tests the interaction with the keyring() function.
"""
# The keyring function raises until initialized
diff --git a/src/lib/python/isc/statistics/Makefile.am b/src/lib/python/isc/statistics/Makefile.am
index 9be1312..699004e 100644
--- a/src/lib/python/isc/statistics/Makefile.am
+++ b/src/lib/python/isc/statistics/Makefile.am
@@ -1,6 +1,6 @@
SUBDIRS = . tests
-python_PYTHON = __init__.py counters.py
+python_PYTHON = __init__.py counters.py dns.py
pythondir = $(pyexecdir)/isc/statistics
CLEANDIRS = __pycache__
diff --git a/src/lib/python/isc/statistics/counters.py b/src/lib/python/isc/statistics/counters.py
index 8138ab6..dcdd247 100644
--- a/src/lib/python/isc/statistics/counters.py
+++ b/src/lib/python/isc/statistics/counters.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 Internet Systems Consortium.
+# Copyright (C) 2012-2013 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
@@ -17,52 +17,34 @@
This module handles the statistics counters for BIND 10 modules. For
using the module `counter.py`, first a counters object should be created
-in each module (like b10-xfrin or b10-xfrout) after importing this
-module. A spec file can be specified as an argument when creating the
-counters object:
+in each module like b10-foo after importing this module. A spec file can
+be specified as an argument when creating the counters object:
from isc.statistics import Counters
self.counters = Counters("/path/to/foo.spec")
The first argument of Counters() can be specified, which is the location
-of the specification file (like src/bin/xfrout/xfrout.spec). If Counters
-is constructed this way, statistics counters can be accessed from each
-module. For example, in case that the item `xfrreqdone` is defined in
-statistics_spec in xfrout.spec, the following methods are
-callable. Since these methods require the string of the zone name in the
-first argument, if we have the following code in b10-xfrout:
+of the specification file. If Counters is constructed this way,
+statistics counters can be accessed from each module. For example, in
+case that the item `counter1` is defined in statistics_spec in foo.spec,
+the following methods are callable.
- self.counters.inc('zones', zone_name, 'xfrreqdone')
+ self.counters.inc('counter1')
-then the counter for xfrreqdone corresponding to zone_name is
-incremented. For getting the current number of this counter, we can use
-the following code:
+Then the counter for `counter1` is incremented. For getting the current
+number of this counter, we can use the following code:
- number = self.counters.get('zones', zone_name, 'xfrreqdone')
+ number = self.counters.get('counter1')
-then the current count is obtained and set in the variable
+Then the current count is obtained and set in the variable
`number`. Such a getter method would be mainly used for unit-testing.
-As other example, for the item `axfr_running`, the decrementer method is
-also callable. This method is used for decrementing a counter. For the
-item `axfr_running`, an argument like zone name is not required:
+The decrementer method is also callable. This method is used for
+decrementing a counter as well as inc().
- self.counters.dec('axfr_running')
+ self.counters.dec('counter2')
-These methods are effective in other modules. For example, in case that
-this module `counter.py` is once imported in a main module such as
-b10-xfrout, then for the item `notifyoutv4`, the `inc()` method can be
-invoked in another module such as notify_out.py, which is firstly
-imported in the main module.
-
- self.counters.inc('zones', zone_name, 'notifyoutv4')
-
-In this example this is for incrementing the counter of the item
-`notifyoutv4`. Thus, such statement can be also written in another
-library like isc.notify.notify_out. If this module `counter.py` isn't
-imported in the main module but imported in such a library module as
-isc.notify.notify_out, in this example, empty methods would be invoked,
-which is directly defined in `counter.py`.
-"""
+Some other methods accessible to a counter are provided by this
+module."""
import threading
import isc.config
@@ -81,19 +63,47 @@ def _add_counter(element, spec, identifier):
return isc.cc.data.find(element, identifier)
except isc.cc.data.DataNotFoundError:
pass
- # check whether spec and identifier are correct
- isc.config.find_spec_part(spec, identifier)
- # examine spec of the top-level item first
- spec_ = isc.config.find_spec_part(spec, identifier.split('/')[0])
- if spec_['item_type'] == 'named_set' and \
- spec_['named_set_item_spec']['item_type'] == 'map':
- map_spec = spec_['named_set_item_spec']['map_item_spec']
- for name in isc.config.spec_name_list(map_spec):
- spec_ = isc.config.find_spec_part(map_spec, name)
- id_str = '%s/%s/%s' % \
- tuple(identifier.split('/')[0:2] + [name])
- isc.cc.data.set(element, id_str, spec_['item_default'])
- else:
+
+ # Note: If there is a named_set type item in the statistics spec
+ # and if there are map type items under it, all of items under the
+ # map type item need to be added. For example, we're assuming that
+ # this method is now adding a counter whose identifier is like
+ # dir1/dir2/dir3/counter1. If both of dir1 and dir2 are named_set
+ # types, and if dir3 is a map type, and if counter1, counter2, and
+ # counter3 are defined as items under dir3 by the statistics spec,
+ # this method would add other two counters:
+ #
+ # dir1/dir2/dir3/counter2
+ # dir1/dir2/dir3/counter3
+ #
+ # Otherwise this method just adds the only counter
+ # dir1/dir2/dir3/counter1.
+
+ # examine spec from the top-level item and know whether
+ # has_named_set, and check whether spec and identifier are correct
+ pidr = ''
+ has_named_set = False
+ for idr in identifier.split('/'):
+ if len(pidr) > 0:
+ idr = pidr + '/' + idr
+ spec_ = isc.config.find_spec_part(spec, idr)
+ if isc.config.spec_part_is_named_set(spec_):
+ has_named_set = True
+ break
+ pidr = idr
+ # add all elements in map type if has_named_set
+ has_map = False
+ if has_named_set:
+ p_idr = identifier.rsplit('/', 1)[0]
+ p_spec = isc.config.find_spec_part(spec, p_idr)
+ if isc.config.spec_part_is_map(p_spec):
+ has_map = True
+ for name in isc.config.spec_name_list(p_spec['map_item_spec']):
+ idr_ = p_idr + '/' + name
+ spc_ = isc.config.find_spec_part(spec, idr_)
+ isc.cc.data.set(element, idr_, spc_['item_default'])
+ # otherwise add a specific element
+ if not has_map:
spec_ = isc.config.find_spec_part(spec, identifier)
isc.cc.data.set(element, identifier, spec_['item_default'])
return isc.cc.data.find(element, identifier)
@@ -141,54 +151,6 @@ def _concat(*args, sep='/'):
"""
return sep.join(args)
-class _Statistics():
- """Statistics data set"""
- # default statistics data
- _data = {}
- # default statistics spec used in case the specfile is omitted when
- # constructing a Counters() object
- _spec = [
- {
- "item_name": "zones",
- "item_type": "named_set",
- "item_optional": False,
- "item_default": {
- "_SERVER_" : {
- "notifyoutv4" : 0,
- "notifyoutv6" : 0
- }
- },
- "item_title": "Zone names",
- "item_description": "Zone names",
- "named_set_item_spec": {
- "item_name": "zonename",
- "item_type": "map",
- "item_optional": False,
- "item_default": {},
- "item_title": "Zone name",
- "item_description": "Zone name",
- "map_item_spec": [
- {
- "item_name": "notifyoutv4",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "IPv4 notifies",
- "item_description": "Number of IPv4 notifies per zone name sent out"
- },
- {
- "item_name": "notifyoutv6",
- "item_type": "integer",
- "item_optional": False,
- "item_default": 0,
- "item_title": "IPv6 notifies",
- "item_description": "Number of IPv6 notifies per zone name sent out"
- }
- ]
- }
- }
- ]
-
class Counters():
"""A class for holding and manipulating all statistics counters
for a module. A Counters object may be created by specifying a spec
@@ -201,87 +163,27 @@ class Counters():
stop_timer() and get() are useful for this. Saved counters can be
cleared by the method clear_all(). Manipulating counters and
timers can be temporarily disabled. If disabled, counter values are
- not changed even if methods to update them are invoked. Including
- per-zone counters, a list of counters which can be handled in the
- class are like the following:
-
- zones/example.com./notifyoutv4
- zones/example.com./notifyoutv6
- zones/example.com./xfrrej
- zones/example.com./xfrreqdone
- zones/example.com./soaoutv4
- zones/example.com./soaoutv6
- zones/example.com./axfrreqv4
- zones/example.com./axfrreqv6
- zones/example.com./ixfrreqv4
- zones/example.com./ixfrreqv6
- zones/example.com./xfrsuccess
- zones/example.com./xfrfail
- zones/example.com./time_to_ixfr
- zones/example.com./time_to_axfr
- ixfr_running
- axfr_running
- socket/unixdomain/open
- socket/unixdomain/openfail
- socket/unixdomain/close
- socket/unixdomain/bindfail
- socket/unixdomain/acceptfail
- socket/unixdomain/accept
- socket/unixdomain/senderr
- socket/unixdomain/recverr
- socket/ipv4/tcp/open
- socket/ipv4/tcp/openfail
- socket/ipv4/tcp/close
- socket/ipv4/tcp/connfail
- socket/ipv4/tcp/conn
- socket/ipv4/tcp/senderr
- socket/ipv4/tcp/recverr
- socket/ipv6/tcp/open
- socket/ipv6/tcp/openfail
- socket/ipv6/tcp/close
- socket/ipv6/tcp/connfail
- socket/ipv6/tcp/conn
- socket/ipv6/tcp/senderr
- socket/ipv6/tcp/recverr
- """
-
- # '_SERVER_' is a special zone name representing an entire
- # count. It doesn't mean a specific zone, but it means an
- # entire count in the server.
- _entire_server = '_SERVER_'
- # zone names are contained under this dirname in the spec file.
- _perzone_prefix = 'zones'
- # default statistics data set
- _statistics = _Statistics()
+ not changed even if methods to update them are invoked."""
- def __init__(self, spec_file_name=None):
+ def __init__(self, spec_file_name):
"""A constructor for the Counters class. A path to the spec file
- can be specified in spec_file_name. Statistics data based on
- statistics spec can be accumulated if spec_file_name is
- specified. If omitted, a default statistics spec is used. The
- default statistics spec is defined in a hidden class named
- _Statistics().
+ can be specified in spec_file_name, which is required. Statistics data
+ based on statistics spec can be accumulated. If an invalid argument
+ including None is specified, ModuleSpecError might be raised.
"""
self._zones_item_list = []
self._start_time = {}
self._disabled = False
self._rlock = threading.RLock()
- if not spec_file_name: return
- # change the default statistics spec
- self._statistics._spec = \
+ self._statistics_data = {}
+ self._statistics_spec = \
isc.config.module_spec_from_file(spec_file_name).\
get_statistics_spec()
- if self._perzone_prefix in \
- isc.config.spec_name_list(self._statistics._spec):
- self._zones_item_list = isc.config.spec_name_list(
- isc.config.find_spec_part(
- self._statistics._spec, self._perzone_prefix)\
- ['named_set_item_spec']['map_item_spec'])
def clear_all(self):
"""clears all statistics data"""
with self._rlock:
- self._statistics._data = {}
+ self._statistics_data = {}
def disable(self):
"""disables incrementing/decrementing counters"""
@@ -301,8 +203,8 @@ class Counters():
identifier = _concat(*args)
with self._rlock:
if self._disabled: return
- _inc_counter(self._statistics._data,
- self._statistics._spec,
+ _inc_counter(self._statistics_data,
+ self._statistics_spec,
identifier, step)
def inc(self, *args):
@@ -322,12 +224,14 @@ class Counters():
of the specified counter. isc.cc.data.DataNotFoundError is
raised when the counter doesn't have a number yet."""
identifier = _concat(*args)
- return _get_counter(self._statistics._data, identifier)
+ return _get_counter(self._statistics_data, identifier)
def start_timer(self, *args):
"""Starts a timer which is identified by args and keeps it
running until stop_timer() is called. It acquires a lock to
- support multi-threaded use."""
+ support multi-threaded use. If the specified timer is already
+ started but not yet stopped, the last start time is
+ overwritten."""
identifier = _concat(*args)
with self._rlock:
if self._disabled: return
@@ -351,8 +255,8 @@ class Counters():
# set the end time
_stop_timer(
start_time,
- self._statistics._data,
- self._statistics._spec,
+ self._statistics_data,
+ self._statistics_spec,
identifier)
# A datetime value of once used timer should be deleted
# for a future use.
@@ -369,33 +273,9 @@ class Counters():
del branch_map[leaf]
def get_statistics(self):
- """Calculates an entire server's counts, and returns statistics
- data in a format to send out to the stats module, including each
- counter. If nothing is counted yet, then it returns an empty
- dictionary."""
+ """Returns statistics data in a format to send out to the
+ stats module, including each counter. If nothing is counted
+ yet, then it returns an empty dictionary."""
# entire copy
- statistics_data = self._statistics._data.copy()
- # If there is no 'zones' found in statistics_data,
- # i.e. statistics_data contains no per-zone counter, it just
- # returns statistics_data because calculating total counts
- # across the zone names isn't necessary.
- if self._perzone_prefix not in statistics_data:
- return statistics_data
- zones = statistics_data[self._perzone_prefix]
- # Start calculation for '_SERVER_' counts
- zones_spec = isc.config.find_spec_part(self._statistics._spec,
- self._perzone_prefix)
- zones_attrs = zones_spec['item_default'][self._entire_server]
- zones_data = {}
- for attr in zones_attrs:
- id_str = '%s/%s' % (self._entire_server, attr)
- sum_ = 0
- for name in zones:
- if attr in zones[name]:
- sum_ += zones[name][attr]
- _set_counter(zones_data, zones_spec, id_str, sum_)
- # insert entire-server counts
- statistics_data[self._perzone_prefix] = dict(
- statistics_data[self._perzone_prefix],
- **zones_data)
+ statistics_data = self._statistics_data.copy()
return statistics_data
diff --git a/src/lib/python/isc/statistics/dns.py b/src/lib/python/isc/statistics/dns.py
new file mode 100644
index 0000000..2cd5fb6
--- /dev/null
+++ b/src/lib/python/isc/statistics/dns.py
@@ -0,0 +1,166 @@
+# Copyright (C) 2013 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.
+
+"""BIND 10 statistics counters module for DNS
+
+This module handles the statistics counters for BIND 10 modules for a
+DNS-specific purpose. For using the module `counter.py`, first a
+counters object should be created in each module (like b10-xfrin or
+b10-xfrout) after importing this module. A spec file can be specified as
+an argument when creating the counters object:
+
+ from isc.statistics.dns import Counters
+ self.counters = Counters("/path/to/xfrout/xfrout.spec")
+
+The first argument of Counters() can be specified, which is the location
+of the specification file. If Counters is constructed this way,
+statistics counters can be accessed from each module. For example, in
+case that the item `xfrreqdone` is defined in statistics_spec in
+xfrout.spec, the following methods are callable. Since these methods
+require the string of the zone name in the first argument, if we have
+the following code in b10-xfrout:
+
+ self.counters.inc('zones', zone_name, 'xfrreqdone')
+
+then the counter for xfrreqdone corresponding to zone_name is
+incremented. For getting the current number of this counter, we can use
+the following code:
+
+ number = self.counters.get('zones', zone_name, 'xfrreqdone')
+
+then the current count is obtained and set in the variable
+`number`. Such a getter method would be mainly used for unit-testing.
+As other example, for the item `axfr_running`, the decrementer method is
+also callable. This method is used for decrementing a counter. For the
+item `axfr_running`, an argument like zone name is not required:
+
+ self.counters.dec('axfr_running')
+
+These methods are effective in other modules. For example, in case that
+this module `counters.py` is once imported in a main module such as
+b10-xfrout, then for the item `notifyoutv4`, the `inc()` method can be
+invoked in another module such as notify_out.py, which is firstly
+imported in the main module.
+
+ self.counters.inc('zones', zone_name, 'notifyoutv4')
+
+In this example this is for incrementing the counter of the item
+`notifyoutv4`. Thus, such statement can be also written in another
+library like isc.notify.notify_out. If this module `counter.py` isn't
+imported in the main module but imported in such a library module as
+isc.notify.notify_out, in this example, empty methods would be invoked,
+which is directly defined in `counter.py`.
+
+This module basically inherits isc.statistics.counters. Also see
+documentation for isc.statistics.counters for details."""
+
+import isc.config
+from isc.statistics import counters
+
+class Counters(counters.Counters):
+ """A list of counters which can be handled in the class are like
+ the following. Also see documentation for
+ isc.statistics.counters.Counters for details.
+
+ zones/IN/example.com./notifyoutv4
+ zones/IN/example.com./notifyoutv6
+ zones/IN/example.com./xfrrej
+ zones/IN/example.com./xfrreqdone
+ zones/IN/example.com./soaoutv4
+ zones/IN/example.com./soaoutv6
+ zones/IN/example.com./axfrreqv4
+ zones/IN/example.com./axfrreqv6
+ zones/IN/example.com./ixfrreqv4
+ zones/IN/example.com./ixfrreqv6
+ zones/IN/example.com./xfrsuccess
+ zones/IN/example.com./xfrfail
+ zones/IN/example.com./last_ixfr_duration
+ zones/IN/example.com./last_axfr_duration
+ ixfr_running
+ axfr_running
+ socket/unixdomain/open
+ socket/unixdomain/openfail
+ socket/unixdomain/close
+ socket/unixdomain/bindfail
+ socket/unixdomain/acceptfail
+ socket/unixdomain/accept
+ socket/unixdomain/senderr
+ socket/unixdomain/recverr
+ socket/ipv4/tcp/open
+ socket/ipv4/tcp/openfail
+ socket/ipv4/tcp/close
+ socket/ipv4/tcp/connfail
+ socket/ipv4/tcp/conn
+ socket/ipv4/tcp/senderr
+ socket/ipv4/tcp/recverr
+ socket/ipv6/tcp/open
+ socket/ipv6/tcp/openfail
+ socket/ipv6/tcp/close
+ socket/ipv6/tcp/connfail
+ socket/ipv6/tcp/conn
+ socket/ipv6/tcp/senderr
+ socket/ipv6/tcp/recverr
+ """
+ # '_SERVER_' is a special zone name representing an entire
+ # count. It doesn't mean a specific zone, but it means an
+ # entire count in the server.
+ _entire_server = '_SERVER_'
+ # zone names are contained under this dirname in the spec file.
+ _perzone_prefix = 'zones'
+
+ def __init__(self, spec_file_name):
+ """If the item `zones` is defined in the spec file, it obtains a
+ list of counter names under it when initiating. For behaviors
+ other than this, see documentation for
+ isc.statistics.counters.Counters.__init__()"""
+ counters.Counters.__init__(self, spec_file_name)
+ if self._perzone_prefix in \
+ isc.config.spec_name_list(self._statistics_spec):
+ self._zones_item_list = isc.config.spec_name_list(
+ isc.config.find_spec_part(
+ self._statistics_spec,
+ '%s/%s/%s' % (self._perzone_prefix,
+ '_CLASS_', self._entire_server)))
+
+ def get_statistics(self):
+ """Calculates an entire server's counts, and returns statistics
+ data in a format to send out to the stats module, including each
+ counter. If nothing is counted yet, then it returns an empty
+ dictionary."""
+ # entire copy
+ statistics_data = self._statistics_data.copy()
+ # If there is no 'zones' found in statistics_data,
+ # i.e. statistics_data contains no per-zone counter, it just
+ # returns statistics_data because calculating total counts
+ # across the zone names isn't necessary.
+ if self._perzone_prefix not in statistics_data:
+ return statistics_data
+ zones = statistics_data[self._perzone_prefix]
+ # Start calculation for '_SERVER_' counts
+ zones_spec = isc.config.find_spec_part(self._statistics_spec,
+ self._perzone_prefix)
+ zones_data = {}
+ for cls in zones.keys():
+ for zone in zones[cls].keys():
+ for (attr, val) in zones[cls][zone].items():
+ id_str1 = '%s/%s/%s' % (cls, zone, attr)
+ id_str2 = '%s/%s/%s' % (cls, self._entire_server, attr)
+ counters._set_counter(zones_data, zones_spec, id_str1, val)
+ counters._inc_counter(zones_data, zones_spec, id_str2, val)
+ # insert entire-server counts
+ statistics_data[self._perzone_prefix] = dict(
+ statistics_data[self._perzone_prefix],
+ **zones_data)
+ return statistics_data
diff --git a/src/lib/python/isc/statistics/tests/Makefile.am b/src/lib/python/isc/statistics/tests/Makefile.am
index 8dcc296..0c02290 100644
--- a/src/lib/python/isc/statistics/tests/Makefile.am
+++ b/src/lib/python/isc/statistics/tests/Makefile.am
@@ -1,5 +1,5 @@
PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
-PYTESTS = counters_test.py
+PYTESTS = counters_test.py dns_test.py
EXTRA_DIST = $(PYTESTS)
EXTRA_DIST += testdata/test_spec1.spec
EXTRA_DIST += testdata/test_spec2.spec
@@ -9,7 +9,7 @@ EXTRA_DIST += testdata/test_spec3.spec
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
else
# Some systems need the ds path even if not all paths are necessary
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs
diff --git a/src/lib/python/isc/statistics/tests/counters_test.py b/src/lib/python/isc/statistics/tests/counters_test.py
index 395a959..9d61ba3 100644
--- a/src/lib/python/isc/statistics/tests/counters_test.py
+++ b/src/lib/python/isc/statistics/tests/counters_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2012 Internet Systems Consortium.
+# Copyright (C) 2012-2013 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,7 +13,7 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-'''Tests for isc.statistics.counter'''
+'''Tests for isc.statistics.counters'''
import unittest
import threading
@@ -22,7 +22,6 @@ import os
import imp
import isc.config
-TEST_ZONE_NAME_STR = "example.com."
TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
from isc.statistics import counters
@@ -50,12 +49,8 @@ class TestBasicMethods(unittest.TestCase):
TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec1.spec'
def setUp(self):
- imp.reload(counters)
self.counters = counters.Counters(self.TEST_SPECFILE_LOCATION)
- def tearDown(self):
- self.counters.clear_all()
-
def test_clear_counters(self):
self.assertRaises(isc.cc.data.DataNotFoundError,
self.counters.get, 'counter')
@@ -132,15 +127,15 @@ class TestBasicMethods(unittest.TestCase):
start_functor(concurrency, number, self.counters.inc,
counter_name)
counters._stop_timer(start_time,
- self.counters._statistics._data,
- self.counters._statistics._spec,
+ self.counters._statistics_data,
+ self.counters._statistics_spec,
timer_name)
self.assertEqual(
- counters._get_counter(self.counters._statistics._data,
+ counters._get_counter(self.counters._statistics_data,
counter_name),
concurrency * number)
self.assertGreaterEqual(
- counters._get_counter(self.counters._statistics._data,
+ counters._get_counter(self.counters._statistics_data,
timer_name), 0.0)
def test_concat(self):
@@ -159,17 +154,19 @@ class TestBasicMethods(unittest.TestCase):
b = a + ({},)
self.assertRaises(TypeError, counters._concat, *b)
+ def test_none_of_arg_of_counters(self):
+ """Test Counters raises ModuleSpecError when specifying not valid
+ argument"""
+ self.assertRaises(isc.config.module_spec.ModuleSpecError,
+ counters.Counters, None)
+ self.assertRaises(isc.config.module_spec.ModuleSpecError,
+ counters.Counters, '/foo/bar')
+
class BaseTestCounters():
def setUp(self):
- imp.reload(counters)
self._statistics_data = {}
self.counters = counters.Counters(self.TEST_SPECFILE_LOCATION)
- self._entire_server = self.counters._entire_server
- self._perzone_prefix = self.counters._perzone_prefix
-
- def tearDown(self):
- self.counters.clear_all()
def check_get_statistics(self):
"""Checks no differences between the value returned from
@@ -189,106 +186,10 @@ class BaseTestCounters():
else:
self.assertTrue(isc.config.ModuleSpec(
{'module_name': 'Foo',
- 'statistics': self.counters._statistics._spec}
+ 'statistics': self.counters._statistics_spec}
).validate_statistics(
False, self._statistics_data))
- def test_perzone_counters(self):
- # for per-zone counters
- for name in self.counters._zones_item_list:
- args = (self._perzone_prefix, TEST_ZONE_NAME_STR, name)
- if name.find('time_to_') == 0:
- self.counters.start_timer(*args)
- self.counters.stop_timer(*args)
- self.assertGreaterEqual(self.counters.get(*args), 0.0)
- sec = self.counters.get(*args)
- for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
- isc.cc.data.set(self._statistics_data,
- '%s/%s/%s' % (args[0], zone_str, name), sec)
- # twice exec stopper, then second is not changed
- self.counters.stop_timer(*args)
- self.assertEqual(self.counters.get(*args), sec)
- else:
- self.counters.inc(*args)
- self.assertEqual(self.counters.get(*args), 1)
- # checks disable/enable
- self.counters.disable()
- self.counters.inc(*args)
- self.assertEqual(self.counters.get(*args), 1)
- self.counters.enable()
- self.counters.inc(*args)
- self.assertEqual(self.counters.get(*args), 2)
- for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
- isc.cc.data.set(self._statistics_data,
- '%s/%s/%s' % (args[0], zone_str, name), 2)
- self.check_get_statistics()
-
- def test_xfrrunning_counters(self):
- # for counters of xfer running
- _suffix = 'xfr_running'
- _xfrrunning_names = \
- isc.config.spec_name_list(self.counters._statistics._spec,
- "", True)
- for name in _xfrrunning_names:
- if name.find(_suffix) != 1: continue
- args = name.split('/')
- self.counters.inc(*args)
- self.assertEqual(self.counters.get(*args), 1)
- self.counters.dec(*args)
- self.assertEqual(self.counters.get(*args), 0)
- # checks disable/enable
- self.counters.disable()
- self.counters.inc(*args)
- self.assertEqual(self.counters.get(*args), 0)
- self.counters.enable()
- self.counters.inc(*args)
- self.assertEqual(self.counters.get(*args), 1)
- self.counters.disable()
- self.counters.dec(*args)
- self.assertEqual(self.counters.get(*args), 1)
- self.counters.enable()
- self.counters.dec(*args)
- self.assertEqual(self.counters.get(*args), 0)
- self._statistics_data[name] = 0
- self.check_get_statistics()
-
- def test_socket_counters(self):
- # for ipsocket/unixsocket counters
- _prefix = 'socket/'
- _socket_names = \
- isc.config.spec_name_list(self.counters._statistics._spec,
- "", True)
- for name in _socket_names:
- if name.find(_prefix) != 0: continue
- args = name.split('/')
- self.counters.inc(*args)
- self.assertEqual(self.counters.get(*args), 1)
- # checks disable/enable
- self.counters.disable()
- self.counters.inc(*args)
- self.assertEqual(self.counters.get(*args), 1)
- self.counters.enable()
- self.counters.inc(*args)
- self.assertEqual(self.counters.get(*args), 2)
- isc.cc.data.set(
- self._statistics_data, '/'.join(args), 2)
- self.check_get_statistics()
-
- def test_perzone_zero_counters(self):
- # setting all counters to zero
- for name in self.counters._zones_item_list:
- args = (self._perzone_prefix, TEST_ZONE_NAME_STR, name)
- if name.find('time_to_') == 0:
- zero = 0.0
- else:
- zero = 0
- # set zero
- self.counters._incdec(*args, step=zero)
- for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
- isc.cc.data.set(self._statistics_data,
- '%s/%s/%s' % (args[0], zone_str, name), zero)
- self.check_get_statistics()
-
def test_undefined_item(self):
# test DataNotFoundError raising when specifying item defined
# in the specfile
@@ -302,130 +203,27 @@ class BaseTestCounters():
self.assertRaises(isc.cc.data.DataNotFoundError,
self.counters.get, '__undefined__')
-class TestCounters0(unittest.TestCase, BaseTestCounters):
- TEST_SPECFILE_LOCATION = None
- def setUp(self):
- BaseTestCounters.setUp(self)
- def tearDown(self):
- BaseTestCounters.tearDown(self)
-
class TestCounters1(unittest.TestCase, BaseTestCounters):
TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec1.spec'
def setUp(self):
BaseTestCounters.setUp(self)
- def tearDown(self):
- BaseTestCounters.tearDown(self)
-
-class TestCounters2(unittest.TestCase, BaseTestCounters):
- TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec2.spec'
- def setUp(self):
- BaseTestCounters.setUp(self)
- def tearDown(self):
- BaseTestCounters.tearDown(self)
-
-class TestCounters3(unittest.TestCase, BaseTestCounters):
- TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec3.spec'
- @classmethod
- def setUpClass(cls):
- imp.reload(counters)
- def setUp(self):
- BaseTestCounters.setUp(self)
- def tearDown(self):
- BaseTestCounters.tearDown(self)
-
-class BaseDummyModule():
- """A base dummy class"""
- TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec2.spec'
- def __init__(self):
- self.counters = counters.Counters(self.TEST_SPECFILE_LOCATION)
-
- def get_counters(self):
- return self.counters.get_statistics()
-
- def clear_counters(self):
- self.counters.clear_all()
-
-class DummyNotifyOut(BaseDummyModule):
- """A dummy class equivalent to notify.notify_out.NotifyOut"""
- def __init__(self):
- self.counters = counters.Counters()
-
- def inc_counters(self):
- """increments counters"""
- self.counters.inc('zones', TEST_ZONE_NAME_STR, 'notifyoutv4')
- self.counters.inc('zones', TEST_ZONE_NAME_STR, 'notifyoutv6')
-
-class DummyXfroutSession(BaseDummyModule):
- """A dummy class equivalent to XfroutSession in b10-xfrout"""
- def inc_counters(self):
- """increments counters"""
- self.counters.inc('zones', TEST_ZONE_NAME_STR, 'xfrreqdone')
- self.counters.inc('zones', TEST_ZONE_NAME_STR, 'xfrrej')
- self.counters.inc('axfr_running')
- self.counters.inc('ixfr_running')
- self.counters.dec('axfr_running')
- self.counters.dec('ixfr_running')
-
-class DummyUnixSockServer(BaseDummyModule):
- """A dummy class equivalent to UnixSockServer in b10-xfrout"""
- def inc_counters(self):
- """increments counters"""
- self.counters.inc('socket', 'unixdomain', 'open')
- self.counters.inc('socket', 'unixdomain', 'close')
-
-class DummyXfroutServer(BaseDummyModule):
- """A dummy class equivalent to XfroutServer in b10-xfrout"""
- def __init__(self):
- super().__init__()
- self.xfrout_sess = DummyXfroutSession()
- self.unix_socket_server = DummyUnixSockServer()
- self.notifier = DummyNotifyOut()
-
- def inc_counters(self):
- self.xfrout_sess.inc_counters()
- self.unix_socket_server.inc_counters()
- self.notifier.inc_counters()
-
-class TestDummyNotifyOut(unittest.TestCase):
- """Tests counters are incremented in which the spec file is not
- loaded"""
- def setUp(self):
- imp.reload(counters)
- self.notifier = DummyNotifyOut()
- self.notifier.inc_counters()
-
- def tearDown(self):
- self.notifier.clear_counters()
def test_counters(self):
- self.assertEqual(
- {'zones': {'_SERVER_': {'notifyoutv4': 1, 'notifyoutv6': 1},
- TEST_ZONE_NAME_STR: {'notifyoutv4': 1, 'notifyoutv6': 1}}},
- self.notifier.get_counters())
-
-class TestDummyXfroutServer(unittest.TestCase):
- """Tests counters are incremented or decremented in which the same
- spec file is multiply loaded in each child class"""
- def setUp(self):
- imp.reload(counters)
- self.xfrout_server = DummyXfroutServer()
- self.xfrout_server.inc_counters()
-
- def tearDown(self):
- self.xfrout_server.clear_counters()
-
- def test_counters(self):
- self.assertEqual(
- {'axfr_running': 0, 'ixfr_running': 0,
- 'socket': {'unixdomain': {'open': 1, 'close': 1}},
- 'zones': {'_SERVER_': {'notifyoutv4': 1,
- 'notifyoutv6': 1,
- 'xfrrej': 1, 'xfrreqdone': 1},
- TEST_ZONE_NAME_STR: {'notifyoutv4': 1,
- 'notifyoutv6': 1,
- 'xfrrej': 1,
- 'xfrreqdone': 1}}},
- self.xfrout_server.get_counters())
+ spec = isc.config.module_spec_from_file(self.TEST_SPECFILE_LOCATION)
+ self.assertEqual(spec.get_statistics_spec(),
+ self.counters._statistics_spec)
+ for name in isc.config.spec_name_list(self.counters._statistics_spec):
+ self.counters.inc(name)
+ self.assertEqual(self.counters.get(name), 1)
+ # checks disable/enable
+ self.counters.disable()
+ self.counters.inc(name)
+ self.assertEqual(self.counters.get(name), 1)
+ self.counters.enable()
+ self.counters.inc(name)
+ self.assertEqual(self.counters.get(name), 2)
+ self._statistics_data = {'counter':2, 'seconds': 2.0}
+ self.check_get_statistics()
if __name__== "__main__":
unittest.main()
diff --git a/src/lib/python/isc/statistics/tests/dns_test.py b/src/lib/python/isc/statistics/tests/dns_test.py
new file mode 100644
index 0000000..1ebe169
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/dns_test.py
@@ -0,0 +1,224 @@
+# Copyright (C) 2013 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.
+
+'''Tests for isc.statistics.dns'''
+
+import unittest
+import os
+import imp
+import isc.config
+import counters_test
+
+TEST_ZONE_NAME_STR = "example.com."
+TEST_ZONE_CLASS_STR = "IN"
+TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
+
+from isc.statistics import dns
+
+class BaseTestCounters(counters_test.BaseTestCounters):
+
+ def setUp(self):
+ self._statistics_data = {}
+ self.counters = dns.Counters(self.TEST_SPECFILE_LOCATION)
+ self._entire_server = self.counters._entire_server
+ self._perzone_prefix = self.counters._perzone_prefix
+
+ def test_perzone_counters(self):
+ # for per-zone counters
+ for name in self.counters._zones_item_list:
+ args = (self._perzone_prefix, TEST_ZONE_CLASS_STR,
+ TEST_ZONE_NAME_STR, name)
+ if name.find('last_') == 0 and name.endswith('_duration'):
+ self.counters.start_timer(*args)
+ self.counters.stop_timer(*args)
+ self.assertGreaterEqual(self.counters.get(*args), 0.0)
+ sec = self.counters.get(*args)
+ for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
+ isc.cc.data.set(self._statistics_data,
+ '%s/%s/%s/%s' % (args[0], args[1],
+ zone_str, name), sec)
+ # twice exec stopper, then second is not changed
+ self.counters.stop_timer(*args)
+ self.assertEqual(self.counters.get(*args), sec)
+ else:
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ # checks disable/enable
+ self.counters.disable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ self.counters.enable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 2)
+ for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
+ isc.cc.data.set(self._statistics_data,
+ '%s/%s/%s/%s' % (args[0], args[1],
+ zone_str, name), 2)
+ self.check_get_statistics()
+
+ def test_xfrrunning_counters(self):
+ # for counters of xfer running
+ _suffix = 'xfr_running'
+ _xfrrunning_names = \
+ isc.config.spec_name_list(self.counters._statistics_spec,
+ "", True)
+ for name in _xfrrunning_names:
+ if name.find(_suffix) != 1: continue
+ args = name.split('/')
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ self.counters.dec(*args)
+ self.assertEqual(self.counters.get(*args), 0)
+ # checks disable/enable
+ self.counters.disable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 0)
+ self.counters.enable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ self.counters.disable()
+ self.counters.dec(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ self.counters.enable()
+ self.counters.dec(*args)
+ self.assertEqual(self.counters.get(*args), 0)
+ self._statistics_data[name] = 0
+ self.check_get_statistics()
+
+ def test_socket_counters(self):
+ # for ipsocket/unixsocket counters
+ _prefix = 'socket/'
+ _socket_names = \
+ isc.config.spec_name_list(self.counters._statistics_spec,
+ "", True)
+ for name in _socket_names:
+ if name.find(_prefix) != 0: continue
+ args = name.split('/')
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ # checks disable/enable
+ self.counters.disable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ self.counters.enable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 2)
+ isc.cc.data.set(
+ self._statistics_data, '/'.join(args), 2)
+ self.check_get_statistics()
+
+ def test_perzone_zero_counters(self):
+ # setting all counters to zero
+ for name in self.counters._zones_item_list:
+ args = (self._perzone_prefix, TEST_ZONE_CLASS_STR,
+ TEST_ZONE_NAME_STR, name)
+ if name.find('last_') == 0 and name.endswith('_duration'):
+ zero = 0.0
+ else:
+ zero = 0
+ # set zero
+ self.counters._incdec(*args, step=zero)
+ for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
+ isc.cc.data.set(self._statistics_data,
+ '%s/%s/%s/%s' % (args[0], args[1],
+ zone_str, name), zero)
+ self.check_get_statistics()
+
+
+class TestCounters2(unittest.TestCase, BaseTestCounters):
+ TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec2.spec'
+ def setUp(self):
+ BaseTestCounters.setUp(self)
+
+class TestCounters3(unittest.TestCase, BaseTestCounters):
+ TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec3.spec'
+ @classmethod
+ def setUp(self):
+ BaseTestCounters.setUp(self)
+
+class BaseDummyModule():
+ """A base dummy class"""
+ TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec2.spec'
+ def __init__(self, counters):
+ self.counters = counters
+
+ def get_counters(self):
+ return self.counters.get_statistics()
+
+class DummyNotifyOut(BaseDummyModule):
+ """A dummy class equivalent to notify.notify_out.NotifyOut"""
+ def inc_counters(self):
+ """increments counters"""
+ self.counters.inc('zones', TEST_ZONE_CLASS_STR,
+ TEST_ZONE_NAME_STR, 'notifyoutv4')
+ self.counters.inc('zones', TEST_ZONE_CLASS_STR,
+ TEST_ZONE_NAME_STR, 'notifyoutv6')
+
+class DummyXfroutSession(BaseDummyModule):
+ """A dummy class equivalent to XfroutSession in b10-xfrout"""
+ def inc_counters(self):
+ """increments counters"""
+ self.counters.inc('zones', TEST_ZONE_CLASS_STR,
+ TEST_ZONE_NAME_STR, 'xfrreqdone')
+ self.counters.inc('zones', TEST_ZONE_CLASS_STR,
+ TEST_ZONE_NAME_STR, 'xfrrej')
+ self.counters.inc('axfr_running')
+ self.counters.inc('ixfr_running')
+ self.counters.dec('axfr_running')
+ self.counters.dec('ixfr_running')
+
+class DummyUnixSockServer(BaseDummyModule):
+ """A dummy class equivalent to UnixSockServer in b10-xfrout"""
+ def inc_counters(self):
+ """increments counters"""
+ self.counters.inc('socket', 'unixdomain', 'open')
+ self.counters.inc('socket', 'unixdomain', 'close')
+
+class DummyXfroutServer(BaseDummyModule):
+ """A dummy class equivalent to XfroutServer in b10-xfrout"""
+ def __init__(self):
+ self.counters = dns.Counters(self.TEST_SPECFILE_LOCATION)
+ self.xfrout_sess = DummyXfroutSession(self.counters)
+ self.unix_socket_server = DummyUnixSockServer(self.counters)
+ self.notifier = DummyNotifyOut(self.counters)
+
+ def inc_counters(self):
+ self.xfrout_sess.inc_counters()
+ self.unix_socket_server.inc_counters()
+ self.notifier.inc_counters()
+
+class TestDummyXfroutServer(unittest.TestCase):
+ """Tests counters are incremented or decremented in which the same
+ spec file is multiply loaded in each child class"""
+ def setUp(self):
+ self.xfrout_server = DummyXfroutServer()
+ self.xfrout_server.inc_counters()
+
+ def test_counters(self):
+ self.assertEqual(
+ {'axfr_running': 0, 'ixfr_running': 0,
+ 'socket': {'unixdomain': {'open': 1, 'close': 1}},
+ 'zones': {TEST_ZONE_CLASS_STR: {
+ '_SERVER_': {'notifyoutv4': 1,
+ 'notifyoutv6': 1,
+ 'xfrrej': 1, 'xfrreqdone': 1},
+ TEST_ZONE_NAME_STR: {'notifyoutv4': 1,
+ 'notifyoutv6': 1,
+ 'xfrrej': 1,
+ 'xfrreqdone': 1}}}},
+ self.xfrout_server.get_counters())
+
+if __name__== "__main__":
+ unittest.main()
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec
index 11b8706..422fc0a 100644
--- a/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec
@@ -9,56 +9,66 @@
"item_type": "named_set",
"item_optional": false,
"item_default": {
- "_SERVER_" : {
- "notifyoutv4" : 0,
- "notifyoutv6" : 0,
- "xfrrej" : 0,
- "xfrreqdone" : 0
+ "IN" : {
+ "_SERVER_" : {
+ "notifyoutv4" : 0,
+ "notifyoutv6" : 0,
+ "xfrrej" : 0,
+ "xfrreqdone" : 0
+ }
}
},
"item_title": "Zone names",
"item_description": "Zone names for Xfrout statistics",
"named_set_item_spec": {
- "item_name": "zonename",
- "item_type": "map",
+ "item_name": "classname",
+ "item_type": "named_set",
"item_optional": false,
"item_default": {},
- "item_title": "Zone name",
- "item_description": "Zone name for Xfrout statistics",
- "map_item_spec": [
- {
- "item_name": "notifyoutv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IPv4 notifies",
- "item_description": "Number of IPv4 notifies per zone name sent out from Xfrout"
- },
- {
- "item_name": "notifyoutv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IPv6 notifies",
- "item_description": "Number of IPv6 notifies per zone name sent out from Xfrout"
- },
- {
- "item_name": "xfrrej",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "XFR rejected requests",
- "item_description": "Number of XFR requests per zone name rejected by Xfrout"
- },
- {
- "item_name": "xfrreqdone",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "Requested zone transfers",
- "item_description": "Number of requested zone transfers completed per zone name"
- }
- ]
+ "item_title": "RR class name",
+ "item_description": "RR class name for Xfrout statistics",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "Zone name for Xfrout statistics",
+ "map_item_spec": [
+ {
+ "item_name": "notifyoutv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IPv4 notifies",
+ "item_description": "Number of IPv4 notifies per zone name sent out from Xfrout"
+ },
+ {
+ "item_name": "notifyoutv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IPv6 notifies",
+ "item_description": "Number of IPv6 notifies per zone name sent out from Xfrout"
+ },
+ {
+ "item_name": "xfrrej",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XFR rejected requests",
+ "item_description": "Number of XFR requests per zone name rejected by Xfrout"
+ },
+ {
+ "item_name": "xfrreqdone",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Requested zone transfers",
+ "item_description": "Number of requested zone transfers completed per zone name"
+ }
+ ]
+ }
}
},
{
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
index c97a09a..f620cad 100644
--- a/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
@@ -10,110 +10,120 @@
"item_type": "named_set",
"item_optional": false,
"item_default": {
- "_SERVER_" : {
- "soaoutv4": 0,
- "soaoutv6": 0,
- "axfrreqv4": 0,
- "axfrreqv6": 0,
- "ixfrreqv4": 0,
- "ixfrreqv6": 0,
- "xfrsuccess": 0,
- "xfrfail": 0,
- "time_to_ixfr": 0.0,
- "time_to_axfr": 0.0
+ "IN" : {
+ "_SERVER_" : {
+ "soaoutv4": 0,
+ "soaoutv6": 0,
+ "axfrreqv4": 0,
+ "axfrreqv6": 0,
+ "ixfrreqv4": 0,
+ "ixfrreqv6": 0,
+ "xfrsuccess": 0,
+ "xfrfail": 0,
+ "last_ixfr_duration": 0.0,
+ "last_axfr_duration": 0.0
+ }
}
},
"item_title": "Zone names",
"item_description": "Zone names for Xfrout statistics",
"named_set_item_spec": {
- "item_name": "zonename",
- "item_type": "map",
+ "item_name": "classname",
+ "item_type": "named_set",
"item_optional": false,
"item_default": {},
- "item_title": "Zone name",
- "item_description": "Zone name for Xfrout statistics",
- "map_item_spec": [
- {
- "item_name": "soaoutv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "SOAOutv4",
- "item_description": "Number of IPv4 SOA queries sent from Xfrin"
- },
- {
- "item_name": "soaoutv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "SOAOutv6",
- "item_description": "Number of IPv6 SOA queries sent from Xfrin"
- },
- {
- "item_name": "axfrreqv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "AXFRReqv4",
- "item_description": "Number of IPv4 AXFR requests sent from Xfrin"
- },
- {
- "item_name": "axfrreqv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "AXFRReqv6",
- "item_description": "Number of IPv6 AXFR requests sent from Xfrin"
- },
- {
- "item_name": "ixfrreqv4",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IXFRReqv4",
- "item_description": "Number of IPv4 IXFR requests sent from Xfrin"
- },
- {
- "item_name": "ixfrreqv6",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "IXFRReqv6",
- "item_description": "Number of IPv6 IXFR requests sent from Xfrin"
- },
- {
- "item_name": "xfrsuccess",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "XfrSuccess",
- "item_description": "Number of zone transfer requests succeeded"
- },
- {
- "item_name": "xfrfail",
- "item_type": "integer",
- "item_optional": false,
- "item_default": 0,
- "item_title": "XfrFail",
- "item_description": "Number of zone transfer requests failed"
- },
- {
- "item_name": "time_to_ixfr",
- "item_type": "real",
- "item_optional": false,
- "item_default": 0.0,
- "item_title": "Time to IXFR",
- "item_description": "Elapsed time in seconds to do the last IXFR"
- },
- {
- "item_name": "time_to_axfr",
- "item_type": "real",
- "item_optional": false,
- "item_default": 0.0,
- "item_title": "Time to AXFR",
- "item_description": "Elapsed time in seconds to do the last AXFR"
- }
- ]
+ "item_title": "RR class name",
+ "item_description": "RR class name for Xfrout statistics",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "Zone name for Xfrout statistics",
+ "map_item_spec": [
+ {
+ "item_name": "soaoutv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOAOutv4",
+ "item_description": "Number of IPv4 SOA queries sent from Xfrin"
+ },
+ {
+ "item_name": "soaoutv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOAOutv6",
+ "item_description": "Number of IPv6 SOA queries sent from Xfrin"
+ },
+ {
+ "item_name": "axfrreqv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRReqv4",
+ "item_description": "Number of IPv4 AXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "axfrreqv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRReqv6",
+ "item_description": "Number of IPv6 AXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "ixfrreqv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRReqv4",
+ "item_description": "Number of IPv4 IXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "ixfrreqv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRReqv6",
+ "item_description": "Number of IPv6 IXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "xfrsuccess",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XfrSuccess",
+ "item_description": "Number of zone transfer requests succeeded"
+ },
+ {
+ "item_name": "xfrfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XfrFail",
+ "item_description": "Number of zone transfer requests failed"
+ },
+ {
+ "item_name": "last_ixfr_duration",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 0.0,
+ "item_title": "Last IXFR duration",
+ "item_description": "Duration of the last IXFR. 0.0 means no successful IXFR done."
+ },
+ {
+ "item_name": "last_axfr_duration",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 0.0,
+ "item_title": "Last AXFR duration",
+ "item_description": "Duration of the last AXFR. 0.0 means no successful AXFR done."
+ }
+ ]
+ }
}
},
{
diff --git a/src/lib/python/isc/sysinfo/sysinfo.py b/src/lib/python/isc/sysinfo/sysinfo.py
index 099ac89..7bf0669 100644
--- a/src/lib/python/isc/sysinfo/sysinfo.py
+++ b/src/lib/python/isc/sysinfo/sysinfo.py
@@ -415,7 +415,7 @@ class SysInfoFreeBSD(SysInfoFreeBSDOSX):
self._platform_is_smp = True # the value doesn't matter
except subprocess.CalledProcessError:
# if this variable isn't defined we should see this exception.
- # intepret it as an indication of non-SMP kernel.
+ # interpret it as an indication of non-SMP kernel.
self._platform_is_smp = False
except OSError:
pass
diff --git a/src/lib/python/isc/sysinfo/tests/Makefile.am b/src/lib/python/isc/sysinfo/tests/Makefile.am
index 9759c77..c0b3f10 100644
--- a/src/lib/python/isc/sysinfo/tests/Makefile.am
+++ b/src/lib/python/isc/sysinfo/tests/Makefile.am
@@ -5,7 +5,7 @@ EXTRA_DIST = $(PYTESTS)
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/util/Makefile.am b/src/lib/python/isc/util/Makefile.am
index 3eaaa12..eaeedd8 100644
--- a/src/lib/python/isc/util/Makefile.am
+++ b/src/lib/python/isc/util/Makefile.am
@@ -1,6 +1,7 @@
SUBDIRS = . cio tests
-python_PYTHON = __init__.py process.py socketserver_mixin.py file.py
+python_PYTHON = __init__.py process.py socketserver_mixin.py file.py
+python_PYTHON += address_formatter.py
pythondir = $(pyexecdir)/isc/util
diff --git a/src/lib/python/isc/util/address_formatter.py b/src/lib/python/isc/util/address_formatter.py
new file mode 100644
index 0000000..35c1e00
--- /dev/null
+++ b/src/lib/python/isc/util/address_formatter.py
@@ -0,0 +1,82 @@
+# Copyright (C) 2013 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 socket
+
+class AddressFormatter:
+ """
+ A utility class to convert an IP address with a port number to a
+ string.
+
+ It takes a tuple (or list) containing and address string and a port
+ number, and optionally a family.
+
+ If the family is IPv4, the __str__ output will be
+ <address>:<port>
+ If the family is IPv6, the __str__ output will be
+ [<address>]:<port>
+
+ If family is not given, the __str__ method will try to figure it out
+ itself, by checking for the ':' character in the address string.
+
+ This class is designed to delay the conversion until it's explicitly
+ requested, so the conversion doesn't happen if the corresponding log
+ message is suppressed because of its log level (which is often the case
+ for debug messages).
+
+ Note: this optimization comes with the cost of instantiating the
+ formatter object itself. It's not really clear which overhead is
+ heavier, and we may conclude it's actually better to just generate
+ the strings unconditionally. Alternatively, we can make the stored
+ address of this object replaceable so that this object can be reused.
+ Right now this is an open issue.
+
+ See also ClientFormatter in the ddns.logger code, which does something
+ similar but based on other criteria (and optional extra value).
+ """
+ def __init__(self, addr, family=None):
+ self.__addr = addr
+ self.__family = family
+
+ def __addr_v4(self):
+ return self.__addr[0] + ':' + str(self.__addr[1])
+
+ def __addr_v6(self):
+ return '[' + self.__addr[0] + ']:' + str(self.__addr[1])
+
+ def __format_addr(self):
+ # Some basic sanity checks, should we leave this out for efficiency?
+ # (especially strings produce unexpected results)
+ if isinstance(self.__addr, str) or\
+ not hasattr(self.__addr, "__getitem__"):
+ raise ValueError("Address must be a list or tuple")
+
+ if self.__family is not None:
+ if self.__family == socket.AF_INET6:
+ return self.__addr_v6()
+ elif self.__family == socket.AF_INET:
+ return self.__addr_v4()
+ else:
+ raise ValueError("Unknown address family: " +
+ str(self.__family))
+ else:
+ if self.__addr[0].find(':') >= 0:
+ return self.__addr_v6()
+ else:
+ return self.__addr_v4()
+
+ def __str__(self):
+ return self.__format_addr()
+
diff --git a/src/lib/python/isc/util/cio/tests/Makefile.am b/src/lib/python/isc/util/cio/tests/Makefile.am
index 3429009..0330ef6 100644
--- a/src/lib/python/isc/util/cio/tests/Makefile.am
+++ b/src/lib/python/isc/util/cio/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/util/socketserver_mixin.py b/src/lib/python/isc/util/socketserver_mixin.py
index e954fe1..da04b6f 100644
--- a/src/lib/python/isc/util/socketserver_mixin.py
+++ b/src/lib/python/isc/util/socketserver_mixin.py
@@ -39,7 +39,7 @@ class NoPollMixIn:
socketserver.BaseServer or some derived classes of it, and it must
be placed before the corresponding socketserver class. In
addition, the constructor of this mix-in must be called
- explicitely in the derived class. For example, a basic TCP server
+ explicitly in the derived class. For example, a basic TCP server
without the problem of polling is created as follows:
class MyServer(NoPollMixIn, socketserver.TCPServer):
diff --git a/src/lib/python/isc/util/tests/Makefile.am b/src/lib/python/isc/util/tests/Makefile.am
index 3b882b4..4df6947 100644
--- a/src/lib/python/isc/util/tests/Makefile.am
+++ b/src/lib/python/isc/util/tests/Makefile.am
@@ -1,18 +1,19 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = process_test.py socketserver_mixin_test.py file_test.py
+PYTESTS += address_formatter_test.py
EXTRA_DIST = $(PYTESTS)
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
if ENABLE_PYTHON_COVERAGE
- touch $(abs_top_srcdir)/.coverage
+ touch $(abs_top_srcdir)/.coverage
rm -f .coverage
${LN_S} $(abs_top_srcdir)/.coverage .coverage
endif
diff --git a/src/lib/python/isc/util/tests/address_formatter_test.py b/src/lib/python/isc/util/tests/address_formatter_test.py
new file mode 100644
index 0000000..295b7c3
--- /dev/null
+++ b/src/lib/python/isc/util/tests/address_formatter_test.py
@@ -0,0 +1,68 @@
+# Copyright (C) 2013 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 socket
+import unittest
+from isc.util.address_formatter import AddressFormatter
+
+class AddressFormatterTest(unittest.TestCase):
+ def test_v4(self):
+ self.assertEqual("127.0.0.1:123",
+ str(AddressFormatter(("127.0.0.1", 123))))
+ self.assertEqual("127.0.0.1:123",
+ str(AddressFormatter(("127.0.0.1", 123), None)))
+ self.assertEqual("192.0.2.1:1",
+ str(AddressFormatter(("192.0.2.1", 1))))
+
+ def test_v6(self):
+ self.assertEqual("[::1]:123",
+ str(AddressFormatter(("::1", 123))));
+ self.assertEqual("[::1]:123",
+ str(AddressFormatter(("::1", 123), None)))
+ self.assertEqual("[2001:db8::]:1",
+ str(AddressFormatter(("2001:db8::", 1))))
+
+ def test_force_family_good(self):
+ self.assertEqual("127.0.0.1:123",
+ str(AddressFormatter(("127.0.0.1", 123),
+ socket.AF_INET)))
+ self.assertEqual("[::1]:123",
+ str(AddressFormatter(("::1", 123),
+ socket.AF_INET6)))
+
+ def test_force_family_bad(self):
+ """
+ These results are 'bad' as in they don't return the value as
+ specified by our guidelines, since the internal check is skipped if
+ the family is given
+ """
+ self.assertEqual("[127.0.0.1]:123",
+ str(AddressFormatter(("127.0.0.1", 123),
+ socket.AF_INET6)))
+ self.assertEqual("::1:123",
+ str(AddressFormatter(("::1", 123),
+ socket.AF_INET)))
+
+ def test_bad_values(self):
+ self.assertRaises(ValueError, str, AddressFormatter("string"))
+ self.assertRaises(ValueError, str, AddressFormatter(None))
+ self.assertRaises(ValueError, str, AddressFormatter(1))
+ self.assertRaises(ValueError, str, AddressFormatter(("::1", 123), 1))
+ self.assertRaises(ValueError, str, AddressFormatter(("::1", 123), 1))
+
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/src/lib/python/isc/xfrin/diff.py b/src/lib/python/isc/xfrin/diff.py
index 4e06eea..31f5f25 100644
--- a/src/lib/python/isc/xfrin/diff.py
+++ b/src/lib/python/isc/xfrin/diff.py
@@ -31,7 +31,7 @@ from isc.log_messages.libxfrin_messages import *
class NoSuchZone(Exception):
"""
- This is raised if a diff for non-existant zone is being created.
+ This is raised if a diff for non-existent zone is being created.
"""
pass
@@ -52,10 +52,10 @@ class Diff:
"""
The class represents a diff against current state of datasource on
one zone. The usual way of working with it is creating it, then putting
- bunch of changes in and commiting at the end.
+ bunch of changes in and committing at the end.
If you change your mind, you can just stop using the object without
- really commiting it. In that case no changes will happen in the data
+ really committing it. In that case no changes will happen in the data
sounce.
The class works as a kind of a buffer as well, it does not direct
@@ -123,12 +123,12 @@ class Diff:
def __check_committed(self):
"""
- This checks if the diff is already commited or broken. If it is, it
+ This checks if the diff is already committed or broken. If it is, it
raises ValueError. This check is for methods that need to work only on
- yet uncommited diffs.
+ yet uncommitted diffs.
"""
if self.__updater is None:
- raise ValueError("The diff is already commited or it has raised " +
+ raise ValueError("The diff is already committed or it has raised " +
"an exception, you come late")
def __append_with_soa_check(self, buf, operation, rr):
@@ -200,7 +200,7 @@ class Diff:
Schedules addition of an RR into the zone in this diff.
The rr is of isc.dns.RRset type and it must contain only one RR.
- If this is not the case or if the diff was already commited, this
+ If this is not the case or if the diff was already committed, this
raises the ValueError exception.
The rr class must match the one of the datasource client. If
@@ -213,7 +213,7 @@ class Diff:
Schedules deleting an RR from the zone in this diff.
The rr is of isc.dns.RRset type and it must contain only one RR.
- If this is not the case or if the diff was already commited, this
+ If this is not the case or if the diff was already committed, this
raises the ValueError exception.
The rr class must match the one of the datasource client. If
@@ -287,7 +287,7 @@ class Diff:
This is called from time to time automatically, but you can call it
manually if you really want to.
- This raises ValueError if the diff was already commited.
+ This raises ValueError if the diff was already committed.
It also can raise isc.datasrc.Error. If that happens, you should stop
using this object and abort the modification.
@@ -344,7 +344,7 @@ class Diff:
self.__updater.commit()
finally:
# Remove the updater. That will free some resources for one, but
- # mark this object as already commited, so we can check
+ # mark this object as already committed, so we can check
# We delete it even in case the commit failed, as that makes us
# unusable.
@@ -594,6 +594,6 @@ class Diff:
This must not be called after a commit, or it'd throw ValueError.
'''
- # Apply itself will check it is not yet commited.
+ # Apply itself will check it is not yet committed.
self.apply()
return self.__updater.get_rrset_collection()
diff --git a/src/lib/python/isc/xfrin/tests/Makefile.am b/src/lib/python/isc/xfrin/tests/Makefile.am
index 459efc3..89769d0 100644
--- a/src/lib/python/isc/xfrin/tests/Makefile.am
+++ b/src/lib/python/isc/xfrin/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/threads/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/lib/python/isc/xfrin/tests/diff_tests.py b/src/lib/python/isc/xfrin/tests/diff_tests.py
index bb83340..ac04dc1 100644
--- a/src/lib/python/isc/xfrin/tests/diff_tests.py
+++ b/src/lib/python/isc/xfrin/tests/diff_tests.py
@@ -256,7 +256,7 @@ class DiffTest(unittest.TestCase):
"""
Try to add few items into the diff and see they are stored in there.
- Also try passing an rrset that has differnt amount of RRs than 1.
+ Also try passing an rrset that has different amount of RRs than 1.
"""
diff = Diff(self, Name('example.org.'))
self.__data_common(diff, diff.add_data, 'add')
diff --git a/src/lib/resolve/Makefile.am b/src/lib/resolve/Makefile.am
index 096a14d..0016684 100644
--- a/src/lib/resolve/Makefile.am
+++ b/src/lib/resolve/Makefile.am
@@ -47,4 +47,3 @@ if USE_CLANGPP
libb10_resolve_la_CXXFLAGS += -Wno-error
endif
libb10_resolve_la_CPPFLAGS = $(AM_CPPFLAGS)
-
diff --git a/src/lib/resolve/recursive_query.cc b/src/lib/resolve/recursive_query.cc
index 8d4ae58..3c54a78 100644
--- a/src/lib/resolve/recursive_query.cc
+++ b/src/lib/resolve/recursive_query.cc
@@ -245,11 +245,6 @@ private:
// normal query state
- // Update the question that will be sent to the server
- void setQuestion(const Question& new_question) {
- question_ = new_question;
- }
-
// TODO: replace by our wrapper
asio::deadline_timer client_timer;
asio::deadline_timer lookup_timer;
@@ -930,7 +925,7 @@ private:
asio::deadline_timer client_timer;
asio::deadline_timer lookup_timer;
- // Make FowardQuery deletes itself safely. for more information see
+ // Make ForwardQuery deletes itself safely. for more information see
// the comments of outstanding_events in RunningQuery.
size_t outstanding_events_;
diff --git a/src/lib/resolve/tests/recursive_query_unittest.cc b/src/lib/resolve/tests/recursive_query_unittest.cc
index e5e46e1..48e5a31 100644
--- a/src/lib/resolve/tests/recursive_query_unittest.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest.cc
@@ -56,7 +56,6 @@
#include <asiolink/io_service.h>
#include <asiolink/io_message.h>
#include <asiolink/io_error.h>
-#include <asiolink/simple_callback.h>
using isc::UnitTestUtil;
using namespace std;
@@ -333,8 +332,7 @@ protected:
// Set up empty DNS Service
// Set up an IO Service queue without any addresses
void setDNSService() {
- dns_service_.reset(new DNSService(io_service_, callback_.get(), NULL,
- NULL));
+ dns_service_.reset(new DNSService(io_service_, callback_.get(), NULL));
}
// Run a simple server test, on either IPv4 or IPv6, and over either
@@ -478,10 +476,12 @@ protected:
};
private:
- class ASIOCallBack : public SimpleCallback {
+ class ASIOCallBack : public DNSLookup {
public:
ASIOCallBack(RecursiveQueryTest* test_obj) : test_obj_(test_obj) {}
- void operator()(const IOMessage& io_message) const {
+ void operator()(const IOMessage& io_message,
+ isc::dns::MessagePtr, isc::dns::MessagePtr,
+ isc::util::OutputBufferPtr, DNSServer*) const {
test_obj_->callBack(io_message);
}
private:
@@ -971,7 +971,7 @@ TEST_F(RecursiveQueryTest, CachedNS) {
// point and ask NSAS for it. NSAS will in turn ask resolver for NS record
// of the delegation point. We then pick it up from the fake resolver
// and check it is the correct one. This checks the delegation point
- // travels safely trough the whole path there (it would be enough to check
+ // travels safely through the whole path there (it would be enough to check
// it up to NSAS, but replacing NSAS is more complicated, so we just
// include in the test as well for simplicity).
diff --git a/src/lib/resolve/tests/recursive_query_unittest_2.cc b/src/lib/resolve/tests/recursive_query_unittest_2.cc
index 0b38c59..d6019d0 100644
--- a/src/lib/resolve/tests/recursive_query_unittest_2.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest_2.cc
@@ -159,7 +159,7 @@ public:
RecursiveQueryTest2() :
debug_(DEBUG_PRINT),
service_(),
- dns_service_(service_, NULL, NULL, NULL),
+ dns_service_(service_, NULL, NULL),
question_(new Question(Name("www.example.org"), RRClass::IN(), RRType::A())),
last_(NONE),
expected_(NONE),
diff --git a/src/lib/resolve/tests/recursive_query_unittest_3.cc b/src/lib/resolve/tests/recursive_query_unittest_3.cc
index 92ec589..df48740 100644
--- a/src/lib/resolve/tests/recursive_query_unittest_3.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest_3.cc
@@ -141,7 +141,7 @@ public:
/// \brief Constructor
RecursiveQueryTest3() :
service_(),
- dns_service_(service_, NULL, NULL, NULL),
+ dns_service_(service_, NULL, NULL),
question_(new Question(Name("ednsfallback"),
RRClass::IN(), RRType::A())),
last_(NONE),
diff --git a/src/lib/server_common/client.cc b/src/lib/server_common/client.cc
index e6383d6..3cc2a07 100644
--- a/src/lib/server_common/client.cc
+++ b/src/lib/server_common/client.cc
@@ -57,8 +57,14 @@ Client::getRequestSourceIPAddress() const {
std::string
Client::toText() const {
std::stringstream ss;
- ss << impl_->request_.getRemoteEndpoint().getAddress().toText()
- << '#' << impl_->request_.getRemoteEndpoint().getPort();
+ const asiolink::IOAddress& addr =
+ impl_->request_.getRemoteEndpoint().getAddress();
+ if (addr.isV6()) {
+ ss << '[' << addr.toText() << ']';
+ } else {
+ ss << addr.toText();
+ }
+ ss << ':' << impl_->request_.getRemoteEndpoint().getPort();
return (ss.str());
}
diff --git a/src/lib/server_common/client.h b/src/lib/server_common/client.h
index 912e7a6..4e344f0 100644
--- a/src/lib/server_common/client.h
+++ b/src/lib/server_common/client.h
@@ -119,7 +119,7 @@ public:
///
/// (In the initial implementation) the format of the resulting string
/// is as follows:
- /// \code <IP address>#<port>
+ /// \code <IP address>:<port>
/// \endcode
/// The IP address is the textual representation of the client's IP
/// address, which is the source address of the request the client has
diff --git a/src/lib/server_common/portconfig.cc b/src/lib/server_common/portconfig.cc
index b214ef5..6120c7d 100644
--- a/src/lib/server_common/portconfig.cc
+++ b/src/lib/server_common/portconfig.cc
@@ -49,6 +49,9 @@ parseAddresses(isc::data::ConstElementPtr addresses,
"address and port");
}
try {
+ // We create an IOAddress object to just check that
+ // construction passes. It is immediately destroyed.
+ // cppcheck-suppress unusedScopedObject
IOAddress(addr->stringValue());
if (port->intValue() < 0 ||
port->intValue() > 0xffff) {
@@ -117,8 +120,14 @@ installListenAddresses(const AddressList& new_addresses,
try {
LOG_DEBUG(logger, DBG_TRACE_BASIC, SRVCOMM_SET_LISTEN);
BOOST_FOREACH(const AddressPair& addr, new_addresses) {
+ string addr_str;
+ if (addr.first.find(':') != string::npos) {
+ addr_str = "[" + addr.first + "]";
+ } else {
+ addr_str = addr.first;
+ }
LOG_DEBUG(logger, DBG_TRACE_VALUES, SRVCOMM_ADDRESS_VALUE).
- arg(addr.first).arg(addr.second);
+ arg(addr_str).arg(addr.second);
}
setAddresses(service, new_addresses, server_options);
address_store = new_addresses;
@@ -129,7 +138,7 @@ installListenAddresses(const AddressList& new_addresses,
* set successuflly or none of them will be used. whether this
* behavior is user desired, maybe we need revisited it later. And
* if address setting is more smarter, it should check whether some
- * part of the new address already in used to avoid interuption the
+ * part of the new address already in used to avoid interrupting the
* service.
*
* If the address setting still failed, we can live with it, since
diff --git a/src/lib/server_common/server_common_messages.mes b/src/lib/server_common/server_common_messages.mes
index 22ce0f3..ba235c9 100644
--- a/src/lib/server_common/server_common_messages.mes
+++ b/src/lib/server_common/server_common_messages.mes
@@ -72,7 +72,7 @@ which it is running. The server will continue running to allow
reconfiguration, but will not be listening on any address or port until
an administrator does so.
-% SRVCOMM_ADDRESS_VALUE address to set: %1#%2
+% SRVCOMM_ADDRESS_VALUE address to set: %1:%2
Debug message. This lists one address and port value of the set of
addresses we are going to listen on (eg. there will be one log message
per pair). This appears only after SRVCOMM_SET_LISTEN, but might
diff --git a/src/lib/server_common/tests/client_unittest.cc b/src/lib/server_common/tests/client_unittest.cc
index 08c24ba..14f6fbc 100644
--- a/src/lib/server_common/tests/client_unittest.cc
+++ b/src/lib/server_common/tests/client_unittest.cc
@@ -92,8 +92,8 @@ TEST_F(ClientTest, constructIPv6) {
}
TEST_F(ClientTest, toText) {
- EXPECT_EQ("192.0.2.1#53214", client4->toText());
- EXPECT_EQ("2001:db8::1#53216", client6->toText());
+ EXPECT_EQ("192.0.2.1:53214", client4->toText());
+ EXPECT_EQ("[2001:db8::1]:53216", client6->toText());
}
// test operator<<. We simply confirm it appends the result of toText().
diff --git a/src/lib/server_common/tests/socket_requestor_test.cc b/src/lib/server_common/tests/socket_requestor_test.cc
index 8da545e..b5f6dfc 100644
--- a/src/lib/server_common/tests/socket_requestor_test.cc
+++ b/src/lib/server_common/tests/socket_requestor_test.cc
@@ -243,7 +243,7 @@ TEST_F(SocketRequestorTest, testBadRequestAnswers) {
const std::string max_len(sizeof(sock_un.sun_path) - 1, 'x');
addAnswer("foo", max_len);
// The failure should NOT contain 'too long'
- // (explicitly checking for existance of nonexistence of 'too long',
+ // (explicitly checking for existence of nonexistence of 'too long',
// as opposed to the actual error, since 'too long' is a value we set).
try {
doRequest();
diff --git a/src/lib/statistics/counter.h b/src/lib/statistics/counter.h
index b0d31e9..eae4a73 100644
--- a/src/lib/statistics/counter.h
+++ b/src/lib/statistics/counter.h
@@ -22,13 +22,15 @@
#include <vector>
+#include <stdint.h>
+
namespace isc {
namespace statistics {
class Counter : boost::noncopyable {
public:
typedef unsigned int Type;
- typedef unsigned int Value;
+ typedef uint64_t Value;
private:
std::vector<Counter::Value> counters_;
@@ -55,7 +57,7 @@ public:
/// \param type %Counter item to increment
///
/// \throw isc::OutOfRange \a type is invalid
- void inc(const Counter::Type type) {
+ void inc(const Counter::Type& type) {
if (type >= counters_.size()) {
isc_throw(isc::OutOfRange, "Counter type is out of range");
}
@@ -68,7 +70,7 @@ public:
/// \param type %Counter item to get the value of
///
/// \throw isc::OutOfRange \a type is invalid
- const Counter::Value& get(const Counter::Type type) const {
+ const Counter::Value& get(const Counter::Type& type) const {
if (type >= counters_.size()) {
isc_throw(isc::OutOfRange, "Counter type is out of range");
}
diff --git a/src/lib/statistics/tests/counter_unittest.cc b/src/lib/statistics/tests/counter_unittest.cc
index e0d29ac..b258d9e 100644
--- a/src/lib/statistics/tests/counter_unittest.cc
+++ b/src/lib/statistics/tests/counter_unittest.cc
@@ -73,6 +73,11 @@ TEST_F(CounterTest, incrementCounterItem) {
EXPECT_EQ(counter.get(ITEM1), 2);
EXPECT_EQ(counter.get(ITEM2), 4);
EXPECT_EQ(counter.get(ITEM3), 6);
+
+ for (long long int i = 0; i < 4294967306LL; i++) {
+ counter.inc(ITEM1);
+ }
+ EXPECT_EQ(counter.get(ITEM1), 4294967308LL); // 4294967306 + 2
}
TEST_F(CounterTest, invalidCounterItem) {
diff --git a/src/lib/testutils/mockups.h b/src/lib/testutils/mockups.h
index 8ba2287..00c2dcd 100644
--- a/src/lib/testutils/mockups.h
+++ b/src/lib/testutils/mockups.h
@@ -40,15 +40,16 @@ public:
MockSession() :
// by default we return a simple "success" message.
msg_(isc::data::Element::fromJSON("{\"result\": [0, \"SUCCESS\"]}")),
- send_ok_(true), receive_ok_(true)
+ send_ok_(true), receive_ok_(true), answer_wanted_(false)
{}
virtual void establish(const char*) {}
virtual void disconnect() {}
- virtual int group_sendmsg(isc::data::ConstElementPtr msg, std::string group,
- std::string, std::string, bool)
+ virtual int group_sendmsg(isc::data::ConstElementPtr msg,
+ std::string group,
+ std::string, std::string, bool want_answer)
{
if (!send_ok_) {
isc_throw(isc::cc::SessionError,
@@ -57,6 +58,7 @@ public:
sent_msg_ = msg;
msg_dest_ = group;
+ answer_wanted_ = want_answer;
return (0);
}
@@ -93,8 +95,12 @@ public:
void disableSend() { send_ok_ = false; }
void disableReceive() { receive_ok_ = false; }
- isc::data::ConstElementPtr getSentMessage() { return (sent_msg_); }
- std::string getMessageDest() { return (msg_dest_); }
+ isc::data::ConstElementPtr getSentMessage() const { return (sent_msg_); }
+ std::string getMessageDest() const { return (msg_dest_); }
+
+ /// \brief Return the value of want_answer parameter of the previous call
+ /// to group_sendmsg().
+ bool wasAnswerWanted() const { return (answer_wanted_); }
private:
isc::data::ConstElementPtr sent_msg_;
@@ -102,12 +108,15 @@ private:
isc::data::ConstElementPtr msg_;
bool send_ok_;
bool receive_ok_;
+ bool answer_wanted_;
};
// This mock object does nothing except for recording passed parameters
// to addServerXXX methods so the test code subsequently checks the parameters.
class MockDNSService : public isc::asiodns::DNSServiceBase {
public:
+ MockDNSService() : tcp_recv_timeout_(0) {}
+
// A helper tuple of parameters passed to addServerUDPFromFD().
struct UDPFdParams {
int fd;
diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am
index 3960a8b..7d3781e 100644
--- a/src/lib/util/Makefile.am
+++ b/src/lib/util/Makefile.am
@@ -4,8 +4,19 @@ 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
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/exceptions -I$(top_builddir)/src/lib/exceptions
AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CPPFLAGS += -DLOCKFILE_DIR=\"${localstatedir}/${PACKAGE_NAME}\"
AM_CXXFLAGS = $(B10_CXXFLAGS)
+# If we use the shared-memory support, corresponding Boost library may
+# cause build failures especially if it's strict about warnings. We've
+# detected it in ./configure and set BOOST_MAPPED_FILE_CXXFLAG to be more
+# lenient as necessary (specifically, when set it'd usually suppress -Werror).
+# This is a module wide setting, and has a possible bad side effect of hiding
+# issues in other files, but making it per-file seems to be too costly.
+# So we begin with the wider setting. If the side effect turns out to be too
+# harmful, we'll consider other measure, e.g, moving the related files into
+# a subdirectory.
+if USE_SHARED_MEMORY
+AM_CXXFLAGS += $(BOOST_MAPPED_FILE_CXXFLAG)
+endif
lib_LTLIBRARIES = libb10-util.la
libb10_util_la_SOURCES = filename.h filename.cc
@@ -13,11 +24,11 @@ libb10_util_la_SOURCES += locks.h lru_list.h
libb10_util_la_SOURCES += strutil.h strutil.cc
libb10_util_la_SOURCES += buffer.h io_utilities.h
libb10_util_la_SOURCES += time_utilities.h time_utilities.cc
-libb10_util_la_SOURCES += interprocess_sync.h
-libb10_util_la_SOURCES += interprocess_sync_file.h interprocess_sync_file.cc
-libb10_util_la_SOURCES += interprocess_sync_null.h interprocess_sync_null.cc
libb10_util_la_SOURCES += memory_segment.h
libb10_util_la_SOURCES += memory_segment_local.h memory_segment_local.cc
+if USE_SHARED_MEMORY
+libb10_util_la_SOURCES += memory_segment_mapped.h memory_segment_mapped.cc
+endif
libb10_util_la_SOURCES += range_utilities.h
libb10_util_la_SOURCES += hash/sha1.h hash/sha1.cc
libb10_util_la_SOURCES += encode/base16_from_binary.h
@@ -34,4 +45,4 @@ libb10_util_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
CLEANFILES = *.gcno *.gcda
libb10_util_includedir = $(includedir)/$(PACKAGE_NAME)/util
-libb10_util_include_HEADERS = buffer.h
+libb10_util_include_HEADERS = buffer.h io_utilities.h
diff --git a/src/lib/util/buffer.h b/src/lib/util/buffer.h
index 4800e99..4aac11e 100644
--- a/src/lib/util/buffer.h
+++ b/src/lib/util/buffer.h
@@ -342,15 +342,17 @@ public:
/// \brief Assignment operator
OutputBuffer& operator =(const OutputBuffer& other) {
- uint8_t* newbuff(static_cast<uint8_t*>(malloc(other.allocated_)));
- if (newbuff == NULL && other.allocated_ != 0) {
- throw std::bad_alloc();
+ if (this != &other) {
+ uint8_t* newbuff(static_cast<uint8_t*>(malloc(other.allocated_)));
+ if (newbuff == NULL && other.allocated_ != 0) {
+ throw std::bad_alloc();
+ }
+ free(buffer_);
+ buffer_ = newbuff;
+ size_ = other.size_;
+ allocated_ = other.allocated_;
+ std::memcpy(buffer_, other.buffer_, size_);
}
- free(buffer_);
- buffer_ = newbuff;
- size_ = other.size_;
- allocated_ = other.allocated_;
- std::memcpy(buffer_, other.buffer_, size_);
return (*this);
}
diff --git a/src/lib/util/encode/base16_from_binary.h b/src/lib/util/encode/base16_from_binary.h
index 8606c61..7ac4fd1 100644
--- a/src/lib/util/encode/base16_from_binary.h
+++ b/src/lib/util/encode/base16_from_binary.h
@@ -83,7 +83,7 @@ class base16_from_binary :
> super_t;
public:
- // make composible buy using templated constructor
+ // make composable by using templated constructor
template<class T>
base16_from_binary(BOOST_PFTO_WRAPPER(T) start) :
super_t(
diff --git a/src/lib/util/encode/base32hex_from_binary.h b/src/lib/util/encode/base32hex_from_binary.h
index 5d16d04..be35c01 100644
--- a/src/lib/util/encode/base32hex_from_binary.h
+++ b/src/lib/util/encode/base32hex_from_binary.h
@@ -85,7 +85,7 @@ class base32hex_from_binary :
> super_t;
public:
- // make composible buy using templated constructor
+ // make composable by using templated constructor
template<class T>
base32hex_from_binary(BOOST_PFTO_WRAPPER(T) start) :
super_t(
diff --git a/src/lib/util/encode/base_n.cc b/src/lib/util/encode/base_n.cc
index c38f901..8c813c3 100644
--- a/src/lib/util/encode/base_n.cc
+++ b/src/lib/util/encode/base_n.cc
@@ -12,17 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <stdint.h>
-#include <cassert>
-#include <iterator>
-#include <string>
-#include <vector>
-
-#include <boost/archive/iterators/base64_from_binary.hpp>
-#include <boost/archive/iterators/binary_from_base64.hpp>
-#include <boost/archive/iterators/transform_width.hpp>
-#include <boost/math/common_factor.hpp>
-
#include <util/encode/base32hex_from_binary.h>
#include <util/encode/binary_from_base32hex.h>
#include <util/encode/base16_from_binary.h>
@@ -32,6 +21,18 @@
#include <exceptions/exceptions.h>
+#include <boost/archive/iterators/base64_from_binary.hpp>
+#include <boost/archive/iterators/binary_from_base64.hpp>
+#include <boost/archive/iterators/transform_width.hpp>
+#include <boost/math/common_factor.hpp>
+
+#include <stdint.h>
+#include <stdexcept>
+#include <cassert>
+#include <iterator>
+#include <string>
+#include <vector>
+
using namespace std;
using namespace boost::archive::iterators;
@@ -39,6 +40,15 @@ namespace isc {
namespace util {
namespace encode {
+// Some versions of clang cannot handle exceptions in unnamed namespaces
+// so this exception is defined in an 'internal' namespace
+namespace clang_unnamed_namespace_workaround {
+// An internally caught exception to unify a few possible cases of the same
+// error.
+class IncompleteBaseInput : public std::exception {
+};
+} // end namespace internal
+
// In the following anonymous namespace, we provide a generic framework
// to encode/decode baseN format. We use the following tools:
// - boost base64_from_binary/binary_from_base64: provide mapping table for
@@ -110,15 +120,15 @@ public:
const vector<uint8_t>::const_iterator& base_end) :
base_(base), base_end_(base_end), in_pad_(false)
{}
- EncodeNormalizer& operator++() {
- if (!in_pad_) {
- ++base_;
- }
- if (base_ == base_end_) {
- in_pad_ = true;
- }
+ EncodeNormalizer& operator++() { // prefix version
+ increment();
return (*this);
}
+ EncodeNormalizer operator++(int) { // postfix version
+ const EncodeNormalizer copy = *this;
+ increment();
+ return (copy);
+ }
const uint8_t& operator*() const {
if (in_pad_) {
return (BINARY_ZERO_CODE);
@@ -130,6 +140,14 @@ public:
return (base_ == other.base_);
}
private:
+ void increment() {
+ if (!in_pad_) {
+ ++base_;
+ }
+ if (base_ == base_end_) {
+ in_pad_ = true;
+ }
+ }
vector<uint8_t>::const_iterator base_;
const vector<uint8_t>::const_iterator base_end_;
bool in_pad_;
@@ -156,16 +174,20 @@ public:
DecodeNormalizer(const char base_zero_code,
const string::const_iterator& base,
const string::const_iterator& base_beginpad,
- const string::const_iterator& base_end) :
+ const string::const_iterator& base_end,
+ size_t* char_count) :
base_zero_code_(base_zero_code),
base_(base), base_beginpad_(base_beginpad), base_end_(base_end),
- in_pad_(false)
+ in_pad_(false), char_count_(char_count)
{
// Skip beginning spaces, if any. We need do it here because
// otherwise the first call to operator*() would be confused.
skipSpaces();
}
DecodeNormalizer& operator++() {
+ if (base_ < base_end_) {
+ ++*char_count_;
+ }
++base_;
skipSpaces();
if (base_ == base_beginpad_) {
@@ -188,15 +210,31 @@ public:
}
const char& operator*() const {
if (base_ == base_end_) {
- // binary_from_baseX calls this operator when it needs more bits
+ // binary_from_baseX can call this operator when it needs more bits
// even if the internal iterator (base_) has reached its end
// (if that happens it means the input is an incomplete baseX
// string and should be rejected). So this is the only point
// we can catch and reject this type of invalid input.
- isc_throw(BadValue, "Unexpected end of input in BASE decoder");
+ //
+ // More recent versions of Boost fixed the behavior and the
+ // out-of-range call to this operator doesn't happen. It's good,
+ // but in that case we need to catch incomplete baseX input in
+ // a different way. It's done via char_count_ and after the
+ // completion of decoding.
+
+ // throw this now and convert it
+ throw clang_unnamed_namespace_workaround::IncompleteBaseInput();
}
- if (in_pad_) {
- return (base_zero_code_);
+ if (*base_ == BASE_PADDING_CHAR) {
+ // Padding can only happen at the end of the input string. We can
+ // detect any violation of this by checking in_pad_, which is
+ // true iff we are on or after the first valid sequence of padding
+ // characters.
+ if (in_pad_) {
+ return (base_zero_code_);
+ } else {
+ isc_throw(BadValue, "Intermediate padding found");
+ }
} else {
return (*base_);
}
@@ -210,6 +248,9 @@ private:
const string::const_iterator base_beginpad_;
const string::const_iterator base_end_;
bool in_pad_;
+ // Store number of non-space decoded characters (incl. pad) here. Define
+ // it as a pointer so we can carry it over to any copied objects.
+ size_t* char_count_;
};
// BitsPerChunk: number of bits to be converted using the baseN mapping table.
@@ -331,10 +372,25 @@ BaseNTransformer<BitsPerChunk, BaseZeroCode, Encoder, Decoder>::decode(
const size_t padbytes = padbits / 8;
try {
+ size_t char_count = 0;
result.assign(Decoder(DecodeNormalizer(BaseZeroCode, input.begin(),
- srit.base(), input.end())),
+ srit.base(), input.end(),
+ &char_count)),
Decoder(DecodeNormalizer(BaseZeroCode, input.end(),
- input.end(), input.end())));
+ input.end(), input.end(),
+ NULL)));
+
+ // Number of bits of the conversion result including padding must be
+ // a multiple of 8; otherwise the decoder reaches the end of input
+ // with some incomplete bits of data, which is invalid.
+ if (((char_count * BitsPerChunk) % 8) != 0) {
+ // catch this immediately below
+ throw clang_unnamed_namespace_workaround::IncompleteBaseInput();
+ }
+ } catch (const clang_unnamed_namespace_workaround::IncompleteBaseInput&) {
+ // we unify error handling for incomplete input here.
+ isc_throw(BadValue, "Incomplete input for " << algorithm
+ << ": " << input);
} catch (const dataflow_exception& ex) {
// convert any boost exceptions into our local one.
isc_throw(BadValue, ex.what());
diff --git a/src/lib/util/encode/binary_from_base16.h b/src/lib/util/encode/binary_from_base16.h
index 50342f1..e9fdd27 100644
--- a/src/lib/util/encode/binary_from_base16.h
+++ b/src/lib/util/encode/binary_from_base16.h
@@ -91,7 +91,7 @@ class binary_from_base16 : public
Base
> super_t;
public:
- // make composible buy using templated constructor
+ // make composable by using templated constructor
template<class T>
binary_from_base16(BOOST_PFTO_WRAPPER(T) start) :
super_t(
diff --git a/src/lib/util/encode/binary_from_base32hex.h b/src/lib/util/encode/binary_from_base32hex.h
index 1d83f54..43b7d64 100644
--- a/src/lib/util/encode/binary_from_base32hex.h
+++ b/src/lib/util/encode/binary_from_base32hex.h
@@ -94,7 +94,7 @@ class binary_from_base32hex : public
Base
> super_t;
public:
- // make composible buy using templated constructor
+ // make composable by using templated constructor
template<class T>
binary_from_base32hex(BOOST_PFTO_WRAPPER(T) start) :
super_t(
diff --git a/src/lib/util/filename.h b/src/lib/util/filename.h
index a4ba47c..a265396 100644
--- a/src/lib/util/filename.h
+++ b/src/lib/util/filename.h
@@ -41,10 +41,10 @@ namespace util {
/// directory specification. Unless this class becomes more widely-used on
/// Windows, there is no point in adding redundant code.
///
-/// Name - everthing from the character after the last "/" up to but not
+/// Name - everything from the character after the last "/" up to but not
/// including the last ".".
///
-/// Extension - everthing from the right-most "." (after the right-most "/") to
+/// Extension - everything from the right-most "." (after the right-most "/") to
/// the end of the string. If there is no "." after the last "/", there is
/// no file extension.
///
diff --git a/src/lib/util/interprocess_sync.h b/src/lib/util/interprocess_sync.h
deleted file mode 100644
index f55f0ac..0000000
--- a/src/lib/util/interprocess_sync.h
+++ /dev/null
@@ -1,149 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef INTERPROCESS_SYNC_H
-#define INTERPROCESS_SYNC_H
-
-#include <string>
-
-namespace isc {
-namespace util {
-
-class InterprocessSyncLocker; // forward declaration
-
-/// \brief Interprocess Sync Class
-///
-/// This class specifies an interface for mutual exclusion among
-/// co-operating processes. This is an abstract class and a real
-/// implementation such as InterprocessSyncFile should be used
-/// in code. Usage is as follows:
-///
-/// 1. Client instantiates a sync object of an implementation (such as
-/// InterprocessSyncFile).
-/// 2. Client then creates an automatic (stack) object of
-/// InterprocessSyncLocker around the sync object. Such an object
-/// destroys itself and releases any acquired lock when it goes out of extent.
-/// 3. Client calls lock() method on the InterprocessSyncLocker.
-/// 4. Client performs task that needs mutual exclusion.
-/// 5. Client frees lock with unlock(), or simply returns from the basic
-/// block which forms the scope for the InterprocessSyncLocker.
-///
-/// NOTE: All implementations of InterprocessSync should keep the
-/// is_locked_ member variable updated whenever their
-/// lock()/tryLock()/unlock() implementations are called.
-class InterprocessSync {
- // InterprocessSyncLocker is the only code outside this class that
- // should be allowed to call the lock(), tryLock() and unlock()
- // methods.
- friend class InterprocessSyncLocker;
-
-public:
- /// \brief Constructor
- ///
- /// Creates an interprocess synchronization object
- ///
- /// \param task_name Name of the synchronization task. This has to be
- /// identical among the various processes that need to be
- /// synchronized for the same task.
- InterprocessSync(const std::string& task_name) :
- task_name_(task_name), is_locked_(false)
- {}
-
- /// \brief Destructor
- virtual ~InterprocessSync() {}
-
-protected:
- /// \brief Acquire the lock (blocks if something else has acquired a
- /// lock on the same task name)
- ///
- /// \return Returns true if the lock was acquired, false otherwise.
- virtual bool lock() = 0;
-
- /// \brief Try to acquire a lock (doesn't block)
- ///
- /// \return Returns true if the lock was acquired, false otherwise.
- virtual bool tryLock() = 0;
-
- /// \brief Release the lock
- ///
- /// \return Returns true if the lock was released, false otherwise.
- virtual bool unlock() = 0;
-
- const std::string task_name_; ///< The task name
- bool is_locked_; ///< Is the lock taken?
-};
-
-/// \brief Interprocess Sync Locker Class
-///
-/// This class is used for making automatic stack objects to manage
-/// locks that are released automatically when the block is exited
-/// (RAII). It is meant to be used along with InterprocessSync objects. See
-/// the description of InterprocessSync.
-class InterprocessSyncLocker {
-public:
- /// \brief Constructor
- ///
- /// Creates a lock manager around a interprocess synchronization object
- ///
- /// \param sync The sync object which has to be locked/unlocked by
- /// this locker object.
- InterprocessSyncLocker(InterprocessSync& sync) :
- sync_(sync)
- {}
-
- /// \brief Destructor
- ~InterprocessSyncLocker() {
- if (isLocked())
- unlock();
- }
-
- /// \brief Acquire the lock (blocks if something else has acquired a
- /// lock on the same task name)
- ///
- /// \return Returns true if the lock was acquired, false otherwise.
- bool lock() {
- return (sync_.lock());
- }
-
- /// \brief Try to acquire a lock (doesn't block)
- ///
- /// \return Returns true if a new lock could be acquired, false
- /// otherwise.
- bool tryLock() {
- return (sync_.tryLock());
- }
-
- /// \brief Check if the lock is taken
- ///
- /// \return Returns true if a lock is currently acquired, false
- /// otherwise.
- bool isLocked() const {
- return (sync_.is_locked_);
- }
-
- /// \brief Release the lock
- ///
- /// \return Returns true if the lock was released, false otherwise.
- bool unlock() {
- return (sync_.unlock());
- }
-
-protected:
- InterprocessSync& sync_; ///< Ref to underlying sync object
-};
-
-} // namespace util
-} // namespace isc
-
-#endif // INTERPROCESS_SYNC_H
diff --git a/src/lib/util/interprocess_sync_file.cc b/src/lib/util/interprocess_sync_file.cc
deleted file mode 100644
index 25af55c..0000000
--- a/src/lib/util/interprocess_sync_file.cc
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include "interprocess_sync_file.h"
-
-#include <string>
-#include <cerrno>
-#include <cstring>
-
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-
-namespace isc {
-namespace util {
-
-InterprocessSyncFile::~InterprocessSyncFile() {
- if (fd_ != -1) {
- // This will also release any applied locks.
- close(fd_);
- // The lockfile will continue to exist, and we must not delete
- // it.
- }
-}
-
-bool
-InterprocessSyncFile::do_lock(int cmd, short l_type) {
- // Open lock file only when necessary (i.e., here). This is so that
- // if a default InterprocessSync object is replaced with another
- // implementation, it doesn't attempt any opens.
- if (fd_ == -1) {
- std::string lockfile_path = LOCKFILE_DIR;
-
- const char* const env = getenv("B10_FROM_BUILD");
- if (env != NULL) {
- lockfile_path = env;
- }
-
- const char* const env2 = getenv("B10_FROM_BUILD_LOCALSTATEDIR");
- if (env2 != NULL) {
- lockfile_path = env2;
- }
-
- const char* const env3 = getenv("B10_LOCKFILE_DIR_FROM_BUILD");
- if (env3 != NULL) {
- lockfile_path = env3;
- }
-
- lockfile_path += "/" + task_name_ + "_lockfile";
-
- // Open the lockfile in the constructor so it doesn't do the access
- // checks every time a message is logged.
- const mode_t mode = umask(0111);
- fd_ = open(lockfile_path.c_str(), O_CREAT | O_RDWR, 0660);
- umask(mode);
-
- if (fd_ == -1) {
- isc_throw(InterprocessSyncFileError,
- "Unable to use interprocess sync lockfile ("
- << std::strerror(errno) << "): " << lockfile_path);
- }
- }
-
- struct flock lock;
-
- memset(&lock, 0, sizeof (lock));
- lock.l_type = l_type;
- lock.l_whence = SEEK_SET;
- lock.l_start = 0;
- lock.l_len = 1;
-
- return (fcntl(fd_, cmd, &lock) == 0);
-}
-
-bool
-InterprocessSyncFile::lock() {
- if (is_locked_) {
- return (true);
- }
-
- if (do_lock(F_SETLKW, F_WRLCK)) {
- is_locked_ = true;
- return (true);
- }
-
- return (false);
-}
-
-bool
-InterprocessSyncFile::tryLock() {
- if (is_locked_) {
- return (true);
- }
-
- if (do_lock(F_SETLK, F_WRLCK)) {
- is_locked_ = true;
- return (true);
- }
-
- return (false);
-}
-
-bool
-InterprocessSyncFile::unlock() {
- if (!is_locked_) {
- return (true);
- }
-
- if (do_lock(F_SETLKW, F_UNLCK)) {
- is_locked_ = false;
- return (true);
- }
-
- return (false);
-}
-
-} // namespace util
-} // namespace isc
diff --git a/src/lib/util/interprocess_sync_file.h b/src/lib/util/interprocess_sync_file.h
deleted file mode 100644
index 153b391..0000000
--- a/src/lib/util/interprocess_sync_file.h
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef INTERPROCESS_SYNC_FILE_H
-#define INTERPROCESS_SYNC_FILE_H
-
-#include <util/interprocess_sync.h>
-#include <exceptions/exceptions.h>
-
-namespace isc {
-namespace util {
-
-/// \brief InterprocessSyncFileError
-///
-/// Exception that is thrown if it's not possible to open the
-/// lock file.
-class InterprocessSyncFileError : public Exception {
-public:
- InterprocessSyncFileError(const char* file, size_t line,
- const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-/// \brief File-based Interprocess Sync Class
-///
-/// This class specifies a concrete implementation for a file-based
-/// interprocess synchronization mechanism. Please see the
-/// InterprocessSync class documentation for usage.
-///
-/// An InterprocessSyncFileError exception may be thrown if there is an
-/// issue opening the lock file.
-///
-/// Lock files are created typically in the local state directory
-/// (var). They are typically named like "<task_name>_lockfile".
-/// This implementation opens lock files lazily (only when
-/// necessary). It also leaves the lock files lying around as multiple
-/// processes may have locks on them.
-class InterprocessSyncFile : public InterprocessSync {
-public:
- /// \brief Constructor
- ///
- /// Creates a file-based interprocess synchronization object
- ///
- /// \param name Name of the synchronization task. This has to be
- /// identical among the various processes that need to be
- /// synchronized for the same task.
- InterprocessSyncFile(const std::string& task_name) :
- InterprocessSync(task_name), fd_(-1)
- {}
-
- /// \brief Destructor
- virtual ~InterprocessSyncFile();
-
-protected:
- /// \brief Acquire the lock (blocks if something else has acquired a
- /// lock on the same task name)
- ///
- /// \return Returns true if the lock was acquired, false otherwise.
- bool lock();
-
- /// \brief Try to acquire a lock (doesn't block)
- ///
- /// \return Returns true if the lock was acquired, false otherwise.
- bool tryLock();
-
- /// \brief Release the lock
- ///
- /// \return Returns true if the lock was released, false otherwise.
- bool unlock();
-
-private:
- bool do_lock(int cmd, short l_type);
-
- int fd_; ///< The descriptor for the open file
-};
-
-} // namespace util
-} // namespace isc
-
-#endif // INTERPROCESS_SYNC_FILE_H
diff --git a/src/lib/util/interprocess_sync_null.cc b/src/lib/util/interprocess_sync_null.cc
deleted file mode 100644
index 5355d57..0000000
--- a/src/lib/util/interprocess_sync_null.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include "interprocess_sync_null.h"
-
-namespace isc {
-namespace util {
-
-InterprocessSyncNull::~InterprocessSyncNull() {
-}
-
-bool
-InterprocessSyncNull::lock() {
- is_locked_ = true;
- return (true);
-}
-
-bool
-InterprocessSyncNull::tryLock() {
- is_locked_ = true;
- return (true);
-}
-
-bool
-InterprocessSyncNull::unlock() {
- is_locked_ = false;
- return (true);
-}
-
-} // namespace util
-} // namespace isc
diff --git a/src/lib/util/interprocess_sync_null.h b/src/lib/util/interprocess_sync_null.h
deleted file mode 100644
index be77514..0000000
--- a/src/lib/util/interprocess_sync_null.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef INTERPROCESS_SYNC_NULL_H
-#define INTERPROCESS_SYNC_NULL_H
-
-#include <util/interprocess_sync.h>
-
-namespace isc {
-namespace util {
-
-/// \brief Null Interprocess Sync Class
-///
-/// This class specifies a concrete implementation for a null (no effect)
-/// interprocess synchronization mechanism. Please see the
-/// InterprocessSync class documentation for usage.
-class InterprocessSyncNull : public InterprocessSync {
-public:
- /// \brief Constructor
- ///
- /// Creates a null interprocess synchronization object
- ///
- /// \param name Name of the synchronization task. This has to be
- /// identical among the various processes that need to be
- /// synchronized for the same task.
- InterprocessSyncNull(const std::string& task_name) :
- InterprocessSync(task_name)
- {}
-
- /// \brief Destructor
- virtual ~InterprocessSyncNull();
-
-protected:
- /// \brief Acquire the lock (never blocks)
- ///
- /// \return Always returns true
- bool lock();
-
- /// \brief Try to acquire a lock (doesn't block)
- ///
- /// \return Always returns true
- bool tryLock();
-
- /// \brief Release the lock
- ///
- /// \return Always returns true
- bool unlock();
-};
-
-} // namespace util
-} // namespace isc
-
-#endif // INTERPROCESS_SYNC_NULL_H
diff --git a/src/lib/util/lru_list.h b/src/lib/util/lru_list.h
index e5db869..332f6ce 100644
--- a/src/lib/util/lru_list.h
+++ b/src/lib/util/lru_list.h
@@ -47,7 +47,7 @@ public:
/// When an object is dropped from the LRU list because it has not been
/// accessed for some time, it is possible that the action should trigger
/// some other functions. For this reason, it is possible to register
- /// a list-wide functor object to execute in this casee.
+ /// a list-wide functor object to execute in this case.
///
/// Note that the function does not execute as the result of a call to
/// remove() - that is an explicit call and it is assumed that the caller
diff --git a/src/lib/util/memory_segment.h b/src/lib/util/memory_segment.h
index 664bd3c..94aa3ec 100644
--- a/src/lib/util/memory_segment.h
+++ b/src/lib/util/memory_segment.h
@@ -15,27 +15,111 @@
#ifndef MEMORY_SEGMENT_H
#define MEMORY_SEGMENT_H
+#include <exceptions/exceptions.h>
+
+#include <utility>
+
#include <stdlib.h>
namespace isc {
namespace util {
+/// \brief Exception that can be thrown when constructing a MemorySegment
+/// object.
+class MemorySegmentOpenError : public Exception {
+public:
+ MemorySegmentOpenError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief Exception that is thrown, when allocating space in a MemorySegment
+/// results in growing the underlying segment.
+///
+/// See MemorySegment::allocate() for details.
+class MemorySegmentGrown : public Exception {
+public:
+ MemorySegmentGrown(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// \brief General error that can be thrown by a MemorySegment
+/// implementation.
+class MemorySegmentError : public Exception {
+public:
+ MemorySegmentError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
/// \brief Memory Segment Class
///
-/// This class specifies an interface for allocating memory
-/// segments. This is an abstract class and a real
-/// implementation such as MemorySegmentLocal should be used
-/// in code.
+/// This class specifies an interface for allocating memory segments.
+/// It's intended to provide a unified interface, whether the underlying
+/// memory is local to a specific process or is sharable by multiple
+/// processes.
+///
+/// This is an abstract class and a real implementation such as
+/// MemorySegmentLocal should be used in code.
class MemorySegment {
public:
/// \brief Destructor
virtual ~MemorySegment() {}
- /// \brief Allocate/acquire a segment of memory. The source of the
- /// memory is dependent on the implementation used.
+ /// \brief Allocate/acquire a fragment of memory.
+ ///
+ /// The source of the memory is dependent on the implementation used.
///
- /// Throws <code>std::bad_alloc</code> if the implementation cannot
- /// allocate the requested storage.
+ /// Depending on the implementation details, it may have to grow the
+ /// internal memory segment (again, in an implementation dependent way)
+ /// to allocate the required size of memory. In that case the
+ /// implementation must grow the internal segment sufficiently so the
+ /// next call to allocate() for the same size will succeed, and throw
+ /// a \c MemorySegmentGrown exception (not really allocating the memory
+ /// yet).
+ ///
+ /// An application that uses this memory segment abstraction to allocate
+ /// memory should expect this exception, and should normally catch it
+ /// at an appropriate layer (which may be immediately after a call to
+ /// \c allocate() or a bit higher layer). It should interpret the
+ /// exception as any raw address that belongs to the segment may have
+ /// been remapped and must be re-fetched via an already established
+ /// named address using the \c getNamedAddress() method.
+ ///
+ /// The intended use case of \c allocate() with the \c MemorySegmentGrown
+ /// exception is to build a complex object that would internally require
+ /// multiple calls to \c allocate():
+ ///
+ /// \code
+ /// ComplicatedStuff* stuff = NULL;
+ /// while (!stuff) { // this must eventually succeed or result in bad_alloc
+ /// try {
+ /// // create() is a factory method that takes a memory segment
+ /// // and calls allocate() on it multiple times. create()
+ /// // provides an exception guarantee that any intermediately
+ /// // allocated memory will be properly deallocate()-ed on
+ /// // exception.
+ /// stuff = ComplicatedStuff::create(mem_segment);
+ /// } catch (const MemorySegmentGrown&) { /* just try again */ }
+ /// }
+ /// \endcode
+ ///
+ /// This way, \c create() can be written as if each call to \c allocate()
+ /// always succeeds.
+ ///
+ /// Alternatively, or in addition to this, we could introduce a "no throw"
+ /// version of this method with a way to tell the caller the reason of
+ /// any failure (whether it's really out of memory or just due to growing
+ /// the segment). That would be more convenient if the caller wants to
+ /// deal with the failures on a per-call basis rather than as a set
+ /// of calls like in the above example. At the moment, we don't expect
+ /// to have such use-cases, so we only provide the exception
+ /// version.
+ ///
+ /// \throw std::bad_alloc The implementation cannot allocate the
+ /// requested storage.
+ /// \throw MemorySegmentGrown The memory segment doesn't have sufficient
+ /// space for the requested size and has grown internally.
+ /// \throw MemorySegmentError An attempt was made to allocate
+ /// storage on a read-only memory segment.
///
/// \param size The size of the memory requested in bytes.
/// \return Returns pointer to the memory allocated.
@@ -50,6 +134,18 @@ public:
/// use this argument in some implementations to test if all allocated
/// memory was deallocated properly.
///
+ /// Specific implementation may also throw \c MemorySegmentError if it
+ /// encounters violation of implementation specific restrictions.
+ ///
+ /// In general, however, this method must succeed and exception free
+ /// as long as the caller passes valid parameters (\c ptr specifies
+ /// memory previously allocated and \c size is correct).
+ ///
+ /// \throw OutOfRange The passed size doesn't match the allocated memory
+ /// size (when identifiable for the implementation).
+ /// \throw MemorySegmentError Failure of implementation specific
+ /// validation.
+ ///
/// \param ptr Pointer to the block of memory to free/release. This
/// should be equal to a value returned by <code>allocate()</code>.
/// \param size The size of the memory to be freed in bytes. This
@@ -58,12 +154,188 @@ public:
/// \brief Check if all allocated memory was deallocated.
///
- /// \return Returns <code>true</code> if all allocated memory was
+ /// \return Returns <code>true</code> if all allocated memory (including
+ /// names associated by memory addresses by \c setNamedAddress()) was
/// deallocated, <code>false</code> otherwise.
virtual bool allMemoryDeallocated() const = 0;
+
+ /// \brief Associate specified address in the segment with a given name.
+ ///
+ /// This method establishes an association between the given name and
+ /// the address in an implementation specific way. The stored address
+ /// is retrieved by the name later by calling \c getNamedAddress().
+ /// If the underlying memory segment is sharable by multiple processes,
+ /// the implementation must ensure the portability of the association;
+ /// if a process gives an address in the shared segment a name, another
+ /// process that shares the same segment should be able to retrieve the
+ /// corresponding address by that name (in such cases the real address
+ /// may be different between these two processes).
+ ///
+ /// Some names are reserved for internal use by this class. If such
+ /// a name is passed to this method, an \c isc::InvalidParameter
+ /// exception will be thrown. See \c validateName() method for details.
+ ///
+ /// \c addr must be 0 (NULL) or an address that belongs to this segment.
+ /// The latter case means it must be the return value of a previous call
+ /// to \c allocate(). The actual implementation is encouraged to detect
+ /// violation of this restriction and signal it with an exception, but
+ /// it's not an API requirement. It's generally the caller's
+ /// responsibility to meet the restriction. Note that NULL is allowed
+ /// as \c addr even if it wouldn't be considered to "belong to" the
+ /// segment in its normal sense; it can be used to indicate that memory
+ /// has not been allocated for the specified name. A subsequent call
+ /// to \c getNamedAddress() will return NamedAddressResult(true, NULL)
+ /// for that name.
+ ///
+ /// \note Naming an address is intentionally separated from allocation
+ /// so that, for example, one module of a program can name a memory
+ /// region allocated by another module of the program.
+ ///
+ /// There can be an existing association for the name; in that case the
+ /// association will be overridden with the newly given address.
+ ///
+ /// While normally unexpected, it's possible that available space in the
+ /// segment is not sufficient to allocate a space (if not already exist)
+ /// for the specified name in the segment. In that case, if possible, the
+ /// implementation should try to grow the internal segment and retry
+ /// establishing the association. The implementation should throw
+ /// std::bad_alloc if even reasonable attempts of retry still fail.
+ ///
+ /// This method should normally return false, but if the internal segment
+ /// had to grow to store the given name, it must return true. The
+ /// application should interpret it just like the case of
+ /// \c MemorySegmentGrown exception thrown from the \c allocate() method.
+ ///
+ /// \note The behavior in case the internal segment grows is different
+ /// from that of \c allocate(). This is intentional. In intended use
+ /// cases (for the moment) this method will be called independently,
+ /// rather than as part of a set of allocations. It's also expected
+ /// that other raw memory addresses (which would have been invalidated
+ /// due to the change to the segment) won't be referenced directly
+ /// immediately after this call. So, the caller should normally be able
+ /// to call this method as mostly never-fail one (except in case of real
+ /// memory exhaustion) and ignore the return value.
+ ///
+ /// \throw std::bad_alloc Allocation of a segment space for the given name
+ /// failed.
+ /// \throw InvalidParameter name is NULL, empty ("") or begins with
+ /// an underscore ('_').
+ /// \throw MemorySegmentError Failure of implementation specific
+ /// validation.
+ ///
+ /// \param name A C string to be associated with \c addr. Must not be NULL.
+ /// \param addr A memory address returned by a prior call to \c allocate.
+ /// \return true if the internal segment has grown to allocate space for
+ /// the name; false otherwise (see above).
+ bool setNamedAddress(const char* name, void* addr) {
+ // This public method implements common validation. The actual
+ // work specific to the derived segment is delegated to the
+ // corresponding protected method.
+ validateName(name);
+ return (setNamedAddressImpl(name, addr));
+ }
+
+ /// \brief Type definition for result returned by getNamedAddress()
+ typedef std::pair<bool, void*> NamedAddressResult;
+
+ /// \brief Return the address in the segment that has the given name.
+ ///
+ /// This method returns the memory address in the segment corresponding
+ /// to the specified \c name. The name and address must have been
+ /// associated by a prior call to \c setNameAddress(). If no address
+ /// associated with the given name is found, it returns NULL.
+ ///
+ /// Some names are reserved for internal use by this class. If such
+ /// a name is passed to this method, an \c isc::InvalidParameter
+ /// exception will be thrown. See \c validateName() method for details.
+ ///
+ /// This method should generally be considered exception free, but there
+ /// can be a small chance it throws, depending on the internal
+ /// implementation (e.g., if it converts the name to std::string), so the
+ /// API doesn't guarantee that property. In general, if this method
+ /// throws it should be considered a fatal condition.
+ ///
+ /// \throw InvalidParameter name is NULL, empty ("") or begins with
+ /// an underscore ('_').
+ ///
+ /// \param name A C string of which the segment memory address is to be
+ /// returned. Must not be NULL.
+ /// \return An std::pair containing a bool (set to true if the name
+ /// was found, or false otherwise) and the address associated with
+ /// the name (which is undefined if the name was not found).
+ NamedAddressResult getNamedAddress(const char* name) const {
+ // This public method implements common validation. The actual
+ // work specific to the derived segment is delegated to the
+ // corresponding protected method.
+ validateName(name);
+ return (getNamedAddressImpl(name));
+ }
+
+ /// \brief Delete a name previously associated with a segment address.
+ ///
+ /// This method deletes the association of the given \c name to
+ /// a corresponding segment address previously established by
+ /// \c setNamedAddress(). If there is no association for the given name
+ /// this method returns false; otherwise it returns true.
+ ///
+ /// Some names are reserved for internal use by this class. If such
+ /// a name is passed to this method, an \c isc::InvalidParameter
+ /// exception will be thrown. See \c validateName() method for details.
+ ///
+ /// See \c getNamedAddress() about exception consideration.
+ ///
+ /// \throw InvalidParameter name is NULL, empty ("") or begins with
+ /// an underscore ('_').
+ /// \throw MemorySegmentError Failure of implementation specific
+ /// validation.
+ ///
+ /// \param name A C string of which the segment memory address is to be
+ /// deleted. Must not be NULL.
+ bool clearNamedAddress(const char* name) {
+ // This public method implements common validation. The actual
+ // work specific to the derived segment is delegated to the
+ // corresponding protected method.
+ validateName(name);
+ return (clearNamedAddressImpl(name));
+ }
+
+private:
+ /// \brief Validate the passed name.
+ ///
+ /// This method validates the passed name (for name/address pairs)
+ /// and throws \c InvalidParameter if the name fails
+ /// validation. Otherwise, it does nothing.
+ ///
+ /// \throw InvalidParameter name is NULL, empty ("") or begins with
+ /// an underscore ('_').
+ static void validateName(const char* name) {
+ if (!name) {
+ isc_throw(InvalidParameter, "NULL is invalid for a name.");
+ } else if (*name == '\0') {
+ isc_throw(InvalidParameter, "Empty names are invalid.");
+ } else if (*name == '_') {
+ isc_throw(InvalidParameter,
+ "Names beginning with '_' are reserved for "
+ "internal use only.");
+ }
+ }
+
+protected:
+ /// \brief Implementation of setNamedAddress beyond common validation.
+ virtual bool setNamedAddressImpl(const char* name, void* addr) = 0;
+
+ /// \brief Implementation of getNamedAddress beyond common validation.
+ virtual NamedAddressResult getNamedAddressImpl(const char* name) const = 0;
+
+ /// \brief Implementation of clearNamedAddress beyond common validation.
+ virtual bool clearNamedAddressImpl(const char* name) = 0;
};
} // namespace util
} // namespace isc
#endif // MEMORY_SEGMENT_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/memory_segment_local.cc b/src/lib/util/memory_segment_local.cc
index 9c345c9..b81fe5e 100644
--- a/src/lib/util/memory_segment_local.cc
+++ b/src/lib/util/memory_segment_local.cc
@@ -48,7 +48,29 @@ MemorySegmentLocal::deallocate(void* ptr, size_t size) {
bool
MemorySegmentLocal::allMemoryDeallocated() const {
- return (allocated_size_ == 0);
+ return (allocated_size_ == 0 && named_addrs_.empty());
+}
+
+MemorySegment::NamedAddressResult
+MemorySegmentLocal::getNamedAddressImpl(const char* name) const {
+ std::map<std::string, void*>::const_iterator found =
+ named_addrs_.find(name);
+ if (found != named_addrs_.end()) {
+ return (NamedAddressResult(true, found->second));
+ }
+ return (NamedAddressResult(false, NULL));
+}
+
+bool
+MemorySegmentLocal::setNamedAddressImpl(const char* name, void* addr) {
+ named_addrs_[name] = addr;
+ return (false);
+}
+
+bool
+MemorySegmentLocal::clearNamedAddressImpl(const char* name) {
+ const size_t n_erased = named_addrs_.erase(name);
+ return (n_erased != 0);
}
} // namespace util
diff --git a/src/lib/util/memory_segment_local.h b/src/lib/util/memory_segment_local.h
index de35b87..de7249e 100644
--- a/src/lib/util/memory_segment_local.h
+++ b/src/lib/util/memory_segment_local.h
@@ -17,6 +17,9 @@
#include <util/memory_segment.h>
+#include <string>
+#include <map>
+
namespace isc {
namespace util {
@@ -63,14 +66,43 @@ public:
/// deallocated, <code>false</code> otherwise.
virtual bool allMemoryDeallocated() const;
+ /// \brief Local segment version of getNamedAddress.
+ ///
+ /// There's a small chance this method could throw std::bad_alloc.
+ /// It should be considered a fatal error.
+ virtual NamedAddressResult getNamedAddressImpl(const char* name) const;
+
+ /// \brief Local segment version of setNamedAddress.
+ ///
+ /// This version does not validate the given address to see whether it
+ /// belongs to this segment.
+ ///
+ /// This implementation of this method always returns \c false (but the
+ /// application should expect a return value of \c true unless it knows
+ /// the memory segment class is \c MemorySegmentLocal and needs to
+ /// exploit the fact).
+ virtual bool setNamedAddressImpl(const char* name, void* addr);
+
+ /// \brief Local segment version of clearNamedAddress.
+ ///
+ /// There's a small chance this method could throw std::bad_alloc.
+ /// It should be considered a fatal error.
+ virtual bool clearNamedAddressImpl(const char* name);
+
private:
// allocated_size_ can underflow, wrap around to max size_t (which
// is unsigned). But because we only do a check against 0 and not a
// relation comparison, this is okay.
size_t allocated_size_;
+
+ std::map<std::string, void*> named_addrs_;
};
} // namespace util
} // namespace isc
#endif // MEMORY_SEGMENT_LOCAL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/memory_segment_mapped.cc b/src/lib/util/memory_segment_mapped.cc
new file mode 100644
index 0000000..8fea5ea
--- /dev/null
+++ b/src/lib/util/memory_segment_mapped.cc
@@ -0,0 +1,466 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/memory_segment_mapped.h>
+#include <util/unittests/check_valgrind.h>
+
+#include <exceptions/exceptions.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/interprocess/exceptions.hpp>
+#include <boost/interprocess/managed_mapped_file.hpp>
+#include <boost/interprocess/offset_ptr.hpp>
+#include <boost/interprocess/mapped_region.hpp>
+#include <boost/interprocess/sync/file_lock.hpp>
+
+#include <cassert>
+#include <string>
+#include <new>
+
+#include <stdint.h>
+
+// boost::interprocess namespace is big and can cause unexpected import
+// (e.g., it has "read_only"), so it's safer to be specific for shortcuts.
+using boost::interprocess::basic_managed_mapped_file;
+using boost::interprocess::rbtree_best_fit;
+using boost::interprocess::null_mutex_family;
+using boost::interprocess::iset_index;
+using boost::interprocess::create_only_t;
+using boost::interprocess::create_only;
+using boost::interprocess::open_or_create_t;
+using boost::interprocess::open_or_create;
+using boost::interprocess::open_read_only;
+using boost::interprocess::open_only;
+using boost::interprocess::offset_ptr;
+
+namespace isc {
+namespace util {
+
+namespace { // unnamed namespace
+
+const char* const RESERVED_NAMED_ADDRESS_STORAGE_NAME =
+ "_RESERVED_NAMED_ADDRESS_STORAGE";
+
+} // end of unnamed namespace
+
+
+// Definition of class static constant so it can be referenced by address
+// or reference.
+const size_t MemorySegmentMapped::INITIAL_SIZE;
+
+// We customize managed_mapped_file to make it completely lock free. In our
+// usage the application (or the system of applications) is expected to ensure
+// there's at most one writer process or concurrent writing the shared memory
+// segment is protected at a higher level. Using the null mutex is mainly for
+// eliminating unnecessary dependency; the default version would require
+// (probably depending on the system) Pthread library that is actually not
+// needed and could cause various build time troubles.
+typedef basic_managed_mapped_file<char,
+ rbtree_best_fit<null_mutex_family>,
+ iset_index> BaseSegment;
+
+struct MemorySegmentMapped::Impl {
+ // Constructor for create-only (and read-write) mode. this case is
+ // tricky because we want to remove any existing file but we also want
+ // to detect possible conflict with other readers or writers using
+ // file lock.
+ Impl(const std::string& filename, create_only_t, size_t initial_size) :
+ read_only_(false), filename_(filename)
+ {
+ try {
+ // First, try opening it in boost create_only mode; it fails if
+ // the file exists (among other reasons).
+ base_sgmt_.reset(new BaseSegment(create_only, filename.c_str(),
+ initial_size));
+ } catch (const boost::interprocess::interprocess_exception& ex) {
+ // We assume this is because the file exists; otherwise creating
+ // file_lock would fail with interprocess_exception, and that's
+ // what we want here (we wouldn't be able to create a segment
+ // anyway).
+ lock_.reset(new boost::interprocess::file_lock(filename.c_str()));
+
+ // Confirm there's no other reader or writer, and then release
+ // the lock before we remove the file; there's a chance of race
+ // here, but this check doesn't intend to guarantee 100% safety
+ // and so it should be okay.
+ checkWriter();
+ lock_.reset();
+
+ // now remove the file (if it happens to have been delete, this
+ // will be no-op), then re-open it with create_only. this time
+ // it should succeed, and if it fails again, that's fatal for this
+ // constructor.
+ boost::interprocess::file_mapping::remove(filename.c_str());
+ base_sgmt_.reset(new BaseSegment(create_only, filename.c_str(),
+ initial_size));
+ }
+
+ // confirm there's no other user and there won't either.
+ lock_.reset(new boost::interprocess::file_lock(filename.c_str()));
+ checkWriter();
+ reserveMemory();
+ }
+
+ // Constructor for open-or-write (and read-write) mode
+ Impl(const std::string& filename, open_or_create_t, size_t initial_size) :
+ read_only_(false), filename_(filename),
+ base_sgmt_(new BaseSegment(open_or_create, filename.c_str(),
+ initial_size)),
+ lock_(new boost::interprocess::file_lock(filename.c_str()))
+ {
+ checkWriter();
+ reserveMemory();
+ }
+
+ // Constructor for existing segment, either read-only or read-write
+ Impl(const std::string& filename, bool read_only) :
+ read_only_(read_only), filename_(filename),
+ base_sgmt_(read_only_ ?
+ new BaseSegment(open_read_only, filename.c_str()) :
+ new BaseSegment(open_only, filename.c_str())),
+ lock_(new boost::interprocess::file_lock(filename.c_str()))
+ {
+ if (read_only_) {
+ checkReader();
+ } else {
+ checkWriter();
+ }
+ reserveMemory();
+ }
+
+ void reserveMemory(bool no_grow = false) {
+ if (!read_only_) {
+ // Reserve a named address for use during
+ // setNamedAddress(). Though this will almost always succeed
+ // on the first try during construction, it may require
+ // multiple attempts later during a call from
+ // allMemoryDeallocated() when the segment has been in use
+ // for a while.
+ while (true) {
+ const offset_ptr<void>* reserved_storage =
+ base_sgmt_->find_or_construct<offset_ptr<void> >(
+ RESERVED_NAMED_ADDRESS_STORAGE_NAME, std::nothrow)();
+
+ if (reserved_storage) {
+ break;
+ }
+ assert(!no_grow);
+
+ growSegment();
+ }
+ }
+ }
+
+ void freeReservedMemory() {
+ if (!read_only_) {
+ const bool deleted = base_sgmt_->destroy<offset_ptr<void> >
+ (RESERVED_NAMED_ADDRESS_STORAGE_NAME);
+ assert(deleted);
+ }
+ }
+
+ // Internal helper to grow the underlying mapped segment.
+ void growSegment() {
+ // We first need to unmap it before calling grow().
+ const size_t prev_size = base_sgmt_->get_size();
+ base_sgmt_->flush();
+ base_sgmt_.reset();
+
+ // Double the segment size. In theory, this process could repeat
+ // so many times, counting to "infinity", and new_size eventually
+ // overflows. That would cause a harsh disruption or unexpected
+ // behavior. But we basically assume grow() would fail before this
+ // happens, so we assert it shouldn't happen.
+ const size_t new_size = prev_size * 2;
+ assert(new_size > prev_size);
+
+ const bool grown = BaseSegment::grow(filename_.c_str(),
+ new_size - prev_size);
+
+ // Remap the file, whether or not grow() succeeded. this should
+ // normally succeed(*), but it's not 100% guaranteed. We abort
+ // if it fails (see the method description in the header file).
+ // (*) Although it's not formally documented, the implementation
+ // of grow() seems to provide strong guarantee, i.e, if it fails
+ // the underlying file can be used with the previous size.
+ try {
+ base_sgmt_.reset(new BaseSegment(open_only, filename_.c_str()));
+ } catch (...) {
+ abort();
+ }
+ if (!grown) {
+ throw std::bad_alloc();
+ }
+ }
+
+ // remember if the segment is opened read-only or not
+ const bool read_only_;
+
+ // mapped file; remember it in case we need to grow it.
+ const std::string filename_;
+
+ // actual Boost implementation of mapped segment.
+ boost::scoped_ptr<BaseSegment> base_sgmt_;
+
+private:
+ // helper methods and member to detect any reader-writer conflict at
+ // the time of construction using an advisory file lock. The lock will
+ // be held throughout the lifetime of the object and will be released
+ // automatically.
+
+ void checkReader() {
+ if (!lock_->try_lock_sharable()) {
+ isc_throw(MemorySegmentOpenError,
+ "mapped memory segment can't be opened as read-only "
+ "with a writer process");
+ }
+ }
+
+ void checkWriter() {
+ if (!lock_->try_lock()) {
+ isc_throw(MemorySegmentOpenError,
+ "mapped memory segment can't be opened as read-write "
+ "with other reader or writer processes");
+ }
+ }
+
+ boost::scoped_ptr<boost::interprocess::file_lock> lock_;
+};
+
+MemorySegmentMapped::MemorySegmentMapped(const std::string& filename) :
+ impl_(NULL)
+{
+ try {
+ impl_ = new Impl(filename, true);
+ } catch (const boost::interprocess::interprocess_exception& ex) {
+ isc_throw(MemorySegmentOpenError,
+ "failed to open mapped memory segment for " << filename
+ << ": " << ex.what());
+ }
+}
+
+MemorySegmentMapped::MemorySegmentMapped(const std::string& filename,
+ OpenMode mode, size_t initial_size) :
+ impl_(NULL)
+{
+ try {
+ switch (mode) {
+ case OPEN_FOR_WRITE:
+ impl_ = new Impl(filename, false);
+ break;
+ case OPEN_OR_CREATE:
+ impl_ = new Impl(filename, open_or_create, initial_size);
+ break;
+ case CREATE_ONLY:
+ impl_ = new Impl(filename, create_only, initial_size);
+ break;
+ default:
+ isc_throw(InvalidParameter,
+ "invalid open mode for MemorySegmentMapped: " << mode);
+ }
+ } catch (const boost::interprocess::interprocess_exception& ex) {
+ isc_throw(MemorySegmentOpenError,
+ "failed to open mapped memory segment for " << filename
+ << ": " << ex.what());
+ }
+}
+
+MemorySegmentMapped::~MemorySegmentMapped() {
+ if (impl_->base_sgmt_ && !impl_->read_only_) {
+ impl_->freeReservedMemory();
+ impl_->base_sgmt_->flush(); // note: this is exception free
+ }
+ delete impl_;
+}
+
+void*
+MemorySegmentMapped::allocate(size_t size) {
+ if (impl_->read_only_) {
+ isc_throw(MemorySegmentError, "allocate attempt on read-only segment");
+ }
+
+ // We explicitly check the free memory size; it appears
+ // managed_mapped_file::allocate() could incorrectly return a seemingly
+ // valid pointer for some very large requested size.
+ if (impl_->base_sgmt_->get_free_memory() >= size) {
+ void* ptr = impl_->base_sgmt_->allocate(size, std::nothrow);
+ if (ptr) {
+ return (ptr);
+ }
+ }
+
+ // Grow the mapped segment doubling the size until we have sufficient
+ // free memory in the revised segment for the requested size.
+ do {
+ impl_->growSegment();
+ } while (impl_->base_sgmt_->get_free_memory() < size);
+ isc_throw(MemorySegmentGrown, "mapped memory segment grown, size: "
+ << impl_->base_sgmt_->get_size() << ", free size: "
+ << impl_->base_sgmt_->get_free_memory());
+}
+
+void
+MemorySegmentMapped::deallocate(void* ptr, size_t) {
+ if (impl_->read_only_) {
+ isc_throw(MemorySegmentError,
+ "deallocate attempt on read-only segment");
+ }
+
+ // the underlying deallocate() would deal with the case where ptr == NULL,
+ // but it's an undocumented behavior, so we handle it ourselves for safety.
+ if (!ptr) {
+ return;
+ }
+
+ impl_->base_sgmt_->deallocate(ptr);
+}
+
+bool
+MemorySegmentMapped::allMemoryDeallocated() const {
+ // This method is not technically const, but it reserves the
+ // const-ness property. In case of exceptions, we abort here. (See
+ // ticket #2850 for additional commentary.)
+ try {
+ impl_->freeReservedMemory();
+ const bool result = impl_->base_sgmt_->all_memory_deallocated();
+ // reserveMemory() should succeed now as the memory was already
+ // allocated, so we set no_grow to true.
+ impl_->reserveMemory(true);
+ return (result);
+ } catch (...) {
+ abort();
+ }
+}
+
+MemorySegment::NamedAddressResult
+MemorySegmentMapped::getNamedAddressImpl(const char* name) const {
+ offset_ptr<void>* storage =
+ impl_->base_sgmt_->find<offset_ptr<void> >(name).first;
+ if (storage) {
+ return (NamedAddressResult(true, storage->get()));
+ }
+ return (NamedAddressResult(false, NULL));
+}
+
+bool
+MemorySegmentMapped::setNamedAddressImpl(const char* name, void* addr) {
+ if (impl_->read_only_) {
+ isc_throw(MemorySegmentError, "setNamedAddress on read-only segment");
+ }
+
+ if (addr && !impl_->base_sgmt_->belongs_to_segment(addr)) {
+ isc_throw(MemorySegmentError, "address is out of segment: " << addr);
+ }
+
+ // Temporarily save the passed addr into pre-allocated offset_ptr in
+ // case there are any relocations caused by allocations.
+ offset_ptr<void>* reserved_storage =
+ impl_->base_sgmt_->find<offset_ptr<void> >(
+ RESERVED_NAMED_ADDRESS_STORAGE_NAME).first;
+ assert(reserved_storage);
+ *reserved_storage = addr;
+
+ bool grown = false;
+ while (true) {
+ offset_ptr<void>* storage =
+ impl_->base_sgmt_->find_or_construct<offset_ptr<void> >(
+ name, std::nothrow)();
+ if (storage) {
+ // Move the address from saved offset_ptr into the
+ // newly-allocated storage.
+ reserved_storage =
+ impl_->base_sgmt_->find<offset_ptr<void> >(
+ RESERVED_NAMED_ADDRESS_STORAGE_NAME).first;
+ assert(reserved_storage);
+ *storage = *reserved_storage;
+ return (grown);
+ }
+
+ impl_->growSegment();
+ grown = true;
+ }
+}
+
+bool
+MemorySegmentMapped::clearNamedAddressImpl(const char* name) {
+ if (impl_->read_only_) {
+ isc_throw(MemorySegmentError,
+ "clearNamedAddress on read-only segment");
+ }
+
+ return (impl_->base_sgmt_->destroy<offset_ptr<void> >(name));
+}
+
+void
+MemorySegmentMapped::shrinkToFit() {
+ if (impl_->read_only_) {
+ isc_throw(MemorySegmentError, "shrinkToFit on read-only segment");
+ }
+
+ // It appears an assertion failure is triggered within Boost if the size
+ // is too small (happening if shrink_to_fit() is called twice without
+ // allocating any memory from the shrunk segment). To work this around
+ // we'll make it no-op if the size is already reasonably small.
+ // Using INITIAL_SIZE is not 100% reliable as it's irrelevant to the
+ // internal constraint of the Boost implementation. But, in practice,
+ // it should be sufficiently large and safe.
+ if (getSize() < INITIAL_SIZE) {
+ return;
+ }
+
+ // First, unmap the underlying file.
+ impl_->base_sgmt_->flush();
+ impl_->base_sgmt_.reset();
+
+ BaseSegment::shrink_to_fit(impl_->filename_.c_str());
+ try {
+ // Remap the shrunk file; this should succeed, but it's not 100%
+ // guaranteed. If it fails we treat it as if we fail to create
+ // the new segment. Note that this is different from the case where
+ // reset() after grow() fails. While the same argument can apply
+ // in theory, it should be less likely that other methods will be
+ // called after shrinkToFit() (and the destructor can still be called
+ // safely), so we give the application an opportunity to handle the
+ // case as gracefully as possible.
+ impl_->base_sgmt_.reset(
+ new BaseSegment(open_only, impl_->filename_.c_str()));
+ } catch (const boost::interprocess::interprocess_exception& ex) {
+ isc_throw(MemorySegmentError,
+ "remap after shrink failed; segment is now unusable");
+ }
+}
+
+size_t
+MemorySegmentMapped::getSize() const {
+ return (impl_->base_sgmt_->get_size());
+}
+
+size_t
+MemorySegmentMapped::getCheckSum() const {
+ const size_t pagesize =
+ boost::interprocess::mapped_region::get_page_size();
+ const uint8_t* const cp_begin = static_cast<const uint8_t*>(
+ impl_->base_sgmt_->get_address());
+ const uint8_t* const cp_end = cp_begin + impl_->base_sgmt_->get_size();
+
+ size_t sum = 0;
+ for (const uint8_t* cp = cp_begin; cp < cp_end; cp += pagesize) {
+ sum += *cp;
+ }
+
+ return (sum);
+}
+
+} // namespace util
+} // namespace isc
diff --git a/src/lib/util/memory_segment_mapped.h b/src/lib/util/memory_segment_mapped.h
new file mode 100644
index 0000000..301b174
--- /dev/null
+++ b/src/lib/util/memory_segment_mapped.h
@@ -0,0 +1,268 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef MEMORY_SEGMENT_MAPPED_H
+#define MEMORY_SEGMENT_MAPPED_H
+
+#include <util/memory_segment.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <string>
+
+namespace isc {
+namespace util {
+
+/// \brief Mapped-file based Memory Segment class.
+///
+/// This implementation of \c MemorySegment uses a concrete file to be mapped
+/// into memory. Multiple processes can share the same mapped memory image.
+///
+/// This class provides two operation modes: read-only and read-write.
+/// A \c MemorySegmentMapped object in the read-only mode cannot modify the
+/// mapped memory image or other internal maintenance data of the object;
+/// In the read-write mode the object can allocate or deallocate memory
+/// from the mapped image, and the owner process can change the content.
+///
+/// Multiple processes can open multiple segments for the same file in
+/// read-only mode at the same time. But there shouldn't be more than
+/// one process that opens segments for the same file in read-write mode
+/// at the same time. Likewise, if one process opens a segment for a
+/// file in read-write mode, there shouldn't be any other process that
+/// opens a segment for the file in read-only mode. If one or more
+/// processes open segments for a file in read-only mode, there
+/// shouldn't be any other process that opens a segment for the file in
+/// read-write mode. This class tries to detect any violation of this
+/// restriction, but this does not intend to provide 100% safety. It's
+/// generally the user's responsibility to ensure this condition.
+///
+/// The same restriction applies within the single process, whether
+/// multi-threaded or not: a process shouldn't open read-only and read-write
+/// (or multiple read-write) segments for the same file. The violation
+/// detection mentioned above may or may not work in such cases due to
+/// limitation of the underlying API. It's completely user's responsibility
+/// to prevent this from happening. A single process may open multiple
+/// segments in read-only mode for the same file, but that shouldn't be
+/// necessary in practice; since it's read-only there wouldn't be a reason
+/// to have a redundant copy.
+class MemorySegmentMapped : boost::noncopyable, public MemorySegment {
+public:
+ /// \brief The default value of the mapped file size when newly created.
+ ///
+ /// Its value, 32KB, is an arbitrary choice, but considered to be
+ /// sufficiently but not too large.
+ static const size_t INITIAL_SIZE = 32768;
+
+ /// \brief Open modes of \c MemorySegmentMapped.
+ ///
+ /// These modes matter only for \c MemorySegmentMapped to be opened
+ /// in read-write mode, and specify further details of open operation.
+ enum OpenMode {
+ OPEN_FOR_WRITE = 0, ///< Open only. File must exist.
+ OPEN_OR_CREATE, ///< If file doesn't exist it's created.
+ CREATE_ONLY ///< New file is created; existing one will be removed.
+ };
+
+ /// \brief Constructor in the read-only mode.
+ ///
+ /// This constructor will map the content of the given file into memory
+ /// in read-only mode; the resulting memory segment object cannot
+ /// be used with methods that would require the mapped memory (see method
+ /// descriptions). Also, if the application tries to modify memory in
+ /// the segment, it will make the application crash.
+ ///
+ /// The file must have been created by the other version of the
+ /// constructor beforehand and must be readable for the process
+ /// constructing this object. Otherwise \c MemorySegmentOpenError
+ /// exception will be thrown.
+ ///
+ /// \throw MemorySegmentOpenError The given file does not exist, is not
+ /// readable, or not valid mappable segment. Or there is another process
+ /// that has already opened a segment for the file.
+ /// \throw std::bad_alloc (rare case) internal resource allocation
+ /// failure.
+ ///
+ /// \param filename The file name to be mapped to memory.
+ MemorySegmentMapped(const std::string& filename);
+
+ /// \brief Constructor in the read-write mode.
+ ///
+ /// This is similar to the read-only version of the constructor, but
+ /// does not have the restrictions that the read-only version has.
+ ///
+ /// The \c mode parameter specifies further details of how the segment
+ /// should be opened.
+ /// - OPEN_FOR_WRITE: this is open-only mode. The file must exist,
+ /// and it will be opened without any initial modification.
+ /// - OPEN_OR_CREATE: similar to OPEN_FOR_WRITE, but if the file does not
+ /// exist, a new one will be created. An existing file will be used
+ /// any initial modification.
+ /// - CREATE_ONLY: a new file (of the given file name) will be created;
+ /// any existing file of the same name will be removed.
+ ///
+ /// If OPEN_FOR_WRITE is specified, the specified file must exist
+ /// and be writable, and have been previously initialized by this
+ /// version of constructor either with OPEN_OR_CREATE or CREATE_ONLY.
+ /// If the mode is OPEN_OR_CREATE or CREATE_ONLY, and the file needs
+ /// to be created, then this method tries to create a new file of the
+ /// name and build internal data on it so that the file will be mappable
+ /// by this class object. If any of these conditions is not met, or
+ /// create or initialization fails, \c MemorySegmentOpenError exception
+ /// will be thrown.
+ ///
+ /// This constructor also throws \c MemorySegmentOpenError when it
+ /// detects violation of the restriction on the mixed open of read-only
+ /// and read-write mode (see the class description).
+ ///
+ /// When initial_size is specified but is too small (including a value of
+ /// 0), the underlying Boost library will reject it, and this constructor
+ /// throws \c MemorySegmentOpenError exception. The Boost documentation
+ /// does not specify how large it should be, but the default
+ /// \c INITIAL_SIZE should be sufficiently large in practice.
+ ///
+ /// \throw MemorySegmentOpenError see the description.
+ ///
+ /// \param filename The file name to be mapped to memory.
+ /// \param mode Open mode (see the description).
+ /// \param initial_size Specifies the size of the newly created file;
+ /// ignored if \c mode is OPEN_FOR_WRITE.
+ MemorySegmentMapped(const std::string& filename, OpenMode mode,
+ size_t initial_size = INITIAL_SIZE);
+
+ /// \brief Destructor.
+ ///
+ /// If the object was constructed in the read-write mode and the underlying
+ /// memory segment wasn't broken due to an exceptional event, the
+ /// destructor ensures the content of the mapped memory is written back to
+ /// the corresponding file.
+ virtual ~MemorySegmentMapped();
+
+ /// \brief Allocate/acquire a segment of memory.
+ ///
+ /// This version can throw \c MemorySegmentGrown. Furthermore, there is
+ /// a very small chance that the object loses its integrity and can't be
+ /// usable in the case where \c MemorySegmentGrown would be thrown.
+ /// In this case, throwing a different exception wouldn't help, because
+ /// an application trying to provide exception safety might then call
+ /// deallocate() or named address APIs on this object, which would simply
+ /// cause a crash. So, while suboptimal, this method just aborts the
+ /// program in this case, indicating there's no hope to shutdown cleanly.
+ ///
+ /// This method cannot be called if the segment object is created in the
+ /// read-only mode; in that case MemorySegmentError will be thrown.
+ virtual void* allocate(size_t size);
+
+ /// \brief Deallocate/release a segment of memory.
+ ///
+ /// This implementation does not check the validity of \c size, because
+ /// if this segment object was constructed for an existing file to map,
+ /// the underlying segment may already contain allocated regions, so
+ /// this object cannot reliably detect whether it's safe to deallocate
+ /// the given size of memory from the underlying segment.
+ ///
+ /// Parameter \c ptr must point to an address that was returned by a
+ /// prior call to \c allocate() of this segment object, and there should
+ /// not be a \c MemorySegmentGrown exception thrown from \c allocate()
+ /// since then; if it was thrown the corresponding address must have been
+ /// adjusted some way; e.g., by re-fetching the latest mapped address
+ /// via \c getNamedAddress().
+ ///
+ /// This method cannot be called if the segment object is created in the
+ /// read-only mode; in that case MemorySegmentError will be thrown.
+ virtual void deallocate(void* ptr, size_t size);
+
+ virtual bool allMemoryDeallocated() const;
+
+ /// \brief Mapped segment version of setNamedAddress.
+ ///
+ /// This implementation detects if \c addr is invalid (see the base class
+ /// description) and throws \c MemorySegmentError in that case.
+ ///
+ /// This version of method should normally return false. However,
+ /// it internally allocates memory in the segment for the name and
+ /// address to be stored, which can require segment extension, just like
+ /// allocate(). So it's possible to return true unlike
+ /// \c MemorySegmentLocal version of the method.
+ ///
+ /// This method cannot be called if the segment object is created in the
+ /// read-only mode; in that case MemorySegmentError will be thrown.
+ virtual bool setNamedAddressImpl(const char* name, void* addr);
+
+ /// \brief Mapped segment version of getNamedAddress.
+ ///
+ /// This version never throws.
+ virtual NamedAddressResult getNamedAddressImpl(const char* name) const;
+
+ /// \brief Mapped segment version of clearNamedAddress.
+ ///
+ /// This method cannot be called if the segment object is created in the
+ /// read-only mode; in that case MemorySegmentError will be thrown.
+ virtual bool clearNamedAddressImpl(const char* name);
+
+ /// \brief Shrink the underlying mapped segment to actually used size.
+ ///
+ /// When a large amount of memory is allocated and then deallocated
+ /// from the segment, this method can be used to keep the resulting
+ /// segment at a reasonable size.
+ ///
+ /// This method works by a best-effort basis, and does not guarantee
+ /// any specific result.
+ ///
+ /// This method is generally expected to be failure-free, but it's still
+ /// possible to fail. For example, the underlying file may not be writable
+ /// at the time of shrink attempt; it also tries to remap the shrunk
+ /// segment internally, and there's a small chance it could fail.
+ /// In such a case it throws \c MemorySegmentError. If it's thrown the
+ /// segment is not usable anymore.
+ ///
+ /// This method cannot be called if the segment object is created in the
+ /// read-only mode; in that case MemorySegmentError will be thrown.
+ ///
+ /// \throw MemorySegmentError see the description.
+ void shrinkToFit();
+
+ /// \brief Return the actual segment size.
+ ///
+ /// This is generally expected to be the file size to map. It's
+ /// provided mainly for diagnosis and testing purposes; the application
+ /// shouldn't rely on specific return values of this method.
+ ///
+ /// \throw None
+ size_t getSize() const;
+
+ /// \brief Calculate a checksum over the memory segment.
+ ///
+ /// This method goes over all pages of the underlying mapped memory
+ /// segment, and returns the sum of the value of the first byte of each
+ /// page (wrapping around upon overflow). It only proves weak integrity
+ /// of the file contents, but can run fast enough and will ensure all
+ /// pages are actually in memory. The latter property will be useful
+ /// if the application cannot allow the initial page fault overhead.
+ ///
+ /// \throw None
+ size_t getCheckSum() const;
+
+private:
+ struct Impl;
+ Impl* impl_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // MEMORY_SEGMENT_MAPPED_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/python/.gitignore b/src/lib/util/python/.gitignore
index c54df80..619c69f 100644
--- a/src/lib/util/python/.gitignore
+++ b/src/lib/util/python/.gitignore
@@ -1,2 +1,3 @@
+/doxygen2pydoc.py
/gen_wiredata.py
/mkpywrapper.py
diff --git a/src/lib/util/python/Makefile.am b/src/lib/util/python/Makefile.am
index 1e05688..c7ddf3d 100644
--- a/src/lib/util/python/Makefile.am
+++ b/src/lib/util/python/Makefile.am
@@ -1,3 +1,3 @@
-noinst_SCRIPTS = gen_wiredata.py mkpywrapper.py const2hdr.py \
+noinst_SCRIPTS = doxygen2pydoc.py gen_wiredata.py mkpywrapper.py const2hdr.py \
pythonize_constants.py
EXTRA_DIST = const2hdr.py pythonize_constants.py
diff --git a/src/lib/util/python/doxygen2pydoc.py.in b/src/lib/util/python/doxygen2pydoc.py.in
new file mode 100755
index 0000000..7aa74ec
--- /dev/null
+++ b/src/lib/util/python/doxygen2pydoc.py.in
@@ -0,0 +1,680 @@
+#!@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.
+
+r"""
+A helper to semi-auto generate Python docstring text from C++ Doxygen
+documentation.
+
+This script converts an XML-format doxygen documentation for C++ library
+into a template Python docstring for the corresponding Python version
+of the library. While it's not perfect and you'll still need to edit the
+output by hand, but past experiments showed the script produces a pretty
+good template. It will help provide more compatible documentation for
+both C++ and Python versions of library from a unified source (C++ Doxygen
+documentation) with minimizing error-prone and boring manual conversion.
+
+HOW TO USE IT
+
+1. Generate XML output by doxygen. Use bind10/doc/Doxyfile-xml:
+
+ % cd bind10/doc
+ % doxygen Doxyfile-xml
+ (XML files will be generated under bind10/doc/html/xml)
+
+2. Identify the xml file of the conversion target (C++ class, function, etc)
+
+ This is a bit tricky. You'll probably need to do manual search.
+ For example, to identify the xml file for a C++ class
+ isc::datasrc::memory::ZoneWriter, you might do:
+
+ % cd bind10/doc/html/xml
+ % grep ZoneWriter *.xml | grep 'kind="class"'
+ index.xml: <compound refid="d4/d3c/classisc_1_1datasrc_1_1memory_1_1ZoneWriter" kind="class"><name>isc::datasrc::memory::ZoneWriter</name>
+
+ In this case the file under the d4/d3c directory (with .xml suffix) would
+ be the file you're looking for.
+
+3. Run this script for the xml file:
+
+ % python3 doxygen2pydoc.py <top_srcdir>/doc/html/xml/d4/d3c/classisc_1_1datasrc_1_1memory_1_1ZoneWriter.xml > output.cc
+
+ The template content is dumped to standard out (redirected to file
+ "output.cc" in this example).
+
+ Sometimes the script produces additional output to standard error,
+ like this:
+
+ Replaced camelCased terms:
+ resetMemorySegment => reset_memory_segment
+ getConfiguration => get_configuration
+
+ In BIND 10 naming convention for methods is different for C++ and
+ Python. This script uses some heuristic guess to convert the
+ C++-style method names to likely Python-style ones, and the converted
+ method names are used in the dumped template. In many cases the guessed
+ names are correct, but you should check this list and make adjustments
+ by hand if necessary.
+
+ If there's no standard error output, this type of conversion didn't
+ happen.
+
+4. Edit and copy the template
+
+ The dumped template has the following organization:
+
+ namespace {
+ #ifdef COPY_THIS_TO_MAIN_CC
+ { "cleanup", ZoneWriter_cleanup, METH_NOARGS,
+ ZoneWriter_cleanup_doc },
+ { "install", ZoneWriter_install, METH_NOARGS,
+ ZoneWriter_install_doc },
+ { "load", ZoneWriter_load, METH_VARARGS,
+ ZoneWriter_load_doc },
+ #endif // COPY_THIS_TO_MAIN_CC
+
+ const char* const ZoneWriter_doc = "\
+ ...
+ ";
+
+ const char* const ZoneWriter_install_doc = "\
+ ...
+ ";
+
+ ...
+ }
+
+ The ifdef-ed block is a template for class methods information
+ to be added to the corresponding PyMethodDef structure array
+ (your wrapper C++ source would have something like ZoneWriter_methods
+ of this type). These lines should be copied there. As long as
+ the method names and corresponding wrapper function (such as
+ ZoneWriter_cleanup) are correct you shouldn't have to edit this part
+ (and they would be normally correct, unless the guessed method name
+ conversion was needed).
+
+ The rest of the content is a sequence of constant C-string variables.
+ Usually the first variable corresponds to the class description, and
+ the rest are method descriptions (note that ZoneWriter_install_doc
+ is referenced from the ifdef-ed block). The content of this part
+ would generally make sense, but you'll often need to make some
+ adjsutments by hand. A common examples of such adjustment is to
+ replace "NULL" with "None". Also, it's not uncommon that some part
+ of the description simply doesn't apply to the Python version or
+ that Python specific notes are needed. So go through the description
+ carefully and make necessary changes. A common practice is to add
+ comments for a summary of adjustments like this:
+
+ // Modifications:
+ // NULL->None
+ // - removed notes about derived classes (which doesn't apply for python)
+ const char* const ZoneWriter_doc = "\
+ ...
+ ";
+ This note will help next time you need to auto-generate and edit the
+ template (probably because the original C++ document is updated).
+
+ You can simply copy this part to the main C++ wrapper file, but since
+ it's relatively large a common practice is to maintain it in a separate
+ file that is exclusively included from the main file: if the name of
+ the main file is zonewriter_python.cc, the pydoc strings would be copied
+ in zonewriter_python_inc.cc, and the main file would have this line:
+
+ #include "zonewriter_inc.cc"
+
+ (In case you are C++ language police: it's okay to use the unnamed
+ name space for a file to be included because it's essentially a part
+ of the single .cc file, not expected to be included by others).
+
+ In either case, the ifdef-ed part should be removed.
+
+ADVANCED FEATURES
+
+You can use a special "xmlonly" doxygen command in C++ doxygent document
+in order to include Python code excerpt (while hiding it from the doxygen
+output for the C++ version). This command will be converted to
+a special XML tag in the XML output.
+
+The block enclosed by \xmlonly and \endxmlonly should contain
+a verbatim XML tag named "pythonlisting", in which the python code should
+be placed.
+/// \code
+/// Name name("example.com");
+/// std::cout << name.toText() << std::endl;
+/// \endcode
+///
+/// \xmlonly <pythonlisting>
+/// name = Name("example.com")
+/// print(name.to_text())
+/// </pythonlisting> \endxmlonly
+
+Note that there must be a blank line between \endcode and \xmlonly.
+doxygen2pydoc assume the pythonlisting tag is in a separate <para> node.
+This blank ensures doxygen will produce the XML file that meets the
+assumption.
+
+INTERNAL MEMO (incomplete, and not very unredable yet)
+
+This simplified utility assumes the following structure:
+...
+ <compounddef ...>
+ <compoundname>isc::dns::TSIGError</compoundname>
+ <sectiondef kind="user-defined">
+ constructor, destructor
+ </sectiondef>
+ <sectiondef kind="public-type">
+ ..
+ </sectiondef>
+ <sectiondef kind="public-func">
+ <memberdef kind="function"...>
+ <type>return type (if any)</type>
+ <argsstring>(...) [const]</argsstring>
+ <name>method name</name>
+ <briefdescription>method's brief description</briefdescription>
+ <detaileddescription>
+ <para>...</para>...
+ <para>
+ <parameterlist kind="exception">
+ <parameteritem>
+ <parameternamelist>
+ <parametername>Exception name</parametername>
+ </parameternamelist>
+ <parameterdescription>
+ <para>exception desc</para>
+ </parameterdescription>
+ </parameteritem>
+ </parameterlist>
+ <parameterlist kind="param">
+ <parameteritem>
+ <parameternamelist>
+ <parametername>param name</parametername>
+ </parameternamelist>
+ <parameterdescription>
+ <para>param desc</para>
+ </parameterdescription>
+ </parameteritem>
+ ...
+ </parameterlist>
+ <simplesect kind="return">Return value</simplesect>
+ </para>
+ </detaileddescription>
+ </memberdef>
+ </sectiondef>
+ <sectiondef kind="public-static-attrib|user-defined">
+ <memberdef kind="variable"...>
+ <name>class-specific-constant</name>
+ <initializer>value</initializer>
+ <brief|detaileddescription>paragraph(s)</brief|detaileddescription>
+ </sectiondef>
+ <briefdescription>
+ class's brief description
+ </briefdescription>
+ <detaileddescription>
+ class's detailed description
+ </detaileddescription>
+ </compounddef>
+"""
+
+import re, string, sys, textwrap
+from xml.dom.minidom import parse
+from textwrap import fill, dedent, TextWrapper
+
+camel_replacements = {}
+member_functions = []
+constructors = []
+class_variables = []
+
+RE_CAMELTERM = re.compile('([\s\.]|^)[a-z]+[A-Z]\S*')
+RE_SIMPLECAMEL = re.compile("([a-z])([A-Z])")
+RE_CAMELAFTERUPPER = re.compile("([A-Z])([A-Z])([a-z])")
+
+class Paragraph:
+ TEXT = 0
+ ITEMIZEDLIST = 1
+ CPPLISTING = 2
+ PYLISTING = 3
+ VERBATIM = 4
+
+ def __init__(self, xml_node):
+ if len(xml_node.getElementsByTagName("pythonlisting")) > 0:
+ self.type = self.PYLISTING
+ self.text = re.sub("///", "", get_text(xml_node))
+ elif len(xml_node.getElementsByTagName("verbatim")) > 0:
+ self.type = self.VERBATIM
+ self.text = get_text(xml_node)
+ elif len(xml_node.getElementsByTagName("programlisting")) > 0:
+ # We ignore node containing a "programlisting" tag.
+ # They are C++ example code, and we are not interested in them
+ # in pydoc.
+ self.type = self.CPPLISTING
+ elif len(xml_node.getElementsByTagName("itemizedlist")) > 0:
+ self.type = self.ITEMIZEDLIST
+ self.items = []
+ for item in xml_node.getElementsByTagName("listitem"):
+ self.items.append(get_text(item))
+ else:
+ self.type = self.TEXT
+
+ # A single textual paragraph could have multiple simple sections
+ # if it contains notes.
+
+ self.texts = []
+ subnodes = []
+ for child in xml_node.childNodes:
+ if child.nodeType == child.ELEMENT_NODE and \
+ child.nodeName == 'simplesect' and \
+ child.getAttribute('kind') == 'note':
+ if len(subnodes) > 0:
+ self.texts.append(get_text_fromnodelist(subnodes))
+ subnodes = []
+ subtext = 'Note: '
+ for t in child.childNodes:
+ subtext += get_text(t)
+ self.texts.append(subtext)
+ else:
+ subnodes.append(child)
+ if len(subnodes) > 0:
+ self.texts.append(get_text_fromnodelist(subnodes))
+
+ def dump(self, f, wrapper):
+ if self.type == self.CPPLISTING:
+ return
+ elif self.type == self.ITEMIZEDLIST:
+ for item in self.items:
+ item_wrapper = TextWrapper(\
+ initial_indent=wrapper.initial_indent + "- ",
+ subsequent_indent=wrapper.subsequent_indent + " ")
+ dump_filled_text(f, item_wrapper, item)
+ f.write("\\n\\\n")
+ elif self.type == self.TEXT:
+ for text in self.texts:
+ if text != self.texts[0]:
+ f.write("\\n\\\n")
+ dump_filled_text(f, wrapper, text)
+ f.write("\\n\\\n")
+ else:
+ dump_filled_text(f, None, self.text)
+ f.write("\\n\\\n")
+ f.write("\\n\\\n")
+
+class NamedItem:
+ def __init__(self, name, desc):
+ self.name = name
+ self.desc = desc
+
+ def dump(self, f, wrapper):
+ # we use deeper indent inside the item list.
+ new_initial_indent = wrapper.initial_indent + " " * 2
+ new_subsequent_indent = wrapper.subsequent_indent + " " * (2 + 11)
+ local_wrapper = TextWrapper(initial_indent=new_initial_indent,
+ subsequent_indent=new_subsequent_indent)
+
+ # concatenate name and description with a fixed width (up to 10 chars)
+ # for the name, and wrap the entire text, then dump it to file.
+ dump_filled_text(f, local_wrapper, "%-10s %s" % (self.name, self.desc))
+ f.write("\\n\\\n")
+
+class FunctionDefinition:
+ # function types
+ CONSTRUCTOR = 0
+ COPY_CONSTRUCTOR = 1
+ DESTRUCTOR = 2
+ ASSIGNMENT_OP = 3
+ OTHER = 4
+
+ def __init__(self):
+ self.type = self.OTHER
+ self.name = None
+ self.pyname = None
+ self.args = ""
+ self.ret_type = None
+ self.brief_desc = None
+ self.detailed_desc = []
+ self.exceptions = []
+ self.parameters = []
+ self.returns = None
+ self.have_param = False
+
+ def dump_doc(self, f, wrapper=TextWrapper()):
+ f.write(self.pyname + "(" + self.args + ")")
+ if self.ret_type is not None:
+ f.write(" -> " + self.ret_type)
+ f.write("\\n\\\n\\n\\\n")
+
+ if self.brief_desc is not None:
+ dump_filled_text(f, wrapper, self.brief_desc)
+ f.write("\\n\\\n\\n\\\n")
+
+ for para in self.detailed_desc:
+ para.dump(f, wrapper)
+
+ if len(self.exceptions) > 0:
+ f.write(wrapper.fill("Exceptions:") + "\\n\\\n")
+ for ex_desc in self.exceptions:
+ ex_desc.dump(f, wrapper)
+ f.write("\\n\\\n")
+ if len(self.parameters) > 0:
+ f.write(wrapper.fill("Parameters:") + "\\n\\\n")
+ for param_desc in self.parameters:
+ param_desc.dump(f, wrapper)
+ f.write("\\n\\\n")
+ if self.returns is not None:
+ dump_filled_text(f, wrapper, "Return Value(s): " + self.returns)
+ f.write("\\n\\\n")
+
+ def dump_pymethod_def(self, f, class_name):
+ f.write(' { "' + self.pyname + '", ')
+ f.write(class_name + '_' + self.name + ', ')
+ if len(self.parameters) == 0:
+ f.write('METH_NOARGS,\n')
+ else:
+ f.write('METH_VARARGS,\n')
+ f.write(' ' + class_name + '_' + self.name + '_doc },\n')
+
+class VariableDefinition:
+ def __init__(self, nodelist):
+ self.value = None
+ self.brief_desc = None
+ self.detailed_desc = []
+
+ for node in nodelist:
+ if node.nodeName == "name":
+ self.name = get_text(node)
+ elif node.nodeName == "initializer":
+ self.value = get_text(node)
+ elif node.nodeName == "briefdescription":
+ self.brief_desc = get_text(node)
+ elif node.nodeName == "detaileddescription":
+ for para in node.childNodes:
+ if para.nodeName != "para":
+ # ignore surrounding empty nodes
+ continue
+ self.detailed_desc.append(Paragraph(para))
+
+ def dump_doc(self, f, wrapper=TextWrapper()):
+ name_value = self.name
+ if self.value is not None:
+ name_value += ' = ' + self.value
+ dump_filled_text(f, wrapper, name_value)
+ f.write('\\n\\\n')
+
+ desc_initial_indent = wrapper.initial_indent + " "
+ desc_subsequent_indent = wrapper.subsequent_indent + " "
+ desc_wrapper = TextWrapper(initial_indent=desc_initial_indent,
+ subsequent_indent=desc_subsequent_indent)
+ if self.brief_desc is not None:
+ dump_filled_text(f, desc_wrapper, self.brief_desc)
+ f.write("\\n\\\n\\n\\\n")
+
+ for para in self.detailed_desc:
+ para.dump(f, desc_wrapper)
+
+def dump_filled_text(f, wrapper, text):
+ """Fill given text using wrapper, and dump it to the given file
+ appending an escaped CR at each end of line.
+ """
+ filled_text = wrapper.fill(text) if wrapper is not None else text
+ f.write("".join(re.sub("\n", r"\\n\\\n", filled_text)))
+
+def camel_to_lowerscores(matchobj):
+ oldtext = matchobj.group(0)
+ newtext = re.sub(RE_SIMPLECAMEL, r"\1_\2", oldtext)
+ newtext = re.sub(RE_CAMELAFTERUPPER, r"\1_\2\3", newtext)
+ newtext = newtext.lower()
+ camel_replacements[oldtext] = newtext
+ return newtext.lower()
+
+def cpp_to_python(text):
+ text = text.replace("::", ".")
+ text = text.replace('"', '\\"')
+
+ # convert camelCase to "_"-concatenated format
+ # (e.g. getLength -> get_length)
+ return re.sub(RE_CAMELTERM, camel_to_lowerscores, text)
+
+def convert_type_name(type_name):
+ """Convert C++ type name to python type name using common conventions"""
+ # strip off leading 'const' and trailing '&/*'
+ type_name = re.sub("^const\S*", "", type_name)
+ type_name = re.sub("\S*[&\*]$", "", type_name)
+
+ # We often typedef smart pointers as [Const]TypePtr. Convert them to
+ # just "Type"
+ type_name = re.sub("^Const", "", type_name)
+ type_name = re.sub("Ptr$", "", type_name)
+
+ if type_name == "std::string":
+ return "string"
+ if re.search(r"(int\d+_t|size_t)", type_name):
+ return "integer"
+ return type_name
+
+def get_text(root, do_convert=True):
+ """Recursively extract bare text inside the specified node (root),
+ concatenate all extracted text and return the result.
+ """
+ nodelist = root.childNodes
+ rc = []
+ for node in nodelist:
+ if node.nodeType == node.TEXT_NODE:
+ if do_convert:
+ rc.append(cpp_to_python(node.data))
+ else:
+ rc.append(node.data)
+ elif node.nodeType == node.ELEMENT_NODE:
+ rc.append(get_text(node))
+ # return the result, removing any leading newlines (that often happens for
+ # brief descriptions, which will cause lines not well aligned)
+ return re.sub("^(\n*)", "", ''.join(rc))
+
+def get_text_fromnodelist(nodelist, do_convert=True):
+ """Recursively extract bare text inside the specified node (root),
+ concatenate all extracted text and return the result.
+ """
+ rc = []
+ for node in nodelist:
+ if node.nodeType == node.TEXT_NODE:
+ if do_convert:
+ rc.append(cpp_to_python(node.data))
+ else:
+ rc.append(node.data)
+ elif node.nodeType == node.ELEMENT_NODE:
+ rc.append(get_text(node))
+ # return the result, removing any leading newlines (that often happens for
+ # brief descriptions, which will cause lines not well aligned)
+ return re.sub("^(\n*)", "", ''.join(rc))
+
+def parse_parameters(nodelist):
+ rc = []
+ for node in nodelist:
+ if node.nodeName != "parameteritem":
+ continue
+ # for simplicity, we assume one parametername and one
+ # parameterdescription for each parameter.
+ name = get_text(node.getElementsByTagName("parametername")[0])
+ desc = get_text(node.getElementsByTagName("parameterdescription")[0])
+ rc.append(NamedItem(name, desc))
+ return rc
+
+def parse_function_description(func_def, nodelist):
+ for node in nodelist:
+ # nodelist contains beginning and ending empty text nodes.
+ # ignore them (otherwise they cause disruption below).
+ if node.nodeName != "para":
+ continue
+
+ if node.getElementsByTagName("parameterlist"):
+ # within this node there may be exception list, parameter list,
+ # and description for return value. parse and store them
+ # seprately.
+ for paramlist in node.getElementsByTagName("parameterlist"):
+ if paramlist.getAttribute("kind") == "exception":
+ func_def.exceptions = \
+ parse_parameters(paramlist.childNodes)
+ elif paramlist.getAttribute("kind") == "param":
+ func_def.parameters = \
+ parse_parameters(paramlist.childNodes)
+ if node.getElementsByTagName("simplesect"):
+ simplesect = node.getElementsByTagName("simplesect")[0]
+ if simplesect.getAttribute("kind") == "return":
+ func_def.returns = get_text(simplesect)
+ else:
+ # for normal text, python listing and itemized list, append them
+ # to the list of paragraphs
+ func_def.detailed_desc.append(Paragraph(node))
+
+def parse_function(func_def, class_name, nodelist):
+ for node in nodelist:
+ if node.nodeName == "name":
+ func_def.name = get_text(node, False)
+ func_def.pyname = cpp_to_python(func_def.name)
+ elif node.nodeName == "argsstring":
+ # extract parameter names only, assuming they immediately follow
+ # their type name + space, and are immeidatelly followed by
+ # either "," or ")". If it's a pointer or reference, */& is
+ # prepended to the parameter name without a space:
+ # e.g. (int var1, char *var2, Foo &var3)
+ args = get_text(node, False)
+ # extract parameter names, possibly with */&
+ func_def.args = ', '.join(re.findall(r"\s(\S+)[,)]", args))
+ # then remove any */& symbols
+ func_def.args = re.sub("[\*&]", "", func_def.args)
+ elif node.nodeName == "type" and node.hasChildNodes():
+ func_def.ret_type = convert_type_name(get_text(node, False))
+ elif node.nodeName == "param":
+ func_def.have_param = True
+ elif node.nodeName == "briefdescription":
+ func_def.brief_desc = get_text(node)
+ elif node.nodeName == "detaileddescription":
+ parse_function_description(func_def, node.childNodes)
+ # identify the type of function using the name and arg
+ if func_def.name == class_name and \
+ re.search("^\(const " + class_name + " &[^,]*$", args):
+ # This function is ClassName(const ClassName& param), which is
+ # the copy constructor.
+ func_def.type = func_def.COPY_CONSTRUCTOR
+ elif func_def.name == class_name:
+ # if it's not the copy ctor but the function name == class name,
+ # it's a constructor.
+ func_def.type = func_def.CONSTRUCTOR
+ elif func_def.name == "~" + class_name:
+ func_def.type = func_def.DESTRUCTOR
+ elif func_def.name == "operator=":
+ func_def.type = func_def.ASSIGNMENT_OP
+
+ # register the definition to the approriate list
+ if func_def.type == func_def.CONSTRUCTOR:
+ constructors.append(func_def)
+ elif func_def.type == func_def.OTHER:
+ member_functions.append(func_def)
+
+def parse_functions(class_name, nodelist):
+ for node in nodelist:
+ if node.nodeName == "memberdef" and \
+ node.getAttribute("kind") == "function":
+ func_def = FunctionDefinition()
+ parse_function(func_def, class_name, node.childNodes)
+
+def parse_class_variables(class_name, nodelist):
+ for node in nodelist:
+ if node.nodeName == "memberdef" and \
+ node.getAttribute("kind") == "variable":
+ class_variables.append(VariableDefinition(node.childNodes))
+
+def dump(f, class_name, class_brief_doc, class_detailed_doc):
+ f.write("namespace {\n")
+
+ f.write('#ifdef COPY_THIS_TO_MAIN_CC\n')
+ for func in member_functions:
+ func.dump_pymethod_def(f, class_name)
+ f.write('#endif // COPY_THIS_TO_MAIN_CC\n\n')
+
+ f.write("const char* const " + class_name + '_doc = "\\\n')
+ if class_brief_doc is not None:
+ f.write("".join(re.sub("\n", r"\\n\\\n", fill(class_brief_doc))))
+ f.write("\\n\\\n")
+ f.write("\\n\\\n")
+ if len(class_detailed_doc) > 0:
+ for para in class_detailed_doc:
+ para.dump(f, wrapper=TextWrapper())
+
+ # dump constructors
+ for func in constructors:
+ indent = " " * 4
+ func.dump_doc(f, wrapper=TextWrapper(initial_indent=indent,
+ subsequent_indent=indent))
+
+ # dump class variables
+ if len(class_variables) > 0:
+ f.write("Class constant data:\\n\\\n")
+ for var in class_variables:
+ var.dump_doc(f)
+
+ f.write("\";\n")
+
+ for func in member_functions:
+ f.write("\n")
+ f.write("const char* const " + class_name + "_" + func.name + \
+ "_doc = \"\\\n");
+ func.dump_doc(f)
+ f.write("\";\n")
+
+ f.write("} // unnamed namespace") # close namespace
+
+if __name__ == '__main__':
+ dom = parse(sys.argv[1])
+ class_elements = dom.getElementsByTagName("compounddef")[0].childNodes
+ class_brief_doc = None
+ class_detailed_doc = []
+ for node in class_elements:
+ if node.nodeName == "compoundname":
+ # class name is the last portion of the period-separated fully
+ # qualified class name. (this should exist)
+ class_name = re.split("\.", get_text(node))[-1]
+ if node.nodeName == "briefdescription":
+ # we assume a brief description consists at most one para
+ class_brief_doc = get_text(node)
+ elif node.nodeName == "detaileddescription":
+ # a detaild description consists of one or more paragraphs
+ for para in node.childNodes:
+ if para.nodeName != "para": # ignore surrounding empty nodes
+ continue
+ class_detailed_doc.append(Paragraph(para))
+ elif node.nodeName == "sectiondef" and \
+ node.getAttribute("kind") == "public-func":
+ parse_functions(class_name, node.childNodes)
+ elif node.nodeName == "sectiondef" and \
+ node.getAttribute("kind") == "public-static-attrib":
+ parse_class_variables(class_name, node.childNodes)
+ elif node.nodeName == "sectiondef" and \
+ node.getAttribute("kind") == "user-defined":
+ # there are two possiblities: functions and variables
+ for child in node.childNodes:
+ if child.nodeName != "memberdef":
+ continue
+ if child.getAttribute("kind") == "function":
+ parse_function(FunctionDefinition(), class_name,
+ child.childNodes)
+ elif child.getAttribute("kind") == "variable":
+ class_variables.append(VariableDefinition(child.childNodes))
+
+ dump(sys.stdout, class_name, class_brief_doc, class_detailed_doc)
+
+ if len(camel_replacements) > 0:
+ sys.stderr.write("Replaced camelCased terms:\n")
+ for oldterm in camel_replacements.keys():
+ sys.stderr.write("%s => %s\n" % (oldterm,
+ camel_replacements[oldterm]))
diff --git a/src/lib/util/python/gen_wiredata.py.in b/src/lib/util/python/gen_wiredata.py.in
index 55724c9..b3858b6 100755
--- a/src/lib/util/python/gen_wiredata.py.in
+++ b/src/lib/util/python/gen_wiredata.py.in
@@ -138,7 +138,7 @@ the type. But there are some common configurable entries. See the
description of the RR class. The most important one would be "as_rr".
It controls whether the entry should be treated as an RR (with name,
type, class and TTL) or only as an RDATA. By default as_rr is
-"False", so if an entry is to be intepreted as an RR, an as_rr entry
+"False", so if an entry is to be interpreted as an RR, an as_rr entry
must be explicitly specified with a value of "True".
Another common entry is "rdlen". It specifies the RDLEN field value
@@ -325,7 +325,7 @@ What you are expected to do is as follows:
examples.
"""
-import configparser, re, time, socket, sys
+import configparser, re, time, socket, sys, base64
from datetime import datetime
from optparse import OptionParser
@@ -413,6 +413,11 @@ def encode_string(name, len=None):
return '%0.*x' % (len * 2, name)
return ''.join(['%02x' % ord(ch) for ch in name])
+def encode_bytes(name, len=None):
+ if type(name) is int and len is not None:
+ return '%0.*x' % (len * 2, name)
+ return ''.join(['%02x' % ch for ch in name])
+
def count_namelabels(name):
if name == '.': # special case
return 0
@@ -762,7 +767,7 @@ class TXT(RR):
value will be used. If this parameter isn't specified either,
the length of the string will be used. Note that it means
this parameter (or any stringlenN) doesn't have to be specified
- unless you want to intentially build a broken character string.
+ unless you want to intentionally build a broken character string.
- string (string): the default string. If nstring >= 1 and the
corresponding stringN isn't specified in the spec file, this
string will be used.
@@ -888,6 +893,42 @@ class AFSDB(RR):
f.write('# SUBTYPE=%d SERVER=%s\n' % (self.subtype, self.server))
f.write('%04x %s\n' % (self.subtype, server_wire))
+class DNSKEY(RR):
+ '''Implements rendering DNSKEY RDATA in the test data format.
+
+ Configurable parameters are as follows (see code below for the
+ default values):
+ - flags (16-bit int): The flags field.
+ - protocol (8-bit int): The protocol field.
+ - algorithm (8-bit int): The algorithm field.
+ - digest (string): The key digest field.
+ '''
+ flags = 257
+ protocol = 3
+ algorithm = 5
+ digest = 'AAECAwQFBgcICQoLDA0ODw=='
+
+ def dump(self, f):
+ decoded_digest = base64.b64decode(bytes(self.digest, 'ascii'))
+ if self.rdlen is None:
+ self.rdlen = 4 + len(decoded_digest)
+ else:
+ self.rdlen = int(self.rdlen)
+
+ self.dump_header(f, self.rdlen)
+
+ f.write('# FLAGS=%d\n' % (self.flags))
+ f.write('%04x\n' % (self.flags))
+
+ f.write('# PROTOCOL=%d\n' % (self.protocol))
+ f.write('%02x\n' % (self.protocol))
+
+ f.write('# ALGORITHM=%d\n' % (self.algorithm))
+ f.write('%02x\n' % (self.algorithm))
+
+ f.write('# DIGEST=%s\n' % (self.digest))
+ f.write('%s\n' % (encode_bytes(decoded_digest)))
+
class NSECBASE(RR):
'''Implements rendering NSEC/NSEC3 type bitmaps commonly used for
these RRs. The NSEC and NSEC3 classes will be inherited from this
@@ -914,9 +955,9 @@ class NSECBASE(RR):
nbitmap = 1 # number of bitmaps
block = 0
maplen = None # default bitmap length, auto-calculate
- bitmap = '040000000003' # an arbtrarily chosen bitmap sample
+ bitmap = '040000000003' # an arbitrarily chosen bitmap sample
def dump(self, f):
- # first, construct the bitmpa data
+ # first, construct the bitmap data
block_list = []
maplen_list = []
bitmap_list = []
diff --git a/src/lib/util/random/qid_gen.h b/src/lib/util/random/qid_gen.h
index 80f532f..fd7ce36 100644
--- a/src/lib/util/random/qid_gen.h
+++ b/src/lib/util/random/qid_gen.h
@@ -25,6 +25,8 @@
#include <boost/random/uniform_int.hpp>
#include <boost/random/variate_generator.hpp>
+#include <stdint.h>
+
namespace isc {
namespace util {
namespace random {
diff --git a/src/lib/util/random/random_number_generator.h b/src/lib/util/random/random_number_generator.h
index f0c0fb3..c765835 100644
--- a/src/lib/util/random/random_number_generator.h
+++ b/src/lib/util/random/random_number_generator.h
@@ -18,6 +18,7 @@
#include <algorithm>
#include <cmath>
#include <numeric>
+#include <vector>
#include <exceptions/exceptions.h>
diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am
index 105322f..ab85fa2 100644
--- a/src/lib/util/tests/Makefile.am
+++ b/src/lib/util/tests/Makefile.am
@@ -31,9 +31,12 @@ run_unittests_SOURCES += filename_unittest.cc
run_unittests_SOURCES += hex_unittest.cc
run_unittests_SOURCES += io_utilities_unittest.cc
run_unittests_SOURCES += lru_list_unittest.cc
-run_unittests_SOURCES += interprocess_sync_file_unittest.cc
-run_unittests_SOURCES += interprocess_sync_null_unittest.cc
run_unittests_SOURCES += memory_segment_local_unittest.cc
+if USE_SHARED_MEMORY
+run_unittests_SOURCES += memory_segment_mapped_unittest.cc
+endif
+run_unittests_SOURCES += memory_segment_common_unittest.h
+run_unittests_SOURCES += memory_segment_common_unittest.cc
run_unittests_SOURCES += qid_gen_unittest.cc
run_unittests_SOURCES += random_number_generator_unittest.cc
run_unittests_SOURCES += sha1_unittest.cc
diff --git a/src/lib/util/tests/buffer_unittest.cc b/src/lib/util/tests/buffer_unittest.cc
index 02ca83d..76b884c 100644
--- a/src/lib/util/tests/buffer_unittest.cc
+++ b/src/lib/util/tests/buffer_unittest.cc
@@ -248,6 +248,11 @@ TEST_F(BufferTest, outputBufferAssign) {
});
}
+// Check assign to self doesn't break stuff
+TEST_F(BufferTest, outputBufferAssignSelf) {
+ EXPECT_NO_THROW(obuffer = obuffer);
+}
+
TEST_F(BufferTest, outputBufferZeroSize) {
// Some OSes might return NULL on malloc for 0 size, so check it works
EXPECT_NO_THROW({
diff --git a/src/lib/util/tests/fd_share_tests.cc b/src/lib/util/tests/fd_share_tests.cc
index b8000e1..bb69204 100644
--- a/src/lib/util/tests/fd_share_tests.cc
+++ b/src/lib/util/tests/fd_share_tests.cc
@@ -66,7 +66,7 @@ TEST(FDShare, transfer) {
if (close(pipes[0])) {
exit(1);
}
- // Send "data" trough the received fd, close it and be done
+ // Send "data" through the received fd, close it and be done
if (!write_data(fd, "data", 4) || close(fd) == -1) {
exit(1);
}
diff --git a/src/lib/util/tests/interprocess_sync_file_unittest.cc b/src/lib/util/tests/interprocess_sync_file_unittest.cc
deleted file mode 100644
index 6f23558..0000000
--- a/src/lib/util/tests/interprocess_sync_file_unittest.cc
+++ /dev/null
@@ -1,183 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include "util/interprocess_sync_file.h"
-
-#include <util/unittests/check_valgrind.h>
-#include <gtest/gtest.h>
-#include <unistd.h>
-
-using namespace std;
-
-namespace isc {
-namespace util {
-
-namespace {
-unsigned char
-parentReadLockedState (int fd) {
- unsigned char locked = 0xff;
-
- fd_set rfds;
- FD_ZERO(&rfds);
- FD_SET(fd, &rfds);
-
- // We use select() here to wait for new data on the input end of
- // the pipe. We wait for 5 seconds (an arbitrary value) for input
- // data, and continue if no data is available. This is done so
- // that read() is not blocked due to some issue in the child
- // process (and the tests continue running).
-
- struct timeval tv;
- tv.tv_sec = 5;
- tv.tv_usec = 0;
-
- const int nfds = select(fd + 1, &rfds, NULL, NULL, &tv);
- EXPECT_EQ(1, nfds);
-
- if (nfds == 1) {
- // Read status
- ssize_t bytes_read = read(fd, &locked, sizeof(locked));
- EXPECT_EQ(sizeof(locked), bytes_read);
- }
-
- return (locked);
-}
-
-TEST(InterprocessSyncFileTest, TestLock) {
- InterprocessSyncFile sync("test");
- InterprocessSyncLocker locker(sync);
-
- EXPECT_FALSE(locker.isLocked());
- EXPECT_TRUE(locker.lock());
- EXPECT_TRUE(locker.isLocked());
-
- if (!isc::util::unittests::runningOnValgrind()) {
-
- int fds[2];
-
- // Here, we check that a lock has been taken by forking and
- // checking from the child that a lock exists. This has to be
- // done from a separate process as we test by trying to lock the
- // range again on the lock file. The lock attempt would pass if
- // done from the same process for the granted range. The lock
- // attempt must fail to pass our check.
-
- EXPECT_EQ(0, pipe(fds));
-
- if (fork() == 0) {
- unsigned char locked = 0;
- // Child writes to pipe
- close(fds[0]);
-
- InterprocessSyncFile sync2("test");
- InterprocessSyncLocker locker2(sync2);
-
- if (!locker2.tryLock()) {
- EXPECT_FALSE(locker2.isLocked());
- locked = 1;
- } else {
- EXPECT_TRUE(locker2.isLocked());
- }
-
- ssize_t bytes_written = write(fds[1], &locked, sizeof(locked));
- EXPECT_EQ(sizeof(locked), bytes_written);
-
- close(fds[1]);
- exit(0);
- } else {
- // Parent reads from pipe
- close(fds[1]);
-
- const unsigned char locked = parentReadLockedState(fds[0]);
-
- close(fds[0]);
-
- EXPECT_EQ(1, locked);
- }
- }
-
- EXPECT_TRUE(locker.unlock());
- EXPECT_FALSE(locker.isLocked());
-
- EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test_lockfile"));
-}
-
-TEST(InterprocessSyncFileTest, TestMultipleFilesDirect) {
- InterprocessSyncFile sync("test1");
- InterprocessSyncLocker locker(sync);
-
- EXPECT_TRUE(locker.lock());
-
- InterprocessSyncFile sync2("test2");
- InterprocessSyncLocker locker2(sync2);
- EXPECT_TRUE(locker2.lock());
- EXPECT_TRUE(locker2.unlock());
-
- EXPECT_TRUE(locker.unlock());
-
- EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test1_lockfile"));
- EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test2_lockfile"));
-}
-
-TEST(InterprocessSyncFileTest, TestMultipleFilesForked) {
- InterprocessSyncFile sync("test1");
- InterprocessSyncLocker locker(sync);
-
- EXPECT_TRUE(locker.lock());
-
- if (!isc::util::unittests::runningOnValgrind()) {
-
- int fds[2];
-
- EXPECT_EQ(0, pipe(fds));
-
- if (fork() == 0) {
- unsigned char locked = 0xff;
- // Child writes to pipe
- close(fds[0]);
-
- InterprocessSyncFile sync2("test2");
- InterprocessSyncLocker locker2(sync2);
-
- if (locker2.tryLock()) {
- locked = 0;
- }
-
- ssize_t bytes_written = write(fds[1], &locked, sizeof(locked));
- EXPECT_EQ(sizeof(locked), bytes_written);
-
- close(fds[1]);
- exit(0);
- } else {
- // Parent reads from pipe
- close(fds[1]);
-
- const unsigned char locked = parentReadLockedState(fds[0]);
-
- close(fds[0]);
-
- EXPECT_EQ(0, locked);
- }
-
- EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test2_lockfile"));
- }
-
- EXPECT_TRUE(locker.unlock());
-
- EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test1_lockfile"));
-}
-
-} // anonymous namespace
-} // namespace util
-} // namespace isc
diff --git a/src/lib/util/tests/interprocess_sync_null_unittest.cc b/src/lib/util/tests/interprocess_sync_null_unittest.cc
deleted file mode 100644
index 70e2b07..0000000
--- a/src/lib/util/tests/interprocess_sync_null_unittest.cc
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include "util/interprocess_sync_null.h"
-#include <gtest/gtest.h>
-
-using namespace std;
-
-namespace isc {
-namespace util {
-
-TEST(InterprocessSyncNullTest, TestNull) {
- InterprocessSyncNull sync("test1");
- InterprocessSyncLocker locker(sync);
-
- // Check if the is_locked_ flag is set correctly during lock().
- EXPECT_FALSE(locker.isLocked());
- EXPECT_TRUE(locker.lock());
- EXPECT_TRUE(locker.isLocked());
-
- // lock() must always return true (this is called 4 times, just an
- // arbitrary number)
- EXPECT_TRUE(locker.lock());
- EXPECT_TRUE(locker.lock());
- EXPECT_TRUE(locker.lock());
- EXPECT_TRUE(locker.lock());
-
- // Check if the is_locked_ flag is set correctly during unlock().
- EXPECT_TRUE(locker.isLocked());
- EXPECT_TRUE(locker.unlock());
- EXPECT_FALSE(locker.isLocked());
-
- // unlock() must always return true (this is called 4 times, just an
- // arbitrary number)
- EXPECT_TRUE(locker.unlock());
- EXPECT_TRUE(locker.unlock());
- EXPECT_TRUE(locker.unlock());
- EXPECT_TRUE(locker.unlock());
-
- // Check if the is_locked_ flag is set correctly during tryLock().
- EXPECT_FALSE(locker.isLocked());
- EXPECT_TRUE(locker.tryLock());
- EXPECT_TRUE(locker.isLocked());
-
- // tryLock() must always return true (this is called 4 times, just an
- // arbitrary number)
- EXPECT_TRUE(locker.tryLock());
- EXPECT_TRUE(locker.tryLock());
- EXPECT_TRUE(locker.tryLock());
- EXPECT_TRUE(locker.tryLock());
-
- // Random order (should all return true)
- EXPECT_TRUE(locker.unlock());
- EXPECT_TRUE(locker.lock());
- EXPECT_TRUE(locker.tryLock());
- EXPECT_TRUE(locker.lock());
- EXPECT_TRUE(locker.unlock());
- EXPECT_TRUE(locker.lock());
- EXPECT_TRUE(locker.tryLock());
- EXPECT_TRUE(locker.unlock());
- EXPECT_TRUE(locker.unlock());
-}
-
-} // namespace util
-} // namespace isc
diff --git a/src/lib/util/tests/io_utilities_unittest.cc b/src/lib/util/tests/io_utilities_unittest.cc
index 4293c7e..8e0c232 100644
--- a/src/lib/util/tests/io_utilities_unittest.cc
+++ b/src/lib/util/tests/io_utilities_unittest.cc
@@ -14,7 +14,7 @@
/// \brief Test of asiolink utilties
///
-/// Tests the fuctionality of the asiolink utilities code by comparing them
+/// Tests the functionality of the asiolink utilities code by comparing them
/// with the equivalent methods in isc::dns::[Input/Output]Buffer.
#include <cstddef>
diff --git a/src/lib/util/tests/lru_list_unittest.cc b/src/lib/util/tests/lru_list_unittest.cc
index c0201ea..5f740e0 100644
--- a/src/lib/util/tests/lru_list_unittest.cc
+++ b/src/lib/util/tests/lru_list_unittest.cc
@@ -311,7 +311,7 @@ TEST_F(LruListTest, Touch) {
EXPECT_EQ(1, entry7_.use_count());
EXPECT_EQ(3, lru.size());
- // Now touch the entry agin to move it to the back of the list.
+ // Now touch the entry again to move it to the back of the list.
// This checks that the iterator stored in the entry as a result of the
// last touch operation is valid.
lru.touch(entry1_);
diff --git a/src/lib/util/tests/memory_segment_common_unittest.cc b/src/lib/util/tests/memory_segment_common_unittest.cc
new file mode 100644
index 0000000..17719ae
--- /dev/null
+++ b/src/lib/util/tests/memory_segment_common_unittest.cc
@@ -0,0 +1,106 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/memory_segment.h>
+
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <cstring>
+#include <stdint.h>
+
+namespace isc {
+namespace util {
+namespace test {
+
+void
+checkSegmentNamedAddress(MemorySegment& segment, bool out_of_segment_ok) {
+ // NULL name is not allowed.
+ EXPECT_THROW(segment.getNamedAddress(NULL), InvalidParameter);
+
+ // If the name does not exist, false should be returned.
+ EXPECT_FALSE(segment.getNamedAddress("test address").first);
+
+ // Now set it
+ void* ptr32 = segment.allocate(sizeof(uint32_t));
+ const uint32_t test_val = 42;
+ *static_cast<uint32_t*>(ptr32) = test_val;
+ EXPECT_FALSE(segment.setNamedAddress("test address", ptr32));
+
+ // NULL name isn't allowed.
+ EXPECT_THROW(segment.setNamedAddress(NULL, ptr32), InvalidParameter);
+ EXPECT_THROW(segment.getNamedAddress(NULL), InvalidParameter);
+ EXPECT_THROW(segment.clearNamedAddress(NULL), InvalidParameter);
+
+ // Empty names are not allowed.
+ EXPECT_THROW(segment.setNamedAddress("", ptr32), InvalidParameter);
+ EXPECT_THROW(segment.getNamedAddress(""), InvalidParameter);
+ EXPECT_THROW(segment.clearNamedAddress(""), InvalidParameter);
+
+ // Names beginning with _ are not allowed.
+ EXPECT_THROW(segment.setNamedAddress("_foo", ptr32), InvalidParameter);
+ EXPECT_THROW(segment.getNamedAddress("_foo"), InvalidParameter);
+ EXPECT_THROW(segment.clearNamedAddress("_foo"), InvalidParameter);
+
+ // we can now get it; the stored value should be intact.
+ MemorySegment::NamedAddressResult result =
+ segment.getNamedAddress("test address");
+ EXPECT_TRUE(result.first);
+ EXPECT_EQ(test_val, *static_cast<const uint32_t*>(result.second));
+
+ // Override it.
+ void* ptr16 = segment.allocate(sizeof(uint16_t));
+ const uint16_t test_val16 = 4200;
+ *static_cast<uint16_t*>(ptr16) = test_val16;
+ EXPECT_FALSE(segment.setNamedAddress("test address", ptr16));
+ result = segment.getNamedAddress("test address");
+ EXPECT_TRUE(result.first);
+ EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(result.second));
+
+ // Clear it. Then we won't be able to find it any more.
+ EXPECT_TRUE(segment.clearNamedAddress("test address"));
+ EXPECT_FALSE(segment.getNamedAddress("test address").first);
+
+ // duplicate attempt of clear will result in false as it doesn't exist.
+ EXPECT_FALSE(segment.clearNamedAddress("test address"));
+
+ // Setting NULL is okay.
+ EXPECT_FALSE(segment.setNamedAddress("null address", NULL));
+ result = segment.getNamedAddress("null address");
+ EXPECT_TRUE(result.first);
+ EXPECT_FALSE(result.second);
+
+ // If the underlying implementation performs explicit check against
+ // out-of-segment address, confirm the behavior.
+ if (!out_of_segment_ok) {
+ uint8_t ch = 'A';
+ EXPECT_THROW(segment.setNamedAddress("local address", &ch),
+ MemorySegmentError);
+ }
+
+ // clean them up all
+ segment.deallocate(ptr32, sizeof(uint32_t));
+ EXPECT_FALSE(segment.allMemoryDeallocated()); // not fully deallocated
+ segment.deallocate(ptr16, sizeof(uint16_t)); // not yet
+ EXPECT_FALSE(segment.allMemoryDeallocated());
+ EXPECT_TRUE(segment.clearNamedAddress("null address"));
+ // null name isn't allowed:
+ EXPECT_THROW(segment.clearNamedAddress(NULL), InvalidParameter);
+ EXPECT_TRUE(segment.allMemoryDeallocated()); // now everything is gone
+}
+
+}
+}
+}
diff --git a/src/lib/util/tests/memory_segment_common_unittest.h b/src/lib/util/tests/memory_segment_common_unittest.h
new file mode 100644
index 0000000..ebc612b
--- /dev/null
+++ b/src/lib/util/tests/memory_segment_common_unittest.h
@@ -0,0 +1,36 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/memory_segment.h>
+
+namespace isc {
+namespace util {
+namespace test {
+
+/// \brief Implementation dependent checks on memory segment named addresses.
+///
+/// This function contains a set of test cases for given memory segment
+/// regarding "named address" methods. The test cases basically only depend
+/// on the base class interfaces, but if the underlying implementation does
+/// not check if the given address to setNamedAddress() belongs to the segment,
+/// out_of_segment_ok should be set to true.
+void checkSegmentNamedAddress(MemorySegment& segment, bool out_of_segment_ok);
+
+}
+}
+}
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/tests/memory_segment_local_unittest.cc b/src/lib/util/tests/memory_segment_local_unittest.cc
index 64b7292..5176c88 100644
--- a/src/lib/util/tests/memory_segment_local_unittest.cc
+++ b/src/lib/util/tests/memory_segment_local_unittest.cc
@@ -12,7 +12,9 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include "util/memory_segment_local.h"
+#include <util/tests/memory_segment_common_unittest.h>
+
+#include <util/memory_segment_local.h>
#include <exceptions/exceptions.h>
#include <gtest/gtest.h>
#include <memory>
@@ -106,4 +108,9 @@ TEST(MemorySegmentLocal, TestNullDeallocate) {
EXPECT_TRUE(segment->allMemoryDeallocated());
}
+TEST(MemorySegmentLocal, namedAddress) {
+ MemorySegmentLocal segment;
+ isc::util::test::checkSegmentNamedAddress(segment, true);
+}
+
} // anonymous namespace
diff --git a/src/lib/util/tests/memory_segment_mapped_unittest.cc b/src/lib/util/tests/memory_segment_mapped_unittest.cc
new file mode 100644
index 0000000..8ae6fea
--- /dev/null
+++ b/src/lib/util/tests/memory_segment_mapped_unittest.cc
@@ -0,0 +1,650 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/tests/memory_segment_common_unittest.h>
+#include <util/unittests/check_valgrind.h>
+#include <util/unittests/interprocess_util.h>
+
+#include <util/memory_segment_mapped.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/interprocess/file_mapping.hpp>
+#include <boost/interprocess/mapped_region.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/foreach.hpp>
+
+#include <stdint.h>
+#include <cstdlib>
+#include <cstring>
+#include <limits>
+#include <stdexcept>
+#include <fstream>
+#include <string>
+#include <vector>
+#include <map>
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+using namespace isc::util;
+using boost::scoped_ptr;
+using isc::util::unittests::parentReadState;
+
+namespace {
+// Shortcut to keep code shorter
+const MemorySegmentMapped::OpenMode OPEN_FOR_WRITE =
+ MemorySegmentMapped::OPEN_FOR_WRITE;
+const MemorySegmentMapped::OpenMode OPEN_OR_CREATE =
+ MemorySegmentMapped::OPEN_OR_CREATE;
+const MemorySegmentMapped::OpenMode CREATE_ONLY =
+ MemorySegmentMapped::CREATE_ONLY;
+
+const char* const mapped_file = TEST_DATA_BUILDDIR "/test.mapped";
+const size_t DEFAULT_INITIAL_SIZE = 32 * 1024; // intentionally hardcoded
+
+// A simple RAII-style wrapper for a pipe. Several tests in this file use
+// pipes, so this helper will be useful.
+class PipeHolder {
+public:
+ PipeHolder() {
+ if (pipe(fds_) == -1) {
+ isc_throw(isc::Unexpected, "pipe failed");
+ }
+ }
+ ~PipeHolder() {
+ close(fds_[0]);
+ close(fds_[1]);
+ }
+ int getReadFD() const { return (fds_[0]); }
+ int getWriteFD() const { return (fds_[1]); }
+private:
+ int fds_[2];
+};
+
+class MemorySegmentMappedTest : public ::testing::Test {
+protected:
+ MemorySegmentMappedTest() {
+ resetSegment();
+ }
+
+ ~MemorySegmentMappedTest() {
+ segment_.reset();
+ boost::interprocess::file_mapping::remove(mapped_file);
+ }
+
+ // For initialization and for tests after the segment possibly becomes
+ // broken.
+ void resetSegment() {
+ segment_.reset();
+ boost::interprocess::file_mapping::remove(mapped_file);
+ segment_.reset(new MemorySegmentMapped(mapped_file, OPEN_OR_CREATE));
+ }
+
+ scoped_ptr<MemorySegmentMapped> segment_;
+};
+
+TEST(MemorySegmentMappedConstantTest, staticVariables) {
+ // Attempt to take address of MemorySegmentMapped::INITIAL_SIZE.
+ // It helps in case we accidentally remove the definition from the main
+ // code.
+ EXPECT_EQ(DEFAULT_INITIAL_SIZE, *(&MemorySegmentMapped::INITIAL_SIZE));
+}
+
+TEST_F(MemorySegmentMappedTest, createAndModify) {
+ // We are going to do the same set of basic tests twice; one after creating
+ // the mapped file, the other by re-opening the existing file in the
+ // read-write mode.
+ for (int i = 0; i < 2; ++i) {
+ // It should have the default size (intentionally hardcoded)
+ EXPECT_EQ(DEFAULT_INITIAL_SIZE, segment_->getSize());
+
+ // By default, nothing is allocated.
+ EXPECT_TRUE(segment_->allMemoryDeallocated());
+
+ void* ptr = segment_->allocate(1024);
+ EXPECT_NE(static_cast<void*>(NULL), ptr);
+
+ // Now, we have an allocation:
+ EXPECT_FALSE(segment_->allMemoryDeallocated());
+
+ // deallocate it; it shouldn't cause disruption.
+ segment_->deallocate(ptr, 1024);
+
+ EXPECT_TRUE(segment_->allMemoryDeallocated());
+
+ // re-open it in read-write mode, but don't try to create it
+ // this time.
+ segment_.reset(); // make sure close is first.
+ segment_.reset(new MemorySegmentMapped(mapped_file, OPEN_FOR_WRITE));
+ }
+}
+
+TEST_F(MemorySegmentMappedTest, createWithSize) {
+ boost::interprocess::file_mapping::remove(mapped_file);
+
+ // Re-create the mapped file with a non-default initial size, and confirm
+ // the size is actually the specified one.
+ const size_t new_size = 64 * 1024;
+ EXPECT_NE(new_size, segment_->getSize());
+ segment_.reset();
+ segment_.reset(new MemorySegmentMapped(mapped_file, OPEN_OR_CREATE,
+ new_size));
+ EXPECT_EQ(new_size, segment_->getSize());
+}
+
+TEST_F(MemorySegmentMappedTest, createOnly) {
+ // First, allocate some data in the existing segment
+ EXPECT_TRUE(segment_->allocate(16));
+ // Close it, and then open it again in the create-only mode. the existing
+ // file should be internally removed, and so the resulting segment
+ // should be "empty" (all deallocated).
+ segment_.reset();
+ segment_.reset(new MemorySegmentMapped(mapped_file, CREATE_ONLY));
+ EXPECT_TRUE(segment_->allMemoryDeallocated());
+}
+
+TEST_F(MemorySegmentMappedTest, openFail) {
+ // The given file is directory
+ EXPECT_THROW(MemorySegmentMapped("/", OPEN_OR_CREATE),
+ MemorySegmentOpenError);
+
+ // file doesn't exist and directory isn't writable (we assume the
+ // following path is not writable for the user running the test).
+ EXPECT_THROW(MemorySegmentMapped("/random-glkwjer098/test.mapped",
+ OPEN_OR_CREATE), MemorySegmentOpenError);
+
+ // It should fail when file doesn't exist and it's read-only (so
+ // open-only).
+ EXPECT_THROW(MemorySegmentMapped(TEST_DATA_BUILDDIR "/nosuchfile.mapped"),
+ MemorySegmentOpenError);
+ // Likewise, it should fail in read-write mode when creation is
+ // suppressed.
+ EXPECT_THROW(MemorySegmentMapped(TEST_DATA_BUILDDIR "/nosuchfile.mapped",
+ OPEN_FOR_WRITE), MemorySegmentOpenError);
+
+ // creating with a very small size fails (for sure about 0, and other
+ // small values should also make it fail, but it's internal restriction
+ // of Boost and cannot be predictable).
+ EXPECT_THROW(MemorySegmentMapped(mapped_file, OPEN_OR_CREATE, 0),
+ MemorySegmentOpenError);
+
+ // invalid read-write mode
+ EXPECT_THROW(MemorySegmentMapped(
+ mapped_file,
+ static_cast<MemorySegmentMapped::OpenMode>(
+ static_cast<int>(CREATE_ONLY) + 1)),
+ isc::InvalidParameter);
+
+ // Close the existing segment, break its file with bogus data, and
+ // try to reopen. It should fail with exception whether in the
+ // read-only or read-write, or "create if not exist" mode.
+ segment_.reset();
+ std::ofstream ofs(mapped_file, std::ios::trunc);
+ ofs << std::string(1024, 'x');
+ ofs.close();
+ EXPECT_THROW(MemorySegmentMapped sgmt(mapped_file), MemorySegmentOpenError);
+ EXPECT_THROW(MemorySegmentMapped sgmt(mapped_file, OPEN_FOR_WRITE),
+ MemorySegmentOpenError);
+ EXPECT_THROW(MemorySegmentMapped sgmt(mapped_file, OPEN_OR_CREATE),
+ MemorySegmentOpenError);
+}
+
+TEST_F(MemorySegmentMappedTest, allocate) {
+ // Various case of allocation. The simplest cases are covered above.
+
+ // Initially, nothing is allocated.
+ EXPECT_TRUE(segment_->allMemoryDeallocated());
+
+ // (Clearly) exceeding the available size, which should cause growing
+ // the segment
+ const size_t prev_size = segment_->getSize();
+ EXPECT_THROW(segment_->allocate(prev_size + 1), MemorySegmentGrown);
+ // The size should have been doubled.
+ EXPECT_EQ(prev_size * 2, segment_->getSize());
+ // But nothing should have been allocated.
+ EXPECT_TRUE(segment_->allMemoryDeallocated());
+
+ // Now, the allocation should now succeed.
+ void* ptr = segment_->allocate(prev_size + 1);
+ EXPECT_NE(static_cast<void*>(NULL), ptr);
+ EXPECT_FALSE(segment_->allMemoryDeallocated());
+
+ // Same set of checks, but for a larger size.
+ EXPECT_THROW(segment_->allocate(prev_size * 10), MemorySegmentGrown);
+ // the segment should have grown to the minimum power-of-2 size that
+ // could allocate the given size of memory.
+ EXPECT_EQ(prev_size * 16, segment_->getSize());
+ // And allocate() should now succeed.
+ ptr = segment_->allocate(prev_size * 10);
+ EXPECT_NE(static_cast<void*>(NULL), ptr);
+
+ // (we'll left the regions created in the file there; the entire file
+ // will be removed at the end of the test)
+}
+
+TEST_F(MemorySegmentMappedTest, badAllocate) {
+ // If the test is run as the root user, the following allocate()
+ // call will result in a successful MemorySegmentGrown exception,
+ // instead of an abort (due to insufficient permissions during
+ // reopen).
+ if (getuid() == 0) {
+ std::cerr << "Skipping test as it's run as the root user" << std::endl;
+ return;
+ }
+
+ // Make the mapped file non-writable; managed_mapped_file::grow() will
+ // fail, resulting in abort.
+ const int ret = chmod(mapped_file, 0444);
+ ASSERT_EQ(0, ret);
+
+ if (!isc::util::unittests::runningOnValgrind()) {
+ EXPECT_DEATH_IF_SUPPORTED(
+ {segment_->allocate(DEFAULT_INITIAL_SIZE * 2);}, "");
+ }
+}
+
+// XXX: this test can cause too strong side effect (creating a very large
+// file), so we disable it by default
+TEST_F(MemorySegmentMappedTest, DISABLED_allocateHuge) {
+ EXPECT_THROW(segment_->allocate(std::numeric_limits<size_t>::max()),
+ std::bad_alloc);
+}
+
+TEST_F(MemorySegmentMappedTest, badDeallocate) {
+ void* ptr = segment_->allocate(4);
+ EXPECT_NE(static_cast<void*>(NULL), ptr);
+
+ segment_->deallocate(ptr, 4); // this is okay
+ // This is duplicate dealloc; should trigger assertion failure.
+ if (!isc::util::unittests::runningOnValgrind()) {
+ EXPECT_DEATH_IF_SUPPORTED({segment_->deallocate(ptr, 4);}, "");
+ resetSegment(); // the segment is possibly broken; reset it.
+ }
+
+ // Deallocating at an invalid address; this would result in crash (the
+ // behavior may not be portable enough; if so we should disable it by
+ // default).
+ if (!isc::util::unittests::runningOnValgrind()) {
+ ptr = segment_->allocate(4);
+ EXPECT_NE(static_cast<void*>(NULL), ptr);
+ EXPECT_DEATH_IF_SUPPORTED({
+ segment_->deallocate(static_cast<char*>(ptr) + 1, 3);
+ }, "");
+ resetSegment();
+ }
+
+ // Invalid size; this implementation doesn't detect such errors.
+ ptr = segment_->allocate(4);
+ EXPECT_NE(static_cast<void*>(NULL), ptr);
+ segment_->deallocate(ptr, 8);
+ EXPECT_TRUE(segment_->allMemoryDeallocated());
+}
+
+// A helper of namedAddress.
+void
+checkNamedData(const std::string& name, const std::vector<uint8_t>& data,
+ MemorySegment& sgmt, bool delete_after_check = false)
+{
+ const MemorySegment::NamedAddressResult result =
+ sgmt.getNamedAddress(name.c_str());
+ ASSERT_TRUE(result.first);
+ ASSERT_TRUE(result.second);
+ EXPECT_EQ(0, std::memcmp(result.second, &data[0], data.size()));
+
+ if (delete_after_check) {
+ sgmt.deallocate(result.second, data.size());
+ sgmt.clearNamedAddress(name.c_str());
+ }
+}
+
+TEST_F(MemorySegmentMappedTest, namedAddress) {
+ // common test cases
+ isc::util::test::checkSegmentNamedAddress(*segment_, false);
+
+ // Set it again and read it in the read-only mode.
+ void* ptr16 = segment_->allocate(sizeof(uint16_t));
+ const uint16_t test_val16 = 42000;
+ *static_cast<uint16_t*>(ptr16) = test_val16;
+ EXPECT_FALSE(segment_->setNamedAddress("test address", ptr16));
+ segment_.reset(); // close it before opening another one
+
+ segment_.reset(new MemorySegmentMapped(mapped_file));
+ MemorySegment::NamedAddressResult result =
+ segment_->getNamedAddress("test address");
+ ASSERT_TRUE(result.first);
+ EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(result.second));
+
+ // try to set an unusually long name. We re-create the file so
+ // creating the name would cause allocation failure and trigger internal
+ // segment extension.
+ segment_.reset();
+ boost::interprocess::file_mapping::remove(mapped_file);
+ segment_.reset(new MemorySegmentMapped(mapped_file, OPEN_OR_CREATE, 1024));
+ const std::string long_name(1025, 'x'); // definitely larger than segment
+ // setNamedAddress should return true, indicating segment has grown.
+ EXPECT_TRUE(segment_->setNamedAddress(long_name.c_str(), NULL));
+ result = segment_->getNamedAddress(long_name.c_str());
+ EXPECT_TRUE(result.first);
+ EXPECT_FALSE(result.second);
+
+ // Check contents pointed by named addresses survive growing and
+ // shrinking segment.
+ segment_.reset();
+ boost::interprocess::file_mapping::remove(mapped_file);
+ segment_.reset(new MemorySegmentMapped(mapped_file, OPEN_OR_CREATE));
+
+ typedef std::map<std::string, std::vector<uint8_t> > TestData;
+
+ TestData data_list;
+ data_list["data1"] =
+ std::vector<uint8_t>(80); // arbitrarily chosen small data
+ data_list["data2"] =
+ std::vector<uint8_t>(5000); // larger than usual segment size
+ data_list["data3"] =
+ std::vector<uint8_t>(65535); // bigger than most usual data
+ bool grown = false;
+
+ // Allocate memory and store data
+ for (TestData::iterator it = data_list.begin(); it != data_list.end();
+ ++it)
+ {
+ std::vector<uint8_t>& data = it->second;
+ for (int i = 0; i < data.size(); ++i) {
+ data[i] = i;
+ }
+ void *dp = NULL;
+ while (!dp) {
+ try {
+ dp = segment_->allocate(data.size());
+ std::memcpy(dp, &data[0], data.size());
+ segment_->setNamedAddress(it->first.c_str(), dp);
+ } catch (const MemorySegmentGrown&) {
+ grown = true;
+ }
+ }
+ }
+ // Confirm there's at least one segment extension
+ EXPECT_TRUE(grown);
+ // Check named data are still valid
+ for (TestData::iterator it = data_list.begin(); it != data_list.end();
+ ++it)
+ {
+ checkNamedData(it->first, it->second, *segment_);
+ }
+
+ // Confirm they are still valid, while we shrink the segment. We'll
+ // intentionally delete bigger data first so it'll be more likely that
+ // shrink has some real effect.
+ const char* const names[] = { "data3", "data2", "data1", NULL };
+ for (int i = 0; names[i]; ++i) {
+ checkNamedData(names[i], data_list[names[i]], *segment_, true);
+ segment_->shrinkToFit();
+ }
+}
+
+TEST_F(MemorySegmentMappedTest, multiProcess) {
+ // Test using fork() doesn't work well on valgrind
+ if (isc::util::unittests::runningOnValgrind()) {
+ return;
+ }
+
+ // allocate some data and name its address
+ void* ptr = segment_->allocate(sizeof(uint32_t));
+ *static_cast<uint32_t*>(ptr) = 424242;
+ segment_->setNamedAddress("test address", ptr);
+
+ // close the read-write segment at this point. our intended use case is
+ // to have one or more reader process or at most one exclusive writer
+ // process. so we don't mix reader and writer.
+ segment_.reset();
+
+ // Spawn another process and have it open and read the same data.
+ PipeHolder pipe_to_child;
+ PipeHolder pipe_to_parent;
+ const pid_t child_pid = fork();
+ ASSERT_NE(-1, child_pid);
+ if (child_pid == 0) {
+ // child: wait until the parent has opened the read-only segment.
+ char from_parent;
+ EXPECT_EQ(1, read(pipe_to_child.getReadFD(), &from_parent,
+ sizeof(from_parent)));
+ EXPECT_EQ(0, from_parent);
+
+ MemorySegmentMapped sgmt(mapped_file);
+ const MemorySegment::NamedAddressResult result =
+ sgmt.getNamedAddress("test address");
+ ASSERT_TRUE(result.first);
+ EXPECT_TRUE(result.second);
+ if (result.second) {
+ const uint32_t val = *static_cast<const uint32_t*>(result.second);
+ EXPECT_EQ(424242, val);
+ // tell the parent whether it succeeded. 0 means it did,
+ // 0xff means it failed.
+ const char ok = (val == 424242) ? 0 : 0xff;
+ EXPECT_EQ(1, write(pipe_to_parent.getWriteFD(), &ok, sizeof(ok)));
+ }
+ exit(0);
+ }
+ // parent: open another read-only segment, then tell the child to open
+ // its own segment.
+ segment_.reset(new MemorySegmentMapped(mapped_file));
+ const MemorySegment::NamedAddressResult result =
+ segment_->getNamedAddress("test address");
+ ASSERT_TRUE(result.first);
+ ASSERT_TRUE(result.second);
+ EXPECT_EQ(424242, *static_cast<const uint32_t*>(result.second));
+ const char some_data = 0;
+ EXPECT_EQ(1, write(pipe_to_child.getWriteFD(), &some_data,
+ sizeof(some_data)));
+
+ // wait for the completion of the child and checks the result.
+ EXPECT_EQ(0, parentReadState(pipe_to_parent.getReadFD()));
+}
+
+TEST_F(MemorySegmentMappedTest, nullDeallocate) {
+ // NULL deallocation is a no-op.
+ EXPECT_NO_THROW(segment_->deallocate(0, 1024));
+ EXPECT_TRUE(segment_->allMemoryDeallocated());
+}
+
+TEST_F(MemorySegmentMappedTest, shrink) {
+ segment_->shrinkToFit();
+ // Normally we should be able to expect that the resulting size is
+ // smaller than the initial default size. But it's not really
+ // guaranteed by the API, so we may have to disable this check (or
+ // use EXPECT_GE).
+ const size_t shrinked_size = segment_->getSize();
+ EXPECT_GT(DEFAULT_INITIAL_SIZE, shrinked_size);
+
+ // Another shrink shouldn't cause disruption. We expect the size is
+ // the same so we confirm it. The underlying library doesn't guarantee
+ // that, so we may have to change it to EXPECT_GE if the test fails
+ // on that (MemorySegmentMapped class doesn't rely on this expectation,
+ // so it's okay even if it does not always hold).
+ segment_->shrinkToFit();
+ EXPECT_EQ(shrinked_size, segment_->getSize());
+
+ // Check that the segment is still usable after shrink.
+ void *p = NULL;
+ while (!p) {
+ try {
+ p = segment_->allocate(sizeof(uint32_t));
+ } catch (const MemorySegmentGrown&) {
+ // Do nothing. Just try again.
+ }
+ }
+ segment_->deallocate(p, sizeof(uint32_t));
+}
+
+TEST_F(MemorySegmentMappedTest, violateReadOnly) {
+ // Create a named address for the tests below, then reset the writer
+ // segment so that it won't fail for different reason (i.e., read-write
+ // conflict).
+ void* ptr = segment_->allocate(sizeof(uint32_t));
+ segment_->setNamedAddress("test address", ptr);
+ segment_.reset();
+
+ // Attempts to modify memory from the read-only segment directly
+ // will result in a crash.
+ if (!isc::util::unittests::runningOnValgrind()) {
+ EXPECT_DEATH_IF_SUPPORTED({
+ MemorySegmentMapped segment_ro(mapped_file);
+ const MemorySegment::NamedAddressResult result =
+ segment_ro.getNamedAddress("test address");
+ ASSERT_TRUE(result.first);
+ ASSERT_TRUE(result.second);
+ *static_cast<uint32_t*>(result.second) = 0;
+ }, "");
+ }
+
+ // If the segment is opened in the read-only mode, modification
+ // attempts are prohibited. When detectable it must result in an
+ // exception.
+ MemorySegmentMapped segment_ro(mapped_file);
+ const MemorySegment::NamedAddressResult result =
+ segment_ro.getNamedAddress("test address");
+ ASSERT_TRUE(result.first);
+ EXPECT_NE(static_cast<void*>(NULL), result.second);
+
+ EXPECT_THROW(segment_ro.deallocate(result.second, 4), MemorySegmentError);
+
+ EXPECT_THROW(segment_ro.allocate(16), MemorySegmentError);
+ // allocation that would otherwise require growing the segment; permission
+ // check should be performed before that.
+ EXPECT_THROW(segment_ro.allocate(DEFAULT_INITIAL_SIZE * 2),
+ MemorySegmentError);
+ EXPECT_THROW(segment_ro.setNamedAddress("test", NULL), MemorySegmentError);
+ EXPECT_THROW(segment_ro.clearNamedAddress("test"), MemorySegmentError);
+ EXPECT_THROW(segment_ro.shrinkToFit(), MemorySegmentError);
+}
+
+TEST_F(MemorySegmentMappedTest, getCheckSum) {
+ const size_t old_cksum = segment_->getCheckSum();
+
+ // We assume the initial segment size is sufficiently larger than
+ // the page size. We'll allocate memory of the page size, and
+ // increment all bytes in that page by one. It will increase our
+ // simple checksum value (which just uses the first byte of each
+ // page) by one, too.
+ const size_t page_sz = boost::interprocess::mapped_region::get_page_size();
+ uint8_t* cp0 = static_cast<uint8_t*>(segment_->allocate(page_sz));
+ for (uint8_t* cp = cp0; cp < cp0 + page_sz; ++cp) {
+ ++*cp;
+ }
+
+ EXPECT_EQ(old_cksum + 1, segment_->getCheckSum());
+}
+
+// Mode of opening segments in the tests below.
+enum TestOpenMode {
+ READER = 0,
+ WRITER_FOR_WRITE,
+ WRITER_OPEN_OR_CREATE,
+ WRITER_CREATE_ONLY
+};
+
+// A shortcut to attempt to open a specified type of segment (generally
+// expecting it to fail)
+void
+setSegment(TestOpenMode mode, scoped_ptr<MemorySegmentMapped>& sgmt_ptr) {
+ switch (mode) {
+ case READER:
+ sgmt_ptr.reset(new MemorySegmentMapped(mapped_file));
+ break;
+ case WRITER_FOR_WRITE:
+ sgmt_ptr.reset(new MemorySegmentMapped(mapped_file, OPEN_FOR_WRITE));
+ break;
+ case WRITER_OPEN_OR_CREATE:
+ sgmt_ptr.reset(new MemorySegmentMapped(mapped_file, OPEN_OR_CREATE));
+ break;
+ case WRITER_CREATE_ONLY:
+ sgmt_ptr.reset(new MemorySegmentMapped(mapped_file, CREATE_ONLY));
+ break;
+ }
+}
+
+// Common logic for conflictReaderWriter test. The segment opened in the
+// parent process will prevent the segment in the child from being used.
+void
+conflictCheck(TestOpenMode parent_mode, TestOpenMode child_mode) {
+ PipeHolder pipe_to_child;
+ PipeHolder pipe_to_parent;
+ const pid_t child_pid = fork();
+ ASSERT_NE(-1, child_pid);
+
+ if (child_pid == 0) {
+ char ch;
+ EXPECT_EQ(1, read(pipe_to_child.getReadFD(), &ch, sizeof(ch)));
+
+ ch = 0; // 0 = open success, 1 = fail
+ try {
+ scoped_ptr<MemorySegmentMapped> sgmt;
+ setSegment(child_mode, sgmt);
+ EXPECT_EQ(1, write(pipe_to_parent.getWriteFD(), &ch, sizeof(ch)));
+ } catch (const MemorySegmentOpenError&) {
+ ch = 1;
+ EXPECT_EQ(1, write(pipe_to_parent.getWriteFD(), &ch, sizeof(ch)));
+ }
+ exit(0);
+ }
+
+ // parent: open a segment, then tell the child to open its own segment of
+ // the specified type.
+ scoped_ptr<MemorySegmentMapped> sgmt;
+ setSegment(parent_mode, sgmt);
+ const char some_data = 0;
+ EXPECT_EQ(1, write(pipe_to_child.getWriteFD(), &some_data,
+ sizeof(some_data)));
+
+ // wait for the completion of the child and checks the result. open at
+ // the child side should fail, so the parent should get the value of 1.
+ EXPECT_EQ(1, parentReadState(pipe_to_parent.getReadFD()));
+}
+
+TEST_F(MemorySegmentMappedTest, conflictReaderWriter) {
+ // Test using fork() doesn't work well on valgrind
+ if (isc::util::unittests::runningOnValgrind()) {
+ return;
+ }
+
+ // Below, we check all combinations of conflicts between reader and writer
+ // will fail. We first make sure there's no other reader or writer.
+ segment_.reset();
+
+ // reader opens segment, then writer (OPEN_FOR_WRITE) tries to open
+ conflictCheck(READER, WRITER_FOR_WRITE);
+ // reader opens segment, then writer (OPEN_OR_CREATE) tries to open
+ conflictCheck(READER, WRITER_OPEN_OR_CREATE);
+ // reader opens segment, then writer (CREATE_ONLY) tries to open
+ conflictCheck(READER, WRITER_CREATE_ONLY);
+
+ // writer (OPEN_FOR_WRITE) opens a segment, then reader tries to open
+ conflictCheck(WRITER_FOR_WRITE, READER);
+ // writer (OPEN_OR_CREATE) opens a segment, then reader tries to open
+ conflictCheck(WRITER_OPEN_OR_CREATE, READER);
+ // writer (CREATE_ONLY) opens a segment, then reader tries to open
+ conflictCheck(WRITER_CREATE_ONLY, READER);
+
+ // writer opens segment, then another writer (OPEN_FOR_WRITE) tries to open
+ conflictCheck(WRITER_FOR_WRITE, WRITER_FOR_WRITE);
+ // writer opens segment, then another writer (OPEN_OR_CREATE) tries to open
+ conflictCheck(WRITER_FOR_WRITE, WRITER_OPEN_OR_CREATE);
+ // writer opens segment, then another writer (CREATE_ONLY) tries to open
+ conflictCheck(WRITER_FOR_WRITE, WRITER_CREATE_ONLY);
+}
+
+}
diff --git a/src/lib/util/tests/random_number_generator_unittest.cc b/src/lib/util/tests/random_number_generator_unittest.cc
index 23d5b88..639d354 100644
--- a/src/lib/util/tests/random_number_generator_unittest.cc
+++ b/src/lib/util/tests/random_number_generator_unittest.cc
@@ -14,14 +14,13 @@
#include <config.h>
+#include <util/random/random_number_generator.h>
+
#include <gtest/gtest.h>
#include <boost/shared_ptr.hpp>
-#include <algorithm>
#include <iostream>
-#include <vector>
-#include <util/random/random_number_generator.h>
namespace isc {
namespace util {
@@ -42,9 +41,9 @@ public:
}
virtual ~UniformRandomIntegerGeneratorTest(){}
- int gen() { return gen_(); }
- int max() const { return max_; }
- int min() const { return min_; }
+ int gen() { return (gen_()); }
+ int max() const { return (max_); }
+ int min() const { return (min_); }
private:
UniformRandomIntegerGenerator gen_;
@@ -82,7 +81,7 @@ TEST_F(UniformRandomIntegerGeneratorTest, IntegerRange) {
vector<int>::iterator it = unique(numbers.begin(), numbers.end());
// make sure the numbers are in range [min, max]
- ASSERT_EQ(it - numbers.begin(), max() - min() + 1);
+ ASSERT_EQ(it - numbers.begin(), max() - min() + 1);
}
/// \brief Test Fixture Class for weighted random number generator
@@ -99,7 +98,8 @@ public:
TEST_F(WeightedRandomIntegerGeneratorTest, Constructor) {
vector<double> probabilities;
- // If no probabilities is provided, the smallest integer will always be generated
+ // If no probabilities is provided, the smallest integer will always
+ // be generated
WeightedRandomIntegerGenerator gen(probabilities, 123);
for (int i = 0; i < 100; ++i) {
ASSERT_EQ(gen(), 123);
diff --git a/src/lib/util/tests/run_unittests.cc b/src/lib/util/tests/run_unittests.cc
index 8789a9c..41761ca 100644
--- a/src/lib/util/tests/run_unittests.cc
+++ b/src/lib/util/tests/run_unittests.cc
@@ -20,6 +20,5 @@ int
main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
- setenv("B10_LOCKFILE_DIR_FROM_BUILD", TEST_DATA_TOPBUILDDIR, 1);
return (isc::util::unittests::run_all());
}
diff --git a/src/lib/util/threads/Makefile.am b/src/lib/util/threads/Makefile.am
index 121e4ab..7ab6f32 100644
--- a/src/lib/util/threads/Makefile.am
+++ b/src/lib/util/threads/Makefile.am
@@ -7,6 +7,7 @@ AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
lib_LTLIBRARIES = libb10-threads.la
libb10_threads_la_SOURCES = sync.h sync.cc
libb10_threads_la_SOURCES += thread.h thread.cc
-libb10_threads_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+libb10_threads_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+libb10_threads_la_LIBADD += $(PTHREAD_LDFLAGS)
CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/util/threads/sync.cc b/src/lib/util/threads/sync.cc
index 46a5646..0e0ec08 100644
--- a/src/lib/util/threads/sync.cc
+++ b/src/lib/util/threads/sync.cc
@@ -169,6 +169,26 @@ Mutex::lock() {
#endif // ENABLE_DEBUG
}
+bool
+Mutex::tryLock() {
+ assert(impl_ != NULL);
+ const int result = pthread_mutex_trylock(&impl_->mutex);
+ // In the case of pthread_mutex_trylock(), if it is called on a
+ // locked mutex from the same thread, some platforms (such as fedora
+ // and debian) return EBUSY whereas others (such as centos 5) return
+ // EDEADLK. We return false and don't pass the lock attempt in both
+ // cases.
+ if (result == EBUSY || result == EDEADLK) {
+ return (false);
+ } else if (result != 0) {
+ isc_throw(isc::InvalidOperation, std::strerror(result));
+ }
+#ifdef ENABLE_DEBUG
+ postLockAction(); // Only in debug mode
+#endif // ENABLE_DEBUG
+ return (true);
+}
+
void
Mutex::unlock() {
assert(impl_ != NULL);
diff --git a/src/lib/util/threads/sync.h b/src/lib/util/threads/sync.h
index 87c78be..c0bc1a9 100644
--- a/src/lib/util/threads/sync.h
+++ b/src/lib/util/threads/sync.h
@@ -15,6 +15,8 @@
#ifndef B10_THREAD_SYNC_H
#define B10_THREAD_SYNC_H
+#include <exceptions/exceptions.h>
+
#include <boost/noncopyable.hpp>
#include <cstdlib> // for NULL.
@@ -77,17 +79,34 @@ public:
/// of function no matter by what means.
class Locker : boost::noncopyable {
public:
+ /// \brief Exception thrown when the mutex is already locked and
+ /// a non-blocking locker is attempted around it.
+ struct AlreadyLocked : public isc::InvalidParameter {
+ AlreadyLocked(const char* file, size_t line, const char* what) :
+ isc::InvalidParameter(file, line, what)
+ {}
+ };
+
/// \brief Constructor.
///
- /// Locks the mutex. May block for extended period of time.
+ /// Locks the mutex. May block for extended period of time if
+ /// \c block is true.
///
/// \throw isc::InvalidOperation when OS reports error. This usually
/// means an attempt to use the mutex in a wrong way (locking
/// a mutex second time from the same thread, for example).
- Locker(Mutex& mutex) :
+ /// \throw AlreadyLocked if \c block is false and the mutex is
+ /// already locked.
+ Locker(Mutex& mutex, bool block = true) :
mutex_(mutex)
{
- mutex.lock();
+ if (block) {
+ mutex.lock();
+ } else {
+ if (!mutex.tryLock()) {
+ isc_throw(AlreadyLocked, "The mutex is already locked");
+ }
+ }
}
/// \brief Destructor.
@@ -107,6 +126,24 @@ public:
///
/// \todo Disable in non-debug build
bool locked() const;
+
+private:
+ /// \brief Lock the mutex
+ ///
+ /// This method blocks until the mutex can be locked.
+ void lock();
+
+ /// \brief Try to lock the mutex
+ ///
+ /// This method doesn't block and returns immediately with a status
+ /// on whether the lock operation was successful.
+ ///
+ /// \return true if the lock was successful, false otherwise.
+ bool tryLock();
+
+ /// \brief Unlock the mutex
+ void unlock();
+
private:
friend class CondVar;
@@ -131,8 +168,6 @@ private:
class Impl;
Impl* impl_;
- void lock();
- void unlock();
};
/// \brief Encapsulation for a condition variable.
diff --git a/src/lib/util/threads/tests/lock_unittest.cc b/src/lib/util/threads/tests/lock_unittest.cc
index 4c4f831..c17999e 100644
--- a/src/lib/util/threads/tests/lock_unittest.cc
+++ b/src/lib/util/threads/tests/lock_unittest.cc
@@ -44,6 +44,44 @@ TEST(MutexTest, lockMultiple) {
Mutex::Locker l2(mutex); // Attempt to lock again.
}, isc::InvalidOperation);
EXPECT_TRUE(mutex.locked()); // Debug-only build
+
+ // block=true explicitly.
+ Mutex mutex2;
+ EXPECT_FALSE(mutex2.locked()); // Debug-only build
+ Mutex::Locker l12(mutex2, true);
+ EXPECT_TRUE(mutex2.locked()); // Debug-only build
+}
+
+void
+testThread(Mutex* mutex)
+{
+ // block=false (tryLock). This should not block indefinitely, but
+ // throw AlreadyLocked. If block were true, this would block
+ // indefinitely here.
+ EXPECT_THROW({
+ Mutex::Locker l3(*mutex, false);
+ }, Mutex::Locker::AlreadyLocked);
+
+ EXPECT_TRUE(mutex->locked()); // Debug-only build
+}
+
+// Test the non-blocking variant using a second thread.
+TEST(MutexTest, lockNonBlocking) {
+ // block=false (tryLock).
+ Mutex mutex;
+ Mutex::Locker l1(mutex, false);
+ EXPECT_TRUE(mutex.locked()); // Debug-only build
+
+ // First, try another locker from the same thread.
+ EXPECT_THROW({
+ Mutex::Locker l2(mutex, false);
+ }, Mutex::Locker::AlreadyLocked);
+
+ EXPECT_TRUE(mutex.locked()); // Debug-only build
+
+ // Now try another locker from a different thread.
+ Thread thread(boost::bind(&testThread, &mutex));
+ thread.wait();
}
#endif // ENABLE_DEBUG
diff --git a/src/lib/util/unittests/Makefile.am b/src/lib/util/unittests/Makefile.am
index 55e0372..657c2aa 100644
--- a/src/lib/util/unittests/Makefile.am
+++ b/src/lib/util/unittests/Makefile.am
@@ -11,6 +11,7 @@ libutil_unittests_la_SOURCES += check_valgrind.h check_valgrind.cc
libutil_unittests_la_SOURCES += run_all.h run_all.cc
libutil_unittests_la_SOURCES += textdata.h
libutil_unittests_la_SOURCES += wiredata.h wiredata.cc
+libutil_unittests_la_SOURCES += interprocess_util.h interprocess_util.cc
endif
# For now, this isn't needed for libutil_unittests
diff --git a/src/lib/util/unittests/interprocess_util.cc b/src/lib/util/unittests/interprocess_util.cc
new file mode 100644
index 0000000..ce858d4
--- /dev/null
+++ b/src/lib/util/unittests/interprocess_util.cc
@@ -0,0 +1,48 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER 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 <sys/select.h>
+#include <cstddef>
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+unsigned char
+parentReadState(int fd) {
+ unsigned char result = 0xff;
+
+ fd_set rfds;
+ FD_ZERO(&rfds);
+ FD_SET(fd, &rfds);
+
+ struct timeval tv = {5, 0};
+
+ const int nfds = select(fd + 1, &rfds, NULL, NULL, &tv);
+ EXPECT_EQ(1, nfds);
+
+ if (nfds == 1) {
+ // Read status
+ const ssize_t bytes_read = read(fd, &result, sizeof(result));
+ EXPECT_EQ(sizeof(result), bytes_read);
+ }
+
+ return (result);
+}
+
+}
+}
+}
diff --git a/src/lib/util/unittests/interprocess_util.h b/src/lib/util/unittests/interprocess_util.h
new file mode 100644
index 0000000..f25ad3e
--- /dev/null
+++ b/src/lib/util/unittests/interprocess_util.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+namespace isc {
+namespace util {
+namespace unittests {
+/// \brief A helper utility for a simple synchronization with another process.
+///
+/// It waits for incoming data on a given file descriptor up to 5 seconds
+/// (arbitrary choice), read one byte data, and return it to the caller.
+/// On any failure it returns 0xff (255), so the sender process should use
+/// a different value to pass.
+unsigned char parentReadState(int fd);
+}
+}
+}
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/unittests/mock_socketsession.h b/src/lib/util/unittests/mock_socketsession.h
index 01ca34f..fb155a2 100644
--- a/src/lib/util/unittests/mock_socketsession.h
+++ b/src/lib/util/unittests/mock_socketsession.h
@@ -42,7 +42,12 @@ class MockSocketSessionForwarder :
public:
MockSocketSessionForwarder() :
is_connected_(false), connect_ok_(true), push_ok_(true),
- close_ok_(true)
+ close_ok_(true),
+ // These are not used until set, but we set them anyway here,
+ // partly to silence cppcheck, and partly to be cleaner. Put some
+ // invalid values in.
+ pushed_sock_(-1), pushed_family_(-1), pushed_type_(-1),
+ pushed_protocol_(-1)
{}
virtual void connectToReceiver() {
diff --git a/src/lib/util/unittests/resolver.h b/src/lib/util/unittests/resolver.h
index 560a892..127fd54 100644
--- a/src/lib/util/unittests/resolver.h
+++ b/src/lib/util/unittests/resolver.h
@@ -60,7 +60,7 @@ class TestResolver : public isc::resolve::ResolverInterface {
PresetAnswers answers_;
public:
typedef std::pair<isc::dns::QuestionPtr, CallbackPtr> Request;
- /// \brief List of requests the tested class sent trough resolve
+ /// \brief List of requests the tested class sent through resolve
std::vector<Request> requests;
/// \brief Destructor
diff --git a/src/lib/util/unittests/run_all.cc b/src/lib/util/unittests/run_all.cc
index 5f50f77..0e3492b 100644
--- a/src/lib/util/unittests/run_all.cc
+++ b/src/lib/util/unittests/run_all.cc
@@ -31,7 +31,7 @@ run_all() {
// The catching of exceptions generated in tests is controlled by the
// B10TEST_CATCH_EXCEPTION environment variable. Setting this to
- // 1 enables the cacthing of exceptions; setting it to 0 disables it.
+ // 1 enables the catching of exceptions; setting it to 0 disables it.
// Anything else causes a message to be printed to stderr and the default
// taken. (The default is to catch exceptions if compiling with clang
// and false if not.)
diff --git a/src/lib/xfr/tests/client_test.cc b/src/lib/xfr/tests/client_test.cc
index 6c9f4ad..ce783b0 100644
--- a/src/lib/xfr/tests/client_test.cc
+++ b/src/lib/xfr/tests/client_test.cc
@@ -24,7 +24,7 @@ using namespace isc::xfr;
namespace {
-TEST(ClientTest, connetFile) {
+TEST(ClientTest, connectFile) {
// File path is too long
struct sockaddr_un s; // can't be const; some compiler complains
EXPECT_THROW(XfroutClient(string(sizeof(s.sun_path), 'x')).connect(),
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 570c0e2..7c6de04 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1 +1 @@
-SUBDIRS = system tools
+SUBDIRS = tools lettuce
diff --git a/tests/lettuce/Makefile.am b/tests/lettuce/Makefile.am
new file mode 100644
index 0000000..82f3e3a
--- /dev/null
+++ b/tests/lettuce/Makefile.am
@@ -0,0 +1 @@
+noinst_SCRIPTS = setup_intree_bind10.sh
diff --git a/tests/lettuce/README b/tests/lettuce/README
index 94bf82b..48c4043 100644
--- a/tests/lettuce/README
+++ b/tests/lettuce/README
@@ -6,7 +6,8 @@ these tests are specific for BIND10, but we are keeping in mind that RFC-related
tests could be separated, so that we can test other systems as well.
Prerequisites:
-- BIND 10 must be compiled or installed
+- BIND 10 must be compiled or installed (even when testing in-tree build;
+ see below)
- dig
- lettuce (http://lettuce.it)
@@ -32,7 +33,9 @@ By default it will use the build tree, but you can use an installed version
of bind10 by passing -I as the first argument of run_lettuce.sh
The tool 'dig' must be in the default search path of your environment. If
-you specified -I, so must the main bind10 program, as well as bindctl.
+you specified -I, so must the main BIND 10 programs. And, with or without
+-I, some BIND 10 programs still have to be installed as they are invoked
+from test tools. Those include bindctl and b10-loadzone.
Due to the default way lettuce prints its output, it is advisable to run it
in a terminal that is wide than the default. If you see a lot of lines twice
@@ -70,6 +73,11 @@ These files *will* be overwritten or deleted if the same scenarios are run
again, so if you want to inspect them after a failed test, either do so
immediately or move the files.
+If you want to keep these output files even for successful runs, you can
+specify the environment variable LETTUCE_KEEP_OUTPUT=1. The files will
+still be overwritten by subsequent runs, but they will not automatically be
+deleted.
+
Adding and extending tests
--------------------------
diff --git a/tests/lettuce/configurations/example.org.inmem.config b/tests/lettuce/configurations/example.org.inmem.config
index 2e9ca41..2eadedb 100644
--- a/tests/lettuce/configurations/example.org.inmem.config
+++ b/tests/lettuce/configurations/example.org.inmem.config
@@ -22,6 +22,12 @@
"params": {
"example.org": "data/example.org"
}
+ },
+ {
+ "type": "broken_libraries_should_be_skipped",
+ "cache-enable": false,
+ "params": {
+ }
}
]
}
diff --git a/tests/lettuce/configurations/glue.config b/tests/lettuce/configurations/glue.config
new file mode 100644
index 0000000..fc301d3
--- /dev/null
+++ b/tests/lettuce/configurations/glue.config
@@ -0,0 +1,34 @@
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [{
+ "severity": "DEBUG",
+ "name": "*",
+ "debuglevel": 99
+ }]
+ },
+ "Auth": {
+ "listen_on": [{
+ "port": 47806,
+ "address": "127.0.0.1"
+ }]
+ },
+ "data_sources": {
+ "classes": {
+ "IN": [
+ {
+ "type": "sqlite3",
+ "params": {
+ "database_file": "data/glue.sqlite3"
+ }
+ }
+ ]
+ }
+ },
+ "Init": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/ixfr-out/testset1-config.db b/tests/lettuce/configurations/ixfr-out/testset1-config.db
index d5eaf83..38d29a7 100644
--- a/tests/lettuce/configurations/ixfr-out/testset1-config.db
+++ b/tests/lettuce/configurations/ixfr-out/testset1-config.db
@@ -2,7 +2,6 @@
"Xfrin": {
"zones": [
{
- "use_ixfr": true,
"class": "IN",
"name": "example.com.",
"master_addr": "178.18.82.80"
diff --git a/tests/lettuce/configurations/xfrin/.gitignore b/tests/lettuce/configurations/xfrin/.gitignore
index 5d56912..0b03cd2 100644
--- a/tests/lettuce/configurations/xfrin/.gitignore
+++ b/tests/lettuce/configurations/xfrin/.gitignore
@@ -1,2 +1,4 @@
/retransfer_master.conf
+/retransfer_master_nons.conf
/retransfer_slave.conf
+/retransfer_slave_notify.conf
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
index 1b2953d..d7ea9a5 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
@@ -33,9 +33,6 @@
"port": 47806
} ]
},
- "Stats": {
- "poll-interval": 1
- },
"Init": {
"components": {
"b10-auth": { "kind": "needed", "special": "auth" },
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master_diffs.conf b/tests/lettuce/configurations/xfrin/retransfer_master_diffs.conf
new file mode 100644
index 0000000..34d2d03
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_master_diffs.conf
@@ -0,0 +1,47 @@
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/xfrin-diffs.sqlite3",
+ "listen_on": [ {
+ "address": "::1",
+ "port": 47807
+ } ]
+ },
+ "data_sources": {
+ "classes": {
+ "IN": [{
+ "type": "sqlite3",
+ "params": {
+ "database_file": "data/xfrin-diffs.sqlite3"
+ }
+ }]
+ }
+ },
+ "Xfrout": {
+ "zone_config": [ {
+ "origin": "example."
+ } ],
+ "also_notify": [ {
+ "address": "::1",
+ "port": 47806
+ } ]
+ },
+ "Stats": {
+ "poll-interval": 1
+ },
+ "Init": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig
new file mode 100644
index 0000000..755c91b
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig
@@ -0,0 +1,45 @@
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/example.org.sqlite3",
+ "listen_on": [ {
+ "address": "127.0.0.1",
+ "port": 47809
+ } ]
+ },
+ "data_sources": {
+ "classes": {
+ "IN": [{
+ "type": "sqlite3",
+ "params": {
+ "database_file": "data/example.org.sqlite3"
+ }
+ }]
+ }
+ },
+ "Xfrout": {
+ "zone_config": [ {
+ "origin": "example.org"
+ } ],
+ "also_notify": [ {
+ "address": "127.0.0.1",
+ "port": 47806
+ } ]
+ },
+ "Init": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-stats": { "address": "Stats", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig
index 2e6b17f..aff8218 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave.conf.orig
@@ -8,7 +8,6 @@
} ]
},
"Auth": {
- "database_file": "data/test_nonexistent_db.sqlite3",
"listen_on": [ {
"address": "::1",
"port": 47806
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf b/tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf
new file mode 100644
index 0000000..24bfa2a
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_diffs.conf
@@ -0,0 +1,41 @@
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "listen_on": [ {
+ "address": "::1",
+ "port": 47806
+ } ]
+ },
+ "Xfrin": {
+ "zones": [ {
+ "name": "example",
+ "master_addr": "::1",
+ "master_port": 47807
+ } ]
+ },
+ "data_sources": {
+ "classes": {
+ "IN": [{
+ "type": "sqlite3",
+ "params": {
+ "database_file": "data/xfrin-before-diffs.sqlite3"
+ }
+ }]
+ }
+ },
+ "Init": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf
deleted file mode 100644
index 1e7e9f4..0000000
--- a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf
+++ /dev/null
@@ -1,52 +0,0 @@
-{
- "version": 3,
- "Logging": {
- "loggers": [ {
- "debuglevel": 99,
- "severity": "DEBUG",
- "name": "*"
- } ]
- },
- "Auth": {
- "database_file": "data/xfrin-notify.sqlite3",
- "listen_on": [ {
- "address": "::1",
- "port": 47806
- } ]
- },
- "data_sources": {
- "classes": {
- "IN": [{
- "type": "sqlite3",
- "params": {
- "database_file": "data/xfrin-notify.sqlite3"
- }
- }]
- }
- },
- "Xfrin": {
- "zones": [ {
- "name": "example.org",
- "master_addr": "::1",
- "master_port": 47807
- } ]
- },
- "Zonemgr": {
- "secondary_zones": [ {
- "name": "example.org",
- "class": "IN"
- } ]
- },
- "Stats": {
- "poll-interval": 1
- },
- "Init": {
- "components": {
- "b10-auth": { "kind": "needed", "special": "auth" },
- "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
- "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
- "b10-stats": { "address": "Stats", "kind": "dispensable" },
- "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
- }
- }
-}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
new file mode 100644
index 0000000..c4ba1ef
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
@@ -0,0 +1,49 @@
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "listen_on": [ {
+ "address": "::1",
+ "port": 47806
+ } ]
+ },
+ "data_sources": {
+ "classes": {
+ "IN": [{
+ "type": "sqlite3",
+ "params": {
+ "database_file": "data/xfrin-notify.sqlite3"
+ }
+ }]
+ }
+ },
+ "Xfrin": {
+ "zones": [ {
+ "name": "example.org",
+ "master_addr": "::1",
+ "master_port": 47807,
+ "request_ixfr": "no"
+ } ]
+ },
+ "Zonemgr": {
+ "secondary_zones": [ {
+ "name": "example.org",
+ "class": "IN"
+ } ]
+ },
+ "Init": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-stats": { "address": "Stats", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf b/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf
new file mode 100644
index 0000000..b99f3f7
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf
@@ -0,0 +1,49 @@
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "listen_on": [ {
+ "address": "127.0.0.1",
+ "port": 47806
+ } ]
+ },
+ "data_sources": {
+ "classes": {
+ "IN": [{
+ "type": "sqlite3",
+ "params": {
+ "database_file": "data/xfrin-notify.sqlite3"
+ }
+ }]
+ }
+ },
+ "Xfrin": {
+ "zones": [ {
+ "name": "example.org",
+ "master_addr": "127.0.0.1",
+ "master_port": 47809,
+ "request_ixfr": "no"
+ } ]
+ },
+ "Zonemgr": {
+ "secondary_zones": [ {
+ "name": "example.org",
+ "class": "IN"
+ } ]
+ },
+ "Init": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-stats": { "address": "Stats", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/configurations/xfrout_master.conf b/tests/lettuce/configurations/xfrout_master.conf
new file mode 100644
index 0000000..755e698
--- /dev/null
+++ b/tests/lettuce/configurations/xfrout_master.conf
@@ -0,0 +1,41 @@
+{
+ "version": 3,
+ "Logging": {
+ "loggers": [ {
+ "debuglevel": 99,
+ "severity": "DEBUG",
+ "name": "*"
+ } ]
+ },
+ "Auth": {
+ "database_file": "data/xfrout.sqlite3",
+ "listen_on": [ {
+ "address": "::1",
+ "port": 47806
+ } ]
+ },
+ "data_sources": {
+ "classes": {
+ "IN": [{
+ "type": "sqlite3",
+ "params": {
+ "database_file": "data/xfrout.sqlite3"
+ }
+ }]
+ }
+ },
+ "Xfrout": {
+ "zone_config": [ {
+ "origin": "example.org"
+ } ]
+ },
+ "Init": {
+ "components": {
+ "b10-auth": { "kind": "needed", "special": "auth" },
+ "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+ "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+ "b10-stats": { "address": "Stats", "kind": "dispensable" },
+ "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+ }
+ }
+}
diff --git a/tests/lettuce/data/.gitignore b/tests/lettuce/data/.gitignore
index 888175e..946228c 100644
--- a/tests/lettuce/data/.gitignore
+++ b/tests/lettuce/data/.gitignore
@@ -1,3 +1,4 @@
/inmem-xfrin.sqlite3
/test_nonexistent_db.sqlite3
+/xfrin-before-diffs.sqlite3
/xfrin-notify.sqlite3
diff --git a/tests/lettuce/data/glue.sqlite3 b/tests/lettuce/data/glue.sqlite3
new file mode 100644
index 0000000..a527de8
Binary files /dev/null and b/tests/lettuce/data/glue.sqlite3 differ
diff --git a/tests/lettuce/data/xfrin-before-diffs.sqlite3.orig b/tests/lettuce/data/xfrin-before-diffs.sqlite3.orig
new file mode 100644
index 0000000..45bf77d
Binary files /dev/null and b/tests/lettuce/data/xfrin-before-diffs.sqlite3.orig differ
diff --git a/tests/lettuce/data/xfrin-diffs.sqlite3 b/tests/lettuce/data/xfrin-diffs.sqlite3
new file mode 100644
index 0000000..55b233d
Binary files /dev/null and b/tests/lettuce/data/xfrin-diffs.sqlite3 differ
diff --git a/tests/lettuce/features/.gitignore b/tests/lettuce/features/.gitignore
new file mode 100644
index 0000000..0634dc7
--- /dev/null
+++ b/tests/lettuce/features/.gitignore
@@ -0,0 +1 @@
+/resolver_basic.feature
diff --git a/tests/lettuce/features/auth_badzone.feature b/tests/lettuce/features/auth_badzone.feature
index edc1a64..8b902b3 100644
--- a/tests/lettuce/features/auth_badzone.feature
+++ b/tests/lettuce/features/auth_badzone.feature
@@ -2,7 +2,7 @@ Feature: Authoritative DNS server with a bad zone
This feature set is for testing the execution of the b10-auth
component when one zone is broken, whereas others are fine. In this
case, b10-auth should not reject the data source, but reject the bad
- zone only and serve the good zones anyway.
+ zone only (with SERVFAIL) and serve the good zones anyway.
Scenario: Bad zone
Given I have bind10 running with configuration auth/auth_badzone.config
@@ -12,9 +12,9 @@ Feature: Authoritative DNS server with a bad zone
# will be logged and we cannot use the 'new' keyword to wait for
# 3 different log messages. *There could still be a race here if
# auth starts very quickly.*
- And wait for new bind10 stderr message DATASRC_LOAD_FROM_FILE_ERROR
- And wait for new bind10 stderr message DATASRC_LOAD_FROM_FILE_ERROR
- And wait for new bind10 stderr message DATASRC_LOAD_FROM_FILE_ERROR
+ And wait for new bind10 stderr message DATASRC_LOAD_ZONE_ERROR
+ And wait for new bind10 stderr message DATASRC_LOAD_ZONE_ERROR
+ And wait for new bind10 stderr message DATASRC_LOAD_ZONE_ERROR
And wait for bind10 stderr message BIND10_STARTED_CC
And wait for bind10 stderr message CMDCTL_STARTED
@@ -24,7 +24,7 @@ Feature: Authoritative DNS server with a bad zone
And bind10 module Resolver should not be running
A query for www.example.org should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have ancount 1
The last query response should have nscount 2
The last query response should have adcount 2
@@ -44,6 +44,6 @@ Feature: Authoritative DNS server with a bad zone
ns2.example.org. 3600 IN A 192.0.2.4
"""
- A query for www.example.com should have rcode REFUSED
- A query for www.example.net should have rcode REFUSED
- A query for www.example.info should have rcode REFUSED
+ A query for www.example.com should have rcode SERVFAIL
+ A query for www.example.net should have rcode SERVFAIL
+ A query for www.example.info should have rcode SERVFAIL
diff --git a/tests/lettuce/features/bindctl_commands.feature b/tests/lettuce/features/bindctl_commands.feature
index b9fef82..a7a455a 100644
--- a/tests/lettuce/features/bindctl_commands.feature
+++ b/tests/lettuce/features/bindctl_commands.feature
@@ -154,3 +154,18 @@ Feature: control with bindctl
bind10 module Xfrout should be running
bind10 module Xfrin should be running
bind10 module Zonemgr should be running
+
+ Scenario: Shutting down a certain module
+ # We could test with several modules, but for now we are particularly
+ # interested in shutting down cmdctl. It previously caused hangup,
+ # so this scenario confirms it's certainly fixed. Note: since cmdctl
+ # is a "needed" component, shutting it down will result in system
+ # shutdown. So "send bind10 command" will fail (it cannot complete
+ # "quit").
+ Given I have bind10 running with configuration bindctl/bindctl.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+
+ When I send bind10 ignoring failure the command Cmdctl shutdown
+ And wait for bind10 stderr message CMDCTL_EXITING
+ And wait for bind10 stderr message BIND10_SHUTDOWN_COMPLETE
diff --git a/tests/lettuce/features/ddns_system.feature b/tests/lettuce/features/ddns_system.feature
index 184c8ae..6747b53 100644
--- a/tests/lettuce/features/ddns_system.feature
+++ b/tests/lettuce/features/ddns_system.feature
@@ -118,6 +118,42 @@ Feature: DDNS System
A query for new3.example.org should have rcode NOERROR
The SOA serial for example.org should be 1236
+ Scenario: Zone validation check
+ Given I have bind10 running with configuration ddns/ddns.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message DDNS_STARTED
+
+ # Sanity check
+ A query for example.org type NS should have rcode NOERROR
+ The answer section of the last query response should be
+ """
+ example.org. 3600 IN NS ns1.example.org.
+ example.org. 3600 IN NS ns2.example.org.
+ example.org. 3600 IN NS ns3.example.org.
+ """
+ The SOA serial for example.org should be 1234
+
+ # Test failed validation. Here, example.org has ns1.example.org
+ # configured as a name server. CNAME records cannot be added for
+ # ns1.example.org.
+ When I use DDNS to add a record ns1.example.org. 3600 IN CNAME ns3.example.org.
+ The DDNS response should be REFUSED
+ A query for ns1.example.org type CNAME should have rcode NXDOMAIN
+ The SOA serial for example.org should be 1234
+
+ # Test passed validation. Here, example.org does not have
+ # ns4.example.org configured as a name server. CNAME records can
+ # be added for ns4.example.org.
+ When I use DDNS to add a record ns4.example.org. 3600 IN CNAME ns3.example.org.
+ The DDNS response should be SUCCESS
+ A query for ns4.example.org type CNAME should have rcode NOERROR
+ The answer section of the last query response should be
+ """
+ ns4.example.org. 3600 IN CNAME ns3.example.org.
+ """
+ The SOA serial for example.org should be 1235
+
#Scenario: DDNS and Xfrout
## Unfortunately, Xfrout can only notify to inzone slaves, and hence only
## to port 53, which we do not want to use for Lettuce tests (for various
diff --git a/tests/lettuce/features/example.feature b/tests/lettuce/features/example.feature
index ecfdcc3..ee84b46 100644
--- a/tests/lettuce/features/example.feature
+++ b/tests/lettuce/features/example.feature
@@ -26,7 +26,7 @@ Feature: Example feature
Scenario: New database
# This test checks whether a database file is automatically created
- # Underwater, we take advantage of our intialization routines so
+ # Underwater, we take advantage of our initialization routines so
# that we are sure this file does not exist, see
# features/terrain/terrain.py
@@ -120,7 +120,7 @@ Feature: Example feature
The last query response should have adcount 0
# When checking flags, we must pass them exactly as they appear in
# the output of dig.
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
A query for www.example.org type TXT should have rcode NOERROR
The last query response should have ancount 0
@@ -161,11 +161,13 @@ Feature: Example feature
A query for www.example.org should have rcode NOERROR
Wait for new bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
Then set bind10 configuration data_sources/classes/IN[0]/params to {"database_file": "data/empty_db.sqlite3"}
- And wait for new bind10 stderr message DATASRC_SQLITE_CONNOPEN
+ # The 'not missing placeholder' check is for #2743
+ And wait for new bind10 stderr message DATASRC_SQLITE_CONNOPEN not Missing placeholder
A query for www.example.org should have rcode REFUSED
Wait for new bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
Then set bind10 configuration data_sources/classes/IN[0]/params to {"database_file": "data/example.org.sqlite3"}
- And wait for new bind10 stderr message DATASRC_SQLITE_CONNOPEN
+ # The 'not missing placeholder' check is for #2743
+ And wait for new bind10 stderr message DATASRC_SQLITE_CONNOPEN not Missing placeholder
A query for www.example.org should have rcode NOERROR
Scenario: two bind10 instances
diff --git a/tests/lettuce/features/nsec3_auth.feature b/tests/lettuce/features/nsec3_auth.feature
index 4e5ed5b..8ead43f 100644
--- a/tests/lettuce/features/nsec3_auth.feature
+++ b/tests/lettuce/features/nsec3_auth.feature
@@ -25,7 +25,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for a.c.x.w.example. should have rcode NXDOMAIN
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 8
@@ -57,7 +57,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for ns1.example. type MX should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 4
@@ -85,7 +85,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for y.w.example. should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 4
@@ -113,7 +113,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for mc.c.example. type MX should have rcode NOERROR
- The last query response should have flags qr rd
+ The last query response should have flags qr
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 6
@@ -148,7 +148,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for a.z.w.example. type MX should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 2
The last query response should have nscount 5
@@ -195,7 +195,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for a.z.w.example. type AAAA should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 8
@@ -227,7 +227,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for example. type DS should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 4
@@ -241,7 +241,7 @@ Feature: NSEC3 Authoritative service
"""
#
- # Below are additional tests, not explicitely stated in RFC5155
+ # Below are additional tests, not explicitly stated in RFC5155
#
Scenario: 7.2.2 other; Name Error where one NSEC3 covers multiple parts of proof (closest encloser)
@@ -259,7 +259,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for b.x.w.example. should have rcode NXDOMAIN
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 6
@@ -289,7 +289,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for a.w.example. should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 6
@@ -319,7 +319,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for *.w.example. type MX should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 2
The last query response should have nscount 3
@@ -362,7 +362,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for *.w.example. type A should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 4
@@ -390,7 +390,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example. type NSEC3 should have rcode NXDOMAIN
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 8
@@ -422,7 +422,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for ai.example. type DS should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 4
@@ -450,7 +450,7 @@ Feature: NSEC3 Authoritative service
And bind10 module StatsHttpd should not be running
A dnssec query for c.example. type DS should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 6
diff --git a/tests/lettuce/features/queries.feature b/tests/lettuce/features/queries.feature
index 8a66d1c..2db6c3e 100644
--- a/tests/lettuce/features/queries.feature
+++ b/tests/lettuce/features/queries.feature
@@ -3,11 +3,62 @@ Feature: Querying feature
for instance whether multiple queries in a row return consistent
answers.
+ Scenario: Glue
+ # Check the auth server returns the correct glue when asked for it.
+ Given I have bind10 running with configuration glue.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ # This query should result in a delegation with two NS; one in the
+ # delegated zone and one in a so called out-of-bailiwick zone for which
+ # the auth server has authority, too. For the former, the server
+ # should return glue in the parent zone. For the latter, BIND 9 and
+ # BIND 10 behave differently; BIND 9 uses "glue" in the parent zone
+ # (since this is the root zone everything can be considered a valid
+ # glue). BIND 10 (using sqlite3 data source) searches the other zone
+ # and uses the authoritative data in that zone (which is intentionally
+ # different from the glue in the root zone).
+ A query for foo.bar.example type A should have rcode NOERROR
+ The answer section of the last query response should be
+ """
+ """
+ The authority section of the last query response should be
+ """
+ example. 172800 IN NS NS1.example.COM.
+ example. 172800 IN NS NS.example.
+ """
+ The additional section of the last query response should be
+ """
+ NS.example. 172800 IN A 192.0.2.1
+ NS.example. 172800 IN A 192.0.2.2
+ NS1.example.COM. 172800 IN A 192.0.2.3
+ """
+ # Test we don't get out-of-zone glue
+ A query for example.net type A should have rcode NOERROR
+ The answer section of the last query response should be
+ """
+ """
+ The authority section of the last query response should be
+ """
+ example.net. 300 IN NS ns2.example.info.
+ example.net. 300 IN NS ns1.example.info.
+ """
+ The additional section of the last query response should be
+ """
+ """
+
Scenario: Repeated queries
Given I have bind10 running with configuration example.org.inmem.config
And wait for bind10 stderr message BIND10_STARTED_CC
And wait for bind10 stderr message CMDCTL_STARTED
And wait for bind10 stderr message AUTH_SERVER_STARTED
+
+ # DATASRC_LIBRARY_ERROR must be generated due to
+ # "broken_libraries_should_be_skipped" in
+ # example.org.inmem.config
+ And wait for bind10 stderr message DATASRC_LIBRARY_ERROR
+
And wait for bind10 stderr message STATS_STARTING
bind10 module Auth should be running
@@ -30,7 +81,7 @@ Feature: Querying feature
The statistics counters are 0 in category .Auth.zones._SERVER_
A query for www.example.org should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have ancount 1
The last query response should have nscount 2
The last query response should have adcount 2
@@ -50,6 +101,10 @@ Feature: Querying feature
ns2.example.org. 3600 IN A 192.0.2.4
"""
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
When I wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
# make sure Auth module receives a command
And wait for new bind10 stderr message AUTH_RECEIVED_COMMAND
@@ -72,7 +127,7 @@ Feature: Querying feature
# Repeat of the above
A query for www.example.org should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have ancount 1
The last query response should have nscount 2
The last query response should have adcount 2
@@ -92,6 +147,10 @@ Feature: Querying feature
ns2.example.org. 3600 IN A 192.0.2.4
"""
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for new bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
When I wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
# make sure Auth module receives a command
And wait for new bind10 stderr message AUTH_RECEIVED_COMMAND
@@ -112,7 +171,7 @@ Feature: Querying feature
| rcode.noerror | 2 |
# And now query something completely different
- A query for nosuchname.example.org should have rcode NXDOMAIN
+ A recursive query for nosuchname.example.org should have rcode NXDOMAIN
The last query response should have flags qr aa rd
The last query response should have ancount 0
The last query response should have nscount 1
@@ -122,6 +181,10 @@ Feature: Querying feature
example.org. 3600 IN SOA ns1.example.org. admin.example.org. 1234 3600 1800 2419200 7200
"""
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for new bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
When I wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
# make sure Auth module receives a command
And wait for new bind10 stderr message AUTH_RECEIVED_COMMAND
@@ -139,6 +202,7 @@ Feature: Querying feature
| responses | 3 |
| qrysuccess | 2 |
| qryauthans | 3 |
+ | qryrecursion | 1 |
| rcode.noerror | 2 |
| rcode.nxdomain | 1 |
@@ -168,7 +232,7 @@ Feature: Querying feature
The statistics counters are 0 in category .Auth.zones._SERVER_
A query for example.org type ANY should have rcode NOERROR
- The last query response should have flags qr aa rd
+ The last query response should have flags qr aa
The last query response should have ancount 4
The last query response should have nscount 0
The last query response should have adcount 3
@@ -186,6 +250,10 @@ Feature: Querying feature
mail.example.org. 3600 IN A 192.0.2.10
"""
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
When I wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
# make sure Auth module receives a command
And wait for new bind10 stderr message AUTH_RECEIVED_COMMAND
@@ -223,7 +291,7 @@ Feature: Querying feature
The statistics counters are 0 in category .Auth.zones._SERVER_
A dnssec query for www.sub.example.org type AAAA should have rcode NOERROR
- The last query response should have flags qr rd
+ The last query response should have flags qr
The last query response should have edns_flags do
The last query response should have ancount 0
The last query response should have nscount 1
@@ -237,6 +305,10 @@ Feature: Querying feature
ns.sub.example.org. 3600 IN A 192.0.2.101
"""
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
When I wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
# make sure Auth module receives a command
And wait for new bind10 stderr message AUTH_RECEIVED_COMMAND
@@ -288,6 +360,10 @@ Feature: Querying feature
A query for example.org type SSHFP should have rcode NOERROR
The last query response should have ancount 0
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
When I wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
# make sure Auth module receives a command
And wait for new bind10 stderr message AUTH_RECEIVED_COMMAND
@@ -313,6 +389,9 @@ Feature: Querying feature
"""
shell.example.org. 3600 IN SSHFP 2 1 123456789abcdef67890123456789abcdef67890
"""
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
When I wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
# make sure Auth module receives a command
diff --git a/tests/lettuce/features/resolver_basic.feature b/tests/lettuce/features/resolver_basic.feature
deleted file mode 100644
index 47fc123..0000000
--- a/tests/lettuce/features/resolver_basic.feature
+++ /dev/null
@@ -1,36 +0,0 @@
-Feature: Basic Resolver
- This feature set is just testing the execution of the b10-resolver
- module. It sees whether it starts up, takes configuration, and
- answers queries.
-
- Scenario: Listen for and answer query
- # This scenario starts a server that runs a real resolver.
- # In order not to send out queries into the wild, we only
- # query for something known to be hardcoded at this moment.
- # NOTE: once real priming has been implemented, this test needs
- # to be revised (as it would then leak, which is probably true
- # for any resolver system test)
- When I start bind10 with configuration resolver/resolver_basic.config
- And wait for bind10 stderr message BIND10_STARTED_CC
- And wait for bind10 stderr message CMDCTL_STARTED
- And wait for bind10 stderr message RESOLVER_STARTED
-
- bind10 module Resolver should be running
- And bind10 module Auth should not be running
- And bind10 module Xfrout should not be running
- And bind10 module Zonemgr should not be running
- And bind10 module Xfrin should not be running
- And bind10 module Stats should not be running
- And bind10 module StatsHttpd should not be running
-
- # The ACL is set to reject any queries
- A query for l.root-servers.net. should have rcode REFUSED
-
- # Test whether acl ACCEPT works
- When I set bind10 configuration Resolver/query_acl[0] to {"action": "ACCEPT", "from": "127.0.0.1"}
- # This address is currently hardcoded, so shouldn't cause outside traffic
- A query for l.root-servers.net. should have rcode NOERROR
-
- # Check whether setting the ACL to reject again works
- When I set bind10 configuration Resolver/query_acl[0] to {"action": "REJECT", "from": "127.0.0.1"}
- A query for l.root-servers.net. should have rcode REFUSED
diff --git a/tests/lettuce/features/resolver_basic.feature.disabled b/tests/lettuce/features/resolver_basic.feature.disabled
new file mode 100644
index 0000000..341c14c
--- /dev/null
+++ b/tests/lettuce/features/resolver_basic.feature.disabled
@@ -0,0 +1,36 @@
+Feature: Basic Resolver
+ This feature set is just testing the execution of the b10-resolver
+ module. It sees whether it starts up, takes configuration, and
+ answers queries.
+
+ Scenario: Listen for and answer query
+ # This scenario starts a server that runs a real resolver.
+ # In order not to send out queries into the wild, we only
+ # query for something known to be hardcoded at this moment.
+ # NOTE: once real priming has been implemented, this test needs
+ # to be revised (as it would then leak, which is probably true
+ # for any resolver system test)
+ When I start bind10 with configuration resolver/resolver_basic.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message RESOLVER_STARTED
+
+ bind10 module Resolver should be running
+ And bind10 module Auth should not be running
+ And bind10 module Xfrout should not be running
+ And bind10 module Zonemgr should not be running
+ And bind10 module Xfrin should not be running
+ And bind10 module Stats should not be running
+ And bind10 module StatsHttpd should not be running
+
+ # The ACL is set to reject any queries
+ A recursive query for l.root-servers.net. should have rcode REFUSED
+
+ # Test whether acl ACCEPT works
+ When I set bind10 configuration Resolver/query_acl[0] to {"action": "ACCEPT", "from": "127.0.0.1"}
+ # This address is currently hardcoded, so shouldn't cause outside traffic
+ A recursive query for l.root-servers.net. should have rcode NOERROR
+
+ # Check whether setting the ACL to reject again works
+ When I set bind10 configuration Resolver/query_acl[0] to {"action": "REJECT", "from": "127.0.0.1"}
+ A recursive query for l.root-servers.net. should have rcode REFUSED
diff --git a/tests/lettuce/features/terrain/bind10_control.py b/tests/lettuce/features/terrain/bind10_control.py
index 2b1c5c0..e288aa9 100644
--- a/tests/lettuce/features/terrain/bind10_control.py
+++ b/tests/lettuce/features/terrain/bind10_control.py
@@ -120,13 +120,15 @@ def have_bind10_running(step, config_file, cmdctl_port, process_name):
step.given(start_step)
# function to send lines to bindctl, and store the result
-def run_bindctl(commands, cmdctl_port=None):
+def run_bindctl(commands, cmdctl_port=None, ignore_failure=False):
"""Run bindctl.
Parameters:
commands: a sequence of strings which will be sent.
cmdctl_port: a port number on which cmdctl is listening, is converted
to string if necessary. If not provided, or None, defaults
to 47805
+ ignore_failure(bool): if set to True, don't examin the result code
+ of bindctl and assert it succeeds.
bindctl's stdout and stderr streams are stored (as one multiline string
in world.last_bindctl_stdout/stderr.
@@ -140,6 +142,8 @@ def run_bindctl(commands, cmdctl_port=None):
for line in commands:
bindctl.stdin.write(line + "\n")
(stdout, stderr) = bindctl.communicate()
+ if ignore_failure:
+ return
result = bindctl.returncode
world.last_bindctl_stdout = stdout
world.last_bindctl_stderr = stderr
@@ -188,7 +192,7 @@ def parse_bindctl_output_as_data_structure():
If it is valid, it is parsed and returned as whatever data
structure it represented.
"""
- # strip any extra output after a charater that commonly terminates a valid
+ # strip any extra output after a character that commonly terminates a valid
# JSON expression, i.e., ']', '}' and '"'. (The extra output would
# contain 'Exit from bindctl' message, and depending on environment some
# other control-like characters...but why is this message even there?)
@@ -306,19 +310,25 @@ def config_remove_command(step, name, value, cmdctl_port):
"quit"]
run_bindctl(commands, cmdctl_port)
- at step('send bind10(?: with cmdctl port (\d+))? the command (.+)')
-def send_command(step, cmdctl_port, command):
+ at step('send bind10(?: with cmdctl port (\d+))?( ignoring failure)? the command (.+)')
+def send_command(step, cmdctl_port, ignore_failure, command):
"""
Run bindctl, send the given command, and exit bindctl.
Parameters:
command ('the command <command>'): The command to send.
cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
the command to. Defaults to 47805.
- Fails if cmdctl does not exit with status code 0.
+ ignore_failure ('ignoring failure', optional): set to None if bindctl
+ is expected to succeed (normal case, which is the default); if it is
+ not None, it means bindctl is expected to fail (and it's acceptable).
+
+ Fails if bindctl does not exit with status code 0 and ignore_failure
+ is not None.
+
"""
commands = [command,
"quit"]
- run_bindctl(commands, cmdctl_port)
+ run_bindctl(commands, cmdctl_port, ignore_failure is not None)
@step('bind10 module (\S+) should( not)? be running')
def module_is_running(step, name, not_str):
@@ -379,67 +389,6 @@ def query_statistics(step, statistics, name, cmdctl_port):
% (port_str, name,\
' name=%s' % statistics if statistics else ''))
-def find_value(dictionary, key):
- """A helper method. Recursively find a value corresponding to the
- key of the dictionary and returns it. Returns None if the
- dictionary is not dict type."""
- if type(dictionary) is not dict:
- return
- if key in dictionary:
- return dictionary[key]
- else:
- for v in dictionary.values():
- return find_value(v, key)
-
- at step('the statistics counter (\S+)(?: in the category (\S+))?'+ \
- '(?: for(?: the zone)? (\S+))? should be' + \
- '(?:( greater than| less than| between))? (\-?\d+)(?: and (\-?\d+))?')
-def check_statistics(step, counter, category, zone_or_sock, gtltbt, number, upper):
- """
- check the output of bindctl for statistics of specified counter
- and zone.
- Parameters:
- counter ('counter <counter>'): The counter name of statistics.
- category ('category <category>', optional): The category of counter.
- zone_or_sock ('<zone_or_sock>', optional): The zone name or socket name.
- gtltbt (' greater than'|' less than'|' between', optional): greater than
- <number> or less than <number> or between <number> and <upper>.
- number ('<number>): The expect counter number. <number> is assumed
- to be an unsigned integer.
- upper ('<upper>, optional): The expect upper counter number when
- using 'between'.
- """
- output = parse_bindctl_output_as_data_structure()
- found = None
- category_str = ""
- zone_or_sock_str = ""
- depth = []
- if category:
- depth.insert(0, category)
- category_str = " for category %s" % category
- if zone_or_sock:
- depth.insert(0, zone_or_sock)
- zone_or_sock_str = " for %s" % zone_or_sock
- for level in depth:
- output = find_value(output, level)
- found = find_value(output, counter)
- assert found is not None, \
- 'Not found statistics counter %s%s%s' % \
- (counter, category_str, zone_or_sock_str)
- msg = "Got %s, expected%s %s as counter %s%s" % \
- (found, '' if gtltbt is None else gtltbt, number, counter, zone_or_sock_str)
- if gtltbt and 'between' in gtltbt and upper:
- msg = "Got %s, expected%s %s and %s as counter %s%s" % \
- (found, gtltbt, number, upper, counter, zone_or_sock_str)
- assert float(number) <= float(found) \
- and float(found) <= float(upper), msg
- elif gtltbt and 'greater' in gtltbt:
- assert float(found) > float(number), msg
- elif gtltbt and 'less' in gtltbt:
- assert float(found) < float(number), msg
- else:
- assert float(found) == float(number), msg
-
@step('statistics counters are 0 in category (\S+)( except for the' + \
' following items)?')
def check_statistics_items(step, category, has_except_for):
@@ -451,9 +400,14 @@ def check_statistics_items(step, category, has_except_for):
with the multiline part.
Expected values of items are taken from the multiline part of the step in
- the scenario. The multiline part has two columns: item_name and item_value.
- item_name is a relative name to category. item_value is an expected value
- for item_name.
+ the scenario. The multiline part has at most four columns: item_name,
+ item_value, min_value, and max_value. item_name is a relative name
+ to category. item_value is an expected value for
+ item_name. min_value and max_value are expected to be used when
+ item_value cannot be specified to be item_value. min_value is the
+ minimum value in the expected range, and max_value is the maximum
+ value in the expected range. Values would be examined if they are
+ in columns corresponding to these.
"""
def flatten(dictionary, prefix=''):
@@ -470,15 +424,55 @@ def check_statistics_items(step, category, has_except_for):
# fetch step tables in the scnario as hashes
for item in step.hashes:
name = category+'.'+item['item_name']
- value = item['item_value']
assert stats.has_key(name), \
'Statistics item %s was not found' % (name)
found = stats[name]
- assert int(found) == int(value), \
- 'Statistics item %s has unexpected value %s (expect %s)' % \
+ if 'item_value' in item and item['item_value']:
+ value = item['item_value']
+ assert int(found) == int(value), \
+ 'Statistics item %s has unexpected value %s (expect %s)' % \
+ (name, found, value)
+ if 'min_value' in item and item['min_value']:
+ value = item['min_value']
+ assert float(value) <= float(found), \
+ 'Statistics item %s has unexpected value %s (expect %s or greater than)' % \
+ (name, found, value)
+ if 'max_value' in item and item['max_value']:
+ value = item['max_value']
+ assert float(found) <= float(value), \
+ 'Statistics item %s has unexpected value %s (expect %s or less than)' % \
(name, found, value)
del(stats[name])
for name, found in stats.items():
assert int(found) == 0, \
'Statistics item %s has unexpected value %s (expect %s)' % \
(name, found, 0)
+
+ at step('check initial statistics(?:( not)? containing (\S+))? for (\S+)'
+ '( with cmdctl port \d+)?( except for the following items)?')
+def check_init_statistics(step, notv, string, name, cmdctl_port, has_except_for):
+ """Checks the initial statistics for the module. Also checks a
+ string is contained or not contained in them. Statistics counters
+ other than zero can follow below.
+ Parameters:
+ notv ('not'): reverse the check (fail if string is found)
+ string ('containing <string>') string to look for
+ name ('module <name>'): The name of the module (case sensitive!)
+ cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
+ the command to.
+ has_except_for ('except for the following items'): checks values of items
+ with the multiline part.
+ """
+ query_str = 'query statistics of bind10 module ' + name
+ if cmdctl_port:
+ query_str = query_str + cmdctl_port
+ notcontain_str = 'last bindctl output should%s contain "%s"'
+ check_str = 'statistics counters are 0 in category .' + name
+ if has_except_for:
+ check_str = check_str + has_except_for + "\n" \
+ + step.represent_hashes()
+ step.given(query_str)
+ step.given(notcontain_str % (' not', 'error'))
+ if string is not None:
+ step.given(notcontain_str % (notv, string))
+ step.given(check_str)
diff --git a/tests/lettuce/features/terrain/loadzone.py b/tests/lettuce/features/terrain/loadzone.py
new file mode 100644
index 0000000..32fc82b
--- /dev/null
+++ b/tests/lettuce/features/terrain/loadzone.py
@@ -0,0 +1,106 @@
+# Copyright (C) 2013 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 lettuce import *
+import subprocess
+import tempfile
+
+def run_loadzone(zone, zone_file, db_file):
+ """Run b10-loadzone.
+
+ It currently only works for an SQLite3-based data source, and takes
+ its DB file as a parameter.
+
+ Parameters:
+ zone (str): the zone name
+ zone_file (str): master zone file for the zone; can be None to make an
+ empty zone.
+ db_file (str): SQLite3 DB file to load the zone into
+
+ """
+ sqlite_datasrc_cfg = '{"database_file": "' + db_file + '"}'
+ if zone_file is not None:
+ args = ['b10-loadzone', '-c', sqlite_datasrc_cfg, zone, zone_file]
+ else:
+ args = ['b10-loadzone', '-c', sqlite_datasrc_cfg, '-e', zone]
+ loadzone = subprocess.Popen(args, 1, None, None,
+ subprocess.PIPE, subprocess.PIPE)
+ (stdout, stderr) = loadzone.communicate()
+ result = loadzone.returncode
+ world.last_loadzone_stdout = stdout
+ world.last_loadzone_stderr = stderr
+ assert result == 0, "loadzone exit code: " + str(result) +\
+ "\nstdout:\n" + str(stdout) +\
+ "stderr:\n" + str(stderr)
+
+ at step('load zone (\S+) to DB file (\S+) from (\S+)')
+def load_zone_to_dbfile(step, zone, db_file, zone_file):
+ """Load a zone into a data source from a zone file.
+
+ It currently only works for an SQLite3-based data source. Its
+ DB file name should be specified.
+
+ Step definition:
+ load zone <zone_name> to DB file <db_file> from <zone_file>
+
+ """
+ run_loadzone(zone, zone_file, db_file)
+
+ at step('make empty zone (\S+) in DB file (\S+)')
+def make_empty_zone_to_dbfile(step, zone, db_file):
+ """Make an empty zone into a data source.
+
+ If a non-empty zone already exists in the data source, it will be emptied;
+ otherwise, a new empty zone will be created.
+
+ It currently only works for an SQLite3-based data source. Its
+ DB file name should be specified.
+
+ Step definition:
+ make empty zone <zone_name> to DB file <db_file>
+
+ """
+ run_loadzone(zone, None, db_file)
+
+ at step('load (\d+) records for zone (\S+) to DB file (\S+)')
+def load_zone_rr_to_dbfile(step, num_records, zone, db_file):
+ """Load a zone with a specified number of RRs into a data source.
+
+ It currently only works for an SQLite3-based data source. Its
+ DB file name should be specified.
+
+ It creates the content of the zone dynamically so the total number of
+ RRs of the zone is the specified number, including mandatory SOA and NS.
+
+ Step definition:
+ load zone <zone_name> to DB file <db_file> from <zone_file>
+
+ """
+ num_records = int(num_records)
+ assert num_records >= 2, 'zone must have at least 2 RRs: SOA and NS'
+ num_records -= 2
+ with tempfile.NamedTemporaryFile(mode='w', prefix='zone-file',
+ dir='data/', delete=True) as f:
+ filename = f.name
+ f.write('$TTL 3600\n')
+ f.write('$ORIGIN .\n') # so it'll work whether or not zone ends with .
+ f.write(zone + ' IN SOA . . 0 0 0 0 0\n')
+ f.write(zone + ' IN NS 0.' + zone + '\n')
+ count = 0
+ while count < num_records:
+ f.write(str(count) + '.' + zone + ' IN A 192.0.2.1\n')
+ count += 1
+ f.flush()
+ run_loadzone(zone, f.name, db_file)
diff --git a/tests/lettuce/features/terrain/nsupdate.py b/tests/lettuce/features/terrain/nsupdate.py
index 946439d..26964f5 100644
--- a/tests/lettuce/features/terrain/nsupdate.py
+++ b/tests/lettuce/features/terrain/nsupdate.py
@@ -21,7 +21,7 @@ def run_nsupdate(commands):
"""Run nsupdate.
Parameters:
commands: a sequence of strings which will be sent.
- update_address: adress to send the update to
+ update_address: address to send the update to
update_port: port to send the update to
zone: zone to update
diff --git a/tests/lettuce/features/terrain/querying.py b/tests/lettuce/features/terrain/querying.py
index abd7c18..ec75490 100644
--- a/tests/lettuce/features/terrain/querying.py
+++ b/tests/lettuce/features/terrain/querying.py
@@ -68,7 +68,7 @@ class QueryResult(object):
name: The domain name to query
qtype: The RR type to query. Defaults to A if it is None.
qclass: The RR class to query. Defaults to IN if it is None.
- address: The IP adress to send the query to.
+ address: The IP address to send the query to.
port: The port number to send the query to.
additional_args: List of additional arguments (e.g. '+dnssec').
All parameters must be either strings or have the correct string
@@ -200,14 +200,19 @@ class QueryResult(object):
"""
pass
- at step('A (dnssec )?query for ([\S]+) (?:type ([A-Z0-9]+) )?' +
+ at step('A (dnssec )?(recursive )?query for ([\S]+) (?:type ([A-Z0-9]+) )?' +
'(?:class ([A-Z]+) )?(?:to ([^:]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))? )?' +
'should have rcode ([\w.]+)')
-def query(step, dnssec, query_name, qtype, qclass, addr, port, rcode):
+def query(step, dnssec, recursive, query_name, qtype, qclass, addr, port,
+ rcode):
"""
Run a query, check the rcode of the response, and store the query
result in world.last_query_result.
Parameters:
+ dnssec ('dnssec'): DO bit is set in the query.
+ Defaults to unset (no DNSSEC).
+ recursive ('recursive'): RD bit is set in the query.
+ Defaults to unset (no recursion).
query_name ('query for <name>'): The domain name to query.
qtype ('type <type>', optional): The RR type to query. Defaults to A.
qclass ('class <class>', optional): The RR class to query. Defaults to IN.
@@ -234,6 +239,9 @@ def query(step, dnssec, query_name, qtype, qclass, addr, port, rcode):
# additional counts, so unless we need dnssec, explicitly
# disable edns0
additional_arguments.append("+noedns")
+ # dig sets RD bit by default.
+ if recursive is None:
+ additional_arguments.append("+norecurse")
query_result = QueryResult(query_name, qtype, qclass, addr, port,
additional_arguments)
assert query_result.rcode == rcode,\
diff --git a/tests/lettuce/features/terrain/steps.py b/tests/lettuce/features/terrain/steps.py
index e470acf..e41bb6d 100644
--- a/tests/lettuce/features/terrain/steps.py
+++ b/tests/lettuce/features/terrain/steps.py
@@ -37,8 +37,10 @@ def wait_for_stderr_message(step, times, new, process_name, message, not_message
output.
Parameter:
times: Check for the string this many times.
- new: (' new', optional): Only check the output printed since last time
- this step was used for this process.
+ new: (' new', optional): Only check the output from the process that has
+ not been covered in previous calls to this
+ function. See RunningProcess._wait_for_output_str
+ for details.
process_name ('<name> stderr'): Name of the process to check the output of.
message ('message <message>'): Output (part) to wait for.
not_message ('not <message>'): Output (part) to wait for, and fail
@@ -60,8 +62,10 @@ def wait_for_stdout_message(step, times, new, process_name, message, not_message
output.
Parameter:
times: Check for the string this many times.
- new: (' new', optional): Only check the output printed since last time
- this step was used for this process.
+ new: (' new', optional): Only check the output from the process that has
+ not been covered in previous calls to this
+ function. See RunningProcess._wait_for_output_str
+ for details.
process_name ('<name> stderr'): Name of the process to check the output of.
message ('message <message>'): Output (part) to wait for, and succeed.
not_message ('not <message>'): Output (part) to wait for, and fail
diff --git a/tests/lettuce/features/terrain/terrain.py b/tests/lettuce/features/terrain/terrain.py
index ce7426b..d0ba4fe 100644
--- a/tests/lettuce/features/terrain/terrain.py
+++ b/tests/lettuce/features/terrain/terrain.py
@@ -26,12 +26,16 @@
from lettuce import *
import subprocess
-import os.path
+import os
import shutil
import re
import sys
import time
+# lettuce cannot directly pass commands to the terrain, so we need to
+# use environment variables to influence behaviour
+KEEP_OUTPUT = 'LETTUCE_KEEP_OUTPUT'
+
# In order to make sure we start all tests with a 'clean' environment,
# We perform a number of initialization steps, like restoring configuration
# files, and removing generated data files.
@@ -64,16 +68,24 @@ copylist = [
"configurations/ddns/noddns.config"],
["configurations/xfrin/retransfer_master.conf.orig",
"configurations/xfrin/retransfer_master.conf"],
+ ["configurations/xfrin/retransfer_master_v4.conf.orig",
+ "configurations/xfrin/retransfer_master_v4.conf"],
["configurations/xfrin/retransfer_master_nons.conf.orig",
"configurations/xfrin/retransfer_master_nons.conf"],
["configurations/xfrin/retransfer_slave.conf.orig",
"configurations/xfrin/retransfer_slave.conf"],
+ ["configurations/xfrin/retransfer_slave_notify.conf.orig",
+ "configurations/xfrin/retransfer_slave_notify.conf"],
["data/inmem-xfrin.sqlite3.orig",
"data/inmem-xfrin.sqlite3"],
+ ["data/xfrin-before-diffs.sqlite3.orig",
+ "data/xfrin-before-diffs.sqlite3"],
["data/xfrin-notify.sqlite3.orig",
"data/xfrin-notify.sqlite3"],
["data/ddns/example.org.sqlite3.orig",
- "data/ddns/example.org.sqlite3"]
+ "data/ddns/example.org.sqlite3"],
+ ["data/empty_db.sqlite3",
+ "data/xfrout.sqlite3"]
]
# This is a list of files that, if present, will be removed before a scenario
@@ -105,11 +117,15 @@ class RunningProcess:
self.process = None
self.step = step
self.process_name = process_name
- self.remove_files_on_exit = True
+ self.remove_files_on_exit = (os.environ.get(KEEP_OUTPUT) != '1')
self._check_output_dir()
self._create_filenames()
self._start_process(args)
+ # used in _wait_for_output_str, map from (filename, (strings))
+ # to a file offset.
+ self.__file_offsets = {}
+
def _start_process(self, args):
"""
Start the process.
@@ -119,7 +135,7 @@ class RunningProcess:
"""
stderr_write = open(self.stderr_filename, "w")
stdout_write = open(self.stdout_filename, "w")
- self.process = subprocess.Popen(args, 1, None, subprocess.PIPE,
+ self.process = subprocess.Popen(args, 0, None, subprocess.PIPE,
stdout_write, stderr_write)
# open them again, this time for reading
self.stderr = open(self.stderr_filename, "r")
@@ -190,11 +206,29 @@ class RunningProcess:
os.remove(self.stderr_filename)
os.remove(self.stdout_filename)
- def _wait_for_output_str(self, filename, running_file, strings, only_new, matches = 1):
- """
- Wait for a line of output in this process. This will (if only_new is
- False) first check all previous output from the process, and if not
- found, check all output since the last time this method was called.
+ def _wait_for_output_str(self, filename, running_file, strings, only_new,
+ matches=1):
+ """
+ Wait for a line of output in this process. This will (if
+ only_new is False) check all output from the process including
+ that may have been checked before. If only_new is True, it
+ only checks output that has not been covered in previous calls
+ to this method for the file (if there was no such previous call to
+ this method, it works same as the case of only_new=False).
+
+ Care should be taken if only_new is to be set to True, as it may cause
+ counter-intuitive results. For example, assume the file is expected
+ to contain a line that has XXX and another line has YYY, but the
+ ordering is not predictable. If this method is called with XXX as
+ the search string, but the line containing YYY appears before the
+ target line, this method remembers the point in the file beyond
+ the line that has XXX. If a next call to this method specifies
+ YYY as the search string with only_new being True, the search will
+ fail. If the same string is expected to appear multiple times
+ and you want to catch the latest one, a more reliable way is to
+ specify the match number and set only_new to False, if the number
+ of matches is predictable.
+
For each line in the output, the given strings array is checked. If
any output lines checked contains one of the strings in the strings
array, that string (not the line!) is returned.
@@ -202,33 +236,34 @@ class RunningProcess:
filename: The filename to read previous output from, if applicable.
running_file: The open file to read new output from.
strings: Array of strings to look for.
- only_new: If true, only check output since last time this method was
- called. If false, first check earlier output.
+ only_new: See above.
matches: Check for the string this many times.
Returns a tuple containing the matched string, and the complete line
it was found in.
Fails if none of the strings was read after 10 seconds
(OUTPUT_WAIT_INTERVAL * OUTPUT_WAIT_MAX_INTERVALS).
"""
+ # Identify the start offset of search. if only_new=True, start from
+ # the farthest point we've reached in the file; otherwise start from
+ # the beginning.
+ if not filename in self.__file_offsets:
+ self.__file_offsets[filename] = 0
+ offset = self.__file_offsets[filename] if only_new else 0
+ running_file.seek(offset)
+
match_count = 0
- if not only_new:
- full_file = open(filename, "r")
- for line in full_file:
- for string in strings:
- if line.find(string) != -1:
- match_count += 1
- if match_count >= matches:
- full_file.close()
- return (string, line)
wait_count = 0
while wait_count < OUTPUT_WAIT_MAX_INTERVALS:
- where = running_file.tell()
line = running_file.readline()
+ where = running_file.tell()
if line:
for string in strings:
if line.find(string) != -1:
match_count += 1
if match_count >= matches:
+ # If we've gone further, update the recorded offset
+ if where > self.__file_offsets[filename]:
+ self.__file_offsets[filename] = where
return (string, line)
else:
wait_count += 1
@@ -241,8 +276,7 @@ class RunningProcess:
Wait for one of the given strings in this process's stderr output.
Parameters:
strings: Array of strings to look for.
- only_new: If true, only check output since last time this method was
- called. If false, first check earlier output.
+ only_new: See _wait_for_output_str.
matches: Check for the string this many times.
Returns a tuple containing the matched string, and the complete line
it was found in.
@@ -257,8 +291,7 @@ class RunningProcess:
Wait for one of the given strings in this process's stdout output.
Parameters:
strings: Array of strings to look for.
- only_new: If true, only check output since last time this method was
- called. If false, first check earlier output.
+ only_new: See _wait_for_output_str.
matches: Check for the string this many times.
Returns a tuple containing the matched string, and the complete line
it was found in.
@@ -338,8 +371,7 @@ class RunningProcesses:
Parameters:
process_name: The name of the process to check the stderr output of.
strings: Array of strings to look for.
- only_new: If true, only check output since last time this method was
- called. If false, first check earlier output.
+ only_new: See _wait_for_output_str.
matches: Check for the string this many times.
Returns the matched string.
Fails if none of the strings was read after 10 seconds
@@ -358,8 +390,7 @@ class RunningProcesses:
Parameters:
process_name: The name of the process to check the stdout output of.
strings: Array of strings to look for.
- only_new: If true, only check output since last time this method was
- called. If false, first check earlier output.
+ only_new: See _wait_for_output_str.
matches: Check for the string this many times.
Returns the matched string.
Fails if none of the strings was read after 10 seconds
diff --git a/tests/lettuce/features/terrain/transfer.py b/tests/lettuce/features/terrain/transfer.py
index eb4ddc0..0983de5 100644
--- a/tests/lettuce/features/terrain/transfer.py
+++ b/tests/lettuce/features/terrain/transfer.py
@@ -18,7 +18,8 @@
# and inspect the results.
#
# Like querying.py, it uses dig to do the transfers, and
-# places its output in a result structure
+# places its output in a result structure. It also uses a custom client
+# implementation for less normal operations.
#
# This is done in a different file with different steps than
# querying, because the format of dig's output is
@@ -58,7 +59,16 @@ class TransferResult(object):
if len(line) > 0 and line[0] != ';':
self.records.append(line)
- at step('An AXFR transfer of ([\w.]+)(?: from ([^:]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))?)?')
+def parse_addr_port(address, port):
+ if address is None:
+ address = "::1" # default address
+ # convert [IPv6_addr] to IPv6_addr:
+ address = re.sub(r"\[(.+)\]", r"\1", address)
+ if port is None:
+ port = 47806 # default port
+ return (address, port)
+
+ at step('An AXFR transfer of ([\w.]+)(?: from ([\d.]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))?)?')
def perform_axfr(step, zone_name, address, port):
"""
Perform an AXFR transfer, and store the result as an instance of
@@ -70,15 +80,60 @@ def perform_axfr(step, zone_name, address, port):
Address defaults to ::1
Port defaults to 47806
"""
- if address is None:
- address = "::1"
- # convert [IPv6_addr] to IPv6_addr:
- address = re.sub(r"\[(.+)\]", r"\1", address)
- if port is None:
- port = 47806
+ (address, port) = parse_addr_port(address, port)
args = [ 'dig', 'AXFR', '@' + str(address), '-p', str(port), zone_name ]
world.transfer_result = TransferResult(args)
+ at step('A customized AXFR transfer of ([\w.]+)(?: from ([\d.]+|\[[0-9a-fA-F:]+\])(?::([0-9]+))?)?(?: with pose of (\d+) seconds?)?')
+def perform_custom_axfr(step, zone_name, address, port, delay):
+ """Checks AXFR transfer, and store the result in the form of internal
+ CustomTransferResult class, which is compatible with TransferResult.
+
+ Step definition:
+ A customized AXFR transfer of <zone_name> [from <address>:<port>] [with pose of <delay> second]
+
+ If optional delay is specified (not None), it waits for the specified
+ seconds after sending the AXFR query before starting receiving
+ responses. This emulates a slower AXFR client.
+
+ """
+
+ class CustomTransferResult:
+ """Store transfer result only on the number of received answer RRs.
+
+ To be compatible with TransferResult it stores the result in the
+ 'records' attribute, which is a list. But its content is
+ meaningless; its only use is to be used with
+ check_transfer_result_count where its length is of concern.
+
+ """
+ def __init__(self):
+ self.records = []
+
+ # Build arguments and run xfr-client.py. On success, it simply dumps
+ # the number of received answer RRs to stdout.
+ (address, port) = parse_addr_port(address, port)
+ args = ['/bin/sh', 'run_python-tool.sh', 'tools/xfr-client.py',
+ '-s', address, '-p', str(port)]
+ if delay is not None:
+ args.extend(['-d', delay])
+ args.append(zone_name)
+ client = subprocess.Popen(args, 1, None, None, subprocess.PIPE,
+ subprocess.PIPE)
+ (stdout, stderr) = client.communicate()
+ result = client.returncode
+ world.last_client_stdout = stdout
+ world.last_client_stderr = stderr
+ assert result == 0, "xfr-client exit code: " + str(result) +\
+ "\nstdout:\n" + str(stdout) +\
+ "stderr:\n" + str(stderr)
+ num_rrs = int(stdout.strip())
+
+ # Make the result object, storing dummy value (None) for the number of
+ # answer RRs in the records list.
+ world.transfer_result = CustomTransferResult()
+ world.transfer_result.records = [None for _ in range(0, num_rrs)]
+
@step('An IXFR transfer of ([\w.]+) (\d+)(?: from ([^:]+)(?::([0-9]+))?)?(?: over (tcp|udp))?')
def perform_ixfr(step, zone_name, serial, address, port, protocol):
"""
diff --git a/tests/lettuce/features/xfrin_bind10.feature b/tests/lettuce/features/xfrin_bind10.feature
index 7ba1ca0..db4bddb 100644
--- a/tests/lettuce/features/xfrin_bind10.feature
+++ b/tests/lettuce/features/xfrin_bind10.feature
@@ -20,10 +20,13 @@ Feature: Xfrin
And wait for bind10 stderr message XFRIN_STARTED
And wait for bind10 stderr message ZONEMGR_STARTED
- # Now we use the first step again to see if the file has been created
+ # Now we use the first step again to see if the file has been created.
+ # The DB currently doesn't know anything about the zone, so we install
+ # an empty zone for xfrin.
The file data/test_nonexistent_db.sqlite3 should exist
-
A query for www.example.org to [::1]:47806 should have rcode REFUSED
+ Then make empty zone example.org in DB file data/test_nonexistent_db.sqlite3
+
When I send bind10 the command Xfrin retransfer example.org IN ::1 47807
# The data we receive contain a NS RRset that refers to three names in the
# example.org. zone. All these three are nonexistent in the data, producing
@@ -33,7 +36,10 @@ Feature: Xfrin
And wait for new bind10 stderr message XFRIN_ZONE_WARN
# But after complaining, the zone data should be accepted.
Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
- Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+ # there's no guarantee this is logged before XFRIN_TRANSFER_SUCCESS, so
+ # we can't reliably use 'wait for new'. In this case this should be the
+ # only occurrence of this message, so this should be okay.
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
A query for www.example.org to [::1]:47806 should have rcode NOERROR
# The transferred zone should have 11 non-NSEC3 RRs and 1 NSEC3 RR.
@@ -56,7 +62,8 @@ Feature: Xfrin
Then I send bind10 the command Xfrin retransfer example.org IN ::1 47807
And wait for new bind10 stderr message XFRIN_ZONE_INVALID
And wait for new bind10 stderr message XFRIN_INVALID_ZONE_DATA
- Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED
+ # We can't use 'wait for new' here; see above.
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED
A query for example.org type NS to [::1]:47806 should have rcode NOERROR
And transfer result should have 13 rrs
@@ -76,13 +83,17 @@ Feature: Xfrin
And wait for bind10 stderr message CMDCTL_STARTED
And wait for bind10 stderr message XFRIN_STARTED
+ # For xfrin make the data source aware of the zone (with empty data)
+ Then make empty zone example.org in DB file data/test_nonexistent_db.sqlite3
+
# Set slave config for 'automatic' xfrin
When I set bind10 configuration Xfrin/zones to [{"master_port": 47806, "name": "example.org", "master_addr": "::1"}]
# Make sure it is fully open
When I send bind10 the command Xfrin retransfer example.org
Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
- And wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+ # this can't be 'wait for new'; see above.
+ And wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
# First to master, a transfer should then fail
When I send bind10 the following commands with cmdctl port 47804:
@@ -130,17 +141,64 @@ Feature: Xfrin
And wait for bind10 stderr message XFRIN_STARTED
And wait for bind10 stderr message ZONEMGR_STARTED
- # Now we use the first step again to see if the file has been created
+ # Now we use the first step again to see if the file has been created,
+ # then install empty zone data
The file data/test_nonexistent_db.sqlite3 should exist
-
A query for www.example.org to [::1]:47806 should have rcode REFUSED
+ Then make empty zone example.org in DB file data/test_nonexistent_db.sqlite3
+
When I send bind10 the command Xfrin retransfer example.org IN ::1 47807
# It should complain once about invalid data, then again that the whole
# zone is invalid and then reject it.
And wait for new bind10 stderr message XFRIN_ZONE_INVALID
And wait for new bind10 stderr message XFRIN_INVALID_ZONE_DATA
- Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED
+ # This can't be 'wait for new'
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED
# The zone still doesn't exist as it is rejected.
# FIXME: This step fails. Probably an empty zone is created in the data
# source :-|. This should be REFUSED, not SERVFAIL.
A query for www.example.org to [::1]:47806 should have rcode SERVFAIL
+
+ # TODO:
+ # * IXFR - generate an sqlite db that contains the journal. Check it was
+ # IXFR by logs.
+ # * IXFR->AXFR fallback if IXFR is not available (even rejected or
+ # something, not just the differences missing).
+ # * Retransfer with short refresh time (without notify).
+ Scenario: With differences
+ # We transfer from one bind10 to other, just like in the Retransfer command
+ # scenario. Just this time, the master contains the differences table
+ # and the slave has a previous version of the zone, so we use the IXFR.
+
+ Given I have bind10 running with configuration xfrin/retransfer_master_diffs.conf with cmdctl port 47804 as master
+ And wait for master stderr message BIND10_STARTED_CC
+ And wait for master stderr message CMDCTL_STARTED
+ And wait for master stderr message AUTH_SERVER_STARTED
+ And wait for master stderr message XFROUT_STARTED
+ And wait for master stderr message ZONEMGR_STARTED
+
+ And I have bind10 running with configuration xfrin/retransfer_slave_diffs.conf
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ A query for example. type SOA to [::1]:47806 should have rcode NOERROR
+ The answer section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. hostmaster.example. 94 3600 900 7200 300
+ """
+
+ # To invoke IXFR we need to use refresh command
+ When I send bind10 the command Xfrin refresh example. IN ::1 47807
+ Then wait for new bind10 stderr message XFRIN_GOT_INCREMENTAL_RESP
+ Then wait for new bind10 stderr message XFRIN_IXFR_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
+ # This can't be 'wait for new'
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+
+ A query for example. type SOA to [::1]:47806 should have rcode NOERROR
+ The answer section of the last query response should be
+ """
+ example. 3600 IN SOA ns1.example. hostmaster.example. 100 3600 900 7200 300
+ """
diff --git a/tests/lettuce/features/xfrin_notify_handling.feature b/tests/lettuce/features/xfrin_notify_handling.feature
index 5aabf59..387fd73 100644
--- a/tests/lettuce/features/xfrin_notify_handling.feature
+++ b/tests/lettuce/features/xfrin_notify_handling.feature
@@ -1,5 +1,10 @@
Feature: Xfrin incoming notify handling
- Tests for Xfrin incoming notify handling.
+ Tests for Xfrin incoming notify handling. They also test
+ statistics counters incremented, which are related to notifying
+ and transferring by Xfrout and receiveing by Xfrin. Some cases are
+ considered: Transferring is done via IPv4 or IPv6 transport. A
+ transfer request from Xfrin is rejected by Xfrout. The master
+ server or slave server is unreachable.
Scenario: Handle incoming notify
Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
@@ -22,92 +27,144 @@ Feature: Xfrin incoming notify handling
#
# Test1 for Xfrout statistics
#
- # check initial statistics
+ check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+ | item_name | item_max | item_min |
+ | socket.unixdomain.open | 1 | 0 |
+ # Note: .Xfrout.socket.unixdomain.open can be either expected to
+ # be 0 or 1 here. The reason is: if b10-xfrout has started up and is
+ # ready for a request from b10-stats, then b10-stats does request
+ # to b10-xfrout and the value results in 1. Otherwise if
+ # b10-xfrout is starting and isn't yet ready, then b10-stats
+ # doesn't request to b10-xfrout and the value still remains to be the
+ # default value(0).
+
+ #
+ # Test2 for Xfrin statistics
#
+ check initial statistics not containing example.org for Xfrin
+
+ When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+ Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
+ Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
+ # From this point we can't reliably 'wait for new' because the ordering
+ # of logs from different processes is unpredictable. But these
+ # should be okay in this case.
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+ Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+ Then wait for bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+ Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
+
+ A query for www.example.org to [::1]:47806 should have rcode NOERROR
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
+ #
+ # Test3 for Xfrout statistics
+ #
+ # check statistics change
+ #
+
+ # wait until the last stats requesting is finished
When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ # note that this does not 100% guarantee the stats updated Xfrout
+ # statistics. But there doesn't seem to be a better log message that
+ # suggests this event.
+ wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
last bindctl output should not contain "error"
- last bindctl output should not contain "example.org."
- Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
- Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 0
- Then the statistics counter xfrrej for the zone _SERVER_ should be 0
- Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
- When I query statistics ixfr_running of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter ixfr_running should be 0
-
- When I query statistics axfr_running of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter axfr_running should be 0
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ The statistics counters are 0 in category .Xfrout.zones.IN except for the following items
+ | item_name | item_value |
+ | _SERVER_.notifyoutv6 | 1 |
+ | _SERVER_.xfrreqdone | 1 |
+ | example.org..notifyoutv6 | 1 |
+ | example.org..xfrreqdone | 1 |
When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter open for unixdomain should be between 0 and 1
- Then the statistics counter openfail for unixdomain should be 0
- Then the statistics counter close for unixdomain should be 0
- Then the statistics counter bindfail for unixdomain should be 0
- Then the statistics counter acceptfail for unixdomain should be 0
- Then the statistics counter accept for unixdomain should be 0
- Then the statistics counter senderr for unixdomain should be 0
- Then the statistics counter recverr for unixdomain should be 0
+ The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+ | item_name | item_value |
+ | open | 1 |
+ | accept | 1 |
#
- # Test2 for Xfrin statistics
+ # Test4 for Xfrin statistics
#
- # check initial statistics
+ # check statistics change
#
+
+ # wait until the last stats requesting is finished
When I query statistics zones of bind10 module Xfrin with cmdctl
+ wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
last bindctl output should not contain "error"
- Then the statistics counter soaoutv4 for the zone _SERVER_ should be 0
- Then the statistics counter soaoutv6 for the zone _SERVER_ should be 0
- Then the statistics counter axfrreqv4 for the zone _SERVER_ should be 0
- Then the statistics counter axfrreqv6 for the zone _SERVER_ should be 0
- Then the statistics counter ixfrreqv4 for the zone _SERVER_ should be 0
- Then the statistics counter ixfrreqv6 for the zone _SERVER_ should be 0
- Then the statistics counter xfrsuccess for the zone _SERVER_ should be 0
- Then the statistics counter xfrfail for the zone _SERVER_ should be 0
- Then the statistics counter time_to_ixfr for the zone _SERVER_ should be 0.0
- Then the statistics counter time_to_axfr for the zone _SERVER_ should be 0.0
-
- When I query statistics ixfr_deferred of bind10 module Xfrin with cmdctl
- Then the statistics counter ixfr_deferred should be 0
- When I query statistics axfr_deferred of bind10 module Xfrin with cmdctl
- Then the statistics counter axfr_deferred should be 0
+ When I query statistics of bind10 module Xfrin with cmdctl
+ The statistics counters are 0 in category .Xfrin except for the following items
+ | item_name | item_value | min_value |
+ | zones.IN._SERVER_.soaoutv6 | 1 | |
+ | zones.IN._SERVER_.axfrreqv6 | 1 | |
+ | zones.IN._SERVER_.xfrsuccess | 1 | |
+ | zones.IN._SERVER_.last_axfr_duration | | 0.0 |
+ | zones.IN.example.org..soaoutv6 | 1 | |
+ | zones.IN.example.org..axfrreqv6 | 1 | |
+ | zones.IN.example.org..xfrsuccess | 1 | |
+ | zones.IN.example.org..last_axfr_duration | | 0.0 |
+ | socket.ipv6.tcp.open | | 1 |
+ | socket.ipv6.tcp.close | | 1 |
+ | socket.ipv6.tcp.conn | | 1 |
+ | socket.ipv6.tcp.connfail | 0 | |
+
+ #
+ # Test for handling incoming notify only in IPv4
+ #
+ Scenario: Handle incoming notify (IPv4)
+ Given I have bind10 running with configuration xfrin/retransfer_master_v4.conf with cmdctl port 47804 as master
+ And wait for master stderr message BIND10_STARTED_CC
+ And wait for master stderr message CMDCTL_STARTED
+ And wait for master stderr message AUTH_SERVER_STARTED
+ And wait for master stderr message XFROUT_STARTED
+ And wait for master stderr message ZONEMGR_STARTED
+ And wait for master stderr message STATS_STARTING
- When I query statistics ixfr_running of bind10 module Xfrin with cmdctl
- Then the statistics counter ixfr_running should be 0
+ And I have bind10 running with configuration xfrin/retransfer_slave_notify_v4.conf
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
- When I query statistics axfr_running of bind10 module Xfrin with cmdctl
- Then the statistics counter axfr_running should be 0
+ A query for www.example.org to 127.0.0.1:47806 should have rcode NXDOMAIN
- When I query statistics soa_in_progress of bind10 module Xfrin with cmdctl
- Then the statistics counter soa_in_progress should be 0
+ #
+ # Test1 for Xfrout statistics
+ #
+ check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+ | item_name | item_max | item_min |
+ | socket.unixdomain.open | 1 | 0 |
+ # Note: See above about .Xfrout.socket.unixdomain.open.
- When I query statistics socket of bind10 module Xfrin with cmdctl
- Then the statistics counter open for ipv4 should be 0
- Then the statistics counter openfail for ipv4 should be 0
- Then the statistics counter close for ipv4 should be 0
- Then the statistics counter connfail for ipv4 should be 0
- Then the statistics counter conn for ipv4 should be 0
- Then the statistics counter senderr for ipv4 should be 0
- Then the statistics counter recverr for ipv4 should be 0
- Then the statistics counter open for ipv6 should be 0
- Then the statistics counter openfail for ipv6 should be 0
- Then the statistics counter close for ipv6 should be 0
- Then the statistics counter connfail for ipv6 should be 0
- Then the statistics counter conn for ipv6 should be 0
- Then the statistics counter senderr for ipv6 should be 0
- Then the statistics counter recverr for ipv6 should be 0
+ #
+ # Test2 for Xfrin statistics
+ #
+ check initial statistics not containing example.org for Xfrin
When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
- Then wait for new bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
- Then wait for new bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
- Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
- Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
- Then wait 5 times for new master stderr message NOTIFY_OUT_SENDING_NOTIFY
- Then wait for new master stderr message NOTIFY_OUT_RETRY_EXCEEDED
+ # From this point we can't reliably 'wait for new' because the ordering
+ # of logs from different processes is unpredictable. But these
+ # should be okay in this case.
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+ Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+ Then wait for bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+ Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
- A query for www.example.org to [::1]:47806 should have rcode NOERROR
+ A query for www.example.org to 127.0.0.1:47806 should have rcode NOERROR
+ # Make sure handling statistics command handling checked below is
+ # after this query
+ And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
#
# Test3 for Xfrout statistics
@@ -116,29 +173,26 @@ Feature: Xfrin incoming notify handling
#
# wait until the last stats requesting is finished
- wait for new master stderr message STATS_SEND_STATISTICS_REQUEST
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ # note that this does not 100% guarantee the stats updated Xfrout
+ # statistics. But there doesn't seem to be a better log message that
+ # suggests this event.
wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
+ last bindctl output should not contain "error"
When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
- last bindctl output should not contain "error"
- Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
- Then the statistics counter notifyoutv4 for the zone example.org. should be 0
- Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 5
- Then the statistics counter notifyoutv6 for the zone example.org. should be 5
- Then the statistics counter xfrrej for the zone _SERVER_ should be 0
- Then the statistics counter xfrrej for the zone example.org. should be 0
- Then the statistics counter xfrreqdone for the zone _SERVER_ should be 1
- Then the statistics counter xfrreqdone for the zone example.org. should be 1
+ The statistics counters are 0 in category .Xfrout.zones.IN except for the following items
+ | item_name | item_value |
+ | _SERVER_.notifyoutv4 | 1 |
+ | _SERVER_.xfrreqdone | 1 |
+ | example.org..notifyoutv4 | 1 |
+ | example.org..xfrreqdone | 1 |
When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter open for unixdomain should be 1
- Then the statistics counter openfail for unixdomain should be 0
- Then the statistics counter close for unixdomain should be 0
- Then the statistics counter bindfail for unixdomain should be 0
- Then the statistics counter acceptfail for unixdomain should be 0
- Then the statistics counter accept for unixdomain should be 1
- Then the statistics counter senderr for unixdomain should be 0
- Then the statistics counter recverr for unixdomain should be 0
+ The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+ | item_name | item_value |
+ | open | 1 |
+ | accept | 1 |
#
# Test4 for Xfrin statistics
@@ -147,47 +201,25 @@ Feature: Xfrin incoming notify handling
#
# wait until the last stats requesting is finished
- wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
- wait for new bind10 stderr message XFRIN_RECEIVED_GETSTATS_COMMAND
-
When I query statistics zones of bind10 module Xfrin with cmdctl
+ wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
last bindctl output should not contain "error"
- Then the statistics counter soaoutv4 for the zone _SERVER_ should be 0
- Then the statistics counter soaoutv4 for the zone example.org. should be 0
- Then the statistics counter soaoutv6 for the zone _SERVER_ should be 1
- Then the statistics counter soaoutv6 for the zone example.org. should be 1
- Then the statistics counter axfrreqv4 for the zone _SERVER_ should be 0
- Then the statistics counter axfrreqv4 for the zone example.org. should be 0
- Then the statistics counter axfrreqv6 for the zone _SERVER_ should be 1
- Then the statistics counter axfrreqv6 for the zone example.org. should be 1
- Then the statistics counter ixfrreqv4 for the zone _SERVER_ should be 0
- Then the statistics counter ixfrreqv4 for the zone example.org. should be 0
- Then the statistics counter ixfrreqv6 for the zone _SERVER_ should be 0
- Then the statistics counter ixfrreqv6 for the zone example.org. should be 0
- Then the statistics counter xfrsuccess for the zone _SERVER_ should be 1
- Then the statistics counter xfrsuccess for the zone example.org. should be 1
- Then the statistics counter xfrfail for the zone _SERVER_ should be 0
- Then the statistics counter xfrfail for the zone example.org. should be 0
- Then the statistics counter time_to_ixfr for the zone _SERVER_ should be 0.0
- Then the statistics counter time_to_ixfr for the zone example.org. should be 0.0
- Then the statistics counter time_to_axfr for the zone _SERVER_ should be greater than 0.0
- Then the statistics counter time_to_axfr for the zone example.org. should be greater than 0.0
- When I query statistics socket of bind10 module Xfrin with cmdctl
- Then the statistics counter open for ipv4 should be 0
- Then the statistics counter openfail for ipv4 should be 0
- Then the statistics counter close for ipv4 should be 0
- Then the statistics counter connfail for ipv4 should be 0
- Then the statistics counter conn for ipv4 should be 0
- Then the statistics counter senderr for ipv4 should be 0
- Then the statistics counter recverr for ipv4 should be 0
- Then the statistics counter open for ipv6 should be greater than 0
- Then the statistics counter openfail for ipv6 should be 0
- Then the statistics counter close for ipv6 should be greater than 0
- Then the statistics counter connfail for ipv6 should be 0
- Then the statistics counter conn for ipv6 should be greater than 0
- Then the statistics counter senderr for ipv6 should be 0
- Then the statistics counter recverr for ipv6 should be 0
+ When I query statistics of bind10 module Xfrin with cmdctl
+ The statistics counters are 0 in category .Xfrin except for the following items
+ | item_name | item_value | min_value |
+ | zones.IN._SERVER_.soaoutv4 | 1 | |
+ | zones.IN._SERVER_.axfrreqv4 | 1 | |
+ | zones.IN._SERVER_.xfrsuccess | 1 | |
+ | zones.IN._SERVER_.last_axfr_duration | | 0.0 |
+ | zones.IN.example.org..soaoutv4 | 1 | |
+ | zones.IN.example.org..axfrreqv4 | 1 | |
+ | zones.IN.example.org..xfrsuccess | 1 | |
+ | zones.IN.example.org..last_axfr_duration | | 0.0 |
+ | socket.ipv4.tcp.open | | 1 |
+ | socket.ipv4.tcp.close | | 1 |
+ | socket.ipv4.tcp.conn | | 1 |
+ | socket.ipv4.tcp.connfail | 0 | |
#
# Test for Xfr request rejected
@@ -211,82 +243,127 @@ Feature: Xfrin incoming notify handling
A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
#
- # Test5 for Xfrout statistics
+ # Test1 for Xfrout statistics
#
- # check initial statistics
+ check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+ | item_name | item_max | item_min |
+ | socket.unixdomain.open | 1 | 0 |
+ # Note: See above about .Xfrout.socket.unixdomain.open.
+
#
+ # Test2 for Xfrin statistics
+ #
+ check initial statistics not containing example.org for Xfrin
+
+ #
+ # set transfer_acl rejection
+ # Local xfr requests from Xfrin module would be rejected here.
+ #
+ When I send bind10 the following commands with cmdctl port 47804
+ """
+ config set Xfrout/zone_config[0]/transfer_acl [{"action": "REJECT", "from": "::1"}]
+ config commit
+ """
+ last bindctl output should not contain Error
+
+ When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+ Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
+ Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
+ # can't use 'wait for new' below.
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+ Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+ Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION not XFRIN_TRANSFER_SUCCESS
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
+ Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
+
+ A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+
+ #
+ # Test3 for Xfrout statistics
+ #
+ # check statistics change
+ #
+
+ # wait until the last stats requesting is finished
When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
last bindctl output should not contain "error"
- last bindctl output should not contain "example.org."
- Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
- Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 0
- Then the statistics counter xfrrej for the zone _SERVER_ should be 0
- Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
- When I query statistics ixfr_running of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter ixfr_running should be 0
-
- When I query statistics axfr_running of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter axfr_running should be 0
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ The statistics counters are 0 in category .Xfrout.zones.IN except for the following items
+ | item_name | item_value | min_value | max_value |
+ | _SERVER_.notifyoutv6 | 1 | | |
+ | _SERVER_.xfrrej | | 1 | 3 |
+ | example.org..notifyoutv6 | 1 | | |
+ | example.org..xfrrej | | 1 | 3 |
+ # Note: The above rejection counters might sometimes be increased
+ # up to 3. See this for details
+ # http://git.bind10.isc.org/~tester/builder/BIND10-lettuce/20120918210000-MacOS/logs/lettuce.out
When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter open for unixdomain should be between 0 and 1
- Then the statistics counter openfail for unixdomain should be 0
- Then the statistics counter close for unixdomain should be 0
- Then the statistics counter bindfail for unixdomain should be 0
- Then the statistics counter acceptfail for unixdomain should be 0
- Then the statistics counter accept for unixdomain should be 0
- Then the statistics counter senderr for unixdomain should be 0
- Then the statistics counter recverr for unixdomain should be 0
+ The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+ | item_name | item_value |
+ | open | 1 |
+ | accept | 1 |
#
- # Test6 for Xfrin statistics
+ # Test4 for Xfrin statistics
#
- # check initial statistics
+ # check statistics change
#
+
+ # wait until the last stats requesting is finished
When I query statistics zones of bind10 module Xfrin with cmdctl
+ wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
last bindctl output should not contain "error"
- Then the statistics counter soaoutv4 for the zone _SERVER_ should be 0
- Then the statistics counter soaoutv6 for the zone _SERVER_ should be 0
- Then the statistics counter axfrreqv4 for the zone _SERVER_ should be 0
- Then the statistics counter axfrreqv6 for the zone _SERVER_ should be 0
- Then the statistics counter ixfrreqv4 for the zone _SERVER_ should be 0
- Then the statistics counter ixfrreqv6 for the zone _SERVER_ should be 0
- Then the statistics counter xfrsuccess for the zone _SERVER_ should be 0
- Then the statistics counter xfrfail for the zone _SERVER_ should be 0
- Then the statistics counter time_to_ixfr for the zone _SERVER_ should be 0.0
- Then the statistics counter time_to_axfr for the zone _SERVER_ should be 0.0
- When I query statistics ixfr_deferred of bind10 module Xfrin with cmdctl
- Then the statistics counter ixfr_deferred should be 0
-
- When I query statistics axfr_deferred of bind10 module Xfrin with cmdctl
- Then the statistics counter axfr_deferred should be 0
+ When I query statistics of bind10 module Xfrin with cmdctl
+ The statistics counters are 0 in category .Xfrin except for the following items
+ | item_name | item_value | min_value |
+ | zones.IN._SERVER_.soaoutv6 | 1 | |
+ | zones.IN._SERVER_.axfrreqv6 | 1 | |
+ | zones.IN._SERVER_.xfrfail | 1 | |
+ | zones.IN.example.org..soaoutv6 | 1 | |
+ | zones.IN.example.org..axfrreqv6 | 1 | |
+ | zones.IN.example.org..xfrfail | 1 | |
+ | socket.ipv6.tcp.open | | 1 |
+ | socket.ipv6.tcp.close | | 1 |
+ | socket.ipv6.tcp.conn | | 1 |
+ | socket.ipv6.tcp.connfail | 0 | |
+
+ #
+ # Test for Xfr request rejected in IPv4
+ #
+ Scenario: Handle incoming notify (XFR request rejected in IPv4)
+ Given I have bind10 running with configuration xfrin/retransfer_master_v4.conf with cmdctl port 47804 as master
+ And wait for master stderr message BIND10_STARTED_CC
+ And wait for master stderr message CMDCTL_STARTED
+ And wait for master stderr message AUTH_SERVER_STARTED
+ And wait for master stderr message XFROUT_STARTED
+ And wait for master stderr message ZONEMGR_STARTED
+ And wait for master stderr message STATS_STARTING
- When I query statistics ixfr_running of bind10 module Xfrin with cmdctl
- Then the statistics counter ixfr_running should be 0
+ And I have bind10 running with configuration xfrin/retransfer_slave_notify_v4.conf
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
- When I query statistics axfr_running of bind10 module Xfrin with cmdctl
- Then the statistics counter axfr_running should be 0
+ A query for www.example.org to 127.0.0.1:47806 should have rcode NXDOMAIN
- When I query statistics soa_in_progress of bind10 module Xfrin with cmdctl
- Then the statistics counter soa_in_progress should be 0
+ #
+ # Test1 for Xfrout statistics
+ #
+ check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+ | item_name | item_max | item_min |
+ | socket.unixdomain.open | 1 | 0 |
+ # Note: See above about .Xfrout.socket.unixdomain.open.
- When I query statistics socket of bind10 module Xfrin with cmdctl
- Then the statistics counter open for ipv4 should be 0
- Then the statistics counter openfail for ipv4 should be 0
- Then the statistics counter close for ipv4 should be 0
- Then the statistics counter connfail for ipv4 should be 0
- Then the statistics counter conn for ipv4 should be 0
- Then the statistics counter senderr for ipv4 should be 0
- Then the statistics counter recverr for ipv4 should be 0
- Then the statistics counter open for ipv6 should be 0
- Then the statistics counter openfail for ipv6 should be 0
- Then the statistics counter close for ipv6 should be 0
- Then the statistics counter connfail for ipv6 should be 0
- Then the statistics counter conn for ipv6 should be 0
- Then the statistics counter senderr for ipv6 should be 0
- Then the statistics counter recverr for ipv6 should be 0
+ #
+ # Test2 for Xfrin statistics
+ #
+ check initial statistics not containing example.org for Xfrin
#
# set transfer_acl rejection
@@ -294,7 +371,7 @@ Feature: Xfrin incoming notify handling
#
When I send bind10 the following commands with cmdctl port 47804
"""
- config set Xfrout/zone_config[0]/transfer_acl [{"action": "REJECT", "from": "::1"}]
+ config set Xfrout/zone_config[0]/transfer_acl [{"action": "REJECT", "from": "127.0.0.1"}]
config commit
"""
last bindctl output should not contain Error
@@ -302,97 +379,67 @@ Feature: Xfrin incoming notify handling
When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
- Then wait for new bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
- Then wait for new bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
- Then wait for new bind10 stderr message XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION not XFRIN_XFR_TRANSFER_STARTED
- Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
- Then wait 5 times for new master stderr message NOTIFY_OUT_SENDING_NOTIFY
- Then wait for new master stderr message NOTIFY_OUT_RETRY_EXCEEDED
+ # can't use 'wait for new' below.
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+ Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+ Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION not XFRIN_TRANSFER_SUCCESS
+ Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
+ Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
- A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+ A query for www.example.org to 127.0.0.1:47806 should have rcode NXDOMAIN
#
- # Test7 for Xfrout statistics
+ # Test3 for Xfrout statistics
#
# check statistics change
#
- # wait until the last stats requesting is finished
- wait for new master stderr message STATS_SEND_STATISTICS_REQUEST
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ # wait until stats request at least after NOTIFY_OUT_REPLY_RECEIVED
wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
+ last bindctl output should not contain "error"
When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
- last bindctl output should not contain "error"
- Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
- Then the statistics counter notifyoutv4 for the zone example.org. should be 0
- Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 5
- Then the statistics counter notifyoutv6 for the zone example.org. should be 5
- # The counts of rejection would be between 1 and 2. They are not
- # fixed. It would depend on timing or the platform.
- Then the statistics counter xfrrej for the zone _SERVER_ should be greater than 0
- Then the statistics counter xfrrej for the zone example.org. should be greater than 0
- Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
- Then the statistics counter xfrreqdone for the zone example.org. should be 0
+ The statistics counters are 0 in category .Xfrout.zones.IN except for the following items
+ | item_name | item_value | min_value | max_value |
+ | _SERVER_.notifyoutv4 | 1 | | |
+ | _SERVER_.xfrrej | | 1 | 3 |
+ | example.org..notifyoutv4 | 1 | | |
+ | example.org..xfrrej | | 1 | 3 |
+ # Note: The above rejection counters might sometimes be increased
+ # up to 3. See this for details
+ # http://git.bind10.isc.org/~tester/builder/BIND10-lettuce/20120918210000-MacOS/logs/lettuce.out
When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter open for unixdomain should be 1
- Then the statistics counter openfail for unixdomain should be 0
- Then the statistics counter close for unixdomain should be 0
- Then the statistics counter bindfail for unixdomain should be 0
- Then the statistics counter acceptfail for unixdomain should be 0
- Then the statistics counter accept for unixdomain should be 1
- Then the statistics counter senderr for unixdomain should be 0
- Then the statistics counter recverr for unixdomain should be 0
+ The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+ | item_name | item_value |
+ | open | 1 |
+ | accept | 1 |
#
- # Test8 for Xfrin statistics
+ # Test4 for Xfrin statistics
#
# check statistics change
#
# wait until the last stats requesting is finished
- wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
- wait for new bind10 stderr message XFRIN_RECEIVED_GETSTATS_COMMAND
-
When I query statistics zones of bind10 module Xfrin with cmdctl
+ wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
last bindctl output should not contain "error"
- Then the statistics counter soaoutv4 for the zone _SERVER_ should be 0
- Then the statistics counter soaoutv4 for the zone example.org. should be 0
- Then the statistics counter soaoutv6 for the zone _SERVER_ should be greater than 0
- Then the statistics counter soaoutv6 for the zone example.org. should be greater than 0
- Then the statistics counter axfrreqv4 for the zone _SERVER_ should be 0
- Then the statistics counter axfrreqv4 for the zone example.org. should be 0
- Then the statistics counter axfrreqv6 for the zone _SERVER_ should be greater than 0
- Then the statistics counter axfrreqv6 for the zone example.org. should be greater than 0
- Then the statistics counter ixfrreqv4 for the zone _SERVER_ should be 0
- Then the statistics counter ixfrreqv4 for the zone example.org. should be 0
- Then the statistics counter ixfrreqv6 for the zone _SERVER_ should be 0
- Then the statistics counter ixfrreqv6 for the zone example.org. should be 0
- Then the statistics counter xfrsuccess for the zone _SERVER_ should be 0
- Then the statistics counter xfrsuccess for the zone example.org. should be 0
- Then the statistics counter xfrfail for the zone _SERVER_ should be greater than 0
- Then the statistics counter xfrfail for the zone example.org. should be greater than 0
- Then the statistics counter time_to_ixfr for the zone _SERVER_ should be 0.0
- Then the statistics counter time_to_ixfr for the zone example.org. should be 0.0
- Then the statistics counter time_to_axfr for the zone _SERVER_ should be 0.0
- Then the statistics counter time_to_axfr for the zone example.org. should be 0.0
- When I query statistics socket of bind10 module Xfrin with cmdctl
- last bindctl output should not contain "error"
- Then the statistics counter open for ipv4 should be 0
- Then the statistics counter openfail for ipv4 should be 0
- Then the statistics counter close for ipv4 should be 0
- Then the statistics counter connfail for ipv4 should be 0
- Then the statistics counter conn for ipv4 should be 0
- Then the statistics counter senderr for ipv4 should be 0
- Then the statistics counter recverr for ipv4 should be 0
- Then the statistics counter open for ipv6 should be greater than 0
- Then the statistics counter openfail for ipv6 should be 0
- Then the statistics counter close for ipv6 should be greater than 0
- Then the statistics counter connfail for ipv6 should be 0
- Then the statistics counter conn for ipv6 should be greater than 0
- Then the statistics counter senderr for ipv6 should be 0
- Then the statistics counter recverr for ipv6 should be 0
+ When I query statistics of bind10 module Xfrin with cmdctl
+ The statistics counters are 0 in category .Xfrin except for the following items
+ | item_name | item_value | min_value |
+ | zones.IN._SERVER_.soaoutv4 | 1 | |
+ | zones.IN._SERVER_.axfrreqv4 | 1 | |
+ | zones.IN._SERVER_.xfrfail | 1 | |
+ | zones.IN.example.org..soaoutv4 | 1 | |
+ | zones.IN.example.org..axfrreqv4 | 1 | |
+ | zones.IN.example.org..xfrfail | 1 | |
+ | socket.ipv4.tcp.open | | 1 |
+ | socket.ipv4.tcp.close | | 1 |
+ | socket.ipv4.tcp.conn | | 1 |
+ | socket.ipv4.tcp.connfail | 0 | |
#
# Test for unreachable slave
@@ -412,40 +459,156 @@ Feature: Xfrin incoming notify handling
Then wait for new master stderr message NOTIFY_OUT_TIMEOUT
#
- # Test9 for Xfrout statistics
+ # Test1 for Xfrout statistics
#
# check statistics change
#
- # wait until the last stats requesting is finished
- wait for new master stderr message STATS_SEND_STATISTICS_REQUEST
+ When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+ # wait until stats request at least after NOTIFY_OUT_TIMEOUT
wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
+ last bindctl output should not contain "error"
When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
- last bindctl output should not contain "error"
- Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
- Then the statistics counter notifyoutv4 for the zone example.org. should be 0
- Then the statistics counter notifyoutv6 for the zone _SERVER_ should be greater than 0
- Then the statistics counter notifyoutv6 for the zone example.org. should be greater than 0
- Then the statistics counter xfrrej for the zone _SERVER_ should be 0
- Then the statistics counter xfrrej for the zone example.org. should be 0
- Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
- Then the statistics counter xfrreqdone for the zone example.org. should be 0
+ The statistics counters are 0 in category .Xfrout.zones.IN except for the following items
+ | item_name | min_value | max_value |
+ | _SERVER_.notifyoutv6 | 1 | 5 |
+ | example.org..notifyoutv6 | 1 | 5 |
When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
- Then the statistics counter open for unixdomain should be 1
- Then the statistics counter openfail for unixdomain should be 0
- Then the statistics counter close for unixdomain should be 0
- Then the statistics counter bindfail for unixdomain should be 0
- Then the statistics counter acceptfail for unixdomain should be 0
- Then the statistics counter accept for unixdomain should be 0
- Then the statistics counter senderr for unixdomain should be 0
- Then the statistics counter recverr for unixdomain should be 0
+ The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+ | item_name | item_value |
+ | open | 1 |
+
+ #
+ # Test for NOTIFY that would result in NOTAUTH
+ #
+ Scenario: Handle incoming notify that does match authoritative zones
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ And wait for master stderr message BIND10_STARTED_CC
+ And wait for master stderr message CMDCTL_STARTED
+ And wait for master stderr message AUTH_SERVER_STARTED
+ And wait for master stderr message XFROUT_STARTED
+ And wait for master stderr message ZONEMGR_STARTED
+ And wait for master stderr message STATS_STARTING
+
+ And I have bind10 running with configuration xfrin/retransfer_slave_notify.conf
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ #
+ # replace master's data source with unmatched zone for slave's zone.
+ # we restart Xfrout to make it sure.
+ #
+ When I send bind10 the following commands with cmdctl port 47804
+ """
+ config set data_sources/classes/IN[0]/params/database_file data/ixfr-out/zones.sqlite3
+ config set Auth/database_file data/ixfr-out/zones.sqlite3
+ config set Xfrout/zone_config[0]/origin example.com
+ config commit
+ Xfrout shutdown
+ """
+ last bindctl output should not contain "error"
+ And wait for new master stderr message XFROUT_STARTED
+
+ A query for www.example.com to [::1]:47806 should have rcode REFUSED
+
+ When I send bind10 with cmdctl port 47804 the command Xfrout notify example.com IN
+ Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
+ Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY_NOTAUTH
+ Then wait for new master stderr message NOTIFY_OUT_REPLY_RECEIVED
+
+ A query for www.example.com to [::1]:47806 should have rcode REFUSED
+
+ #
+ # Test for NOTIFY that's not in the secondaries list
+ #
+ Scenario: Handle incoming notify that is not in the secondaries list
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ And wait for master stderr message BIND10_STARTED_CC
+ And wait for master stderr message CMDCTL_STARTED
+ And wait for master stderr message AUTH_SERVER_STARTED
+ And wait for master stderr message XFROUT_STARTED
+ And wait for master stderr message ZONEMGR_STARTED
+ And wait for master stderr message STATS_STARTING
+
+ And I have bind10 running with configuration xfrin/retransfer_slave_notify.conf
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ #
+ # Empty slave's secondaries list, and restart zonemgr to make it sure
+ #
+ When I send bind10 the following commands with cmdctl
+ """
+ config remove Zonemgr/secondary_zones[0]
+ config commit
+ Zonemgr shutdown
+ """
+ last bindctl output should not contain "error"
+ And wait for new bind10 stderr message ZONEMGR_STARTED
+
+ A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+
+ When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+ Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
+ Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
+ Then wait for new bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+ Then wait for new bind10 stderr message ZONEMGR_ZONE_NOTIFY_NOT_SECONDARY
+ Then wait for new master stderr message NOTIFY_OUT_REPLY_RECEIVED
+
+ A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+
+ #
+ # Test for NOTIFY when zonemgr is not running
+ #
+ Scenario: Handle incoming notify while zonemgr is not running
+ Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
+ And wait for master stderr message BIND10_STARTED_CC
+ And wait for master stderr message CMDCTL_STARTED
+ And wait for master stderr message AUTH_SERVER_STARTED
+ And wait for master stderr message XFROUT_STARTED
+ And wait for master stderr message ZONEMGR_STARTED
+ And wait for master stderr message STATS_STARTING
+
+ And I have bind10 running with configuration xfrin/retransfer_slave_notify.conf
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFRIN_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ # remove zonemgr from the system. a subsequent notify is ignored, but
+ # an error message shouldn't be logged at auth.
+ When I send bind10 the following commands with cmdctl
+ """
+ config remove Init/components b10-zonemgr
+ config commit
+ """
+ last bindctl output should not contain "error"
+ And wait for new bind10 stderr message BIND10_PROCESS_ENDED
+
+ A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+
+ When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+ Then wait for master stderr message XFROUT_NOTIFY_COMMAND
+ Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
+ Then wait for new bind10 stderr message AUTH_ZONEMGR_NOTEXIST not AUTH_ZONEMGR_ERROR
+ Then wait for master stderr message NOTIFY_OUT_TIMEOUT not NOTIFY_OUT_REPLY_RECEIVED
+
+ A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
#
# Test for unreachable master
#
Scenario: Handle incoming notify (unreachable master)
+
And I have bind10 running with configuration xfrin/retransfer_slave_notify.conf
And wait for bind10 stderr message BIND10_STARTED_CC
And wait for bind10 stderr message CMDCTL_STARTED
@@ -456,6 +619,11 @@ Feature: Xfrin incoming notify handling
A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
#
+ # Test1 for Xfrin statistics
+ #
+ check initial statistics not containing example.org for Xfrin
+
+ #
# execute reftransfer for Xfrin
#
When I send bind10 the command Xfrin retransfer example.org IN
@@ -463,27 +631,18 @@ Feature: Xfrin incoming notify handling
Then wait for new bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED
#
- # Test10 for Xfrin statistics
+ # Test2 for Xfrin statistics
#
# check initial statistics
#
# wait until the last stats requesting is finished
wait for new bind10 stderr message STATS_SEND_STATISTICS_REQUEST
- wait for new bind10 stderr message XFRIN_RECEIVED_GETSTATS_COMMAND
+ wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
When I query statistics socket of bind10 module Xfrin with cmdctl
- Then the statistics counter open for ipv4 should be 0
- Then the statistics counter openfail for ipv4 should be 0
- Then the statistics counter close for ipv4 should be 0
- Then the statistics counter connfail for ipv4 should be 0
- Then the statistics counter conn for ipv4 should be 0
- Then the statistics counter senderr for ipv4 should be 0
- Then the statistics counter recverr for ipv4 should be 0
- Then the statistics counter open for ipv6 should be greater than 0
- Then the statistics counter openfail for ipv6 should be 0
- Then the statistics counter close for ipv6 should be greater than 0
- Then the statistics counter connfail for ipv6 should be greater than 0
- Then the statistics counter conn for ipv6 should be 0
- Then the statistics counter senderr for ipv6 should be 0
- Then the statistics counter recverr for ipv6 should be 0
+ The statistics counters are 0 in category .Xfrin.socket.ipv6.tcp except for the following items
+ | item_name | min_value |
+ | open | 1 |
+ | close | 1 |
+ | connfail | 1 |
diff --git a/tests/lettuce/features/xfrout_bind10.feature b/tests/lettuce/features/xfrout_bind10.feature
new file mode 100644
index 0000000..7f4e4de
--- /dev/null
+++ b/tests/lettuce/features/xfrout_bind10.feature
@@ -0,0 +1,39 @@
+Feature: Xfrout
+ Tests for Xfrout, specific for BIND 10 behaviour.
+
+ Scenario: normal transfer with a moderate number of RRs
+
+ Load 100 records for zone example.org to DB file data/xfrout.sqlite3
+
+ Given I have bind10 running with configuration xfrout_master.conf
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFROUT_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ # The transferred zone should have the generated 100 RRs plush one
+ # trailing SOA.
+ When I do a customized AXFR transfer of example.org
+ Then transfer result should have 101 rrs
+
+ # Similar to the previous one, but using a much larger zone, and with
+ # a small delay at the client side. It should still succeed.
+ # The specific delay (5 seconds) was chosen for an environment that
+ # revealed a bug which is now fixed to reproduce the issue; shorter delays
+ # didn't trigger the problem. Depending on the OS implementation, machine
+ # speed, etc, the same delay may be too long or too short, but in any case
+ # the test should succeed now.
+ Scenario: transfer a large zone
+
+ Load 50000 records for zone example.org to DB file data/xfrout.sqlite3
+
+ Given I have bind10 running with configuration xfrout_master.conf
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message CMDCTL_STARTED
+ And wait for bind10 stderr message AUTH_SERVER_STARTED
+ And wait for bind10 stderr message XFROUT_STARTED
+ And wait for bind10 stderr message ZONEMGR_STARTED
+
+ When I do a customized AXFR transfer of example.org from [::1]:47806 with pose of 5 seconds
+ Then transfer result should have 50001 rrs
diff --git a/tests/lettuce/run_python-tool.sh b/tests/lettuce/run_python-tool.sh
new file mode 100755
index 0000000..e93068e
--- /dev/null
+++ b/tests/lettuce/run_python-tool.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+# Copyright (C) 2013 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 script runs the specified python program, referring to the in-tree
+# BIND 10 Python libraries (in case the program needs them)
+# usage example: run_python-tool.sh tools/xfr-client.py -p 5300 example.org
+
+. ./setup_intree_bind10.sh
+$PYTHON_EXEC $*
diff --git a/tests/lettuce/setup_intree_bind10.sh.in b/tests/lettuce/setup_intree_bind10.sh.in
old mode 100644
new mode 100755
index 63b90ff..600f5c4
--- a/tests/lettuce/setup_intree_bind10.sh.in
+++ b/tests/lettuce/setup_intree_bind10.sh.in
@@ -20,7 +20,7 @@ export PYTHON_EXEC
BIND10_PATH=@abs_top_builddir@/src/bin/bind10
-PATH=@abs_top_builddir@/src/bin/bind10:@abs_top_builddir@/src/bin/bindctl:@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:$PATH
+PATH=@abs_top_builddir@/src/bin/bind10:@abs_top_builddir@/src/bin/bindctl:@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:@abs_top_builddir@/src/bin/loadzone:$PATH
export PATH
PYTHONPATH=@abs_top_builddir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python/isc/cc:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:@abs_top_builddir@/src/lib/python/isc/datasrc/.libs:$PYTHONPATH
@@ -30,10 +30,15 @@ export PYTHONPATH
# required by loadable python modules.
SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
if test $SET_ENV_LIBRARY_PATH = yes; then
- @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/threads/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
export @ENV_LIBRARY_PATH@
fi
+BUILD_EXPERIMENTAL_RESOLVER=@BUILD_EXPERIMENTAL_RESOLVER@
+if test $BUILD_EXPERIMENTAL_RESOLVER = yes; then
+ cp -f @srcdir@/features/resolver_basic.feature.disabled @srcdir@/features/resolver_basic.feature
+fi
+
B10_FROM_SOURCE=@abs_top_srcdir@
export B10_FROM_SOURCE
# TODO: We need to do this feature based (ie. no general from_source)
diff --git a/tests/lettuce/tools/xfr-client.py b/tests/lettuce/tools/xfr-client.py
new file mode 100755
index 0000000..662e1ed
--- /dev/null
+++ b/tests/lettuce/tools/xfr-client.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2013 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.
+
+# A simple XFR client program with some customized behavior.
+# This is intended to provide some less common or even invalid client behavior
+# for some specific tests on outbound zone transfer.
+# It currently only supports AXFR, but can be extended to support IXFR
+# as we see the need for it.
+#
+# For command line usage, run this program with -h option.
+
+from isc.dns import *
+import sys
+import socket
+import struct
+import time
+from optparse import OptionParser
+
+parser = OptionParser(usage='usage: %prog [options] zone_name')
+parser.add_option('-d', '--delay', dest='delay', action='store', default=None,
+ help='delay (sec) before receiving responses, ' +
+ 'emulating slow clients')
+parser.add_option('-s', '--server', dest='server_addr', action='store',
+ default='::1',
+ help="master server's address [default: %default]")
+parser.add_option('-p', '--port', dest='server_port', action='store',
+ default=53,
+ help="master server's TCP port [default: %default]")
+(options, args) = parser.parse_args()
+
+if len(args) == 0:
+ parser.error('missing argument')
+
+# Parse arguments and options, and creates client socket.
+zone_name = Name(args[0])
+gai = socket.getaddrinfo(options.server_addr, int(options.server_port), 0,
+ socket.SOCK_STREAM, socket.IPPROTO_TCP,
+ socket.AI_NUMERICHOST|socket.AI_NUMERICSERV)
+server_family, server_socktype, server_proto, server_sockaddr = \
+ (gai[0][0], gai[0][1], gai[0][2], gai[0][4])
+s = socket.socket(server_family, server_socktype, server_proto)
+s.connect(server_sockaddr)
+s.settimeout(60) # safety net in case of hangup situation
+
+# Build XFR query.
+axfr_qry = Message(Message.RENDER)
+axfr_qry.set_rcode(Rcode.NOERROR)
+axfr_qry.set_opcode(Opcode.QUERY)
+axfr_qry.add_question(Question(zone_name, RRClass.IN, RRType.AXFR))
+
+renderer = MessageRenderer()
+axfr_qry.to_wire(renderer)
+qry_data = renderer.get_data()
+
+# Send the query
+hlen_data = struct.pack('H', socket.htons(len(qry_data)))
+s.send(hlen_data)
+s.send(qry_data)
+
+# If specified wait for the given period
+if options.delay is not None:
+ time.sleep(int(options.delay))
+
+def get_request_response(s, size):
+ """A helper function to receive data of specified length from a socket."""
+ recv_size = 0
+ data = b''
+ while recv_size < size:
+ need_recv_size = size - recv_size
+ tmp_data = s.recv(need_recv_size)
+ if len(tmp_data) == 0:
+ return None
+ recv_size += len(tmp_data)
+ data += tmp_data
+ return data
+
+# Receive responses until the connection is terminated, and dump the
+# number of received answer RRs to stdout.
+num_rrs = 0
+while True:
+ hlen_data = get_request_response(s, 2)
+ if hlen_data is None:
+ break
+ resp_data = get_request_response(
+ s, socket.ntohs(struct.unpack('H', hlen_data)[0]))
+ msg = Message(Message.PARSE)
+ msg.from_wire(resp_data, Message.PRESERVE_ORDER)
+ num_rrs += msg.get_rr_count(Message.SECTION_ANSWER)
+print(str(num_rrs))
diff --git a/tests/system/.gitignore b/tests/system/.gitignore
deleted file mode 100644
index 76f87fe..0000000
--- a/tests/system/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/conf.sh
-/run.sh
diff --git a/tests/system/Makefile.am b/tests/system/Makefile.am
deleted file mode 100644
index aed1d79..0000000
--- a/tests/system/Makefile.am
+++ /dev/null
@@ -1,16 +0,0 @@
-systest:
- sh $(srcdir)/runall.sh
-
-distclean-local:
- sh $(srcdir)/cleanall.sh
-
-# Most of the files under this directory (including test subdirectories)
-# must be listed in EXTRA_DIST.
-EXTRA_DIST = README cleanall.sh ifconfig.sh start.pl stop.pl runall.sh
-EXTRA_DIST += common/default_user.csv
-EXTRA_DIST += glue/auth.good glue/example.good glue/noglue.good glue/test.good
-EXTRA_DIST += glue/tests.sh glue/clean.sh
-EXTRA_DIST += glue/nsx1/com.db glue/nsx1/net.db glue/nsx1/root-servers.nil.db
-EXTRA_DIST += glue/nsx1/root.db
-EXTRA_DIST += bindctl/tests.sh bindctl/clean.sh bindctl/setup.sh
-EXTRA_DIST += bindctl/nsx1/root.db bindctl/nsx1/example-normalized.db
diff --git a/tests/system/README b/tests/system/README
deleted file mode 100644
index a1c0a97..0000000
--- a/tests/system/README
+++ /dev/null
@@ -1,64 +0,0 @@
-Copyright (C) 2004, 2010, 2011 Internet Systems Consortium, Inc. ("ISC")
-Copyright (C) 2000, 2001 Internet Software Consortium.
-See COPYRIGHT in the source root or http://isc.org/copyright.html for terms.
-
-This is a simple test environment for running BIND 10 system tests
-involving multiple name servers. It was originally developed for BIND
-9, and has been ported to test BIND 10 implementations. Ideally we
-should share the same framework for both versions, so some part of the
-original setup are kept, even though they are BIND 9 specific and not
-currently used.
-
-Also, these tests generally rely on BIND 9 programs, most commonly
-its dig, and will sometimes be its name server (named). So, the test
-environment assumes that there's a source tree of BIND 9 where its
-programs are built, and that an environment variable "BIND9_TOP" is set
-to point to the top directory of the source tree.
-
-There are multiple test suites, each in a separate subdirectory and
-involving a different DNS setup. They are:
-
- bindctl/ Some basic management operations using the bindctl tool
- glue/ Glue handling tests
- ixfr/ Incremental transfer tests
-
-(the following tests are planned to be added soon)
- dnssec/ DNSSEC tests
- masterfile/ Master file parser
- axfr/ Full-transfer tests
-
-Typically each test suite sets up 2-5 instances of BIND 10 (or BIND 9
-named) and then performs one or more tests against them. Within the test
-suite subdirectory, each instance has a separate subdirectory containing
-its configuration data. By convention, these subdirectories are named
-"nsx1", "nsx2", etc for BIND 10 ("x" means BIND 10), and "ns1", "ns2",
-etc. for BIND 9.
-
-The tests are completely self-contained and do not require access to
-the real DNS. Generally, one of the test servers (ns[x]1) is set up as
-a root name server and is listed in the hints file of the others.
-
-To enable all servers to run on the same machine, they bind to separate
-virtual IP address on the loopback interface. ns[x]1 runs on 10.53.0.1,
-ns[x]2 on 10.53.0.2, etc. Before running any tests, you must set up
-these addresses by running "ifconfig.sh up" as root.
-
-Mac OS X:
-If you wish to make the interfaces survive across reboots copy
-org.isc.bind.system and org.isc.bind.system to /Library/LaunchDaemons
-then run "launchctl load /Library/LaunchDaemons/org.isc.bind.system.plist"
-as root.
-
-The servers use port 53210 instead of the usual port 53, so they can be
-run without root privileges once the interfaces have been set up.
-
-The tests can be run individually like this:
-
- sh run.sh xfer
- sh run.sh glue
- etc.
-
-To run all the tests, just type "make systest" either on this directory
-or on the top source directory. Note: currently these tests cannot be
-run when built under a separate build directory. Everything must be
-run within the original source tree.
diff --git a/tests/system/bindctl/clean.sh b/tests/system/bindctl/clean.sh
deleted file mode 100755
index f691512..0000000
--- a/tests/system/bindctl/clean.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000, 2001 Internet Software Consortium.
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-rm -f */b10-config.db
-rm -f dig.out.* bindctl.out.*
-rm -f */msgq_socket */zone.sqlite3
diff --git a/tests/system/bindctl/nsx1/.gitignore b/tests/system/bindctl/nsx1/.gitignore
deleted file mode 100644
index 4a8ce05..0000000
--- a/tests/system/bindctl/nsx1/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-/b10-config.db.template
-/bind10.run
-/bindctl.out
diff --git a/tests/system/bindctl/nsx1/b10-config.db.template.in b/tests/system/bindctl/nsx1/b10-config.db.template.in
deleted file mode 100644
index 4d6f287..0000000
--- a/tests/system/bindctl/nsx1/b10-config.db.template.in
+++ /dev/null
@@ -1,29 +0,0 @@
-{"version": 2,
- "Auth": {
- "listen_on": [{"address": "10.53.0.1", "port": 53210}],
- "database_file": "@abs_builddir@/zone.sqlite3"
- },
- "data_sources": {
- "classes": {
- "IN": [{
- "type": "sqlite3",
- "params": {
- "database_file": "@abs_builddir@/zone.sqlite3"
- }
- }]
- }
- },
- "Logging": {
- "loggers": [
- {
- "name": "*",
- "severity": "DEBUG",
- "output_options": [],
- "debuglevel": 99
- }
- ]
- },
- "Stats": {
- "poll-interval": 1
- }
-}
diff --git a/tests/system/bindctl/nsx1/example-normalized.db b/tests/system/bindctl/nsx1/example-normalized.db
deleted file mode 100644
index 7129522..0000000
--- a/tests/system/bindctl/nsx1/example-normalized.db
+++ /dev/null
@@ -1,3 +0,0 @@
-com. 300 IN SOA postmaster.example. ns.example.com. 2000042100 600 600 1200 600
-com. 300 IN NS ns.example.com.
-ns.example.com. 300 IN A 192.0.2.2
diff --git a/tests/system/bindctl/nsx1/root.db b/tests/system/bindctl/nsx1/root.db
deleted file mode 100644
index 31293de..0000000
--- a/tests/system/bindctl/nsx1/root.db
+++ /dev/null
@@ -1,25 +0,0 @@
-; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-; Copyright (C) 2000, 2001 Internet Software Consortium.
-;
-; Permission to use, copy, modify, and/or distribute this software for any
-; purpose with or without fee is hereby granted, provided that the above
-; copyright notice and this permission notice appear in all copies.
-;
-; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-; PERFORMANCE OF THIS SOFTWARE.
-
-$TTL 300
-. IN SOA postmaster.example. a.root.servers.nil. (
- 2000042100 ; serial
- 600 ; refresh
- 600 ; retry
- 1200 ; expire
- 600 ; minimum
- )
-. NS ns.example.com.
-ns.example.com. A 192.0.2.1
diff --git a/tests/system/bindctl/setup.sh b/tests/system/bindctl/setup.sh
deleted file mode 100755
index 7a9a323..0000000
--- a/tests/system/bindctl/setup.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-SYSTEMTESTTOP=..
-. $SYSTEMTESTTOP/conf.sh
-
-SUBTEST_TOP=${TEST_TOP}/bindctl
-
-cp ${SUBTEST_TOP}/nsx1/b10-config.db.template ${SUBTEST_TOP}/nsx1/b10-config.db
-
-rm -f ${SUBTEST_TOP}/*/zone.sqlite3
-${B10_LOADZONE} -i 1 -c "{\"database_file\": \"${SUBTEST_TOP}/nsx1/zone.sqlite3\"}" \
- . ${SUBTEST_TOP}//nsx1/root.db
diff --git a/tests/system/bindctl/tests.sh b/tests/system/bindctl/tests.sh
deleted file mode 100755
index e1e533a..0000000
--- a/tests/system/bindctl/tests.sh
+++ /dev/null
@@ -1,238 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-SYSTEMTESTTOP=..
-. $SYSTEMTESTTOP/conf.sh
-
-#
-# Do bindctl tests.
-#
-
-status=0
-n=0
-
-# TODO: consider consistency with statistics definition in auth.spec
-
-# flatten JSON
-awk_flatten_json='
-function join(ary, len) {
- ret = "";
- for (i = 1; i <= len; ++i) {
- ret = ret""ary[i];
- }
- return ret;
-}
-BEGIN {
- depth = 0;
-}
-/.+{$/ {
- label[++depth] = $1;
- next;
-}
-/},?/ {
- --depth;
- next;
-}
-/:/ {
- print join(label,depth)""$1" "$2;
-}
-'
-# Check the counters have expected values given with 1st argument.
-# This function tests only these counters will be incremented in every checks
-# since the content of datasource and requests are not changed in this test.
-test_counters () {
- status=0
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"request":"v4": '$1 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"request":"v6": '0 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"request":"udp": '$1 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"request":"tcp": '0 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"opcode":"query": '$1 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"responses": '$1 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"rcode":"noerror": '$1 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"qrysuccess": '$1 > \
- /dev/null || status=1
- $AWK "$awk_flatten_json" bindctl.out.$n | \
- grep '"Auth":"zones":"_SERVER_":"qryauthans": '$1 > \
- /dev/null || status=1
- return $status
-}
-expected_count=0
-
-echo "I:Checking b10-auth is disabled by default ($n)"
-$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A > /dev/null && status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Starting b10-auth and checking that it works ($n)"
-echo 'config add Init/components b10-auth
-config set Init/components/b10-auth { "special": "auth", "kind": "needed" }
-config commit
-quit
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
-$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
-# perform a simple check on the output (digcomp would be too much for this)
-grep 192.0.2.1 dig.out.$n > /dev/null || status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Checking BIND 10 statistics after a pause ($n)"
-# wait for 2sec to make sure b10-stats gets the latest statistics.
-sleep 2
-echo 'Stats show
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
-# the server should have received 1 request
-expected_count=`expr $expected_count + 1`
-test_counters $expected_count
-if [ $? != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Stopping b10-auth and checking that ($n)"
-echo 'config remove Init/components b10-auth
-config commit
-quit
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
-# dig should exit with a failure code.
-$DIG +tcp +norec @10.53.0.1 -p 53210 ns.example.com. A > /dev/null && status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Restarting b10-auth and checking that ($n)"
-echo 'config add Init/components b10-auth
-config set Init/components/b10-auth { "special": "auth", "kind": "needed" }
-config commit
-quit
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
-sleep 2
-$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
-# perform a simple check on the output (digcomp would be too much for this)
-grep 192.0.2.1 dig.out.$n > /dev/null || status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Rechecking BIND 10 statistics after a pause ($n)"
-sleep 2
-echo 'Stats show
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
-# The statistics counters can not be reset even after auth
-# restarts. Because stats preserves the query counts which the dying
-# auth sent. Then it cumulates them and new counts which the living
-# auth sends. This note assumes that the issue would have been
-# resolved : "#1941 stats lossage (multiple auth servers)".
-expected_count=`expr $expected_count + 1`
-test_counters $expected_count
-if [ $? != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Changing the data source from sqlite3 to in-memory ($n)"
-DATASRC_SPEC='{"type": "MasterFiles", "cache-enable": true, "params": {"com":'
-DATASRC_SPEC="${DATASRC_SPEC} \"${TEST_TOP}/bindctl/nsx1/example-normalized.db\"}}"
-echo "config set data_sources/classes/IN[0] ${DATASRC_SPEC}
-config commit
-quit
-" | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
-sleep 2
-$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
-grep 192.0.2.2 dig.out.$n > /dev/null || status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Rechecking BIND 10 statistics after changing the datasource ($n)"
-sleep 2
-echo 'Stats show
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
-# The statistics counters shouldn't be reset due to hot-swapping datasource.
-expected_count=`expr $expected_count + 1`
-test_counters $expected_count
-if [ $? != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Starting more b10-auths and checking that ($n)"
-for i in 2 3
-do
- echo 'config add Init/components b10-auth-'$i'
-config set Init/components/b10-auth-'$i' { "special": "auth", "kind": "needed" }
-config commit
-quit
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
-done
-sleep 2
-$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
-grep 192.0.2.2 dig.out.$n > /dev/null || status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-echo "I:Rechecking BIND 10 statistics consistency after a pause ($n)"
-sleep 2
-expected_count=`expr $expected_count + 1`
-# Rechecking some times
-for i in 1 2 3 4
-do
- echo 'Stats show
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
- # The statistics counters should keep being consistent even while
- # multiple b10-auths are running.
-
- test_counters $expected_count
- if [ $? != 0 ]; then echo "I:failed "; break ; fi
-done
-n=`expr $n + 1`
-
-echo "I:Stopping extra b10-auths and checking that ($n)"
-for i in 3 2
-do
- echo 'config remove Init/components b10-auth-'$i'
-config commit
-quit
-' | $RUN_BINDCTL \
- --csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
-done
-sleep 2
-$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
-grep 192.0.2.2 dig.out.$n > /dev/null || status=1
-if [ $status != 0 ]; then echo "I:failed"; fi
-n=`expr $n + 1`
-
-# The statistics counters can not be rechecked here because the auth
-# instance seems to hang up after one of the multiple auth instances
-# was removed via bindctl. This reason seems to be the same reason as
-# #1703.
-
-echo "I:exit status: $status"
-exit $status
diff --git a/tests/system/cleanall.sh b/tests/system/cleanall.sh
deleted file mode 100755
index 434c6b1..0000000
--- a/tests/system/cleanall.sh
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000, 2001 Internet Software Consortium.
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-#
-# Clean up after system tests.
-#
-
-find . -type f \( \
- -name 'K*' -o -name '*~' -o -name '*.core' -o -name '*.log' \
- -o -name '*.pid' -o -name '*.keyset' -o -name named.run \
- -o -name bind10.run -o -name lwresd.run -o -name ans.run \) -print | \
- xargs rm -f
-
-status=0
-
-for d in ./.* ./* ./*/*
-do
- case $d in ./.|./..) continue ;; esac
- test -d $d || continue
-
- test ! -f $d/clean.sh || ( cd $d && sh clean.sh )
-done
diff --git a/tests/system/common/default_user.csv b/tests/system/common/default_user.csv
deleted file mode 100644
index e13e194..0000000
--- a/tests/system/common/default_user.csv
+++ /dev/null
@@ -1 +0,0 @@
-root,bind10
diff --git a/tests/system/common/rndc.conf b/tests/system/common/rndc.conf
deleted file mode 100644
index a897548..0000000
--- a/tests/system/common/rndc.conf
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
- * Copyright (C) 2000, 2001 Internet Software Consortium.
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-options {
- default-key "rndc_key";
-};
-
-key rndc_key {
- algorithm hmac-md5;
- secret "1234abcd8765";
-};
diff --git a/tests/system/common/rndc.key b/tests/system/common/rndc.key
deleted file mode 100644
index c2c3457..0000000
--- a/tests/system/common/rndc.key
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-/* $Id: rndc.key,v 1.3 2011-03-12 04:59:47 tbox Exp $ */
-
-key rndc_key {
- secret "1234abcd8765";
- algorithm hmac-md5;
-};
diff --git a/tests/system/conf.sh.in b/tests/system/conf.sh.in
deleted file mode 100755
index 4948d63..0000000
--- a/tests/system/conf.sh.in
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004-2011 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000-2003 Internet Software Consortium.
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-#
-# Common configuration data for system tests, to be sourced into
-# other shell scripts.
-#
-
-# Prerequisite check
-if [ @srcdir@ != @builddir@ ]; then
- echo "Currently systest doesn't work for a separate build tree."
- echo "Rebuild BIND 10 on the source tree and run the tests."
- exit 1
-fi
-
-if [ -z $BIND9_TOP ]; then
- echo "systest assumes there's a compiled tree of BIND 9 which can be"
- echo "accessed via the BIND9_TOP environment variable."
- echo "Please make sure this assumption is met."
- exit 1
-fi
-
-# Find the top of the source and test trees.
-export TOP=@abs_top_srcdir@
-export TEST_TOP=@abs_builddir@
-
-# Programs
-export RUN_BIND10=$TOP/src/bin/bind10/run_bind10.sh
-export RUN_BINDCTL=$TOP/src/bin/bindctl/run_bindctl.sh
-export BINDCTL_CSV_DIR=@abs_srcdir@/common/
-export B10_LOADZONE=$TOP/src/bin/loadzone/run_loadzone.sh
-export BIND9_NAMED=$BIND9_TOP/bin/named/named
-export DIG=$BIND9_TOP/bin/dig/dig
-export RNDC=$BIND9_TOP/bin/rndc/rndc
-
-# Test tools borrowed from BIND 9's system test (without change).
-export TESTSOCK=$BIND9_TOP/bin/tests/system/testsock.pl
-export DIGCOMP=$BIND9_TOP/bin/tests/system/digcomp.pl
-
-# bindctl test doesn't work right now and is disabled (see #2568)
-#export SUBDIRS="bindctl glue ixfr/in-2"
-export SUBDIRS="glue ixfr/in-2"
-
-# Add appropriate subdirectories to the above statement as the tests become
-# available.
-#SUBDIRS="dnssec masterfile ixfr/in-1 ixfr/in-2 ixfr/in-4"
-
-# PERL will be an empty string if no perl interpreter was found. A similar
-# comment applies to AWK.
-export PERL=@PERL@
-export AWK=@AWK@
-
-# Other constants
-export RNDC_PORT=9953
-export DNS_PORT=53210
-
-export TESTS_TOP=$TOP/tests
-export SYSTEM_TOP=$TESTS_TOP/system
-export IXFR_TOP=$SYSTEM_TOP/ixfr
diff --git a/tests/system/glue/.gitignore b/tests/system/glue/.gitignore
deleted file mode 100644
index 87e08bf..0000000
--- a/tests/system/glue/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/setup.sh
diff --git a/tests/system/glue/auth.good b/tests/system/glue/auth.good
deleted file mode 100644
index 2c619f6..0000000
--- a/tests/system/glue/auth.good
+++ /dev/null
@@ -1,15 +0,0 @@
-
-; <<>> DiG 9.0 <<>> +norec @10.53.0.1 -p 5300 foo.bar.example.org. a
-;; global options: printcmd
-;; Got answer:
-;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41239
-;; flags: qr ad; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
-
-;; QUESTION SECTION:
-;foo.bar.example.org. IN A
-
-;; AUTHORITY SECTION:
-example.org. 172800 IN NS b.root-servers.nil.
-
-;; ADDITIONAL SECTION:
-b.root-servers.nil. 300 IN A 10.53.0.2
diff --git a/tests/system/glue/clean.sh b/tests/system/glue/clean.sh
deleted file mode 100755
index b2c1e02..0000000
--- a/tests/system/glue/clean.sh
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000, 2001 Internet Software Consortium.
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-#
-# Clean up after glue tests.
-#
-
-rm -f dig.out.*
-rm -f */msgq_socket */zone.sqlite3
diff --git a/tests/system/glue/example.good b/tests/system/glue/example.good
deleted file mode 100644
index b6d5708..0000000
--- a/tests/system/glue/example.good
+++ /dev/null
@@ -1,22 +0,0 @@
-
-; <<>> DiG 9.0 <<>> +norec @10.53.0.1 -p 5300 foo.bar.example. A
-;; global options: printcmd
-;; Got answer:
-;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58772
-;; flags: qr ad; QUERY: 1, ANSWER: 0, AUTHORITY: 6, ADDITIONAL: 7
-
-;; QUESTION SECTION:
-;foo.bar.example. IN A
-
-;; AUTHORITY SECTION:
-example. 172800 IN NS NS1.example.COM.
-example. 172800 IN NS NS.example.
-
-;; ADDITIONAL SECTION:
-NS.example. 172800 IN A 192.0.2.1
-NS.example. 172800 IN A 192.0.2.2
-NS1.example.COM. 172800 IN A 192.0.2.3
-;; These are not used now - they are in a different master file
-;; than the answer.
-; NS1.example.COM. 172800 IN A 192.0.2.101
-; NS1.example.COM. 172800 IN AAAA 2001:db8::1
diff --git a/tests/system/glue/noglue.good b/tests/system/glue/noglue.good
deleted file mode 100644
index 57a2211..0000000
--- a/tests/system/glue/noglue.good
+++ /dev/null
@@ -1,14 +0,0 @@
-
-; <<>> DiG 9.0 <<>> @10.53.0.1 -p 5300 example.net a
-;; global options: printcmd
-;; Got answer:
-;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29409
-;; flags: qr rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 0
-
-;; QUESTION SECTION:
-;example.net. IN A
-
-;; AUTHORITY SECTION:
-example.net. 300 IN NS ns2.example.info.
-example.net. 300 IN NS ns1.example.info.
-
diff --git a/tests/system/glue/nsx1/.gitignore b/tests/system/glue/nsx1/.gitignore
deleted file mode 100644
index c0750b3..0000000
--- a/tests/system/glue/nsx1/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-/b10-config.db
-/bind10.run
-/bindctl.out
diff --git a/tests/system/glue/nsx1/b10-config.db.in b/tests/system/glue/nsx1/b10-config.db.in
deleted file mode 100644
index 6802c53..0000000
--- a/tests/system/glue/nsx1/b10-config.db.in
+++ /dev/null
@@ -1,36 +0,0 @@
-{"version": 2,
- "Auth": {
- "listen_on": [{"address": "10.53.0.1", "port": 53210}],
- "database_file": "@abs_builddir@/zone.sqlite3"
- },
- "data_sources": {
- "classes": {
- "IN": [{
- "type": "sqlite3",
- "params": {
- "database_file": "@abs_builddir@/zone.sqlite3"
- }
- }]
- }
- },
- "Logging": {
- "loggers": [
- {
- "name": "*",
- "severity": "DEBUG",
- "output_options": [],
- "debuglevel": 99
- }
- ]
- },
- "Init": {
- "components": {
- "b10-auth": {"kind": "needed", "special": "auth" },
- "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
- "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
- "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
- "b10-stats": { "address": "Stats", "kind": "dispensable" },
- "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
- }
- }
-}
diff --git a/tests/system/glue/nsx1/com.db b/tests/system/glue/nsx1/com.db
deleted file mode 100644
index c4b94e1..0000000
--- a/tests/system/glue/nsx1/com.db
+++ /dev/null
@@ -1,31 +0,0 @@
-; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-; Copyright (C) 2000, 2001 Internet Software Consortium.
-;
-; Permission to use, copy, modify, and/or distribute this software for any
-; purpose with or without fee is hereby granted, provided that the above
-; copyright notice and this permission notice appear in all copies.
-;
-; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-; PERFORMANCE OF THIS SOFTWARE.
-
-$ORIGIN com.
-$TTL 300
-@ IN SOA root.example.com. a.root.servers.nil. (
- 2000042100 ; serial
- 600 ; refresh
- 600 ; retry
- 1200 ; expire
- 600 ; minimum
- )
-@ NS a.root-servers.nil.
-
-example.com. NS ns1.example.com.
-example.com. NS ns2.example.com.
-ns1.example.com. 172800 IN A 192.0.2.101
-ns1.example.com. 172800 IN AAAA 2001:db8::1
-ns2.example.com. 172800 IN A 192.0.2.102
diff --git a/tests/system/glue/nsx1/net.db b/tests/system/glue/nsx1/net.db
deleted file mode 100644
index 8b66521..0000000
--- a/tests/system/glue/nsx1/net.db
+++ /dev/null
@@ -1,32 +0,0 @@
-; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-; Copyright (C) 2000, 2001 Internet Software Consortium.
-;
-; Permission to use, copy, modify, and/or distribute this software for any
-; purpose with or without fee is hereby granted, provided that the above
-; copyright notice and this permission notice appear in all copies.
-;
-; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-; PERFORMANCE OF THIS SOFTWARE.
-
-$ORIGIN net.
-$TTL 300
-@ IN SOA root.example.net. a.root.servers.nil. (
- 2000042100 ; serial
- 600 ; refresh
- 600 ; retry
- 1200 ; expire
- 600 ; minimum
- )
-@ NS a.root-servers.nil.
-
-; Referral outside of server authority, but with glue records present.
-; Don't hand out the glue.
-example.net. NS ns1.example.info.
-example.net. NS ns2.example.info.
-ns1.example.info. 172800 IN A 192.0.2.101
-ns2.example.info. 172800 IN A 192.0.2.102
diff --git a/tests/system/glue/nsx1/root-servers.nil.db b/tests/system/glue/nsx1/root-servers.nil.db
deleted file mode 100644
index 45050a9..0000000
--- a/tests/system/glue/nsx1/root-servers.nil.db
+++ /dev/null
@@ -1,26 +0,0 @@
-; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-; Copyright (C) 2000, 2001 Internet Software Consortium.
-;
-; Permission to use, copy, modify, and/or distribute this software for any
-; purpose with or without fee is hereby granted, provided that the above
-; copyright notice and this permission notice appear in all copies.
-;
-; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-; PERFORMANCE OF THIS SOFTWARE.
-
-$TTL 300
-@ IN SOA ns hostmaster (
- 1
- 3600
- 1800
- 1814400
- 3600
- )
- NS a
-a A 10.53.0.1
-b A 10.53.0.2
diff --git a/tests/system/glue/nsx1/root.db b/tests/system/glue/nsx1/root.db
deleted file mode 100644
index 271a4d8..0000000
--- a/tests/system/glue/nsx1/root.db
+++ /dev/null
@@ -1,53 +0,0 @@
-; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-; Copyright (C) 2000, 2001 Internet Software Consortium.
-;
-; Permission to use, copy, modify, and/or distribute this software for any
-; purpose with or without fee is hereby granted, provided that the above
-; copyright notice and this permission notice appear in all copies.
-;
-; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-; PERFORMANCE OF THIS SOFTWARE.
-
-$TTL 300
-. IN SOA postmaster.example. a.root.servers.nil. (
- 2000042100 ; serial
- 600 ; refresh
- 600 ; retry
- 1200 ; expire
- 600 ; minimum
- )
-. NS a.root-servers.nil.
-
-root-servers.nil. NS a.root-servers.nil.
-a.root-servers.nil. A 10.53.0.1
-
-; Delegate some domains that contain name servers for the sample
-; ccTLDs below.
-com. 172800 IN NS a.root-servers.nil.
-
-;
-; A sample TLD
-;
-example. 172800 IN NS NS.example.
-example. 172800 IN NS NS1.example.COM.
-NS.example. 172800 IN A 192.0.2.1
-NS.example. 172800 IN A 192.0.2.2
-NS1.example.COM. 172800 IN A 192.0.2.3
-
-;
-;
-;
-test. 172800 IN NS ns.test.
-test. 172800 IN NS ns1.example.net.
-ns.test. 172800 IN A 192.0.2.200
-ns1.example.net. 172800 IN A 192.0.2.201
-
-;
-; A hypothetical ccTLD where we are authoritative for the NS glue.
-;
-example.org 172800 IN NS b.root-servers.nil.
diff --git a/tests/system/glue/setup.sh.in b/tests/system/glue/setup.sh.in
deleted file mode 100755
index 2f9f886..0000000
--- a/tests/system/glue/setup.sh.in
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-SYSTEMTESTTOP=..
-. $SYSTEMTESTTOP/conf.sh
-
-rm -f */zone.sqlite3
-${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' . @builddir@/nsx1/root.db
-${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' root-servers.nil \
- @builddir@/nsx1/root-servers.nil.db
-${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' com @builddir@/nsx1/com.db
-${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' net @builddir@/nsx1/net.db
diff --git a/tests/system/glue/test.good b/tests/system/glue/test.good
deleted file mode 100644
index b9b4719..0000000
--- a/tests/system/glue/test.good
+++ /dev/null
@@ -1,19 +0,0 @@
-
-; <<>> DiG 9.8.0 <<>> @127.0.0.1 -p 5300 foo.bar.test
-; (1 server found)
-;; global options: +cmd
-;; Got answer:
-;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 55069
-;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 2
-;; WARNING: recursion requested but not available
-
-;; QUESTION SECTION:
-;foo.bar.test. IN A
-
-;; AUTHORITY SECTION:
-test. 172800 IN NS ns.test.
-test. 172800 IN NS ns1.example.net.
-
-;; ADDITIONAL SECTION:
-ns.test. 172800 IN A 192.0.2.200
-ns1.example.net. 172800 IN A 192.0.2.201
diff --git a/tests/system/glue/tests.sh b/tests/system/glue/tests.sh
deleted file mode 100755
index dafb1ad..0000000
--- a/tests/system/glue/tests.sh
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000, 2001, 2003 Internet Software Consortium.
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-SYSTEMTESTTOP=..
-. $SYSTEMTESTTOP/conf.sh
-
-#
-# Do glue tests.
-#
-
-status=0
-n=0
-
-# This query should result in a delegation with two NS; one in the delegated
-# zone and one in a so called out-of-bailiwick zone for which the auth server
-# has authority, too. For the former, the server should return glue in the
-# parent zone. For the latter, BIND 9 and BIND 10 behave differently; BIND 9
-# uses "glue" in the parent zone (since this is the root zone everything can
-# be considered a valid glue). BIND 10 (using sqlite3 data source) searches
-# the other zone and uses the authoritative data in that zone (which is
-# intentionally different from the glue in the root zone).
-echo "I:testing that a TLD referral gets a full glue set from the root zone ($n)"
-$DIG +norec @10.53.0.1 -p 53210 foo.bar.example. A >dig.out.$n || status=1
-$PERL $DIGCOMP example.good dig.out.$n || status=1
-n=`expr $n + 1`
-
-# Disabling this test, as it checks for looking up glue in a different zone
-# finder than the answer is from. This is not supported now.
-#echo "I:testing that we find glue A RRs we are authoritative for ($n)"
-#$DIG +norec @10.53.0.1 -p 53210 foo.bar.example.org. a >dig.out.$n || status=1
-#$PERL $DIGCOMP auth.good dig.out.$n || status=1
-#n=`expr $n + 1`
-
-# We cannot do this test for BIND 10 because b10-auth doesn't act as a
-# recursive (caching) server (by design)
-# echo "I:testing that we find glue A/AAAA RRs in the cache ($n)"
-# $DIG +norec @10.53.0.1 -p 53210 foo.bar.yy. a >dig.out.$n || status=1
-# $PERL $DIGCOMP yy.good dig.out.$n || status=1
-# n=`expr $n + 1`
-
-echo "I:testing that we don't find out-of-zone glue ($n)"
-$DIG +norec @10.53.0.1 -p 53210 example.net. a > dig.out.$n || status=1
-$PERL $DIGCOMP noglue.good dig.out.$n || status=1
-n=`expr $n + 1`
-
-# This test currently fails (additional section will be empty, which is
-# incorrect). See Trac ticket #646.
-#echo "I:testing that we are finding partial out-of-zone glue ($n)"
-#$DIG +norec @10.53.0.1 -p 53210 foo.bar.test. a >dig.out.$n || status=1
-#$PERL $DIGCOMP test.good dig.out.$n || status=1
-#n=`expr $n + 1`
-
-echo "I:exit status: $status"
-exit $status
diff --git a/tests/system/ifconfig.sh b/tests/system/ifconfig.sh
deleted file mode 100755
index 7c695e2..0000000
--- a/tests/system/ifconfig.sh
+++ /dev/null
@@ -1,226 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007-2010 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000-2003 Internet Software Consortium.
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-#
-# Set up interface aliases for bind9 system tests.
-#
-# IPv4: 10.53.0.{1..7} RFC 1918
-# IPv6: fd92:7065:b8e:ffff::{1..7} ULA
-#
-
-config_guess=""
-for f in ./config.guess ../../config.guess
-do
- if test -f $f
- then
- config_guess=$f
- fi
-done
-
-if test "X$config_guess" = "X"
-then
- cat <<EOF >&2
-$0: must be run from the top level source directory or the
-bin/tests/system directory
-EOF
- exit 1
-fi
-
-# If running on hp-ux, don't even try to run config.guess.
-# It will try to create a temporary file in the current directory,
-# which fails when running as root with the current directory
-# on a NFS mounted disk.
-
-case `uname -a` in
- *HP-UX*) sys=hpux ;;
- *) sys=`sh $config_guess` ;;
-esac
-
-case "$2" in
-[0-9]|[1-9][0-9]|[1-9][0-9][0-9]) base=$2;;
-*) base=""
-esac
-
-case "$3" in
-[0-9]|[1-9][0-9]|[1-9][0-9][0-9]) base6=$2;;
-*) base6=""
-esac
-
-case "$1" in
-
- start|up)
- for ns in 1 2 3 4 5 6 7 8
- do
- if test -n "$base"
- then
- int=`expr $ns + $base - 1`
- else
- int=$ns
- fi
- if test -n "$base6"
- then
- int6=`expr $ns + $base6 - 1`
- else
- int6=$ns
- fi
- case "$sys" in
- *-pc-solaris2.5.1)
- ifconfig lo0:$int 10.53.0.$ns netmask 0xffffffff up
- ;;
- *-sun-solaris2.[6-7])
- ifconfig lo0:$int 10.53.0.$ns netmask 0xffffffff up
- ;;
- *-*-solaris2.[8-9]|*-*-solaris2.1[0-9])
- /sbin/ifconfig lo0:$int plumb
- /sbin/ifconfig lo0:$int 10.53.0.$ns up
- if test -n "$int6"
- then
- /sbin/ifconfig lo0:$int6 inet6 plumb
- /sbin/ifconfig lo0:$int6 \
- inet6 fd92:7065:b8e:ffff::$ns up
- fi
- ;;
- *-*-linux*)
- ifconfig lo:$int 10.53.0.$ns up netmask 255.255.255.0
- ifconfig lo inet6 add fd92:7065:b8e:ffff::$ns/64
- ;;
- *-unknown-freebsd*)
- ifconfig lo0 10.53.0.$ns alias netmask 0xffffffff
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
- ;;
- *-unknown-netbsd*)
- ifconfig lo0 10.53.0.$ns alias netmask 255.255.255.0
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
- ;;
- *-unknown-openbsd*)
- ifconfig lo0 10.53.0.$ns alias netmask 255.255.255.0
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
- ;;
- *-*-bsdi[3-5].*)
- ifconfig lo0 add 10.53.0.$ns netmask 255.255.255.0
- ;;
- *-dec-osf[4-5].*)
- ifconfig lo0 alias 10.53.0.$ns
- ;;
- *-sgi-irix6.*)
- ifconfig lo0 alias 10.53.0.$ns
- ;;
- *-*-sysv5uw7*|*-*-sysv*UnixWare*|*-*-sysv*OpenUNIX*)
- ifconfig lo0 10.53.0.$ns alias netmask 0xffffffff
- ;;
- *-ibm-aix4.*|*-ibm-aix5.*)
- ifconfig lo0 alias 10.53.0.$ns
- ifconfig lo0 inet6 alias -dad fd92:7065:b8e:ffff::$ns/64
- ;;
- hpux)
- ifconfig lo0:$int 10.53.0.$ns netmask 255.255.255.0 up
- ifconfig lo0:$int inet6 fd92:7065:b8e:ffff::$ns up
- ;;
- *-sco3.2v*)
- ifconfig lo0 alias 10.53.0.$ns
- ;;
- *-darwin*)
- ifconfig lo0 alias 10.53.0.$ns
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias
- ;;
- *)
- echo "Don't know how to set up interface. Giving up."
- exit 1
- esac
- done
- ;;
-
- stop|down)
- for ns in 8 7 6 5 4 3 2 1
- do
- if test -n "$base"
- then
- int=`expr $ns + $base - 1`
- else
- int=$ns
- fi
- case "$sys" in
- *-pc-solaris2.5.1)
- ifconfig lo0:$int 0.0.0.0 down
- ;;
- *-sun-solaris2.[6-7])
- ifconfig lo0:$int 10.53.0.$ns down
- ;;
- *-*-solaris2.[8-9]|*-*-solaris2.1[0-9])
- ifconfig lo0:$int 10.53.0.$ns down
- ifconfig lo0:$int 10.53.0.$ns unplumb
- if test -n "$int6"
- then
- ifconfig lo0:$int6 inet6 down
- ifconfig lo0:$int6 inet6 unplumb
- fi
- ;;
- *-*-linux*)
- ifconfig lo:$int 10.53.0.$ns down
- ifconfig lo inet6 del fd92:7065:b8e:ffff::$ns/64
- ;;
- *-unknown-freebsd*)
- ifconfig lo0 10.53.0.$ns delete
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
- ;;
- *-unknown-netbsd*)
- ifconfig lo0 10.53.0.$ns delete
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
- ;;
- *-unknown-openbsd*)
- ifconfig lo0 10.53.0.$ns delete
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
- ;;
- *-*-bsdi[3-5].*)
- ifconfig lo0 remove 10.53.0.$ns
- ;;
- *-dec-osf[4-5].*)
- ifconfig lo0 -alias 10.53.0.$ns
- ;;
- *-sgi-irix6.*)
- ifconfig lo0 -alias 10.53.0.$ns
- ;;
- *-*-sysv5uw7*|*-*-sysv*UnixWare*|*-*-sysv*OpenUNIX*)
- ifconfig lo0 -alias 10.53.0.$ns
- ;;
- *-ibm-aix4.*|*-ibm-aix5.*)
- ifconfig lo0 delete 10.53.0.$ns
- ifconfig lo0 delete inet6 fd92:7065:b8e:ffff::$ns/64
- ;;
- hpux)
- ifconfig lo0:$int 0.0.0.0
- ifconfig lo0:$int inet6 ::
- ;;
- *-sco3.2v*)
- ifconfig lo0 -alias 10.53.0.$ns
- ;;
- *darwin*)
- ifconfig lo0 -alias 10.53.0.$ns
- ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete
- ;;
- *)
- echo "Don't know how to destroy interface. Giving up."
- exit 1
- esac
- done
-
- ;;
-
- *)
- echo "Usage: $0 { up | down } [base]"
- exit 1
-esac
diff --git a/tests/system/ixfr/.gitignore b/tests/system/ixfr/.gitignore
deleted file mode 100644
index 027d45e..0000000
--- a/tests/system/ixfr/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-/b10-config.db
-/common_tests.sh
-/db.example.n0
-/db.example.n2
-/db.example.n2.refresh
-/db.example.n4
-/db.example.n6
-/ixfr_init.sh
diff --git a/tests/system/ixfr/README b/tests/system/ixfr/README
deleted file mode 100644
index 51cba8a..0000000
--- a/tests/system/ixfr/README
+++ /dev/null
@@ -1,86 +0,0 @@
-Introduction
-============
-The directories in-1 to in-4 implement the following tests of the IXFR-in
-capability of BIND 10.
-
-in-1: Check that BIND 10 can receive IXFR in a single UDP packet.
-in-2: Check that BIND 10 can receive IXFR via TCP.
-in-3: Check that BIND 10 will request AXFR if the server does not support IXFR.
-in-4: Check that BIND 10 will request IXFR when its SOA refresh times out
-
-The tests are described more fully in the document:
-
-http://bind10.isc.org/wiki/IxfrSystemTests
-
-Overview
-========
-All the tests use two nameservers:
-
-* A BIND 9 nameserver acting as the IXFR server (using the nomenclature
-of RFC 1995).
-* A BIND 10 nameserver acting at the IXFR client.
-
-In general, the tests attempt to set up the server and client independently.
-Communication is established between the systems by updating their
-configurations and a notification sent to the client. This should cause the
-client to request an IXFR from the server. (The exception is test 4, where the
-request is a result of the expiration of the SOA refresh time.)
-
-A check of zone files - or in these tests, of SOA serial number - can only
-reveal that a transfer has taken place. To check what has happened,
-e.g. whether the transfer was via UDP or whether a TCP request took place,
-the BIND 10 log file is searched for known message IDs.
-
-The searching of the log files for message IDs is one of the reasons that,
-unlike other system tests, the IXFR set of tests is broken up into separate
-tests that require the stopping and starting of nameservers (and tidying up of
-log files) between each test. Doing this means that only the existence of a
-particular message ID needs to be checked - there is no risk that another test
-produced it. The other reason is that the each IXFR test requires the
-nameservers to be in a specific state at the start of the test; this is easier
-to assure if they are not updating one another as the result of configuration
-settings established in the previous test.
-
-Test Files
-==========
-
-Data Files
-----------
-(All within tests/system/ixfr. Some .in files are processed to substitute
-for build variables in the build process to give the files listed here.)
-
-db.example.nX. These files hold the RRs for a zone for which should not
-fit within a single UDP packet. The files are different versions of the zone
-- the N-0 version (i.e. the latest version - "N" - the "-0" is present so
-that the files have a consistent name), N-2 etc. (See the full description
-of the tests for the meaning of N-2 etc.)
-
-db.example.common: A set of RRs to bulk out the zone to be larger than can
-be contained in a single UDP packet.
-
-db.example.n2.refresh: The N-2 version of the zone, but with a small SOA
-refresh time (for test 4).
-
-named_xxxx.conf: Various BIND 9 configuration files with NOTIFYs and/or
-IXFR enabled or disabled.
-
-Directories
------------
-The tests/system/ixfr directory holds the IXFR tests. Within that
-directory are subdirectories in-1 through in-4 for each test. And within
-each test directory are the directories ns1 (for the BIND 9 nameserver)
-and nsx2 (for the BIND 10 nameserver).
-
-Shell Scripts
--------------
-The IXFR tests use the same framework as the rest of the system tests,
-being based around shell scripts. Many have a ".in" form as they require
-substitution of build variables before they can be used, and so are
-listed in configure.ac. The files specific to the IXFR tests are:
-
-tests/system/ixfr/ixfr_init.sh.in: defines environment variables and shell
-subroutines used in the tests. (This references system/conf.sh.in which
-defines most of them.)
-
-tests/system/ixfr/common_tests.sh.in: tests in-1 and in-2 are virtually
-identical - this holds the common code.
diff --git a/tests/system/ixfr/b10-config.db.in b/tests/system/ixfr/b10-config.db.in
deleted file mode 100644
index a36117d..0000000
--- a/tests/system/ixfr/b10-config.db.in
+++ /dev/null
@@ -1,51 +0,0 @@
-{"version": 2,
- "Xfrin": {
- "zones": [{
- "master_addr": "10.53.0.1",
- "master_port": 53210,
- "name": "example.",
- "use_ixfr": true
- }]
- },
- "Auth": {
- "listen_on": [{
- "address": "10.53.0.2",
- "port": 53210
- }],
- "database_file": "@abs_builddir@/zone.sqlite3"
- },
- "data_sources": {
- "classes": {
- "IN": [{
- "type": "sqlite3",
- "params": {
- "database_file": "@abs_builddir@/zone.sqlite3"
- }
- }]
- }
- },
- "Logging": {
- "loggers": [{
- "name": "*",
- "severity": "DEBUG",
- "output_options": [],
- "debuglevel": 99
- }]
- },
- "Zonemgr": {
- "secondary_zones": [{
- "name": "example.",
- "class": "IN"
- }]
- },
- "Init": {
- "components": {
- "b10-auth": {"kind": "needed", "special": "auth" },
- "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
- "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
- "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
- "b10-stats": { "address": "Stats", "kind": "dispensable" },
- "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
- }
- }
-}
diff --git a/tests/system/ixfr/clean_ns.sh b/tests/system/ixfr/clean_ns.sh
deleted file mode 100644
index 88f4ff1..0000000
--- a/tests/system/ixfr/clean_ns.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# Clean up nameserver directories after zone transfer tests.
-
-rm -f ns1/named.conf
-rm -f ns1/db.example*
-rm -f ns1/named.memstats
-
-rm -f nsx2/bind10.run
-rm -f nsx2/b10-config.db
-rm -f ../zone.sqlite3
-
-rm -f client.dig
-rm -f server.dig
diff --git a/tests/system/ixfr/common_tests.sh.in b/tests/system/ixfr/common_tests.sh.in
deleted file mode 100644
index 90d0284..0000000
--- a/tests/system/ixfr/common_tests.sh.in
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# \file
-# This script is used in a couple of IXFR tests.
-#
-# Preconditions:\n
-# The BIND 9 nameserver (ns1, 10.53.0.1, acting as the IXFR server) is loaded
-# with the N-4 version of the zone. (It may hold prior versions as well.)
-# Notifications are disabled.
-#
-# The BIND 10 nameserver (nsx2, 10.53.0.2, acting as the IXFR client) is loaded
-# with an earlier (unspecified) version of the zone.
-#
-# Actions:\n
-# This script updates the IXFR server with the N-2 and N-0 versions of the zone.
-# It then updates the BIND 10 configuration so that it looks for IXFRs from
-# the IXFR server and causes the server to send the client a NOTIFY. After
-# waiting for the client to update from the server, it compares ther zones of
-# the two system, reporting an error if they are different.
-#
-# Caller Actions:\n
-# The caller can pre-load the BIND 10 IXFR client with whatever version of the
-# zone it requires. It can also load the BIND 9 IXFR server with zones earlier
-# than N-4.
-#
-# After this test has finished, it is up to the caller to check the logs
-# to see if they report the expected behavior.
-#
-# \return 0 if the script executed successfully, non-zero otherwise
-
-# Set up variables etc.
-. @abs_top_builddir@/tests/system/conf.sh
-. $IXFR_TOP/ixfr_init.sh
-
-set -e
-
-# Store the SOA serial number of the BIND 10 client for later use.
-old_client_serial=`$DIG_SOA @$CLIENT_IP | $AWK '{print $3}'`
-echo "I:$CLIENT_NAME SOA serial of IXFR client is $old_client_serial"
-
-# Load the BIND 9 system (the IXFR server) with the "n - 2" and "n" version of
-# the zones. With ixfr-from-differences set to "yes", the nameserver should
-# generate the differences between them.
-echo "I:$SERVER_NAME updating IXFR-server for ixfr-in tests"
-update_server_zone $SERVER_NAME $SERVER_IP $IXFR_TOP/db.example.n2
-
-# Wait a bit - it seems that if two updates are loaded in quick succession,
-# the second sometimes gets lost.
-sleep 5
-update_server_zone $SERVER_NAME $SERVER_IP $IXFR_TOP/db.example.n0
-
-echo "I:$CLIENT_NAME forcing IXFR client to retrieve new version of the zone"
-$RUN_BINDCTL << .
-Xfrin retransfer zone_name="example"
-.
-
-# Wait for the client to update itself.
-wait_for_update $CLIENT_NAME $CLIENT_IP $old_client_serial
-
-# Has updated, compare the client and server's versions of the zone s- they
-# should be the same.
-compare_zones $SERVER_NAME $SERVER_IP $CLIENT_NAME $CLIENT_IP
-
-set +e
diff --git a/tests/system/ixfr/db.example.common b/tests/system/ixfr/db.example.common
deleted file mode 100644
index 90435ce..0000000
--- a/tests/system/ixfr/db.example.common
+++ /dev/null
@@ -1,1556 +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.
-
-; This files holds a number of AAAA records to bulk out a zone file beyond
-; 16kB. It is used in tests where it is required that the contents of a zone
-; do not fit into a single UDP packet.
-
-aaaa-000 IN AAAA 2001:db8::0000
-aaaa-001 IN AAAA 2001:db8::0001
-aaaa-002 IN AAAA 2001:db8::0002
-aaaa-003 IN AAAA 2001:db8::0003
-aaaa-004 IN AAAA 2001:db8::0004
-aaaa-005 IN AAAA 2001:db8::0005
-aaaa-006 IN AAAA 2001:db8::0006
-aaaa-007 IN AAAA 2001:db8::0007
-aaaa-008 IN AAAA 2001:db8::0008
-aaaa-009 IN AAAA 2001:db8::0009
-aaaa-010 IN AAAA 2001:db8::000a
-aaaa-011 IN AAAA 2001:db8::000b
-aaaa-012 IN AAAA 2001:db8::000c
-aaaa-013 IN AAAA 2001:db8::000d
-aaaa-014 IN AAAA 2001:db8::000e
-aaaa-015 IN AAAA 2001:db8::000f
-aaaa-016 IN AAAA 2001:db8::0010
-aaaa-017 IN AAAA 2001:db8::0011
-aaaa-018 IN AAAA 2001:db8::0012
-aaaa-019 IN AAAA 2001:db8::0013
-aaaa-020 IN AAAA 2001:db8::0014
-aaaa-021 IN AAAA 2001:db8::0015
-aaaa-022 IN AAAA 2001:db8::0016
-aaaa-023 IN AAAA 2001:db8::0017
-aaaa-024 IN AAAA 2001:db8::0018
-aaaa-025 IN AAAA 2001:db8::0019
-aaaa-026 IN AAAA 2001:db8::001a
-aaaa-027 IN AAAA 2001:db8::001b
-aaaa-028 IN AAAA 2001:db8::001c
-aaaa-029 IN AAAA 2001:db8::001d
-aaaa-030 IN AAAA 2001:db8::001e
-aaaa-031 IN AAAA 2001:db8::001f
-aaaa-032 IN AAAA 2001:db8::0020
-aaaa-033 IN AAAA 2001:db8::0021
-aaaa-034 IN AAAA 2001:db8::0022
-aaaa-035 IN AAAA 2001:db8::0023
-aaaa-036 IN AAAA 2001:db8::0024
-aaaa-037 IN AAAA 2001:db8::0025
-aaaa-038 IN AAAA 2001:db8::0026
-aaaa-039 IN AAAA 2001:db8::0027
-aaaa-040 IN AAAA 2001:db8::0028
-aaaa-041 IN AAAA 2001:db8::0029
-aaaa-042 IN AAAA 2001:db8::002a
-aaaa-043 IN AAAA 2001:db8::002b
-aaaa-044 IN AAAA 2001:db8::002c
-aaaa-045 IN AAAA 2001:db8::002d
-aaaa-046 IN AAAA 2001:db8::002e
-aaaa-047 IN AAAA 2001:db8::002f
-aaaa-048 IN AAAA 2001:db8::0030
-aaaa-049 IN AAAA 2001:db8::0031
-aaaa-050 IN AAAA 2001:db8::0032
-aaaa-051 IN AAAA 2001:db8::0033
-aaaa-052 IN AAAA 2001:db8::0034
-aaaa-053 IN AAAA 2001:db8::0035
-aaaa-054 IN AAAA 2001:db8::0036
-aaaa-055 IN AAAA 2001:db8::0037
-aaaa-056 IN AAAA 2001:db8::0038
-aaaa-057 IN AAAA 2001:db8::0039
-aaaa-058 IN AAAA 2001:db8::003a
-aaaa-059 IN AAAA 2001:db8::003b
-aaaa-060 IN AAAA 2001:db8::003c
-aaaa-061 IN AAAA 2001:db8::003d
-aaaa-062 IN AAAA 2001:db8::003e
-aaaa-063 IN AAAA 2001:db8::003f
-aaaa-064 IN AAAA 2001:db8::0040
-aaaa-065 IN AAAA 2001:db8::0041
-aaaa-066 IN AAAA 2001:db8::0042
-aaaa-067 IN AAAA 2001:db8::0043
-aaaa-068 IN AAAA 2001:db8::0044
-aaaa-069 IN AAAA 2001:db8::0045
-aaaa-070 IN AAAA 2001:db8::0046
-aaaa-071 IN AAAA 2001:db8::0047
-aaaa-072 IN AAAA 2001:db8::0048
-aaaa-073 IN AAAA 2001:db8::0049
-aaaa-074 IN AAAA 2001:db8::004a
-aaaa-075 IN AAAA 2001:db8::004b
-aaaa-076 IN AAAA 2001:db8::004c
-aaaa-077 IN AAAA 2001:db8::004d
-aaaa-078 IN AAAA 2001:db8::004e
-aaaa-079 IN AAAA 2001:db8::004f
-aaaa-080 IN AAAA 2001:db8::0050
-aaaa-081 IN AAAA 2001:db8::0051
-aaaa-082 IN AAAA 2001:db8::0052
-aaaa-083 IN AAAA 2001:db8::0053
-aaaa-084 IN AAAA 2001:db8::0054
-aaaa-085 IN AAAA 2001:db8::0055
-aaaa-086 IN AAAA 2001:db8::0056
-aaaa-087 IN AAAA 2001:db8::0057
-aaaa-088 IN AAAA 2001:db8::0058
-aaaa-089 IN AAAA 2001:db8::0059
-aaaa-090 IN AAAA 2001:db8::005a
-aaaa-091 IN AAAA 2001:db8::005b
-aaaa-092 IN AAAA 2001:db8::005c
-aaaa-093 IN AAAA 2001:db8::005d
-aaaa-094 IN AAAA 2001:db8::005e
-aaaa-095 IN AAAA 2001:db8::005f
-aaaa-096 IN AAAA 2001:db8::0060
-aaaa-097 IN AAAA 2001:db8::0061
-aaaa-098 IN AAAA 2001:db8::0062
-aaaa-099 IN AAAA 2001:db8::0063
-aaaa-100 IN AAAA 2001:db8::0064
-aaaa-101 IN AAAA 2001:db8::0065
-aaaa-102 IN AAAA 2001:db8::0066
-aaaa-103 IN AAAA 2001:db8::0067
-aaaa-104 IN AAAA 2001:db8::0068
-aaaa-105 IN AAAA 2001:db8::0069
-aaaa-106 IN AAAA 2001:db8::006a
-aaaa-107 IN AAAA 2001:db8::006b
-aaaa-108 IN AAAA 2001:db8::006c
-aaaa-109 IN AAAA 2001:db8::006d
-aaaa-110 IN AAAA 2001:db8::006e
-aaaa-111 IN AAAA 2001:db8::006f
-aaaa-112 IN AAAA 2001:db8::0070
-aaaa-113 IN AAAA 2001:db8::0071
-aaaa-114 IN AAAA 2001:db8::0072
-aaaa-115 IN AAAA 2001:db8::0073
-aaaa-116 IN AAAA 2001:db8::0074
-aaaa-117 IN AAAA 2001:db8::0075
-aaaa-118 IN AAAA 2001:db8::0076
-aaaa-119 IN AAAA 2001:db8::0077
-aaaa-120 IN AAAA 2001:db8::0078
-aaaa-121 IN AAAA 2001:db8::0079
-aaaa-122 IN AAAA 2001:db8::007a
-aaaa-123 IN AAAA 2001:db8::007b
-aaaa-124 IN AAAA 2001:db8::007c
-aaaa-125 IN AAAA 2001:db8::007d
-aaaa-126 IN AAAA 2001:db8::007e
-aaaa-127 IN AAAA 2001:db8::007f
-aaaa-128 IN AAAA 2001:db8::0080
-aaaa-129 IN AAAA 2001:db8::0081
-aaaa-130 IN AAAA 2001:db8::0082
-aaaa-131 IN AAAA 2001:db8::0083
-aaaa-132 IN AAAA 2001:db8::0084
-aaaa-133 IN AAAA 2001:db8::0085
-aaaa-134 IN AAAA 2001:db8::0086
-aaaa-135 IN AAAA 2001:db8::0087
-aaaa-136 IN AAAA 2001:db8::0088
-aaaa-137 IN AAAA 2001:db8::0089
-aaaa-138 IN AAAA 2001:db8::008a
-aaaa-139 IN AAAA 2001:db8::008b
-aaaa-140 IN AAAA 2001:db8::008c
-aaaa-141 IN AAAA 2001:db8::008d
-aaaa-142 IN AAAA 2001:db8::008e
-aaaa-143 IN AAAA 2001:db8::008f
-aaaa-144 IN AAAA 2001:db8::0090
-aaaa-145 IN AAAA 2001:db8::0091
-aaaa-146 IN AAAA 2001:db8::0092
-aaaa-147 IN AAAA 2001:db8::0093
-aaaa-148 IN AAAA 2001:db8::0094
-aaaa-149 IN AAAA 2001:db8::0095
-aaaa-150 IN AAAA 2001:db8::0096
-aaaa-151 IN AAAA 2001:db8::0097
-aaaa-152 IN AAAA 2001:db8::0098
-aaaa-153 IN AAAA 2001:db8::0099
-aaaa-154 IN AAAA 2001:db8::009a
-aaaa-155 IN AAAA 2001:db8::009b
-aaaa-156 IN AAAA 2001:db8::009c
-aaaa-157 IN AAAA 2001:db8::009d
-aaaa-158 IN AAAA 2001:db8::009e
-aaaa-159 IN AAAA 2001:db8::009f
-aaaa-160 IN AAAA 2001:db8::00a0
-aaaa-161 IN AAAA 2001:db8::00a1
-aaaa-162 IN AAAA 2001:db8::00a2
-aaaa-163 IN AAAA 2001:db8::00a3
-aaaa-164 IN AAAA 2001:db8::00a4
-aaaa-165 IN AAAA 2001:db8::00a5
-aaaa-166 IN AAAA 2001:db8::00a6
-aaaa-167 IN AAAA 2001:db8::00a7
-aaaa-168 IN AAAA 2001:db8::00a8
-aaaa-169 IN AAAA 2001:db8::00a9
-aaaa-170 IN AAAA 2001:db8::00aa
-aaaa-171 IN AAAA 2001:db8::00ab
-aaaa-172 IN AAAA 2001:db8::00ac
-aaaa-173 IN AAAA 2001:db8::00ad
-aaaa-174 IN AAAA 2001:db8::00ae
-aaaa-175 IN AAAA 2001:db8::00af
-aaaa-176 IN AAAA 2001:db8::00b0
-aaaa-177 IN AAAA 2001:db8::00b1
-aaaa-178 IN AAAA 2001:db8::00b2
-aaaa-179 IN AAAA 2001:db8::00b3
-aaaa-180 IN AAAA 2001:db8::00b4
-aaaa-181 IN AAAA 2001:db8::00b5
-aaaa-182 IN AAAA 2001:db8::00b6
-aaaa-183 IN AAAA 2001:db8::00b7
-aaaa-184 IN AAAA 2001:db8::00b8
-aaaa-185 IN AAAA 2001:db8::00b9
-aaaa-186 IN AAAA 2001:db8::00ba
-aaaa-187 IN AAAA 2001:db8::00bb
-aaaa-188 IN AAAA 2001:db8::00bc
-aaaa-189 IN AAAA 2001:db8::00bd
-aaaa-190 IN AAAA 2001:db8::00be
-aaaa-191 IN AAAA 2001:db8::00bf
-aaaa-192 IN AAAA 2001:db8::00c0
-aaaa-193 IN AAAA 2001:db8::00c1
-aaaa-194 IN AAAA 2001:db8::00c2
-aaaa-195 IN AAAA 2001:db8::00c3
-aaaa-196 IN AAAA 2001:db8::00c4
-aaaa-197 IN AAAA 2001:db8::00c5
-aaaa-198 IN AAAA 2001:db8::00c6
-aaaa-199 IN AAAA 2001:db8::00c7
-aaaa-200 IN AAAA 2001:db8::00c8
-aaaa-201 IN AAAA 2001:db8::00c9
-aaaa-202 IN AAAA 2001:db8::00ca
-aaaa-203 IN AAAA 2001:db8::00cb
-aaaa-204 IN AAAA 2001:db8::00cc
-aaaa-205 IN AAAA 2001:db8::00cd
-aaaa-206 IN AAAA 2001:db8::00ce
-aaaa-207 IN AAAA 2001:db8::00cf
-aaaa-208 IN AAAA 2001:db8::00d0
-aaaa-209 IN AAAA 2001:db8::00d1
-aaaa-210 IN AAAA 2001:db8::00d2
-aaaa-211 IN AAAA 2001:db8::00d3
-aaaa-212 IN AAAA 2001:db8::00d4
-aaaa-213 IN AAAA 2001:db8::00d5
-aaaa-214 IN AAAA 2001:db8::00d6
-aaaa-215 IN AAAA 2001:db8::00d7
-aaaa-216 IN AAAA 2001:db8::00d8
-aaaa-217 IN AAAA 2001:db8::00d9
-aaaa-218 IN AAAA 2001:db8::00da
-aaaa-219 IN AAAA 2001:db8::00db
-aaaa-220 IN AAAA 2001:db8::00dc
-aaaa-221 IN AAAA 2001:db8::00dd
-aaaa-222 IN AAAA 2001:db8::00de
-aaaa-223 IN AAAA 2001:db8::00df
-aaaa-224 IN AAAA 2001:db8::00e0
-aaaa-225 IN AAAA 2001:db8::00e1
-aaaa-226 IN AAAA 2001:db8::00e2
-aaaa-227 IN AAAA 2001:db8::00e3
-aaaa-228 IN AAAA 2001:db8::00e4
-aaaa-229 IN AAAA 2001:db8::00e5
-aaaa-230 IN AAAA 2001:db8::00e6
-aaaa-231 IN AAAA 2001:db8::00e7
-aaaa-232 IN AAAA 2001:db8::00e8
-aaaa-233 IN AAAA 2001:db8::00e9
-aaaa-234 IN AAAA 2001:db8::00ea
-aaaa-235 IN AAAA 2001:db8::00eb
-aaaa-236 IN AAAA 2001:db8::00ec
-aaaa-237 IN AAAA 2001:db8::00ed
-aaaa-238 IN AAAA 2001:db8::00ee
-aaaa-239 IN AAAA 2001:db8::00ef
-aaaa-240 IN AAAA 2001:db8::00f0
-aaaa-241 IN AAAA 2001:db8::00f1
-aaaa-242 IN AAAA 2001:db8::00f2
-aaaa-243 IN AAAA 2001:db8::00f3
-aaaa-244 IN AAAA 2001:db8::00f4
-aaaa-245 IN AAAA 2001:db8::00f5
-aaaa-246 IN AAAA 2001:db8::00f6
-aaaa-247 IN AAAA 2001:db8::00f7
-aaaa-248 IN AAAA 2001:db8::00f8
-aaaa-249 IN AAAA 2001:db8::00f9
-aaaa-250 IN AAAA 2001:db8::00fa
-aaaa-251 IN AAAA 2001:db8::00fb
-aaaa-252 IN AAAA 2001:db8::00fc
-aaaa-253 IN AAAA 2001:db8::00fd
-aaaa-254 IN AAAA 2001:db8::00fe
-aaaa-255 IN AAAA 2001:db8::00ff
-aaaa-256 IN AAAA 2001:db8::0100
-aaaa-257 IN AAAA 2001:db8::0101
-aaaa-258 IN AAAA 2001:db8::0102
-aaaa-259 IN AAAA 2001:db8::0103
-aaaa-260 IN AAAA 2001:db8::0104
-aaaa-261 IN AAAA 2001:db8::0105
-aaaa-262 IN AAAA 2001:db8::0106
-aaaa-263 IN AAAA 2001:db8::0107
-aaaa-264 IN AAAA 2001:db8::0108
-aaaa-265 IN AAAA 2001:db8::0109
-aaaa-266 IN AAAA 2001:db8::010a
-aaaa-267 IN AAAA 2001:db8::010b
-aaaa-268 IN AAAA 2001:db8::010c
-aaaa-269 IN AAAA 2001:db8::010d
-aaaa-270 IN AAAA 2001:db8::010e
-aaaa-271 IN AAAA 2001:db8::010f
-aaaa-272 IN AAAA 2001:db8::0110
-aaaa-273 IN AAAA 2001:db8::0111
-aaaa-274 IN AAAA 2001:db8::0112
-aaaa-275 IN AAAA 2001:db8::0113
-aaaa-276 IN AAAA 2001:db8::0114
-aaaa-277 IN AAAA 2001:db8::0115
-aaaa-278 IN AAAA 2001:db8::0116
-aaaa-279 IN AAAA 2001:db8::0117
-aaaa-280 IN AAAA 2001:db8::0118
-aaaa-281 IN AAAA 2001:db8::0119
-aaaa-282 IN AAAA 2001:db8::011a
-aaaa-283 IN AAAA 2001:db8::011b
-aaaa-284 IN AAAA 2001:db8::011c
-aaaa-285 IN AAAA 2001:db8::011d
-aaaa-286 IN AAAA 2001:db8::011e
-aaaa-287 IN AAAA 2001:db8::011f
-aaaa-288 IN AAAA 2001:db8::0120
-aaaa-289 IN AAAA 2001:db8::0121
-aaaa-290 IN AAAA 2001:db8::0122
-aaaa-291 IN AAAA 2001:db8::0123
-aaaa-292 IN AAAA 2001:db8::0124
-aaaa-293 IN AAAA 2001:db8::0125
-aaaa-294 IN AAAA 2001:db8::0126
-aaaa-295 IN AAAA 2001:db8::0127
-aaaa-296 IN AAAA 2001:db8::0128
-aaaa-297 IN AAAA 2001:db8::0129
-aaaa-298 IN AAAA 2001:db8::012a
-aaaa-299 IN AAAA 2001:db8::012b
-aaaa-300 IN AAAA 2001:db8::012c
-aaaa-301 IN AAAA 2001:db8::012d
-aaaa-302 IN AAAA 2001:db8::012e
-aaaa-303 IN AAAA 2001:db8::012f
-aaaa-304 IN AAAA 2001:db8::0130
-aaaa-305 IN AAAA 2001:db8::0131
-aaaa-306 IN AAAA 2001:db8::0132
-aaaa-307 IN AAAA 2001:db8::0133
-aaaa-308 IN AAAA 2001:db8::0134
-aaaa-309 IN AAAA 2001:db8::0135
-aaaa-310 IN AAAA 2001:db8::0136
-aaaa-311 IN AAAA 2001:db8::0137
-aaaa-312 IN AAAA 2001:db8::0138
-aaaa-313 IN AAAA 2001:db8::0139
-aaaa-314 IN AAAA 2001:db8::013a
-aaaa-315 IN AAAA 2001:db8::013b
-aaaa-316 IN AAAA 2001:db8::013c
-aaaa-317 IN AAAA 2001:db8::013d
-aaaa-318 IN AAAA 2001:db8::013e
-aaaa-319 IN AAAA 2001:db8::013f
-aaaa-320 IN AAAA 2001:db8::0140
-aaaa-321 IN AAAA 2001:db8::0141
-aaaa-322 IN AAAA 2001:db8::0142
-aaaa-323 IN AAAA 2001:db8::0143
-aaaa-324 IN AAAA 2001:db8::0144
-aaaa-325 IN AAAA 2001:db8::0145
-aaaa-326 IN AAAA 2001:db8::0146
-aaaa-327 IN AAAA 2001:db8::0147
-aaaa-328 IN AAAA 2001:db8::0148
-aaaa-329 IN AAAA 2001:db8::0149
-aaaa-330 IN AAAA 2001:db8::014a
-aaaa-331 IN AAAA 2001:db8::014b
-aaaa-332 IN AAAA 2001:db8::014c
-aaaa-333 IN AAAA 2001:db8::014d
-aaaa-334 IN AAAA 2001:db8::014e
-aaaa-335 IN AAAA 2001:db8::014f
-aaaa-336 IN AAAA 2001:db8::0150
-aaaa-337 IN AAAA 2001:db8::0151
-aaaa-338 IN AAAA 2001:db8::0152
-aaaa-339 IN AAAA 2001:db8::0153
-aaaa-340 IN AAAA 2001:db8::0154
-aaaa-341 IN AAAA 2001:db8::0155
-aaaa-342 IN AAAA 2001:db8::0156
-aaaa-343 IN AAAA 2001:db8::0157
-aaaa-344 IN AAAA 2001:db8::0158
-aaaa-345 IN AAAA 2001:db8::0159
-aaaa-346 IN AAAA 2001:db8::015a
-aaaa-347 IN AAAA 2001:db8::015b
-aaaa-348 IN AAAA 2001:db8::015c
-aaaa-349 IN AAAA 2001:db8::015d
-aaaa-350 IN AAAA 2001:db8::015e
-aaaa-351 IN AAAA 2001:db8::015f
-aaaa-352 IN AAAA 2001:db8::0160
-aaaa-353 IN AAAA 2001:db8::0161
-aaaa-354 IN AAAA 2001:db8::0162
-aaaa-355 IN AAAA 2001:db8::0163
-aaaa-356 IN AAAA 2001:db8::0164
-aaaa-357 IN AAAA 2001:db8::0165
-aaaa-358 IN AAAA 2001:db8::0166
-aaaa-359 IN AAAA 2001:db8::0167
-aaaa-360 IN AAAA 2001:db8::0168
-aaaa-361 IN AAAA 2001:db8::0169
-aaaa-362 IN AAAA 2001:db8::016a
-aaaa-363 IN AAAA 2001:db8::016b
-aaaa-364 IN AAAA 2001:db8::016c
-aaaa-365 IN AAAA 2001:db8::016d
-aaaa-366 IN AAAA 2001:db8::016e
-aaaa-367 IN AAAA 2001:db8::016f
-aaaa-368 IN AAAA 2001:db8::0170
-aaaa-369 IN AAAA 2001:db8::0171
-aaaa-370 IN AAAA 2001:db8::0172
-aaaa-371 IN AAAA 2001:db8::0173
-aaaa-372 IN AAAA 2001:db8::0174
-aaaa-373 IN AAAA 2001:db8::0175
-aaaa-374 IN AAAA 2001:db8::0176
-aaaa-375 IN AAAA 2001:db8::0177
-aaaa-376 IN AAAA 2001:db8::0178
-aaaa-377 IN AAAA 2001:db8::0179
-aaaa-378 IN AAAA 2001:db8::017a
-aaaa-379 IN AAAA 2001:db8::017b
-aaaa-380 IN AAAA 2001:db8::017c
-aaaa-381 IN AAAA 2001:db8::017d
-aaaa-382 IN AAAA 2001:db8::017e
-aaaa-383 IN AAAA 2001:db8::017f
-aaaa-384 IN AAAA 2001:db8::0180
-aaaa-385 IN AAAA 2001:db8::0181
-aaaa-386 IN AAAA 2001:db8::0182
-aaaa-387 IN AAAA 2001:db8::0183
-aaaa-388 IN AAAA 2001:db8::0184
-aaaa-389 IN AAAA 2001:db8::0185
-aaaa-390 IN AAAA 2001:db8::0186
-aaaa-391 IN AAAA 2001:db8::0187
-aaaa-392 IN AAAA 2001:db8::0188
-aaaa-393 IN AAAA 2001:db8::0189
-aaaa-394 IN AAAA 2001:db8::018a
-aaaa-395 IN AAAA 2001:db8::018b
-aaaa-396 IN AAAA 2001:db8::018c
-aaaa-397 IN AAAA 2001:db8::018d
-aaaa-398 IN AAAA 2001:db8::018e
-aaaa-399 IN AAAA 2001:db8::018f
-aaaa-400 IN AAAA 2001:db8::0190
-aaaa-401 IN AAAA 2001:db8::0191
-aaaa-402 IN AAAA 2001:db8::0192
-aaaa-403 IN AAAA 2001:db8::0193
-aaaa-404 IN AAAA 2001:db8::0194
-aaaa-405 IN AAAA 2001:db8::0195
-aaaa-406 IN AAAA 2001:db8::0196
-aaaa-407 IN AAAA 2001:db8::0197
-aaaa-408 IN AAAA 2001:db8::0198
-aaaa-409 IN AAAA 2001:db8::0199
-aaaa-410 IN AAAA 2001:db8::019a
-aaaa-411 IN AAAA 2001:db8::019b
-aaaa-412 IN AAAA 2001:db8::019c
-aaaa-413 IN AAAA 2001:db8::019d
-aaaa-414 IN AAAA 2001:db8::019e
-aaaa-415 IN AAAA 2001:db8::019f
-aaaa-416 IN AAAA 2001:db8::01a0
-aaaa-417 IN AAAA 2001:db8::01a1
-aaaa-418 IN AAAA 2001:db8::01a2
-aaaa-419 IN AAAA 2001:db8::01a3
-aaaa-420 IN AAAA 2001:db8::01a4
-aaaa-421 IN AAAA 2001:db8::01a5
-aaaa-422 IN AAAA 2001:db8::01a6
-aaaa-423 IN AAAA 2001:db8::01a7
-aaaa-424 IN AAAA 2001:db8::01a8
-aaaa-425 IN AAAA 2001:db8::01a9
-aaaa-426 IN AAAA 2001:db8::01aa
-aaaa-427 IN AAAA 2001:db8::01ab
-aaaa-428 IN AAAA 2001:db8::01ac
-aaaa-429 IN AAAA 2001:db8::01ad
-aaaa-430 IN AAAA 2001:db8::01ae
-aaaa-431 IN AAAA 2001:db8::01af
-aaaa-432 IN AAAA 2001:db8::01b0
-aaaa-433 IN AAAA 2001:db8::01b1
-aaaa-434 IN AAAA 2001:db8::01b2
-aaaa-435 IN AAAA 2001:db8::01b3
-aaaa-436 IN AAAA 2001:db8::01b4
-aaaa-437 IN AAAA 2001:db8::01b5
-aaaa-438 IN AAAA 2001:db8::01b6
-aaaa-439 IN AAAA 2001:db8::01b7
-aaaa-440 IN AAAA 2001:db8::01b8
-aaaa-441 IN AAAA 2001:db8::01b9
-aaaa-442 IN AAAA 2001:db8::01ba
-aaaa-443 IN AAAA 2001:db8::01bb
-aaaa-444 IN AAAA 2001:db8::01bc
-aaaa-445 IN AAAA 2001:db8::01bd
-aaaa-446 IN AAAA 2001:db8::01be
-aaaa-447 IN AAAA 2001:db8::01bf
-aaaa-448 IN AAAA 2001:db8::01c0
-aaaa-449 IN AAAA 2001:db8::01c1
-aaaa-450 IN AAAA 2001:db8::01c2
-aaaa-451 IN AAAA 2001:db8::01c3
-aaaa-452 IN AAAA 2001:db8::01c4
-aaaa-453 IN AAAA 2001:db8::01c5
-aaaa-454 IN AAAA 2001:db8::01c6
-aaaa-455 IN AAAA 2001:db8::01c7
-aaaa-456 IN AAAA 2001:db8::01c8
-aaaa-457 IN AAAA 2001:db8::01c9
-aaaa-458 IN AAAA 2001:db8::01ca
-aaaa-459 IN AAAA 2001:db8::01cb
-aaaa-460 IN AAAA 2001:db8::01cc
-aaaa-461 IN AAAA 2001:db8::01cd
-aaaa-462 IN AAAA 2001:db8::01ce
-aaaa-463 IN AAAA 2001:db8::01cf
-aaaa-464 IN AAAA 2001:db8::01d0
-aaaa-465 IN AAAA 2001:db8::01d1
-aaaa-466 IN AAAA 2001:db8::01d2
-aaaa-467 IN AAAA 2001:db8::01d3
-aaaa-468 IN AAAA 2001:db8::01d4
-aaaa-469 IN AAAA 2001:db8::01d5
-aaaa-470 IN AAAA 2001:db8::01d6
-aaaa-471 IN AAAA 2001:db8::01d7
-aaaa-472 IN AAAA 2001:db8::01d8
-aaaa-473 IN AAAA 2001:db8::01d9
-aaaa-474 IN AAAA 2001:db8::01da
-aaaa-475 IN AAAA 2001:db8::01db
-aaaa-476 IN AAAA 2001:db8::01dc
-aaaa-477 IN AAAA 2001:db8::01dd
-aaaa-478 IN AAAA 2001:db8::01de
-aaaa-479 IN AAAA 2001:db8::01df
-aaaa-480 IN AAAA 2001:db8::01e0
-aaaa-481 IN AAAA 2001:db8::01e1
-aaaa-482 IN AAAA 2001:db8::01e2
-aaaa-483 IN AAAA 2001:db8::01e3
-aaaa-484 IN AAAA 2001:db8::01e4
-aaaa-485 IN AAAA 2001:db8::01e5
-aaaa-486 IN AAAA 2001:db8::01e6
-aaaa-487 IN AAAA 2001:db8::01e7
-aaaa-488 IN AAAA 2001:db8::01e8
-aaaa-489 IN AAAA 2001:db8::01e9
-aaaa-490 IN AAAA 2001:db8::01ea
-aaaa-491 IN AAAA 2001:db8::01eb
-aaaa-492 IN AAAA 2001:db8::01ec
-aaaa-493 IN AAAA 2001:db8::01ed
-aaaa-494 IN AAAA 2001:db8::01ee
-aaaa-495 IN AAAA 2001:db8::01ef
-aaaa-496 IN AAAA 2001:db8::01f0
-aaaa-497 IN AAAA 2001:db8::01f1
-aaaa-498 IN AAAA 2001:db8::01f2
-aaaa-499 IN AAAA 2001:db8::01f3
-aaaa-500 IN AAAA 2001:db8::01f4
-aaaa-501 IN AAAA 2001:db8::01f5
-aaaa-502 IN AAAA 2001:db8::01f6
-aaaa-503 IN AAAA 2001:db8::01f7
-aaaa-504 IN AAAA 2001:db8::01f8
-aaaa-505 IN AAAA 2001:db8::01f9
-aaaa-506 IN AAAA 2001:db8::01fa
-aaaa-507 IN AAAA 2001:db8::01fb
-aaaa-508 IN AAAA 2001:db8::01fc
-aaaa-509 IN AAAA 2001:db8::01fd
-aaaa-510 IN AAAA 2001:db8::01fe
-aaaa-511 IN AAAA 2001:db8::01ff
-
-bbbb-000 IN AAAA 2001:db8::1:0000
-bbbb-001 IN AAAA 2001:db8::1:0001
-bbbb-002 IN AAAA 2001:db8::1:0002
-bbbb-003 IN AAAA 2001:db8::1:0003
-bbbb-004 IN AAAA 2001:db8::1:0004
-bbbb-005 IN AAAA 2001:db8::1:0005
-bbbb-006 IN AAAA 2001:db8::1:0006
-bbbb-007 IN AAAA 2001:db8::1:0007
-bbbb-008 IN AAAA 2001:db8::1:0008
-bbbb-009 IN AAAA 2001:db8::1:0009
-bbbb-010 IN AAAA 2001:db8::1:000a
-bbbb-011 IN AAAA 2001:db8::1:000b
-bbbb-012 IN AAAA 2001:db8::1:000c
-bbbb-013 IN AAAA 2001:db8::1:000d
-bbbb-014 IN AAAA 2001:db8::1:000e
-bbbb-015 IN AAAA 2001:db8::1:000f
-bbbb-016 IN AAAA 2001:db8::1:0010
-bbbb-017 IN AAAA 2001:db8::1:0011
-bbbb-018 IN AAAA 2001:db8::1:0012
-bbbb-019 IN AAAA 2001:db8::1:0013
-bbbb-020 IN AAAA 2001:db8::1:0014
-bbbb-021 IN AAAA 2001:db8::1:0015
-bbbb-022 IN AAAA 2001:db8::1:0016
-bbbb-023 IN AAAA 2001:db8::1:0017
-bbbb-024 IN AAAA 2001:db8::1:0018
-bbbb-025 IN AAAA 2001:db8::1:0019
-bbbb-026 IN AAAA 2001:db8::1:001a
-bbbb-027 IN AAAA 2001:db8::1:001b
-bbbb-028 IN AAAA 2001:db8::1:001c
-bbbb-029 IN AAAA 2001:db8::1:001d
-bbbb-030 IN AAAA 2001:db8::1:001e
-bbbb-031 IN AAAA 2001:db8::1:001f
-bbbb-032 IN AAAA 2001:db8::1:0020
-bbbb-033 IN AAAA 2001:db8::1:0021
-bbbb-034 IN AAAA 2001:db8::1:0022
-bbbb-035 IN AAAA 2001:db8::1:0023
-bbbb-036 IN AAAA 2001:db8::1:0024
-bbbb-037 IN AAAA 2001:db8::1:0025
-bbbb-038 IN AAAA 2001:db8::1:0026
-bbbb-039 IN AAAA 2001:db8::1:0027
-bbbb-040 IN AAAA 2001:db8::1:0028
-bbbb-041 IN AAAA 2001:db8::1:0029
-bbbb-042 IN AAAA 2001:db8::1:002a
-bbbb-043 IN AAAA 2001:db8::1:002b
-bbbb-044 IN AAAA 2001:db8::1:002c
-bbbb-045 IN AAAA 2001:db8::1:002d
-bbbb-046 IN AAAA 2001:db8::1:002e
-bbbb-047 IN AAAA 2001:db8::1:002f
-bbbb-048 IN AAAA 2001:db8::1:0030
-bbbb-049 IN AAAA 2001:db8::1:0031
-bbbb-050 IN AAAA 2001:db8::1:0032
-bbbb-051 IN AAAA 2001:db8::1:0033
-bbbb-052 IN AAAA 2001:db8::1:0034
-bbbb-053 IN AAAA 2001:db8::1:0035
-bbbb-054 IN AAAA 2001:db8::1:0036
-bbbb-055 IN AAAA 2001:db8::1:0037
-bbbb-056 IN AAAA 2001:db8::1:0038
-bbbb-057 IN AAAA 2001:db8::1:0039
-bbbb-058 IN AAAA 2001:db8::1:003a
-bbbb-059 IN AAAA 2001:db8::1:003b
-bbbb-060 IN AAAA 2001:db8::1:003c
-bbbb-061 IN AAAA 2001:db8::1:003d
-bbbb-062 IN AAAA 2001:db8::1:003e
-bbbb-063 IN AAAA 2001:db8::1:003f
-bbbb-064 IN AAAA 2001:db8::1:0040
-bbbb-065 IN AAAA 2001:db8::1:0041
-bbbb-066 IN AAAA 2001:db8::1:0042
-bbbb-067 IN AAAA 2001:db8::1:0043
-bbbb-068 IN AAAA 2001:db8::1:0044
-bbbb-069 IN AAAA 2001:db8::1:0045
-bbbb-070 IN AAAA 2001:db8::1:0046
-bbbb-071 IN AAAA 2001:db8::1:0047
-bbbb-072 IN AAAA 2001:db8::1:0048
-bbbb-073 IN AAAA 2001:db8::1:0049
-bbbb-074 IN AAAA 2001:db8::1:004a
-bbbb-075 IN AAAA 2001:db8::1:004b
-bbbb-076 IN AAAA 2001:db8::1:004c
-bbbb-077 IN AAAA 2001:db8::1:004d
-bbbb-078 IN AAAA 2001:db8::1:004e
-bbbb-079 IN AAAA 2001:db8::1:004f
-bbbb-080 IN AAAA 2001:db8::1:0050
-bbbb-081 IN AAAA 2001:db8::1:0051
-bbbb-082 IN AAAA 2001:db8::1:0052
-bbbb-083 IN AAAA 2001:db8::1:0053
-bbbb-084 IN AAAA 2001:db8::1:0054
-bbbb-085 IN AAAA 2001:db8::1:0055
-bbbb-086 IN AAAA 2001:db8::1:0056
-bbbb-087 IN AAAA 2001:db8::1:0057
-bbbb-088 IN AAAA 2001:db8::1:0058
-bbbb-089 IN AAAA 2001:db8::1:0059
-bbbb-090 IN AAAA 2001:db8::1:005a
-bbbb-091 IN AAAA 2001:db8::1:005b
-bbbb-092 IN AAAA 2001:db8::1:005c
-bbbb-093 IN AAAA 2001:db8::1:005d
-bbbb-094 IN AAAA 2001:db8::1:005e
-bbbb-095 IN AAAA 2001:db8::1:005f
-bbbb-096 IN AAAA 2001:db8::1:0060
-bbbb-097 IN AAAA 2001:db8::1:0061
-bbbb-098 IN AAAA 2001:db8::1:0062
-bbbb-099 IN AAAA 2001:db8::1:0063
-bbbb-100 IN AAAA 2001:db8::1:0064
-bbbb-101 IN AAAA 2001:db8::1:0065
-bbbb-102 IN AAAA 2001:db8::1:0066
-bbbb-103 IN AAAA 2001:db8::1:0067
-bbbb-104 IN AAAA 2001:db8::1:0068
-bbbb-105 IN AAAA 2001:db8::1:0069
-bbbb-106 IN AAAA 2001:db8::1:006a
-bbbb-107 IN AAAA 2001:db8::1:006b
-bbbb-108 IN AAAA 2001:db8::1:006c
-bbbb-109 IN AAAA 2001:db8::1:006d
-bbbb-110 IN AAAA 2001:db8::1:006e
-bbbb-111 IN AAAA 2001:db8::1:006f
-bbbb-112 IN AAAA 2001:db8::1:0070
-bbbb-113 IN AAAA 2001:db8::1:0071
-bbbb-114 IN AAAA 2001:db8::1:0072
-bbbb-115 IN AAAA 2001:db8::1:0073
-bbbb-116 IN AAAA 2001:db8::1:0074
-bbbb-117 IN AAAA 2001:db8::1:0075
-bbbb-118 IN AAAA 2001:db8::1:0076
-bbbb-119 IN AAAA 2001:db8::1:0077
-bbbb-120 IN AAAA 2001:db8::1:0078
-bbbb-121 IN AAAA 2001:db8::1:0079
-bbbb-122 IN AAAA 2001:db8::1:007a
-bbbb-123 IN AAAA 2001:db8::1:007b
-bbbb-124 IN AAAA 2001:db8::1:007c
-bbbb-125 IN AAAA 2001:db8::1:007d
-bbbb-126 IN AAAA 2001:db8::1:007e
-bbbb-127 IN AAAA 2001:db8::1:007f
-bbbb-128 IN AAAA 2001:db8::1:0080
-bbbb-129 IN AAAA 2001:db8::1:0081
-bbbb-130 IN AAAA 2001:db8::1:0082
-bbbb-131 IN AAAA 2001:db8::1:0083
-bbbb-132 IN AAAA 2001:db8::1:0084
-bbbb-133 IN AAAA 2001:db8::1:0085
-bbbb-134 IN AAAA 2001:db8::1:0086
-bbbb-135 IN AAAA 2001:db8::1:0087
-bbbb-136 IN AAAA 2001:db8::1:0088
-bbbb-137 IN AAAA 2001:db8::1:0089
-bbbb-138 IN AAAA 2001:db8::1:008a
-bbbb-139 IN AAAA 2001:db8::1:008b
-bbbb-140 IN AAAA 2001:db8::1:008c
-bbbb-141 IN AAAA 2001:db8::1:008d
-bbbb-142 IN AAAA 2001:db8::1:008e
-bbbb-143 IN AAAA 2001:db8::1:008f
-bbbb-144 IN AAAA 2001:db8::1:0090
-bbbb-145 IN AAAA 2001:db8::1:0091
-bbbb-146 IN AAAA 2001:db8::1:0092
-bbbb-147 IN AAAA 2001:db8::1:0093
-bbbb-148 IN AAAA 2001:db8::1:0094
-bbbb-149 IN AAAA 2001:db8::1:0095
-bbbb-150 IN AAAA 2001:db8::1:0096
-bbbb-151 IN AAAA 2001:db8::1:0097
-bbbb-152 IN AAAA 2001:db8::1:0098
-bbbb-153 IN AAAA 2001:db8::1:0099
-bbbb-154 IN AAAA 2001:db8::1:009a
-bbbb-155 IN AAAA 2001:db8::1:009b
-bbbb-156 IN AAAA 2001:db8::1:009c
-bbbb-157 IN AAAA 2001:db8::1:009d
-bbbb-158 IN AAAA 2001:db8::1:009e
-bbbb-159 IN AAAA 2001:db8::1:009f
-bbbb-160 IN AAAA 2001:db8::1:00a0
-bbbb-161 IN AAAA 2001:db8::1:00a1
-bbbb-162 IN AAAA 2001:db8::1:00a2
-bbbb-163 IN AAAA 2001:db8::1:00a3
-bbbb-164 IN AAAA 2001:db8::1:00a4
-bbbb-165 IN AAAA 2001:db8::1:00a5
-bbbb-166 IN AAAA 2001:db8::1:00a6
-bbbb-167 IN AAAA 2001:db8::1:00a7
-bbbb-168 IN AAAA 2001:db8::1:00a8
-bbbb-169 IN AAAA 2001:db8::1:00a9
-bbbb-170 IN AAAA 2001:db8::1:00aa
-bbbb-171 IN AAAA 2001:db8::1:00ab
-bbbb-172 IN AAAA 2001:db8::1:00ac
-bbbb-173 IN AAAA 2001:db8::1:00ad
-bbbb-174 IN AAAA 2001:db8::1:00ae
-bbbb-175 IN AAAA 2001:db8::1:00af
-bbbb-176 IN AAAA 2001:db8::1:00b0
-bbbb-177 IN AAAA 2001:db8::1:00b1
-bbbb-178 IN AAAA 2001:db8::1:00b2
-bbbb-179 IN AAAA 2001:db8::1:00b3
-bbbb-180 IN AAAA 2001:db8::1:00b4
-bbbb-181 IN AAAA 2001:db8::1:00b5
-bbbb-182 IN AAAA 2001:db8::1:00b6
-bbbb-183 IN AAAA 2001:db8::1:00b7
-bbbb-184 IN AAAA 2001:db8::1:00b8
-bbbb-185 IN AAAA 2001:db8::1:00b9
-bbbb-186 IN AAAA 2001:db8::1:00ba
-bbbb-187 IN AAAA 2001:db8::1:00bb
-bbbb-188 IN AAAA 2001:db8::1:00bc
-bbbb-189 IN AAAA 2001:db8::1:00bd
-bbbb-190 IN AAAA 2001:db8::1:00be
-bbbb-191 IN AAAA 2001:db8::1:00bf
-bbbb-192 IN AAAA 2001:db8::1:00c0
-bbbb-193 IN AAAA 2001:db8::1:00c1
-bbbb-194 IN AAAA 2001:db8::1:00c2
-bbbb-195 IN AAAA 2001:db8::1:00c3
-bbbb-196 IN AAAA 2001:db8::1:00c4
-bbbb-197 IN AAAA 2001:db8::1:00c5
-bbbb-198 IN AAAA 2001:db8::1:00c6
-bbbb-199 IN AAAA 2001:db8::1:00c7
-bbbb-200 IN AAAA 2001:db8::1:00c8
-bbbb-201 IN AAAA 2001:db8::1:00c9
-bbbb-202 IN AAAA 2001:db8::1:00ca
-bbbb-203 IN AAAA 2001:db8::1:00cb
-bbbb-204 IN AAAA 2001:db8::1:00cc
-bbbb-205 IN AAAA 2001:db8::1:00cd
-bbbb-206 IN AAAA 2001:db8::1:00ce
-bbbb-207 IN AAAA 2001:db8::1:00cf
-bbbb-208 IN AAAA 2001:db8::1:00d0
-bbbb-209 IN AAAA 2001:db8::1:00d1
-bbbb-210 IN AAAA 2001:db8::1:00d2
-bbbb-211 IN AAAA 2001:db8::1:00d3
-bbbb-212 IN AAAA 2001:db8::1:00d4
-bbbb-213 IN AAAA 2001:db8::1:00d5
-bbbb-214 IN AAAA 2001:db8::1:00d6
-bbbb-215 IN AAAA 2001:db8::1:00d7
-bbbb-216 IN AAAA 2001:db8::1:00d8
-bbbb-217 IN AAAA 2001:db8::1:00d9
-bbbb-218 IN AAAA 2001:db8::1:00da
-bbbb-219 IN AAAA 2001:db8::1:00db
-bbbb-220 IN AAAA 2001:db8::1:00dc
-bbbb-221 IN AAAA 2001:db8::1:00dd
-bbbb-222 IN AAAA 2001:db8::1:00de
-bbbb-223 IN AAAA 2001:db8::1:00df
-bbbb-224 IN AAAA 2001:db8::1:00e0
-bbbb-225 IN AAAA 2001:db8::1:00e1
-bbbb-226 IN AAAA 2001:db8::1:00e2
-bbbb-227 IN AAAA 2001:db8::1:00e3
-bbbb-228 IN AAAA 2001:db8::1:00e4
-bbbb-229 IN AAAA 2001:db8::1:00e5
-bbbb-230 IN AAAA 2001:db8::1:00e6
-bbbb-231 IN AAAA 2001:db8::1:00e7
-bbbb-232 IN AAAA 2001:db8::1:00e8
-bbbb-233 IN AAAA 2001:db8::1:00e9
-bbbb-234 IN AAAA 2001:db8::1:00ea
-bbbb-235 IN AAAA 2001:db8::1:00eb
-bbbb-236 IN AAAA 2001:db8::1:00ec
-bbbb-237 IN AAAA 2001:db8::1:00ed
-bbbb-238 IN AAAA 2001:db8::1:00ee
-bbbb-239 IN AAAA 2001:db8::1:00ef
-bbbb-240 IN AAAA 2001:db8::1:00f0
-bbbb-241 IN AAAA 2001:db8::1:00f1
-bbbb-242 IN AAAA 2001:db8::1:00f2
-bbbb-243 IN AAAA 2001:db8::1:00f3
-bbbb-244 IN AAAA 2001:db8::1:00f4
-bbbb-245 IN AAAA 2001:db8::1:00f5
-bbbb-246 IN AAAA 2001:db8::1:00f6
-bbbb-247 IN AAAA 2001:db8::1:00f7
-bbbb-248 IN AAAA 2001:db8::1:00f8
-bbbb-249 IN AAAA 2001:db8::1:00f9
-bbbb-250 IN AAAA 2001:db8::1:00fa
-bbbb-251 IN AAAA 2001:db8::1:00fb
-bbbb-252 IN AAAA 2001:db8::1:00fc
-bbbb-253 IN AAAA 2001:db8::1:00fd
-bbbb-254 IN AAAA 2001:db8::1:00fe
-bbbb-255 IN AAAA 2001:db8::1:00ff
-bbbb-256 IN AAAA 2001:db8::1:0100
-bbbb-257 IN AAAA 2001:db8::1:0101
-bbbb-258 IN AAAA 2001:db8::1:0102
-bbbb-259 IN AAAA 2001:db8::1:0103
-bbbb-260 IN AAAA 2001:db8::1:0104
-bbbb-261 IN AAAA 2001:db8::1:0105
-bbbb-262 IN AAAA 2001:db8::1:0106
-bbbb-263 IN AAAA 2001:db8::1:0107
-bbbb-264 IN AAAA 2001:db8::1:0108
-bbbb-265 IN AAAA 2001:db8::1:0109
-bbbb-266 IN AAAA 2001:db8::1:010a
-bbbb-267 IN AAAA 2001:db8::1:010b
-bbbb-268 IN AAAA 2001:db8::1:010c
-bbbb-269 IN AAAA 2001:db8::1:010d
-bbbb-270 IN AAAA 2001:db8::1:010e
-bbbb-271 IN AAAA 2001:db8::1:010f
-bbbb-272 IN AAAA 2001:db8::1:0110
-bbbb-273 IN AAAA 2001:db8::1:0111
-bbbb-274 IN AAAA 2001:db8::1:0112
-bbbb-275 IN AAAA 2001:db8::1:0113
-bbbb-276 IN AAAA 2001:db8::1:0114
-bbbb-277 IN AAAA 2001:db8::1:0115
-bbbb-278 IN AAAA 2001:db8::1:0116
-bbbb-279 IN AAAA 2001:db8::1:0117
-bbbb-280 IN AAAA 2001:db8::1:0118
-bbbb-281 IN AAAA 2001:db8::1:0119
-bbbb-282 IN AAAA 2001:db8::1:011a
-bbbb-283 IN AAAA 2001:db8::1:011b
-bbbb-284 IN AAAA 2001:db8::1:011c
-bbbb-285 IN AAAA 2001:db8::1:011d
-bbbb-286 IN AAAA 2001:db8::1:011e
-bbbb-287 IN AAAA 2001:db8::1:011f
-bbbb-288 IN AAAA 2001:db8::1:0120
-bbbb-289 IN AAAA 2001:db8::1:0121
-bbbb-290 IN AAAA 2001:db8::1:0122
-bbbb-291 IN AAAA 2001:db8::1:0123
-bbbb-292 IN AAAA 2001:db8::1:0124
-bbbb-293 IN AAAA 2001:db8::1:0125
-bbbb-294 IN AAAA 2001:db8::1:0126
-bbbb-295 IN AAAA 2001:db8::1:0127
-bbbb-296 IN AAAA 2001:db8::1:0128
-bbbb-297 IN AAAA 2001:db8::1:0129
-bbbb-298 IN AAAA 2001:db8::1:012a
-bbbb-299 IN AAAA 2001:db8::1:012b
-bbbb-300 IN AAAA 2001:db8::1:012c
-bbbb-301 IN AAAA 2001:db8::1:012d
-bbbb-302 IN AAAA 2001:db8::1:012e
-bbbb-303 IN AAAA 2001:db8::1:012f
-bbbb-304 IN AAAA 2001:db8::1:0130
-bbbb-305 IN AAAA 2001:db8::1:0131
-bbbb-306 IN AAAA 2001:db8::1:0132
-bbbb-307 IN AAAA 2001:db8::1:0133
-bbbb-308 IN AAAA 2001:db8::1:0134
-bbbb-309 IN AAAA 2001:db8::1:0135
-bbbb-310 IN AAAA 2001:db8::1:0136
-bbbb-311 IN AAAA 2001:db8::1:0137
-bbbb-312 IN AAAA 2001:db8::1:0138
-bbbb-313 IN AAAA 2001:db8::1:0139
-bbbb-314 IN AAAA 2001:db8::1:013a
-bbbb-315 IN AAAA 2001:db8::1:013b
-bbbb-316 IN AAAA 2001:db8::1:013c
-bbbb-317 IN AAAA 2001:db8::1:013d
-bbbb-318 IN AAAA 2001:db8::1:013e
-bbbb-319 IN AAAA 2001:db8::1:013f
-bbbb-320 IN AAAA 2001:db8::1:0140
-bbbb-321 IN AAAA 2001:db8::1:0141
-bbbb-322 IN AAAA 2001:db8::1:0142
-bbbb-323 IN AAAA 2001:db8::1:0143
-bbbb-324 IN AAAA 2001:db8::1:0144
-bbbb-325 IN AAAA 2001:db8::1:0145
-bbbb-326 IN AAAA 2001:db8::1:0146
-bbbb-327 IN AAAA 2001:db8::1:0147
-bbbb-328 IN AAAA 2001:db8::1:0148
-bbbb-329 IN AAAA 2001:db8::1:0149
-bbbb-330 IN AAAA 2001:db8::1:014a
-bbbb-331 IN AAAA 2001:db8::1:014b
-bbbb-332 IN AAAA 2001:db8::1:014c
-bbbb-333 IN AAAA 2001:db8::1:014d
-bbbb-334 IN AAAA 2001:db8::1:014e
-bbbb-335 IN AAAA 2001:db8::1:014f
-bbbb-336 IN AAAA 2001:db8::1:0150
-bbbb-337 IN AAAA 2001:db8::1:0151
-bbbb-338 IN AAAA 2001:db8::1:0152
-bbbb-339 IN AAAA 2001:db8::1:0153
-bbbb-340 IN AAAA 2001:db8::1:0154
-bbbb-341 IN AAAA 2001:db8::1:0155
-bbbb-342 IN AAAA 2001:db8::1:0156
-bbbb-343 IN AAAA 2001:db8::1:0157
-bbbb-344 IN AAAA 2001:db8::1:0158
-bbbb-345 IN AAAA 2001:db8::1:0159
-bbbb-346 IN AAAA 2001:db8::1:015a
-bbbb-347 IN AAAA 2001:db8::1:015b
-bbbb-348 IN AAAA 2001:db8::1:015c
-bbbb-349 IN AAAA 2001:db8::1:015d
-bbbb-350 IN AAAA 2001:db8::1:015e
-bbbb-351 IN AAAA 2001:db8::1:015f
-bbbb-352 IN AAAA 2001:db8::1:0160
-bbbb-353 IN AAAA 2001:db8::1:0161
-bbbb-354 IN AAAA 2001:db8::1:0162
-bbbb-355 IN AAAA 2001:db8::1:0163
-bbbb-356 IN AAAA 2001:db8::1:0164
-bbbb-357 IN AAAA 2001:db8::1:0165
-bbbb-358 IN AAAA 2001:db8::1:0166
-bbbb-359 IN AAAA 2001:db8::1:0167
-bbbb-360 IN AAAA 2001:db8::1:0168
-bbbb-361 IN AAAA 2001:db8::1:0169
-bbbb-362 IN AAAA 2001:db8::1:016a
-bbbb-363 IN AAAA 2001:db8::1:016b
-bbbb-364 IN AAAA 2001:db8::1:016c
-bbbb-365 IN AAAA 2001:db8::1:016d
-bbbb-366 IN AAAA 2001:db8::1:016e
-bbbb-367 IN AAAA 2001:db8::1:016f
-bbbb-368 IN AAAA 2001:db8::1:0170
-bbbb-369 IN AAAA 2001:db8::1:0171
-bbbb-370 IN AAAA 2001:db8::1:0172
-bbbb-371 IN AAAA 2001:db8::1:0173
-bbbb-372 IN AAAA 2001:db8::1:0174
-bbbb-373 IN AAAA 2001:db8::1:0175
-bbbb-374 IN AAAA 2001:db8::1:0176
-bbbb-375 IN AAAA 2001:db8::1:0177
-bbbb-376 IN AAAA 2001:db8::1:0178
-bbbb-377 IN AAAA 2001:db8::1:0179
-bbbb-378 IN AAAA 2001:db8::1:017a
-bbbb-379 IN AAAA 2001:db8::1:017b
-bbbb-380 IN AAAA 2001:db8::1:017c
-bbbb-381 IN AAAA 2001:db8::1:017d
-bbbb-382 IN AAAA 2001:db8::1:017e
-bbbb-383 IN AAAA 2001:db8::1:017f
-bbbb-384 IN AAAA 2001:db8::1:0180
-bbbb-385 IN AAAA 2001:db8::1:0181
-bbbb-386 IN AAAA 2001:db8::1:0182
-bbbb-387 IN AAAA 2001:db8::1:0183
-bbbb-388 IN AAAA 2001:db8::1:0184
-bbbb-389 IN AAAA 2001:db8::1:0185
-bbbb-390 IN AAAA 2001:db8::1:0186
-bbbb-391 IN AAAA 2001:db8::1:0187
-bbbb-392 IN AAAA 2001:db8::1:0188
-bbbb-393 IN AAAA 2001:db8::1:0189
-bbbb-394 IN AAAA 2001:db8::1:018a
-bbbb-395 IN AAAA 2001:db8::1:018b
-bbbb-396 IN AAAA 2001:db8::1:018c
-bbbb-397 IN AAAA 2001:db8::1:018d
-bbbb-398 IN AAAA 2001:db8::1:018e
-bbbb-399 IN AAAA 2001:db8::1:018f
-bbbb-400 IN AAAA 2001:db8::1:0190
-bbbb-401 IN AAAA 2001:db8::1:0191
-bbbb-402 IN AAAA 2001:db8::1:0192
-bbbb-403 IN AAAA 2001:db8::1:0193
-bbbb-404 IN AAAA 2001:db8::1:0194
-bbbb-405 IN AAAA 2001:db8::1:0195
-bbbb-406 IN AAAA 2001:db8::1:0196
-bbbb-407 IN AAAA 2001:db8::1:0197
-bbbb-408 IN AAAA 2001:db8::1:0198
-bbbb-409 IN AAAA 2001:db8::1:0199
-bbbb-410 IN AAAA 2001:db8::1:019a
-bbbb-411 IN AAAA 2001:db8::1:019b
-bbbb-412 IN AAAA 2001:db8::1:019c
-bbbb-413 IN AAAA 2001:db8::1:019d
-bbbb-414 IN AAAA 2001:db8::1:019e
-bbbb-415 IN AAAA 2001:db8::1:019f
-bbbb-416 IN AAAA 2001:db8::1:01a0
-bbbb-417 IN AAAA 2001:db8::1:01a1
-bbbb-418 IN AAAA 2001:db8::1:01a2
-bbbb-419 IN AAAA 2001:db8::1:01a3
-bbbb-420 IN AAAA 2001:db8::1:01a4
-bbbb-421 IN AAAA 2001:db8::1:01a5
-bbbb-422 IN AAAA 2001:db8::1:01a6
-bbbb-423 IN AAAA 2001:db8::1:01a7
-bbbb-424 IN AAAA 2001:db8::1:01a8
-bbbb-425 IN AAAA 2001:db8::1:01a9
-bbbb-426 IN AAAA 2001:db8::1:01aa
-bbbb-427 IN AAAA 2001:db8::1:01ab
-bbbb-428 IN AAAA 2001:db8::1:01ac
-bbbb-429 IN AAAA 2001:db8::1:01ad
-bbbb-430 IN AAAA 2001:db8::1:01ae
-bbbb-431 IN AAAA 2001:db8::1:01af
-bbbb-432 IN AAAA 2001:db8::1:01b0
-bbbb-433 IN AAAA 2001:db8::1:01b1
-bbbb-434 IN AAAA 2001:db8::1:01b2
-bbbb-435 IN AAAA 2001:db8::1:01b3
-bbbb-436 IN AAAA 2001:db8::1:01b4
-bbbb-437 IN AAAA 2001:db8::1:01b5
-bbbb-438 IN AAAA 2001:db8::1:01b6
-bbbb-439 IN AAAA 2001:db8::1:01b7
-bbbb-440 IN AAAA 2001:db8::1:01b8
-bbbb-441 IN AAAA 2001:db8::1:01b9
-bbbb-442 IN AAAA 2001:db8::1:01ba
-bbbb-443 IN AAAA 2001:db8::1:01bb
-bbbb-444 IN AAAA 2001:db8::1:01bc
-bbbb-445 IN AAAA 2001:db8::1:01bd
-bbbb-446 IN AAAA 2001:db8::1:01be
-bbbb-447 IN AAAA 2001:db8::1:01bf
-bbbb-448 IN AAAA 2001:db8::1:01c0
-bbbb-449 IN AAAA 2001:db8::1:01c1
-bbbb-450 IN AAAA 2001:db8::1:01c2
-bbbb-451 IN AAAA 2001:db8::1:01c3
-bbbb-452 IN AAAA 2001:db8::1:01c4
-bbbb-453 IN AAAA 2001:db8::1:01c5
-bbbb-454 IN AAAA 2001:db8::1:01c6
-bbbb-455 IN AAAA 2001:db8::1:01c7
-bbbb-456 IN AAAA 2001:db8::1:01c8
-bbbb-457 IN AAAA 2001:db8::1:01c9
-bbbb-458 IN AAAA 2001:db8::1:01ca
-bbbb-459 IN AAAA 2001:db8::1:01cb
-bbbb-460 IN AAAA 2001:db8::1:01cc
-bbbb-461 IN AAAA 2001:db8::1:01cd
-bbbb-462 IN AAAA 2001:db8::1:01ce
-bbbb-463 IN AAAA 2001:db8::1:01cf
-bbbb-464 IN AAAA 2001:db8::1:01d0
-bbbb-465 IN AAAA 2001:db8::1:01d1
-bbbb-466 IN AAAA 2001:db8::1:01d2
-bbbb-467 IN AAAA 2001:db8::1:01d3
-bbbb-468 IN AAAA 2001:db8::1:01d4
-bbbb-469 IN AAAA 2001:db8::1:01d5
-bbbb-470 IN AAAA 2001:db8::1:01d6
-bbbb-471 IN AAAA 2001:db8::1:01d7
-bbbb-472 IN AAAA 2001:db8::1:01d8
-bbbb-473 IN AAAA 2001:db8::1:01d9
-bbbb-474 IN AAAA 2001:db8::1:01da
-bbbb-475 IN AAAA 2001:db8::1:01db
-bbbb-476 IN AAAA 2001:db8::1:01dc
-bbbb-477 IN AAAA 2001:db8::1:01dd
-bbbb-478 IN AAAA 2001:db8::1:01de
-bbbb-479 IN AAAA 2001:db8::1:01df
-bbbb-480 IN AAAA 2001:db8::1:01e0
-bbbb-481 IN AAAA 2001:db8::1:01e1
-bbbb-482 IN AAAA 2001:db8::1:01e2
-bbbb-483 IN AAAA 2001:db8::1:01e3
-bbbb-484 IN AAAA 2001:db8::1:01e4
-bbbb-485 IN AAAA 2001:db8::1:01e5
-bbbb-486 IN AAAA 2001:db8::1:01e6
-bbbb-487 IN AAAA 2001:db8::1:01e7
-bbbb-488 IN AAAA 2001:db8::1:01e8
-bbbb-489 IN AAAA 2001:db8::1:01e9
-bbbb-490 IN AAAA 2001:db8::1:01ea
-bbbb-491 IN AAAA 2001:db8::1:01eb
-bbbb-492 IN AAAA 2001:db8::1:01ec
-bbbb-493 IN AAAA 2001:db8::1:01ed
-bbbb-494 IN AAAA 2001:db8::1:01ee
-bbbb-495 IN AAAA 2001:db8::1:01ef
-bbbb-496 IN AAAA 2001:db8::1:01f0
-bbbb-497 IN AAAA 2001:db8::1:01f1
-bbbb-498 IN AAAA 2001:db8::1:01f2
-bbbb-499 IN AAAA 2001:db8::1:01f3
-bbbb-500 IN AAAA 2001:db8::1:01f4
-bbbb-501 IN AAAA 2001:db8::1:01f5
-bbbb-502 IN AAAA 2001:db8::1:01f6
-bbbb-503 IN AAAA 2001:db8::1:01f7
-bbbb-504 IN AAAA 2001:db8::1:01f8
-bbbb-505 IN AAAA 2001:db8::1:01f9
-bbbb-506 IN AAAA 2001:db8::1:01fa
-bbbb-507 IN AAAA 2001:db8::1:01fb
-bbbb-508 IN AAAA 2001:db8::1:01fc
-bbbb-509 IN AAAA 2001:db8::1:01fd
-bbbb-510 IN AAAA 2001:db8::1:01fe
-bbbb-511 IN AAAA 2001:db8::1:01ff
-
-cccc-000 IN AAAA 2001:db8::2:0000
-cccc-001 IN AAAA 2001:db8::2:0001
-cccc-002 IN AAAA 2001:db8::2:0002
-cccc-003 IN AAAA 2001:db8::2:0003
-cccc-004 IN AAAA 2001:db8::2:0004
-cccc-005 IN AAAA 2001:db8::2:0005
-cccc-006 IN AAAA 2001:db8::2:0006
-cccc-007 IN AAAA 2001:db8::2:0007
-cccc-008 IN AAAA 2001:db8::2:0008
-cccc-009 IN AAAA 2001:db8::2:0009
-cccc-010 IN AAAA 2001:db8::2:000a
-cccc-011 IN AAAA 2001:db8::2:000b
-cccc-012 IN AAAA 2001:db8::2:000c
-cccc-013 IN AAAA 2001:db8::2:000d
-cccc-014 IN AAAA 2001:db8::2:000e
-cccc-015 IN AAAA 2001:db8::2:000f
-cccc-016 IN AAAA 2001:db8::2:0010
-cccc-017 IN AAAA 2001:db8::2:0011
-cccc-018 IN AAAA 2001:db8::2:0012
-cccc-019 IN AAAA 2001:db8::2:0013
-cccc-020 IN AAAA 2001:db8::2:0014
-cccc-021 IN AAAA 2001:db8::2:0015
-cccc-022 IN AAAA 2001:db8::2:0016
-cccc-023 IN AAAA 2001:db8::2:0017
-cccc-024 IN AAAA 2001:db8::2:0018
-cccc-025 IN AAAA 2001:db8::2:0019
-cccc-026 IN AAAA 2001:db8::2:001a
-cccc-027 IN AAAA 2001:db8::2:001b
-cccc-028 IN AAAA 2001:db8::2:001c
-cccc-029 IN AAAA 2001:db8::2:001d
-cccc-030 IN AAAA 2001:db8::2:001e
-cccc-031 IN AAAA 2001:db8::2:001f
-cccc-032 IN AAAA 2001:db8::2:0020
-cccc-033 IN AAAA 2001:db8::2:0021
-cccc-034 IN AAAA 2001:db8::2:0022
-cccc-035 IN AAAA 2001:db8::2:0023
-cccc-036 IN AAAA 2001:db8::2:0024
-cccc-037 IN AAAA 2001:db8::2:0025
-cccc-038 IN AAAA 2001:db8::2:0026
-cccc-039 IN AAAA 2001:db8::2:0027
-cccc-040 IN AAAA 2001:db8::2:0028
-cccc-041 IN AAAA 2001:db8::2:0029
-cccc-042 IN AAAA 2001:db8::2:002a
-cccc-043 IN AAAA 2001:db8::2:002b
-cccc-044 IN AAAA 2001:db8::2:002c
-cccc-045 IN AAAA 2001:db8::2:002d
-cccc-046 IN AAAA 2001:db8::2:002e
-cccc-047 IN AAAA 2001:db8::2:002f
-cccc-048 IN AAAA 2001:db8::2:0030
-cccc-049 IN AAAA 2001:db8::2:0031
-cccc-050 IN AAAA 2001:db8::2:0032
-cccc-051 IN AAAA 2001:db8::2:0033
-cccc-052 IN AAAA 2001:db8::2:0034
-cccc-053 IN AAAA 2001:db8::2:0035
-cccc-054 IN AAAA 2001:db8::2:0036
-cccc-055 IN AAAA 2001:db8::2:0037
-cccc-056 IN AAAA 2001:db8::2:0038
-cccc-057 IN AAAA 2001:db8::2:0039
-cccc-058 IN AAAA 2001:db8::2:003a
-cccc-059 IN AAAA 2001:db8::2:003b
-cccc-060 IN AAAA 2001:db8::2:003c
-cccc-061 IN AAAA 2001:db8::2:003d
-cccc-062 IN AAAA 2001:db8::2:003e
-cccc-063 IN AAAA 2001:db8::2:003f
-cccc-064 IN AAAA 2001:db8::2:0040
-cccc-065 IN AAAA 2001:db8::2:0041
-cccc-066 IN AAAA 2001:db8::2:0042
-cccc-067 IN AAAA 2001:db8::2:0043
-cccc-068 IN AAAA 2001:db8::2:0044
-cccc-069 IN AAAA 2001:db8::2:0045
-cccc-070 IN AAAA 2001:db8::2:0046
-cccc-071 IN AAAA 2001:db8::2:0047
-cccc-072 IN AAAA 2001:db8::2:0048
-cccc-073 IN AAAA 2001:db8::2:0049
-cccc-074 IN AAAA 2001:db8::2:004a
-cccc-075 IN AAAA 2001:db8::2:004b
-cccc-076 IN AAAA 2001:db8::2:004c
-cccc-077 IN AAAA 2001:db8::2:004d
-cccc-078 IN AAAA 2001:db8::2:004e
-cccc-079 IN AAAA 2001:db8::2:004f
-cccc-080 IN AAAA 2001:db8::2:0050
-cccc-081 IN AAAA 2001:db8::2:0051
-cccc-082 IN AAAA 2001:db8::2:0052
-cccc-083 IN AAAA 2001:db8::2:0053
-cccc-084 IN AAAA 2001:db8::2:0054
-cccc-085 IN AAAA 2001:db8::2:0055
-cccc-086 IN AAAA 2001:db8::2:0056
-cccc-087 IN AAAA 2001:db8::2:0057
-cccc-088 IN AAAA 2001:db8::2:0058
-cccc-089 IN AAAA 2001:db8::2:0059
-cccc-090 IN AAAA 2001:db8::2:005a
-cccc-091 IN AAAA 2001:db8::2:005b
-cccc-092 IN AAAA 2001:db8::2:005c
-cccc-093 IN AAAA 2001:db8::2:005d
-cccc-094 IN AAAA 2001:db8::2:005e
-cccc-095 IN AAAA 2001:db8::2:005f
-cccc-096 IN AAAA 2001:db8::2:0060
-cccc-097 IN AAAA 2001:db8::2:0061
-cccc-098 IN AAAA 2001:db8::2:0062
-cccc-099 IN AAAA 2001:db8::2:0063
-cccc-100 IN AAAA 2001:db8::2:0064
-cccc-101 IN AAAA 2001:db8::2:0065
-cccc-102 IN AAAA 2001:db8::2:0066
-cccc-103 IN AAAA 2001:db8::2:0067
-cccc-104 IN AAAA 2001:db8::2:0068
-cccc-105 IN AAAA 2001:db8::2:0069
-cccc-106 IN AAAA 2001:db8::2:006a
-cccc-107 IN AAAA 2001:db8::2:006b
-cccc-108 IN AAAA 2001:db8::2:006c
-cccc-109 IN AAAA 2001:db8::2:006d
-cccc-110 IN AAAA 2001:db8::2:006e
-cccc-111 IN AAAA 2001:db8::2:006f
-cccc-112 IN AAAA 2001:db8::2:0070
-cccc-113 IN AAAA 2001:db8::2:0071
-cccc-114 IN AAAA 2001:db8::2:0072
-cccc-115 IN AAAA 2001:db8::2:0073
-cccc-116 IN AAAA 2001:db8::2:0074
-cccc-117 IN AAAA 2001:db8::2:0075
-cccc-118 IN AAAA 2001:db8::2:0076
-cccc-119 IN AAAA 2001:db8::2:0077
-cccc-120 IN AAAA 2001:db8::2:0078
-cccc-121 IN AAAA 2001:db8::2:0079
-cccc-122 IN AAAA 2001:db8::2:007a
-cccc-123 IN AAAA 2001:db8::2:007b
-cccc-124 IN AAAA 2001:db8::2:007c
-cccc-125 IN AAAA 2001:db8::2:007d
-cccc-126 IN AAAA 2001:db8::2:007e
-cccc-127 IN AAAA 2001:db8::2:007f
-cccc-128 IN AAAA 2001:db8::2:0080
-cccc-129 IN AAAA 2001:db8::2:0081
-cccc-130 IN AAAA 2001:db8::2:0082
-cccc-131 IN AAAA 2001:db8::2:0083
-cccc-132 IN AAAA 2001:db8::2:0084
-cccc-133 IN AAAA 2001:db8::2:0085
-cccc-134 IN AAAA 2001:db8::2:0086
-cccc-135 IN AAAA 2001:db8::2:0087
-cccc-136 IN AAAA 2001:db8::2:0088
-cccc-137 IN AAAA 2001:db8::2:0089
-cccc-138 IN AAAA 2001:db8::2:008a
-cccc-139 IN AAAA 2001:db8::2:008b
-cccc-140 IN AAAA 2001:db8::2:008c
-cccc-141 IN AAAA 2001:db8::2:008d
-cccc-142 IN AAAA 2001:db8::2:008e
-cccc-143 IN AAAA 2001:db8::2:008f
-cccc-144 IN AAAA 2001:db8::2:0090
-cccc-145 IN AAAA 2001:db8::2:0091
-cccc-146 IN AAAA 2001:db8::2:0092
-cccc-147 IN AAAA 2001:db8::2:0093
-cccc-148 IN AAAA 2001:db8::2:0094
-cccc-149 IN AAAA 2001:db8::2:0095
-cccc-150 IN AAAA 2001:db8::2:0096
-cccc-151 IN AAAA 2001:db8::2:0097
-cccc-152 IN AAAA 2001:db8::2:0098
-cccc-153 IN AAAA 2001:db8::2:0099
-cccc-154 IN AAAA 2001:db8::2:009a
-cccc-155 IN AAAA 2001:db8::2:009b
-cccc-156 IN AAAA 2001:db8::2:009c
-cccc-157 IN AAAA 2001:db8::2:009d
-cccc-158 IN AAAA 2001:db8::2:009e
-cccc-159 IN AAAA 2001:db8::2:009f
-cccc-160 IN AAAA 2001:db8::2:00a0
-cccc-161 IN AAAA 2001:db8::2:00a1
-cccc-162 IN AAAA 2001:db8::2:00a2
-cccc-163 IN AAAA 2001:db8::2:00a3
-cccc-164 IN AAAA 2001:db8::2:00a4
-cccc-165 IN AAAA 2001:db8::2:00a5
-cccc-166 IN AAAA 2001:db8::2:00a6
-cccc-167 IN AAAA 2001:db8::2:00a7
-cccc-168 IN AAAA 2001:db8::2:00a8
-cccc-169 IN AAAA 2001:db8::2:00a9
-cccc-170 IN AAAA 2001:db8::2:00aa
-cccc-171 IN AAAA 2001:db8::2:00ab
-cccc-172 IN AAAA 2001:db8::2:00ac
-cccc-173 IN AAAA 2001:db8::2:00ad
-cccc-174 IN AAAA 2001:db8::2:00ae
-cccc-175 IN AAAA 2001:db8::2:00af
-cccc-176 IN AAAA 2001:db8::2:00b0
-cccc-177 IN AAAA 2001:db8::2:00b1
-cccc-178 IN AAAA 2001:db8::2:00b2
-cccc-179 IN AAAA 2001:db8::2:00b3
-cccc-180 IN AAAA 2001:db8::2:00b4
-cccc-181 IN AAAA 2001:db8::2:00b5
-cccc-182 IN AAAA 2001:db8::2:00b6
-cccc-183 IN AAAA 2001:db8::2:00b7
-cccc-184 IN AAAA 2001:db8::2:00b8
-cccc-185 IN AAAA 2001:db8::2:00b9
-cccc-186 IN AAAA 2001:db8::2:00ba
-cccc-187 IN AAAA 2001:db8::2:00bb
-cccc-188 IN AAAA 2001:db8::2:00bc
-cccc-189 IN AAAA 2001:db8::2:00bd
-cccc-190 IN AAAA 2001:db8::2:00be
-cccc-191 IN AAAA 2001:db8::2:00bf
-cccc-192 IN AAAA 2001:db8::2:00c0
-cccc-193 IN AAAA 2001:db8::2:00c1
-cccc-194 IN AAAA 2001:db8::2:00c2
-cccc-195 IN AAAA 2001:db8::2:00c3
-cccc-196 IN AAAA 2001:db8::2:00c4
-cccc-197 IN AAAA 2001:db8::2:00c5
-cccc-198 IN AAAA 2001:db8::2:00c6
-cccc-199 IN AAAA 2001:db8::2:00c7
-cccc-200 IN AAAA 2001:db8::2:00c8
-cccc-201 IN AAAA 2001:db8::2:00c9
-cccc-202 IN AAAA 2001:db8::2:00ca
-cccc-203 IN AAAA 2001:db8::2:00cb
-cccc-204 IN AAAA 2001:db8::2:00cc
-cccc-205 IN AAAA 2001:db8::2:00cd
-cccc-206 IN AAAA 2001:db8::2:00ce
-cccc-207 IN AAAA 2001:db8::2:00cf
-cccc-208 IN AAAA 2001:db8::2:00d0
-cccc-209 IN AAAA 2001:db8::2:00d1
-cccc-210 IN AAAA 2001:db8::2:00d2
-cccc-211 IN AAAA 2001:db8::2:00d3
-cccc-212 IN AAAA 2001:db8::2:00d4
-cccc-213 IN AAAA 2001:db8::2:00d5
-cccc-214 IN AAAA 2001:db8::2:00d6
-cccc-215 IN AAAA 2001:db8::2:00d7
-cccc-216 IN AAAA 2001:db8::2:00d8
-cccc-217 IN AAAA 2001:db8::2:00d9
-cccc-218 IN AAAA 2001:db8::2:00da
-cccc-219 IN AAAA 2001:db8::2:00db
-cccc-220 IN AAAA 2001:db8::2:00dc
-cccc-221 IN AAAA 2001:db8::2:00dd
-cccc-222 IN AAAA 2001:db8::2:00de
-cccc-223 IN AAAA 2001:db8::2:00df
-cccc-224 IN AAAA 2001:db8::2:00e0
-cccc-225 IN AAAA 2001:db8::2:00e1
-cccc-226 IN AAAA 2001:db8::2:00e2
-cccc-227 IN AAAA 2001:db8::2:00e3
-cccc-228 IN AAAA 2001:db8::2:00e4
-cccc-229 IN AAAA 2001:db8::2:00e5
-cccc-230 IN AAAA 2001:db8::2:00e6
-cccc-231 IN AAAA 2001:db8::2:00e7
-cccc-232 IN AAAA 2001:db8::2:00e8
-cccc-233 IN AAAA 2001:db8::2:00e9
-cccc-234 IN AAAA 2001:db8::2:00ea
-cccc-235 IN AAAA 2001:db8::2:00eb
-cccc-236 IN AAAA 2001:db8::2:00ec
-cccc-237 IN AAAA 2001:db8::2:00ed
-cccc-238 IN AAAA 2001:db8::2:00ee
-cccc-239 IN AAAA 2001:db8::2:00ef
-cccc-240 IN AAAA 2001:db8::2:00f0
-cccc-241 IN AAAA 2001:db8::2:00f1
-cccc-242 IN AAAA 2001:db8::2:00f2
-cccc-243 IN AAAA 2001:db8::2:00f3
-cccc-244 IN AAAA 2001:db8::2:00f4
-cccc-245 IN AAAA 2001:db8::2:00f5
-cccc-246 IN AAAA 2001:db8::2:00f6
-cccc-247 IN AAAA 2001:db8::2:00f7
-cccc-248 IN AAAA 2001:db8::2:00f8
-cccc-249 IN AAAA 2001:db8::2:00f9
-cccc-250 IN AAAA 2001:db8::2:00fa
-cccc-251 IN AAAA 2001:db8::2:00fb
-cccc-252 IN AAAA 2001:db8::2:00fc
-cccc-253 IN AAAA 2001:db8::2:00fd
-cccc-254 IN AAAA 2001:db8::2:00fe
-cccc-255 IN AAAA 2001:db8::2:00ff
-cccc-256 IN AAAA 2001:db8::2:0100
-cccc-257 IN AAAA 2001:db8::2:0101
-cccc-258 IN AAAA 2001:db8::2:0102
-cccc-259 IN AAAA 2001:db8::2:0103
-cccc-260 IN AAAA 2001:db8::2:0104
-cccc-261 IN AAAA 2001:db8::2:0105
-cccc-262 IN AAAA 2001:db8::2:0106
-cccc-263 IN AAAA 2001:db8::2:0107
-cccc-264 IN AAAA 2001:db8::2:0108
-cccc-265 IN AAAA 2001:db8::2:0109
-cccc-266 IN AAAA 2001:db8::2:010a
-cccc-267 IN AAAA 2001:db8::2:010b
-cccc-268 IN AAAA 2001:db8::2:010c
-cccc-269 IN AAAA 2001:db8::2:010d
-cccc-270 IN AAAA 2001:db8::2:010e
-cccc-271 IN AAAA 2001:db8::2:010f
-cccc-272 IN AAAA 2001:db8::2:0110
-cccc-273 IN AAAA 2001:db8::2:0111
-cccc-274 IN AAAA 2001:db8::2:0112
-cccc-275 IN AAAA 2001:db8::2:0113
-cccc-276 IN AAAA 2001:db8::2:0114
-cccc-277 IN AAAA 2001:db8::2:0115
-cccc-278 IN AAAA 2001:db8::2:0116
-cccc-279 IN AAAA 2001:db8::2:0117
-cccc-280 IN AAAA 2001:db8::2:0118
-cccc-281 IN AAAA 2001:db8::2:0119
-cccc-282 IN AAAA 2001:db8::2:011a
-cccc-283 IN AAAA 2001:db8::2:011b
-cccc-284 IN AAAA 2001:db8::2:011c
-cccc-285 IN AAAA 2001:db8::2:011d
-cccc-286 IN AAAA 2001:db8::2:011e
-cccc-287 IN AAAA 2001:db8::2:011f
-cccc-288 IN AAAA 2001:db8::2:0120
-cccc-289 IN AAAA 2001:db8::2:0121
-cccc-290 IN AAAA 2001:db8::2:0122
-cccc-291 IN AAAA 2001:db8::2:0123
-cccc-292 IN AAAA 2001:db8::2:0124
-cccc-293 IN AAAA 2001:db8::2:0125
-cccc-294 IN AAAA 2001:db8::2:0126
-cccc-295 IN AAAA 2001:db8::2:0127
-cccc-296 IN AAAA 2001:db8::2:0128
-cccc-297 IN AAAA 2001:db8::2:0129
-cccc-298 IN AAAA 2001:db8::2:012a
-cccc-299 IN AAAA 2001:db8::2:012b
-cccc-300 IN AAAA 2001:db8::2:012c
-cccc-301 IN AAAA 2001:db8::2:012d
-cccc-302 IN AAAA 2001:db8::2:012e
-cccc-303 IN AAAA 2001:db8::2:012f
-cccc-304 IN AAAA 2001:db8::2:0130
-cccc-305 IN AAAA 2001:db8::2:0131
-cccc-306 IN AAAA 2001:db8::2:0132
-cccc-307 IN AAAA 2001:db8::2:0133
-cccc-308 IN AAAA 2001:db8::2:0134
-cccc-309 IN AAAA 2001:db8::2:0135
-cccc-310 IN AAAA 2001:db8::2:0136
-cccc-311 IN AAAA 2001:db8::2:0137
-cccc-312 IN AAAA 2001:db8::2:0138
-cccc-313 IN AAAA 2001:db8::2:0139
-cccc-314 IN AAAA 2001:db8::2:013a
-cccc-315 IN AAAA 2001:db8::2:013b
-cccc-316 IN AAAA 2001:db8::2:013c
-cccc-317 IN AAAA 2001:db8::2:013d
-cccc-318 IN AAAA 2001:db8::2:013e
-cccc-319 IN AAAA 2001:db8::2:013f
-cccc-320 IN AAAA 2001:db8::2:0140
-cccc-321 IN AAAA 2001:db8::2:0141
-cccc-322 IN AAAA 2001:db8::2:0142
-cccc-323 IN AAAA 2001:db8::2:0143
-cccc-324 IN AAAA 2001:db8::2:0144
-cccc-325 IN AAAA 2001:db8::2:0145
-cccc-326 IN AAAA 2001:db8::2:0146
-cccc-327 IN AAAA 2001:db8::2:0147
-cccc-328 IN AAAA 2001:db8::2:0148
-cccc-329 IN AAAA 2001:db8::2:0149
-cccc-330 IN AAAA 2001:db8::2:014a
-cccc-331 IN AAAA 2001:db8::2:014b
-cccc-332 IN AAAA 2001:db8::2:014c
-cccc-333 IN AAAA 2001:db8::2:014d
-cccc-334 IN AAAA 2001:db8::2:014e
-cccc-335 IN AAAA 2001:db8::2:014f
-cccc-336 IN AAAA 2001:db8::2:0150
-cccc-337 IN AAAA 2001:db8::2:0151
-cccc-338 IN AAAA 2001:db8::2:0152
-cccc-339 IN AAAA 2001:db8::2:0153
-cccc-340 IN AAAA 2001:db8::2:0154
-cccc-341 IN AAAA 2001:db8::2:0155
-cccc-342 IN AAAA 2001:db8::2:0156
-cccc-343 IN AAAA 2001:db8::2:0157
-cccc-344 IN AAAA 2001:db8::2:0158
-cccc-345 IN AAAA 2001:db8::2:0159
-cccc-346 IN AAAA 2001:db8::2:015a
-cccc-347 IN AAAA 2001:db8::2:015b
-cccc-348 IN AAAA 2001:db8::2:015c
-cccc-349 IN AAAA 2001:db8::2:015d
-cccc-350 IN AAAA 2001:db8::2:015e
-cccc-351 IN AAAA 2001:db8::2:015f
-cccc-352 IN AAAA 2001:db8::2:0160
-cccc-353 IN AAAA 2001:db8::2:0161
-cccc-354 IN AAAA 2001:db8::2:0162
-cccc-355 IN AAAA 2001:db8::2:0163
-cccc-356 IN AAAA 2001:db8::2:0164
-cccc-357 IN AAAA 2001:db8::2:0165
-cccc-358 IN AAAA 2001:db8::2:0166
-cccc-359 IN AAAA 2001:db8::2:0167
-cccc-360 IN AAAA 2001:db8::2:0168
-cccc-361 IN AAAA 2001:db8::2:0169
-cccc-362 IN AAAA 2001:db8::2:016a
-cccc-363 IN AAAA 2001:db8::2:016b
-cccc-364 IN AAAA 2001:db8::2:016c
-cccc-365 IN AAAA 2001:db8::2:016d
-cccc-366 IN AAAA 2001:db8::2:016e
-cccc-367 IN AAAA 2001:db8::2:016f
-cccc-368 IN AAAA 2001:db8::2:0170
-cccc-369 IN AAAA 2001:db8::2:0171
-cccc-370 IN AAAA 2001:db8::2:0172
-cccc-371 IN AAAA 2001:db8::2:0173
-cccc-372 IN AAAA 2001:db8::2:0174
-cccc-373 IN AAAA 2001:db8::2:0175
-cccc-374 IN AAAA 2001:db8::2:0176
-cccc-375 IN AAAA 2001:db8::2:0177
-cccc-376 IN AAAA 2001:db8::2:0178
-cccc-377 IN AAAA 2001:db8::2:0179
-cccc-378 IN AAAA 2001:db8::2:017a
-cccc-379 IN AAAA 2001:db8::2:017b
-cccc-380 IN AAAA 2001:db8::2:017c
-cccc-381 IN AAAA 2001:db8::2:017d
-cccc-382 IN AAAA 2001:db8::2:017e
-cccc-383 IN AAAA 2001:db8::2:017f
-cccc-384 IN AAAA 2001:db8::2:0180
-cccc-385 IN AAAA 2001:db8::2:0181
-cccc-386 IN AAAA 2001:db8::2:0182
-cccc-387 IN AAAA 2001:db8::2:0183
-cccc-388 IN AAAA 2001:db8::2:0184
-cccc-389 IN AAAA 2001:db8::2:0185
-cccc-390 IN AAAA 2001:db8::2:0186
-cccc-391 IN AAAA 2001:db8::2:0187
-cccc-392 IN AAAA 2001:db8::2:0188
-cccc-393 IN AAAA 2001:db8::2:0189
-cccc-394 IN AAAA 2001:db8::2:018a
-cccc-395 IN AAAA 2001:db8::2:018b
-cccc-396 IN AAAA 2001:db8::2:018c
-cccc-397 IN AAAA 2001:db8::2:018d
-cccc-398 IN AAAA 2001:db8::2:018e
-cccc-399 IN AAAA 2001:db8::2:018f
-cccc-400 IN AAAA 2001:db8::2:0190
-cccc-401 IN AAAA 2001:db8::2:0191
-cccc-402 IN AAAA 2001:db8::2:0192
-cccc-403 IN AAAA 2001:db8::2:0193
-cccc-404 IN AAAA 2001:db8::2:0194
-cccc-405 IN AAAA 2001:db8::2:0195
-cccc-406 IN AAAA 2001:db8::2:0196
-cccc-407 IN AAAA 2001:db8::2:0197
-cccc-408 IN AAAA 2001:db8::2:0198
-cccc-409 IN AAAA 2001:db8::2:0199
-cccc-410 IN AAAA 2001:db8::2:019a
-cccc-411 IN AAAA 2001:db8::2:019b
-cccc-412 IN AAAA 2001:db8::2:019c
-cccc-413 IN AAAA 2001:db8::2:019d
-cccc-414 IN AAAA 2001:db8::2:019e
-cccc-415 IN AAAA 2001:db8::2:019f
-cccc-416 IN AAAA 2001:db8::2:01a0
-cccc-417 IN AAAA 2001:db8::2:01a1
-cccc-418 IN AAAA 2001:db8::2:01a2
-cccc-419 IN AAAA 2001:db8::2:01a3
-cccc-420 IN AAAA 2001:db8::2:01a4
-cccc-421 IN AAAA 2001:db8::2:01a5
-cccc-422 IN AAAA 2001:db8::2:01a6
-cccc-423 IN AAAA 2001:db8::2:01a7
-cccc-424 IN AAAA 2001:db8::2:01a8
-cccc-425 IN AAAA 2001:db8::2:01a9
-cccc-426 IN AAAA 2001:db8::2:01aa
-cccc-427 IN AAAA 2001:db8::2:01ab
-cccc-428 IN AAAA 2001:db8::2:01ac
-cccc-429 IN AAAA 2001:db8::2:01ad
-cccc-430 IN AAAA 2001:db8::2:01ae
-cccc-431 IN AAAA 2001:db8::2:01af
-cccc-432 IN AAAA 2001:db8::2:01b0
-cccc-433 IN AAAA 2001:db8::2:01b1
-cccc-434 IN AAAA 2001:db8::2:01b2
-cccc-435 IN AAAA 2001:db8::2:01b3
-cccc-436 IN AAAA 2001:db8::2:01b4
-cccc-437 IN AAAA 2001:db8::2:01b5
-cccc-438 IN AAAA 2001:db8::2:01b6
-cccc-439 IN AAAA 2001:db8::2:01b7
-cccc-440 IN AAAA 2001:db8::2:01b8
-cccc-441 IN AAAA 2001:db8::2:01b9
-cccc-442 IN AAAA 2001:db8::2:01ba
-cccc-443 IN AAAA 2001:db8::2:01bb
-cccc-444 IN AAAA 2001:db8::2:01bc
-cccc-445 IN AAAA 2001:db8::2:01bd
-cccc-446 IN AAAA 2001:db8::2:01be
-cccc-447 IN AAAA 2001:db8::2:01bf
-cccc-448 IN AAAA 2001:db8::2:01c0
-cccc-449 IN AAAA 2001:db8::2:01c1
-cccc-450 IN AAAA 2001:db8::2:01c2
-cccc-451 IN AAAA 2001:db8::2:01c3
-cccc-452 IN AAAA 2001:db8::2:01c4
-cccc-453 IN AAAA 2001:db8::2:01c5
-cccc-454 IN AAAA 2001:db8::2:01c6
-cccc-455 IN AAAA 2001:db8::2:01c7
-cccc-456 IN AAAA 2001:db8::2:01c8
-cccc-457 IN AAAA 2001:db8::2:01c9
-cccc-458 IN AAAA 2001:db8::2:01ca
-cccc-459 IN AAAA 2001:db8::2:01cb
-cccc-460 IN AAAA 2001:db8::2:01cc
-cccc-461 IN AAAA 2001:db8::2:01cd
-cccc-462 IN AAAA 2001:db8::2:01ce
-cccc-463 IN AAAA 2001:db8::2:01cf
-cccc-464 IN AAAA 2001:db8::2:01d0
-cccc-465 IN AAAA 2001:db8::2:01d1
-cccc-466 IN AAAA 2001:db8::2:01d2
-cccc-467 IN AAAA 2001:db8::2:01d3
-cccc-468 IN AAAA 2001:db8::2:01d4
-cccc-469 IN AAAA 2001:db8::2:01d5
-cccc-470 IN AAAA 2001:db8::2:01d6
-cccc-471 IN AAAA 2001:db8::2:01d7
-cccc-472 IN AAAA 2001:db8::2:01d8
-cccc-473 IN AAAA 2001:db8::2:01d9
-cccc-474 IN AAAA 2001:db8::2:01da
-cccc-475 IN AAAA 2001:db8::2:01db
-cccc-476 IN AAAA 2001:db8::2:01dc
-cccc-477 IN AAAA 2001:db8::2:01dd
-cccc-478 IN AAAA 2001:db8::2:01de
-cccc-479 IN AAAA 2001:db8::2:01df
-cccc-480 IN AAAA 2001:db8::2:01e0
-cccc-481 IN AAAA 2001:db8::2:01e1
-cccc-482 IN AAAA 2001:db8::2:01e2
-cccc-483 IN AAAA 2001:db8::2:01e3
-cccc-484 IN AAAA 2001:db8::2:01e4
-cccc-485 IN AAAA 2001:db8::2:01e5
-cccc-486 IN AAAA 2001:db8::2:01e6
-cccc-487 IN AAAA 2001:db8::2:01e7
-cccc-488 IN AAAA 2001:db8::2:01e8
-cccc-489 IN AAAA 2001:db8::2:01e9
-cccc-490 IN AAAA 2001:db8::2:01ea
-cccc-491 IN AAAA 2001:db8::2:01eb
-cccc-492 IN AAAA 2001:db8::2:01ec
-cccc-493 IN AAAA 2001:db8::2:01ed
-cccc-494 IN AAAA 2001:db8::2:01ee
-cccc-495 IN AAAA 2001:db8::2:01ef
-cccc-496 IN AAAA 2001:db8::2:01f0
-cccc-497 IN AAAA 2001:db8::2:01f1
-cccc-498 IN AAAA 2001:db8::2:01f2
-cccc-499 IN AAAA 2001:db8::2:01f3
-cccc-500 IN AAAA 2001:db8::2:01f4
-cccc-501 IN AAAA 2001:db8::2:01f5
-cccc-502 IN AAAA 2001:db8::2:01f6
-cccc-503 IN AAAA 2001:db8::2:01f7
-cccc-504 IN AAAA 2001:db8::2:01f8
-cccc-505 IN AAAA 2001:db8::2:01f9
-cccc-506 IN AAAA 2001:db8::2:01fa
-cccc-507 IN AAAA 2001:db8::2:01fb
-cccc-508 IN AAAA 2001:db8::2:01fc
-cccc-509 IN AAAA 2001:db8::2:01fd
-cccc-510 IN AAAA 2001:db8::2:01fe
-cccc-511 IN AAAA 2001:db8::2:01ff
diff --git a/tests/system/ixfr/db.example.n0.in b/tests/system/ixfr/db.example.n0.in
deleted file mode 100644
index 92fa0b0..0000000
--- a/tests/system/ixfr/db.example.n0.in
+++ /dev/null
@@ -1,29 +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.
-
-$ORIGIN example.
-$TTL 3600
-
-@ IN SOA ns1.example. hostmaster.example. 100 3600 900 7200 300
-
- IN NS ns1.example.
- IN NS ns2.example.
-
-ns1 IN A 192.0.2.1
-ns2 IN A 192.0.2.2
-
-a-1 IN A 192.0.2.101
-b-1 IN A 192.0.2.201
-
-$INCLUDE @abs_top_builddir@/tests/system/ixfr/db.example.common
diff --git a/tests/system/ixfr/db.example.n2.in b/tests/system/ixfr/db.example.n2.in
deleted file mode 100644
index 6a999af..0000000
--- a/tests/system/ixfr/db.example.n2.in
+++ /dev/null
@@ -1,28 +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.
-
-$ORIGIN example.
-$TTL 3600
-
-@ IN SOA ns1.example. hostmaster.example. 98 3600 900 7200 300
-
- IN NS ns1.example.
- IN NS ns2.example.
-
-ns1 IN A 192.0.2.1
-ns2 IN A 192.0.2.2
-
-a-1 IN A 192.0.2.101
-
-$INCLUDE @abs_top_builddir@/tests/system/ixfr/db.example.common
diff --git a/tests/system/ixfr/db.example.n2.refresh.in b/tests/system/ixfr/db.example.n2.refresh.in
deleted file mode 100644
index 2c59416..0000000
--- a/tests/system/ixfr/db.example.n2.refresh.in
+++ /dev/null
@@ -1,28 +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.
-
-$ORIGIN example.
-$TTL 3600
-
-@ IN SOA ns1.example. hostmaster.example. 98 30 2 7200 300
-
- IN NS ns1.example.
- IN NS ns2.example.
-
-ns1 IN A 192.0.2.1
-ns2 IN A 192.0.2.2
-
-a-1 IN A 192.0.2.101
-
-$INCLUDE @abs_top_builddir@/tests/system/ixfr/db.example.common
diff --git a/tests/system/ixfr/db.example.n4.in b/tests/system/ixfr/db.example.n4.in
deleted file mode 100644
index ae15a54..0000000
--- a/tests/system/ixfr/db.example.n4.in
+++ /dev/null
@@ -1,31 +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.
-
-$ORIGIN example.
-$TTL 3600
-
-@ IN SOA ns1.example. hostmaster.example. 96 3600 900 7200 300
-
- IN NS ns1.example.
- IN NS ns2.example.
-
-ns1 IN A 192.0.2.1
-ns2 IN A 192.0.2.2
-
-a-1 IN A 192.0.2.101
-a-2 IN A 192.0.2.102
-b-1 IN A 192.0.2.201
-b-2 IN A 192.0.2.202
-
-$INCLUDE @abs_top_builddir@/tests/system/ixfr/db.example.common
diff --git a/tests/system/ixfr/db.example.n6.in b/tests/system/ixfr/db.example.n6.in
deleted file mode 100644
index 33a82a0..0000000
--- a/tests/system/ixfr/db.example.n6.in
+++ /dev/null
@@ -1,29 +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.
-
-$ORIGIN example.
-$TTL 3600
-
-@ IN SOA ns1.example. hostmaster.example. 94 3600 900 7200 300
-
- IN NS ns1.example.
- IN NS ns2.example.
-
-ns1 IN A 192.0.2.1
-ns2 IN A 192.0.2.2
-
-a-1 IN A 192.0.2.101
-a-2 IN A 192.0.2.102
-b-1 IN A 192.0.2.201
-b-2 IN A 192.0.2.202
diff --git a/tests/system/ixfr/in-1/.gitignore b/tests/system/ixfr/in-1/.gitignore
deleted file mode 100644
index 87e08bf..0000000
--- a/tests/system/ixfr/in-1/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/setup.sh
diff --git a/tests/system/ixfr/in-1/clean.sh b/tests/system/ixfr/in-1/clean.sh
deleted file mode 120000
index 099bebd..0000000
--- a/tests/system/ixfr/in-1/clean.sh
+++ /dev/null
@@ -1 +0,0 @@
-../clean_ns.sh
\ No newline at end of file
diff --git a/tests/system/ixfr/in-1/ns1/README b/tests/system/ixfr/in-1/ns1/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-1/ns1/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-1/nsx2/README b/tests/system/ixfr/in-1/nsx2/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-1/nsx2/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-1/setup.sh.in b/tests/system/ixfr/in-1/setup.sh.in
deleted file mode 100644
index 4332930..0000000
--- a/tests/system/ixfr/in-1/setup.sh.in
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007, 2011 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2001, 2002 Internet Software Consortium.
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-. @abs_top_builddir@/tests/system/conf.sh
-
-# Clean up from last time
-sh clean.sh
-
-# Set up the initial version of the IXFR server - load the n-4 version of the
-# zone. The configuration file enables IXFR and disabled notifies.
-cp -f $IXFR_TOP/named_nonotify.conf ns1/named.conf
-cp -f $IXFR_TOP/db.example.n4 ns1/db.example
-
-# Set up the IXFR client - load the same version of the zone.
-cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n4
diff --git a/tests/system/ixfr/in-1/tests.sh b/tests/system/ixfr/in-1/tests.sh
deleted file mode 100644
index 2f49ddf..0000000
--- a/tests/system/ixfr/in-1/tests.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# \file
-# This script performs the first IXFR-IN test. A BIND 9 nameserver (the
-# "server") contains a version of the zone (version N) and two previous
-# versions, N-2 and N-4. A BIND 10 nameserver (the "client") is loaded with
-# version N-4 of the zone. A NOTIFY is sent to it, and it is expected that
-# it will send an IXFR to the server and update itself with the latest version
-# of the zone. (The changes are such that the update should be in the form of
-# a single UDP packet.)
-#
-# The pre-requisites for this test are the same as for the common tests, so
-# we can execute that directly.
-
-. ../common_tests.sh
-status=$?
-
-# TODO: Check the BIND 10 log, looking for the IXFR messages that indicate that
-# it has initiated an IXFR and that it received the update within a single
-# packet.
-
-echo "I:exit status: $status"
-exit $status
diff --git a/tests/system/ixfr/in-2/.gitignore b/tests/system/ixfr/in-2/.gitignore
deleted file mode 100644
index 87e08bf..0000000
--- a/tests/system/ixfr/in-2/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/setup.sh
diff --git a/tests/system/ixfr/in-2/clean.sh b/tests/system/ixfr/in-2/clean.sh
deleted file mode 120000
index 099bebd..0000000
--- a/tests/system/ixfr/in-2/clean.sh
+++ /dev/null
@@ -1 +0,0 @@
-../clean_ns.sh
\ No newline at end of file
diff --git a/tests/system/ixfr/in-2/ns1/.gitignore b/tests/system/ixfr/in-2/ns1/.gitignore
deleted file mode 100644
index 35ae1cb..0000000
--- a/tests/system/ixfr/in-2/ns1/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/named.run
diff --git a/tests/system/ixfr/in-2/ns1/README b/tests/system/ixfr/in-2/ns1/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-2/ns1/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-2/nsx2/.gitignore b/tests/system/ixfr/in-2/nsx2/.gitignore
deleted file mode 100644
index d31eb18..0000000
--- a/tests/system/ixfr/in-2/nsx2/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/bindctl.out
diff --git a/tests/system/ixfr/in-2/nsx2/README b/tests/system/ixfr/in-2/nsx2/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-2/nsx2/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-2/setup.sh.in b/tests/system/ixfr/in-2/setup.sh.in
deleted file mode 100644
index a210636..0000000
--- a/tests/system/ixfr/in-2/setup.sh.in
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-. @abs_top_builddir@/tests/system/conf.sh
-
-# Clean up from last time
-sh clean.sh
-
-# Set up the initial version of the IXFR server - load the n-6 version of the
-# zone. The configuration file enables IXFR and disables notifies.
-cp -f $IXFR_TOP/named_nonotify.conf ns1/named.conf
-cp -f $IXFR_TOP/db.example.n6 ns1/db.example
-
-# Set up the IXFR client - load an earlier version of the zone
-cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n6
diff --git a/tests/system/ixfr/in-2/tests.sh b/tests/system/ixfr/in-2/tests.sh
deleted file mode 100644
index 3050713..0000000
--- a/tests/system/ixfr/in-2/tests.sh
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# \file
-# This script performs the first IXFR-IN test. A BIND 9 nameserver (the
-# "server") contains a version of the zone (version N) and three previous
-# versions, N-2, N-4 and N-6. A BIND 10 nameserver (the "client") is loaded
-# with version N-6 of the zone. A NOTIFY is sent to it, and it is expected that
-# it will send an IXFR to the server and update itself with the latest version
-# of the zone. (The changes are such that the update will have to take place
-# over TCP.)
-
-. ../ixfr_init.sh
-
-# On entry, the IXFR server is at version N-6. The common tests assume that
-# it is an N-4, so update it.
-echo "I:$SERVER_NAME updating IXFR-server to suitable start version"
-update_server_zone $SERVER_NAME $SERVER_IP $IXFR_TOP/db.example.n4
-if [ $? -ne 0 ];
-then
- exit 1
-fi
-
-# The pre-requisites for this test are the same as for the common tests, so
-# we can execute that directly.
-. ../common_tests.sh
-if [ $? -ne 0 ];
-then
- exit 1
-fi
-
-# TEMPORARY: at the time of writing (October 2011) BIND 10 does not attempt
-# a UDP transfer first. Therefore just check for TCP transfer.
-
-# Check that the client initiated and completed an IXFR. Use a simple grep as
-# the syntax and capabilities of egrep may vary between systems.
-grep XFRIN_XFR_TRANSFER_STARTED nsx2/bind10.run | grep IXFR > /dev/null
-if [ $? -ne 0 ];
-then
- echo "R:$CLIENT_NAME FAIL no 'IXFR started' message in the BIND 10 log"
- exit 1
-fi
-
-grep XFRIN_IXFR_TRANSFER_SUCCESS nsx2/bind10.run | grep IXFR > /dev/null
-if [ $? -ne 0 ];
-then
- echo "R:$CLIENT_NAME FAIL no 'IXFR successful' message in the BIND 10 log"
- exit 1
-fi
-
-# Look in the named log file to see if a TCP IXFR was requested. Again use a
-# simple grep.
-grep "transfer of" ns1/named.run | grep "sending TCP message" > /dev/null
-if [ $? -ne 0 ];
-then
- echo "R:$SERVER_NAME FAIL no 'sending TCP' message in the BIND 9 log"
- exit 1
-fi
-
-grep "IXFR ended" ns1/named.run > /dev/null
-if [ $? -ne 0 ];
-then
- echo "R:$SERVER_NAME FAIL no 'IXFR ended' message in the BIND 9 log"
- exit 1
-fi
-
-echo "I:exit status: 0"
-exit 0
diff --git a/tests/system/ixfr/in-3/.gitignore b/tests/system/ixfr/in-3/.gitignore
deleted file mode 100644
index 87e08bf..0000000
--- a/tests/system/ixfr/in-3/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/setup.sh
diff --git a/tests/system/ixfr/in-3/clean.sh b/tests/system/ixfr/in-3/clean.sh
deleted file mode 120000
index 099bebd..0000000
--- a/tests/system/ixfr/in-3/clean.sh
+++ /dev/null
@@ -1 +0,0 @@
-../clean_ns.sh
\ No newline at end of file
diff --git a/tests/system/ixfr/in-3/ns1/README b/tests/system/ixfr/in-3/ns1/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-3/ns1/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-3/nsx2/README b/tests/system/ixfr/in-3/nsx2/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-3/nsx2/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-3/setup.sh.in b/tests/system/ixfr/in-3/setup.sh.in
deleted file mode 100644
index 2a08c58..0000000
--- a/tests/system/ixfr/in-3/setup.sh.in
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-. @abs_top_builddir@/tests/system/conf.sh
-# Clean up from last time
-
-sh clean.sh
-
-# Set up the initial version of the IXFR server - load the latest version of
-# the zone.
-cp -f $IXFR_TOP/named_noixfr.conf ns1/named.conf
-cp -f $IXFR_TOP/db.example.n0 ns1/db.example
-
-# Set up the IXFR client - load a previous version of the zone.
-cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n2
diff --git a/tests/system/ixfr/in-3/tests.sh b/tests/system/ixfr/in-3/tests.sh
deleted file mode 100644
index d47a221..0000000
--- a/tests/system/ixfr/in-3/tests.sh
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# \file
-# This script performs the third IXFR-IN test. A BIND 9 nameserver (the
-# "server") contains a version of the zone (version N) and has IXFRs disabled.
-# A BIND 10 nameserver (the "client") is loaded with version N-2 of the zone.
-# A NOTIFY is sent to it, and it is expected that it will send an IXFR to the
-# server; the server should not respond to the request, so the client should
-# then send an AXFR request and receive the latest copy of the zone.
-
-# TODO It seems bind9 still allows IXFR even when provide-ixfr on;
-
-. ../ixfr_init.sh
-status=$?
-
-# Store the SOA serial number of the BIND 10 client for later use.
-old_client_serial=`$DIG_SOA @$CLIENT_IP | $AWK '{print $3}'`
-echo "I:SOA serial of IXFR client $CLIENT_NAME is $old_client_serial"
-
-# If required, get the IXFR server to notify the IXFR client of the new zone.
-# Do this by allowing notifies and then triggering a re-notification of the
-# server.
-echo "I:notifying IXFR-client $CLIENT_NAME of presence of new version of zone"
-do_rndc $SERVER_NAME $SERVER_IP notify example
-status=`expr $status + $?`
-
-# Wait for the client to update itself.
-wait_for_update $CLIENT_NAME $CLIENT_IP $old_client_serial
-status=`expr $status + $?`
-
-# Has updated, get the latest serial of the client and server - they
-# should be the same.
-compare_soa $SERVER_NAME $SERVER_IP $CLIENT_NAME $CLIENT_IP
-status=`expr $status + $?`
-
-# Check the log there's the IXFR and fallback
-grep XFRIN_XFR_TRANSFER_STARTED nsx2/bind10.run | grep IXFR
-if [ $? -ne 0 ];
-then
- echo "R:$CLIENT_NAME FAIL no 'IXFR started' message in the BIND 10 log"
- exit 1
-fi
-
-grep XFRIN_XFR_TRANSFER_FALLBACK nsx2/bind10.run
-if [ $? -ne 0 ];
-then
- echo "R:$CLIENT_NAME FAIL no fallback message in BIND10 log"
- exit 1
-fi
-
-echo "I:exit status: $status"
-exit $status
diff --git a/tests/system/ixfr/in-4/.gitignore b/tests/system/ixfr/in-4/.gitignore
deleted file mode 100644
index 87e08bf..0000000
--- a/tests/system/ixfr/in-4/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/setup.sh
diff --git a/tests/system/ixfr/in-4/clean.sh b/tests/system/ixfr/in-4/clean.sh
deleted file mode 120000
index 099bebd..0000000
--- a/tests/system/ixfr/in-4/clean.sh
+++ /dev/null
@@ -1 +0,0 @@
-../clean_ns.sh
\ No newline at end of file
diff --git a/tests/system/ixfr/in-4/ns1/README b/tests/system/ixfr/in-4/ns1/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-4/ns1/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-4/nsx2/README b/tests/system/ixfr/in-4/nsx2/README
deleted file mode 100644
index aaa8a31..0000000
--- a/tests/system/ixfr/in-4/nsx2/README
+++ /dev/null
@@ -1,3 +0,0 @@
-This directory should be empty. A README file is placed here to ensure git
-notes the directory's presence. It can be removed if other files are placed
-here.
diff --git a/tests/system/ixfr/in-4/setup.sh.in b/tests/system/ixfr/in-4/setup.sh.in
deleted file mode 100644
index 1c2e9c8..0000000
--- a/tests/system/ixfr/in-4/setup.sh.in
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-. @abs_top_builddir@/tests/system/conf.sh
-# Clean up from last time
-
-sh clean.sh
-
-# Set up the initial version of the ixfr server - load the last-but-one version
-# of the zone.
-cp $IXFR_TOP/named_nonotify.conf ns1/named.conf
-cp -f $IXFR_TOP/db.example.n2.refresh ns1/db.example
-
-# Set up the IXFR client - load a previous version of the zone with a short
-# refresh time.
-cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n2.refresh
diff --git a/tests/system/ixfr/in-4/tests.sh b/tests/system/ixfr/in-4/tests.sh
deleted file mode 100644
index 3024253..0000000
--- a/tests/system/ixfr/in-4/tests.sh
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# \file
-# This script performs the fourth IXFR-IN test. A BIND 9 nameserver (the
-# "server") contains a version of the zone (version N) and has IXFRs enabled.
-# A BIND 10 nameserver (the "client") is loaded with version N-2 of the zone
-# and a small refresh time. After this expires, the IXFR client should send
-# an IXFR request to the IXFR server.
-
-. ../ixfr_init.sh
-status=$?
-
-# Ensure the server has the latest copy of the zone. The implicit assumption
-# here is that starting the two systems and reloading the IXFR server takes
-# less time than the SOA refresh time set in the "db.example.n2.refresh" zone
-# file.
-cp $IXFR_TOP/db.example.n0 ns1/db.example
-do_rndc $SERVER_NAME $SERVER_IP reload
-
-# Store the SOA serial number of the BIND 10 client for later use.
-old_client_serial=`$DIG_SOA @$CLIENT_IP | $AWK '{print $3}'`
-echo "I:SOA serial of IXFR client $CLIENT_NAME is $old_client_serial"
-
-# Wait for the client to update itself. 30 seconds has been given as the
-# refresh interface and 2 seconds as the retry interval. The wait_for_update
-# function will check for up to a minute looking for the new serial.
-wait_for_update $CLIENT_NAME $CLIENT_IP $old_client_serial
-status=`expr $status + $?`
-
-# Has updated, get the latest serial of the client and server - they
-# should be the same.
-compare_soa $SERVER_NAME $SERVER_IP $CLIENT_NAME $CLIENT_IP
-status=`expr $status + $?`
-
-# TODO: Check the BIND 10 log, looking for the IXFR messages that indicate that
-# the client has initiated the IXFR.
-
-echo "I:exit status: $status"
-exit $status
diff --git a/tests/system/ixfr/ixfr_init.sh.in b/tests/system/ixfr/ixfr_init.sh.in
deleted file mode 100644
index ba6049e..0000000
--- a/tests/system/ixfr/ixfr_init.sh.in
+++ /dev/null
@@ -1,330 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2011 Internet Software Consortium.
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# \file
-# This file should be run by all IXFR tests before doing anything else. It
-# includes the main configuration script to set the environment variables as
-# well as defining useful shell subroutines.
-
-. @abs_top_builddir@/tests/system/conf.sh
-
-# Useful symbols used in the IXFR tests.
-
-# Short-hand for getting SOA - just supply address of the server
-DIG_SOA="$DIG +norecurse +short -p $DNS_PORT example. SOA"
-
-# All IXFR tests use a BIND 9 server serving a BIND 10 client. These have the
-# smae name and use the same address in all tests.
-SERVER_NAME=ns1
-SERVER_IP=10.53.0.1 # BIND 9
-
-CLIENT_NAME=nsx2
-CLIENT_IP=10.53.0.2 # BIND 10
-
-# \brief Check Arguments
-#
-# Most functions take the name of nameserver as the first argument and its IP
-# address as the second. This function is passed "$*" and just checks that
-# both $1 and $2 are defined.
-#
-# \arg $* Arguments passed to caller
-#
-# \return status 0 => $1 and $2 are defined, 1 => they are not.
-check_name_ip() {
-
- if [ "$1" = "" ];
- then
- echo "R:FAIL name of server not supplied"
- return 1
- fi
-
- if [ "$2" = "" ];
- then
- echo "R:FAIL IP address of server not supplied"
- return 1
- fi
-
- return 0
-}
-
-
-# \brief Perform RNDC Command
-#
-# Controls the BIND 9 IXFR server. Called do_rndc (instead of rndc) to avoid
-# confusion if rndc itself is in the search path.
-#
-# \arg $1 - Name of the server (ns1, nsx2 etc.)
-# \arg $2 - IP address of the server
-# \arg $* - Command to execute (which may be multiple tokens)
-#
-# \return 0 on success, 1 on failure (in which case an error message will
-# have been output).
-do_rndc () {
-
- # If the following checks fail, the code is wrong.
-
- check_name_ip $*
- if [ $? -ne 0 ];
- then
- echo "R:FAIL do_rndc - name or ip address of server not supplied"
- return 1
- fi
-
- name=$1
- shift
- ip=$1
- shift
-
- if [ "$1" = "" ];
- then
- echo "R:FAIL do_rndc - rndc command not supplied"
- return 1
- fi
-
- $RNDC -c $SYSTEM_TOP/common/rndc.conf -s $ip -p $RNDC_PORT $* 2>&1 \
- | sed "s/^/I:$name /"
-}
-
-# \brief Wait for update
-#
-# Given a serial number and a server, poll the nameserver until the SOA serial
-# number is different from that given. The poll takes place every five seconds
-# for a minute.
-#
-# \arg $1 - Name of the server
-# \arg $2 - IP address of the server
-# \arg $3 - Serial number to check against
-#
-# \return 0 if the serial number is different (requires another poll to obtain
-# it), 1 if the serial number has not changed after one minute.
-wait_for_update() {
-
- # If the following checks fail, the code is wrong.
-
- check_name_ip $*
- if [ $? -ne 0 ];
- then
- echo "R:FAIL wait_for_update - name or ip address of system not supplied"
- return 1
-
- fi
-
- name=$1
- shift
- ip=$1
- shift
-
- serial=$1
- if [ "$serial" = "" ];
- then
- echo "R:FAIL wait_for_update - serial number not supplied"
- return 1
- fi
-
- # Now poll the server looking for the new serial number
-
- echo "I:$name waiting for SOA serial to change from $serial"
- for i in 1 2 3 4 5 6 7 8 9 10 11 12
- do
- if [ $i -gt 1 ];
- then
- sleep 5
- fi
-
- new_serial=`$DIG_SOA @$ip | $AWK '{print $3}'`
- if [ "$new_serial" != "$serial" ];
- then
- echo "I:$name SOA serial was at $serial, now at $new_serial"
- return 0
- fi
- done
-
- echo "R:$name FAIL serial number has not updated"
- return 1
-}
-
-
-
-# \brief Update server zone
-#
-# Reloads the example. zone in the BIND 9 IXFR server and waits a maximum of
-# one minute for it to be served.
-#
-# \arg $1 - Name of the server (ns1, nsx2 etc.)
-# \arg $2 - IP address of the server
-# \arg $3 - Zone file to load
-# \arg $* - Command to execute (which may be multiple tokens)
-#
-# \return 0 on success, 1 on failure (for which an error message will have
-# been output).
-update_server_zone() {
-
- # If the following checks fail, the code is wrong.
-
- check_name_ip $*
- if [ $? -ne 0 ];
- then
- echo "R:FAIL update_server_zone - name or ip address of server not supplied"
- return 1
- fi
-
- name=$1
- shift
- ip=$1
- shift
-
- file=$1
- shift
- if [ "$file" = "" ];
- then
- echo "R:FAIL update_server_zone - new zone file not supplied"
- return 1
- fi
-
- if [ ! -e $file ];
- then
- echo "R:FAIL update_server_zone - zone file does not exist: $file"
- return 1
- fi
-
- old_serial=`$DIG_SOA @$ip | $AWK '{print $3}'`
-
- echo "I:$name IXFR server loading $file"
- cp $file $name/db.example
- do_rndc $name $ip reload
- if [ $? -ne 0 ];
- then
- return 1 # Message will have already been output
- fi
-
- wait_for_update $name $ip $old_serial
- if [ $? -ne 0 ];
- then
- echo "R:$name FAIL IXFR server did not update zone after reload"
- return 1
- fi
- new_serial=`$DIG_SOA @$ip | $AWK '{print $3}'`
-
- return 0
-}
-
-# \brief Compare client and server SOAs
-#
-# Checks the SOAs of two systems and reports if they are not equal.
-#
-# \arg $1 Name of the IXFR server
-# \arg $2 IP of the IXFR server
-# \arg $3 Name of the IXFR client
-# \arg $4 IP of the IXFR client
-#
-# \return 0 if the systems have the same SOA, 1 if not. In the latter case,
-# an error will be output.
-compare_soa() {
-
- # If the following checks fail, the code is wrong.
-
- check_name_ip $*
- if [ $? -ne 0 ];
- then
- echo "R:FAIL compare_soa - name or ip address of server not supplied"
- return 1
- fi
-
- server_name=$1
- shift
- server_ip=$1
- shift
-
- check_name_ip $*
- if [ $? -ne 0 ];
- then
- echo "R:FAIL compare_soa - name or ip address of client not supplied"
- return 1
- fi
-
- client_name=$1
- shift
- client_ip=$1
- shift
-
- client_serial=`$DIG_SOA @$client_ip | $AWK '{print $3}'`
- server_serial=`$DIG_SOA @$server_ip | $AWK '{print $3}'`
- if [ "$client_serial" != "$server_serial" ];
- then
- echo "R:FAIL client $client_name serial $client_serial not same as server $server_name serial $server_serial"
- return 1
- fi
-
- return 0
-}
-
-# \brief Compare client and server zones
-#
-# Checks the zones of two systems and reports if they are not identical.
-#
-# The check is simplistic. Each zone is listed via "dig", after which comment
-# lines, blank lines and spaces/tabs are removed, and the result sorted. The
-# output from each system is then compared. They should be identical.
-#
-# \arg $1 Name of the IXFR server
-# \arg $2 IP of the IXFR server
-# \arg $3 Name of the IXFR client
-# \arg $4 IP of the IXFR client
-#
-# \return 0 if the zones are the same, 1 if not.
-compare_zones() {
-
- # If the following checks fail, the code is wrong.
-
- check_name_ip $*
- if [ $? -ne 0 ];
- then
- echo "R:FAIL compare_zones - name or ip address of server not supplied"
- return 1
- fi
-
- server_name=$1
- shift
- server_ip=$1
- shift
-
- check_name_ip $*
- if [ $? -ne 0 ];
- then
- echo "R:FAIL compare_zones - name or ip address of client not supplied"
- return 1
- fi
-
- client_name=$1
- shift
- client_ip=$1
- shift
-
- $DIG @$client_ip -p $DNS_PORT example. axfr | grep -v '^;' | grep -v '^$' \
- | sed -e 's/ //g' -e 's/\t//g' | sort > client.dig
- $DIG @$server_ip -p $DNS_PORT example. axfr | grep -v '^;' | grep -v '^$' \
- | sed -e 's/ //g' -e 's/\t//g' | sort > server.dig
- diff client.dig server.dig
- if [ $? -eq 0 ];
- then
- echo "I:client and server zones identical"
- else
- echo "R:FAIL client $client_name zone not same as server $server_name zone"
- return 1
- fi
-
- return 0
-}
diff --git a/tests/system/ixfr/named_noixfr.conf b/tests/system/ixfr/named_noixfr.conf
deleted file mode 100644
index d171876..0000000
--- a/tests/system/ixfr/named_noixfr.conf
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2004, 2007, 2011 Internet Systems Consortium, Inc. ("ISC")
- * Copyright (C) 2000, 2001 Internet Software Consortium.
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-include "../../../common/rndc.key";
-
-controls {
- inet 10.53.0.1 port 9953 allow { any; } keys { rndc_key; };
-};
-
-options {
- query-source address 10.53.0.1;
- notify-source 10.53.0.1;
- transfer-source 10.53.0.1;
- port 53210;
- pid-file "named.pid";
- listen-on { 10.53.0.1; };
- listen-on-v6 { none; };
- recursion no;
- ixfr-from-differences no;
- notify explicit;
- also-notify { 10.53.0.2; };
- provide-ixfr no;
-};
-
-zone "example" {
- type master;
- file "db.example";
-};
diff --git a/tests/system/ixfr/named_nonotify.conf b/tests/system/ixfr/named_nonotify.conf
deleted file mode 100644
index c08c212..0000000
--- a/tests/system/ixfr/named_nonotify.conf
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2004, 2007, 2011 Internet Systems Consortium, Inc. ("ISC")
- * Copyright (C) 2000, 2001 Internet Software Consortium.
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-include "../../../common/rndc.key";
-
-controls {
- inet 10.53.0.1 port 9953 allow { any; } keys { rndc_key; };
-};
-
-options {
- query-source address 10.53.0.1;
- notify-source 10.53.0.1;
- transfer-source 10.53.0.1;
- port 53210;
- pid-file "named.pid";
- listen-on { 10.53.0.1; };
- listen-on-v6 { none; };
- recursion no;
- ixfr-from-differences yes;
- notify no;
-};
-
-zone "example" {
- type master;
- file "db.example";
-};
diff --git a/tests/system/ixfr/named_notify.conf b/tests/system/ixfr/named_notify.conf
deleted file mode 100644
index df45e6f..0000000
--- a/tests/system/ixfr/named_notify.conf
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2004, 2007, 2011 Internet Systems Consortium, Inc. ("ISC")
- * Copyright (C) 2000, 2001 Internet Software Consortium.
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
- * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- */
-
-include "../../../common/rndc.key";
-
-controls {
- inet 10.53.0.1 port 9953 allow { any; } keys { rndc_key; };
-};
-
-options {
- query-source address 10.53.0.1;
- notify-source 10.53.0.1;
- transfer-source 10.53.0.1;
- port 53210;
- pid-file "named.pid";
- listen-on { 10.53.0.1; };
- listen-on-v6 { none; };
- recursion no;
- ixfr-from-differences yes;
- notify explicit;
- also-notify { 10.53.0.2; };
-};
-
-zone "example" {
- type master;
- file "db.example";
-};
diff --git a/tests/system/run.sh.in b/tests/system/run.sh.in
deleted file mode 100755
index 619b865..0000000
--- a/tests/system/run.sh.in
+++ /dev/null
@@ -1,125 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007, 2010 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000, 2001 Internet Software Consortium.
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-#
-# Run a system test.
-#
-
-SYSTEMTOP=@abs_top_builddir@/tests/system
-. $SYSTEMTOP/conf.sh
-
-stopservers=true
-
-case $1 in
- --keep) stopservers=false; shift ;;
-esac
-
-test $# -gt 0 || { echo "usage: $0 [--keep] test-directory" >&2; exit 1; }
-
-test=$1
-shift
-
-test -d $test || { echo "$0: $test: no such test" >&2; exit 1; }
-
-echo "S:$test:`date`" >&2
-echo "T:$test:1:A" >&2
-echo "A:System test $test" >&2
-
-if [ x$PERL = x ]
-then
- echo "I:Perl not available. Skipping test." >&2
- echo "R:UNTESTED" >&2
- echo "E:$test:`date`" >&2
- exit 0;
-fi
-
-$PERL $TESTSOCK || {
- echo "I:Network interface aliases not set up. Skipping test." >&2;
- echo "R:UNTESTED" >&2;
- echo "E:$test:`date`" >&2;
- exit 0;
-}
-
-
-# Check for test-specific prerequisites.
-test ! -f $test/prereq.sh || ( cd $test && sh prereq.sh "$@" )
-result=$?
-
-if [ $result -eq 0 ]; then
- : prereqs ok
-else
- echo "I:Prerequisites for $test missing, skipping test." >&2
- [ $result -eq 255 ] && echo "R:SKIPPED" || echo "R:UNTESTED"
- echo "E:$test:`date`" >&2
- exit 0
-fi
-
-# Check for PKCS#11 support
-if
- test ! -f $test/usepkcs11 || sh cleanpkcs11.sh
-then
- : pkcs11 ok
-else
- echo "I:Need PKCS#11 for $test, skipping test." >&2
- echo "R:PKCS11ONLY" >&2
- echo "E:$test:`date`" >&2
- exit 0
-fi
-
-# Set up any dynamically generated test data
-if test -f $test/setup.sh
-then
- ( cd $test && sh setup.sh "$@" )
-fi
-
-# Start name servers running
-$PERL $SYSTEMTOP/start.pl $test || exit 1
-
-# Run the tests
-( cd $test ; sh tests.sh )
-
-status=$?
-
-if $stopservers
-then
- :
-else
- exit $status
-fi
-
-# Shutdown
-$PERL $SYSTEMTOP/stop.pl $test
-
-status=`expr $status + $?`
-
-if [ $status != 0 ]; then
- echo "R:FAIL"
- # Don't clean up - we need the evidence.
- find . -name core -exec chmod 0644 '{}' \;
-else
- echo "R:PASS"
-
- # Clean up.
- if test -f $test/clean.sh
- then
- ( cd $test && sh clean.sh "$@" )
- fi
-fi
-
-echo "E:$test:`date`"
-
-exit $status
diff --git a/tests/system/runall.sh b/tests/system/runall.sh
deleted file mode 100755
index 5d0fe9b..0000000
--- a/tests/system/runall.sh
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/bin/sh
-#
-# Copyright (C) 2004, 2007, 2010 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2000, 2001 Internet Software Consortium.
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-#
-# Run all the system tests.
-#
-
-SYSTEMTESTTOP=.
-. $SYSTEMTESTTOP/conf.sh
-
-status=0
-
-for d in $SUBDIRS
-do
- sh run.sh $d || status=1
-done
-
-$PERL $TESTSOCK || {
- cat <<EOF >&2
-I:
-I:NOTE: Many of the tests were skipped because they require that
-I: the IP addresses 10.53.0.1 through 10.53.0.7 are configured
-I: as alias addresses on the loopback interface. Please run
-I: "tests/system/ifconfig.sh up" as root to configure them
-I: and rerun the tests.
-EOF
- exit 0;
-}
-
-exit $status
diff --git a/tests/system/start.pl b/tests/system/start.pl
deleted file mode 100755
index 32284de..0000000
--- a/tests/system/start.pl
+++ /dev/null
@@ -1,229 +0,0 @@
-#!/usr/bin/perl -w
-#
-# Copyright (C) 2004-2008, 2010 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2001 Internet Software Consortium.
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# Framework for starting test servers.
-# Based on the type of server specified, check for port availability, remove
-# temporary files, start the server, and verify that the server is running.
-# If a server is specified, start it. Otherwise, start all servers for test.
-
-use strict;
-use Cwd 'abs_path';
-use Getopt::Long;
-
-# Option handling
-# --noclean test [server [options]]
-#
-# --noclean - Do not cleanup files in server directory
-# test - name of the test directory
-# server - name of the server directory
-# options - alternate options for the server
-
-my $usage = "usage: $0 [--noclean] test-directory [server-directory [server-options]]";
-my $noclean;
-GetOptions('noclean' => \$noclean);
-my $test = $ARGV[0];
-my $server = $ARGV[1];
-my $options = $ARGV[2];
-
-if (!$test) {
- print "$usage\n";
-}
-if (!-d $test) {
- print "No test directory: \"$test\"\n";
-}
-if ($server && !-d "$test/$server") {
- print "No server directory: \"$test/$server\"\n";
-}
-
-# Global variables
-my $topdir = abs_path("$test/..");
-my $testdir = abs_path("$test");
-my $RUN_BIND10 = $ENV{'RUN_BIND10'};
-my $RUN_BINDCTL = $ENV{'RUN_BINDCTL'};
-my $BINDCTL_CSV_DIR = $ENV{'BINDCTL_CSV_DIR'};
-my $NAMED = $ENV{'BIND9_NAMED'};
-my $LWRESD = $ENV{'LWRESD'};
-my $DIG = $ENV{'DIG'};
-my $PERL = $ENV{'PERL'};
-my $TESTSOCK = $ENV{'TESTSOCK'};
-
-# Start the server(s)
-
-if ($server) {
- if ($server =~ /^ns/) {
- &check_ports($server);
- }
- &start_server($server, $options);
- if ($server =~ /^ns/) {
- &verify_server($server);
- }
-} else {
- # Determine which servers need to be started for this test.
- opendir DIR, $testdir;
- my @files = sort readdir DIR;
- closedir DIR;
-
- my @ns = grep /^nsx?[0-9]*$/, @files;
- my @lwresd = grep /^lwresd[0-9]*$/, @files;
- my @ans = grep /^ans[0-9]*$/, @files;
-
- # Start the servers we found.
- &check_ports();
- foreach my $s (@ns, @lwresd, @ans) {
- &start_server($s);
- }
- foreach my $s (@ns) {
- &verify_server($s);
- }
-}
-
-# Subroutines
-
-sub check_ports {
- my $server = shift;
- my $options = "";
-
- if ($server && $server =~ /(\d+)$/) {
- $options = "-i $1";
- }
-
- my $tries = 0;
- while (1) {
- my $return = system("$PERL $TESTSOCK -p 53210 $options");
- last if ($return == 0);
- if (++$tries > 4) {
- print "$0: could not bind to server addresses, still running?\n";
- print "I:server sockets not available\n";
- print "R:FAIL\n";
- system("$PERL $topdir/stop.pl $testdir"); # Is this the correct behavior?
- exit 1;
- }
- print "I:Couldn't bind to socket (yet)\n";
- sleep 2;
- }
-}
-
-sub start_server {
- my $server = shift;
- my $options = shift;
-
- my $cleanup_files;
- my $command;
- my $pid_file;
-
- if ($server =~ /^nsx/) {
- $cleanup_files = "{bind10.run}";
- $command = "B10_FROM_SOURCE_LOCALSTATEDIR=$testdir/$server/ ";
- $command .= "$RUN_BIND10 ";
- if ($options) {
- $command .= "$options";
- } else {
- $command .= "--msgq-socket-file=$testdir/$server/msgq_socket ";
- $command .= "--pid-file=$testdir/$server/bind10.pid ";
- $command .= "-v";
- }
- $command .= " >bind10.run 2>&1 &";
- $pid_file = "bind10.pid";
- } elsif ($server =~ /^ns/) {
- $cleanup_files = "{*.jnl,*.bk,*.st,named.run}";
- $command = "$NAMED ";
- if ($options) {
- $command .= "$options";
- } else {
- $command .= "-m record,size,mctx ";
- $command .= "-T clienttest ";
- $command .= "-T nosoa "
- if (-e "$testdir/$server/named.nosoa");
- $command .= "-T noaa "
- if (-e "$testdir/$server/named.noaa");
- $command .= "-c named.conf -d 99 -g";
- }
- $command .= " >named.run 2>&1 &";
- $pid_file = "named.pid";
- } elsif ($server =~ /^lwresd/) {
- $cleanup_files = "{lwresd.run}";
- $command = "$LWRESD ";
- if ($options) {
- $command .= "$options";
- } else {
- $command .= "-m record,size,mctx ";
- $command .= "-T clienttest ";
- $command .= "-C resolv.conf -d 99 -g ";
- $command .= "-i lwresd.pid -P 9210 -p 53210";
- }
- $command .= " >lwresd.run 2>&1 &";
- $pid_file = "lwresd.pid";
- } elsif ($server =~ /^ans/) {
- $cleanup_files = "{ans.run}";
- $command = "$PERL ./ans.pl ";
- if ($options) {
- $command .= "$options";
- } else {
- $command .= "";
- }
- $command .= " >ans.run 2>&1 &";
- $pid_file = "ans.pid";
- } else {
- print "I:Unknown server type $server\n";
- print "R:FAIL\n";
- system "$PERL $topdir/stop.pl $testdir";
- exit 1;
- }
-
- print "I:starting server $server\n";
-
- chdir "$testdir/$server";
-
- unless ($noclean) {
- unlink glob $cleanup_files;
- }
-
- system "$command";
-
- my $tries = 0;
- while (!-f $pid_file) {
- if (++$tries > 14) {
- print "I:Couldn't start server $server\n";
- print "R:FAIL\n";
- system "$PERL $topdir/stop.pl $testdir";
- exit 1;
- }
- sleep 1;
- }
-}
-
-sub verify_server {
- my $server = shift;
- my $n = $server;
- $n =~ s/^nsx?//;
-
- my $tries = 0;
- while (1) {
- my $return = system("echo \"Stats show\" | $RUN_BINDCTL --csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out");
- last if ($return == 0);
- if (++$tries >= 30) {
- print "I:no response from $server\n";
- print "R:FAIL\n";
- system("$PERL $topdir/stop.pl $testdir");
- exit 1;
- } else {
- print "I:no response from $server. retrying.\n";
- }
- sleep 2;
- }
- unlink "dig.out";
-}
diff --git a/tests/system/stop.pl b/tests/system/stop.pl
deleted file mode 100755
index a803f52..0000000
--- a/tests/system/stop.pl
+++ /dev/null
@@ -1,188 +0,0 @@
-#!/usr/bin/perl -w
-#
-# Copyright (C) 2004-2007 Internet Systems Consortium, Inc. ("ISC")
-# Copyright (C) 2001 Internet Software Consortium.
-#
-# Permission to use, copy, modify, and/or distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-# PERFORMANCE OF THIS SOFTWARE.
-
-# Framework for stopping test servers
-# Based on the type of server specified, signal the server to stop, wait
-# briefly for it to die, and then kill it if it is still alive.
-# If a server is specified, stop it. Otherwise, stop all servers for test.
-
-use strict;
-use Cwd 'abs_path';
-
-# Option handling
-# [--use-rndc] test [server]
-#
-# test - name of the test directory
-# server - name of the server directory
-
-my $usage = "usage: $0 [--use-rndc] test-directory [server-directory]";
-my $use_rndc;
-
-while (@ARGV && $ARGV[0] =~ /^-/) {
- my $opt = shift @ARGV;
- if ($opt eq '--use-rndc') {
- $use_rndc = 1;
- } else {
- die "$usage\n";
- }
-}
-
-my $test = $ARGV[0];
-my $server = $ARGV[1];
-
-my $errors = 0;
-
-die "$usage\n" unless defined($test);
-die "No test directory: \"$test\"\n" unless (-d $test);
-die "No server directory: \"$server\"\n" if (defined($server) && !-d "$test/$server");
-
-# Global variables
-my $testdir = abs_path($test);
-my @servers;
-
-
-# Determine which servers need to be stopped.
-if (defined $server) {
- @servers = ($server);
-} else {
- local *DIR;
- opendir DIR, $testdir or die "$testdir: $!\n";
- my @files = sort readdir DIR;
- closedir DIR;
-
- my @ns = grep /^nsx?[0-9]*$/, @files;
- my @lwresd = grep /^lwresd[0-9]*$/, @files;
- my @ans = grep /^ans[0-9]*$/, @files;
-
- push @servers, @ns, @lwresd, @ans;
-}
-
-
-# Stop the server(s), pass 1: rndc.
-if ($use_rndc) {
- foreach my $server (grep /^ns/, @servers) {
- stop_rndc($server);
- }
-
- wait_for_servers(30, grep /^ns/, @servers);
-}
-
-
-# Pass 2: SIGTERM
-foreach my $server (@servers) {
- stop_signal($server, "TERM");
-}
-
-wait_for_servers(60, @servers);
-
-# Pass 3: SIGABRT
-foreach my $server (@servers) {
- stop_signal($server, "ABRT");
-}
-
-exit($errors ? 1 : 0);
-
-# Subroutines
-
-# Return the full path to a given server's PID file.
-sub server_pid_file {
- my($server) = @_;
-
- my $pid_file;
- if ($server =~ /^nsx/) {
- $pid_file = "bind10.pid";
- } elsif ($server =~ /^ns/) {
- $pid_file = "named.pid";
- } elsif ($server =~ /^lwresd/) {
- $pid_file = "lwresd.pid";
- } elsif ($server =~ /^ans/) {
- $pid_file = "ans.pid";
- } else {
- print "I:Unknown server type $server\n";
- exit 1;
- }
- $pid_file = "$testdir/$server/$pid_file";
-}
-
-# Read a PID.
-sub read_pid {
- my($pid_file) = @_;
-
- local *FH;
- my $result = open FH, "< $pid_file";
- if (!$result) {
- print "I:$pid_file: $!\n";
- unlink $pid_file;
- return;
- }
-
- my $pid = <FH>;
- chomp($pid);
- return $pid;
-}
-
-# Stop a named process with rndc.
-sub stop_rndc {
- my($server) = @_;
-
- return unless ($server =~ /^ns(\d+)$/);
- my $ip = "10.53.0.$1";
-
- # Ugly, but should work.
- system("$ENV{RNDC} -c $testdir/../common/rndc.conf -s $ip -p 9953 stop | sed 's/^/I:$server /'");
- return;
-}
-
-# Stop a server by sending a signal to it.
-sub stop_signal {
- my($server, $sig) = @_;
-
- my $pid_file = server_pid_file($server);
- return unless -f $pid_file;
-
- my $pid = read_pid($pid_file);
- return unless defined($pid);
-
- if ($sig eq 'ABRT') {
- print "I:$server didn't die when sent a SIGTERM\n";
- $errors++;
- }
-
- my $result = kill $sig, $pid;
- if (!$result) {
- print "I:$server died before a SIG$sig was sent\n";
- unlink $pid_file;
- $errors++;
- }
-
- return;
-}
-
-sub wait_for_servers {
- my($timeout, @servers) = @_;
-
- my @pid_files = grep { defined($_) }
- map { server_pid_file($_) } @servers;
-
- while ($timeout > 0 && @pid_files > 0) {
- @pid_files = grep { -f $_ } @pid_files;
- sleep 1 if (@pid_files > 0);
- $timeout--;
- }
-
- return;
-}
diff --git a/tests/tools/badpacket/option_info.h b/tests/tools/badpacket/option_info.h
index aebeb66..8cc0fe9 100644
--- a/tests/tools/badpacket/option_info.h
+++ b/tests/tools/badpacket/option_info.h
@@ -129,7 +129,7 @@ public:
///
/// \return The offset of the field corresponding to this option in the DNS
/// message flags field. The returned value is only valid for
- /// options that correpond to fields in the flags word.
+ /// options that correspond to fields in the flags word.
static int offset(int index);
/// \brief Return minimum allowed value of an option
@@ -151,7 +151,7 @@ public:
/// \param index A valid index (one of the values in the 'Index' enum).
///
/// \return Maximum allowed value for this option. If the option is a bit
- /// in the flags field of the DNS message hearder, this will be 1.
+ /// in the flags field of the DNS message header, this will be 1.
static uint32_t maxval(int index);
/// \brief Check Array Index
diff --git a/tests/tools/dhcp-ubench/benchmark.h b/tests/tools/dhcp-ubench/benchmark.h
index 6001e81..fc33606 100644
--- a/tests/tools/dhcp-ubench/benchmark.h
+++ b/tests/tools/dhcp-ubench/benchmark.h
@@ -178,7 +178,7 @@ protected:
/// Number of operations (e.g. insert lease num times)
uint32_t num_;
- /// Synchronous or asynchonous mode?
+ /// Synchronous or asynchronous mode?
bool sync_;
/// Should the test print out extra information?
diff --git a/tests/tools/dhcp-ubench/dhcp-perf-guide.xml b/tests/tools/dhcp-ubench/dhcp-perf-guide.xml
index 583b155..0115cd6 100644
--- a/tests/tools/dhcp-ubench/dhcp-perf-guide.xml
+++ b/tests/tools/dhcp-ubench/dhcp-perf-guide.xml
@@ -172,7 +172,7 @@
<para>
The framework attempts to do the same amount of work for every
- backend thus allowing fair complarison between them.
+ backend thus allowing fair comparison between them.
</para>
</section>
@@ -316,7 +316,7 @@
turned to several modes of operation. Its value can be
modified in SQLite_uBenchmark::connect(). See
http://www.sqlite.org/pragma.html#pragma_journal_mode for
- detailed explanantion.</para>
+ a detailed explanation.</para>
<para>sqlite_bench supports precompiled statements. Please use
'-c no|yes' to define which should be used: basic SQL query (no) or
@@ -898,7 +898,7 @@ SQLite version: 3.7.9sourceid version is 2011-11-01 00:52:41 c7c6050ef060877ebe7
</para>
<para>
- It should be emphaisized that obtained measurements indicate
+ It should be emphasized that obtained measurements indicate
only database performance and they cannot be directly
translated to expected leases per second or queries per second
performance by an actual server. The DHCP server must do much
@@ -1160,7 +1160,7 @@ The exit status is:
<para>
Currently, perfdhcp is seen from the server perspective as relay agent.
This simplifies its implementation: specifically there is no need to
- receive traffic sent to braodcast addresses. However, it does impose
+ receive traffic sent to broadcast addresses. However, it does impose
a requirement that the IPv4
address has to be set manually on the interface that will be used to
communicate with the server. For example, if the DHCPv4 server is listening
@@ -1384,7 +1384,7 @@ collected packets: 0
The content in template files is encoded as series of ASCII hexadecimal
digits (each byte represented by two ASCII chars 00..FF). Data in a
template file is laid in network byte order and it can be used on the
- systems with different endianess.
+ systems with different endianness.
perfdhcp forms the packet by replacing parts of the message buffer read
from the file with variable data such as elapsed time, hardware address, DUID
etc. The offsets where such variable data is placed is specific to the
diff --git a/tests/tools/perfdhcp/command_options.cc b/tests/tools/perfdhcp/command_options.cc
index 9433442..c0ae6fa 100644
--- a/tests/tools/perfdhcp/command_options.cc
+++ b/tests/tools/perfdhcp/command_options.cc
@@ -433,7 +433,7 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
}
if (server_name_.empty()) {
isc_throw(InvalidParameter,
- "without an inteface server is required");
+ "without an interface, server is required");
}
// If DUID is not specified from command line we need to
@@ -548,7 +548,7 @@ CommandOptions::decodeDuid(const std::string& base) {
} catch (isc::InvalidParameter&) {
isc_throw(isc::InvalidParameter,
"invalid characters in DUID provided,"
- " exepected hex digits");
+ " expected hex digits");
}
duid_template.push_back(static_cast<uint8_t>(ui));
}
diff --git a/tests/tools/perfdhcp/command_options.h b/tests/tools/perfdhcp/command_options.h
index 4b804dc..246fea3 100644
--- a/tests/tools/perfdhcp/command_options.h
+++ b/tests/tools/perfdhcp/command_options.h
@@ -443,7 +443,7 @@ private:
std::vector<int> xid_offset_;
/// Random value offset in templates. Random value offset
/// points to last octet of DUID. Up to 4 last octets of
- /// DUID are randomized to simulate differnt clients.
+ /// DUID are randomized to simulate different clients.
std::vector<int> rnd_offset_;
/// Offset of elapsed time option in template packet.
int elp_offset_;
diff --git a/tests/tools/perfdhcp/perf_pkt4.cc b/tests/tools/perfdhcp/perf_pkt4.cc
index 8b7e974..05d1c6c 100644
--- a/tests/tools/perfdhcp/perf_pkt4.cc
+++ b/tests/tools/perfdhcp/perf_pkt4.cc
@@ -40,7 +40,7 @@ PerfPkt4::rawPack() {
options_,
getTransidOffset(),
getTransid(),
- bufferOut_));
+ buffer_out_));
}
bool
diff --git a/tests/tools/perfdhcp/pkt_transform.h b/tests/tools/perfdhcp/pkt_transform.h
index c94e9ba..51c1c0b 100644
--- a/tests/tools/perfdhcp/pkt_transform.h
+++ b/tests/tools/perfdhcp/pkt_transform.h
@@ -54,7 +54,7 @@ public:
/// if the option's offset + its size is beyond the packet's size.
///
/// \param universe Universe used, V4 or V6
- /// \param in_buffer Input buffer holding intial packet
+ /// \param in_buffer Input buffer holding initial packet
/// data, this can be directly read from template file
/// \param options Options collection with offsets
/// \param transid_offset offset of transaction id in a packet,
diff --git a/tests/tools/perfdhcp/stats_mgr.h b/tests/tools/perfdhcp/stats_mgr.h
index 537e81d..a8b5d98 100644
--- a/tests/tools/perfdhcp/stats_mgr.h
+++ b/tests/tools/perfdhcp/stats_mgr.h
@@ -104,7 +104,7 @@ public:
private:
/// \brief Default constructor.
///
- /// Default constrcutor is private because we don't want client
+ /// Default constructor is private because we don't want client
/// class to call it because we want client class to specify
/// counter's name.
CustomCounter() { };
@@ -262,7 +262,8 @@ public:
/// In this mode all packets are stored throughout the test execution.
ExchangeStats(const ExchangeType xchg_type,
const double drop_time,
- const bool archive_enabled)
+ const bool archive_enabled,
+ const boost::posix_time::ptime boot_time)
: xchg_type_(xchg_type),
sent_packets_(),
rcvd_packets_(),
@@ -279,7 +280,8 @@ public:
unordered_lookups_(0),
ordered_lookups_(0),
sent_packets_num_(0),
- rcvd_packets_num_(0)
+ rcvd_packets_num_(0),
+ boot_time_(boot_time)
{
next_sent_ = sent_packets_.begin();
}
@@ -453,9 +455,16 @@ public:
packet_period.length().total_seconds() +
(static_cast<double>(packet_period.length().fractional_seconds())
/ packet_period.length().ticks_per_second());
- if (drop_time_ > 0 &&
- (period_fractional > drop_time_)) {
- eraseSent(sent_packets_.template project<0>(it));
+ if (drop_time_ > 0 && (period_fractional > drop_time_)) {
+ // The packet pointed to by 'it' is timed out so we
+ // have to remove it. Removal may invalidate the
+ // next_sent_ pointer if it points to the packet
+ // being removed. So, we set the next_sent_ to point
+ // to the next packet after removed one. This
+ // pointer will be further updated in the following
+ // iterations, if the subsequent packets are also
+ // timed out.
+ next_sent_ = eraseSent(sent_packets_.template project<0>(it));
++collected_;
}
}
@@ -494,7 +503,7 @@ public:
/// \return maximum delay between packets.
double getMaxDelay() const { return(max_delay_); }
- /// \brief Return avarage packet delay.
+ /// \brief Return average packet delay.
///
/// Method returns average packet delay. If no packets have been
/// received for this exchange avg delay can't be calculated and
@@ -668,7 +677,7 @@ public:
if (rcvd_packets_num_ == 0) {
std::cout << "Unavailable! No packets received." << std::endl;
}
- // We will be using boost::posix_time extensivelly here
+ // We will be using boost::posix_time extensively here
using namespace boost::posix_time;
// Iterate through all received packets.
@@ -699,9 +708,8 @@ public:
"packet time is not set");
}
// Calculate durations of packets from beginning of epoch.
- ptime epoch_time(min_date_time);
- time_period sent_period(epoch_time, sent_time);
- time_period rcvd_period(epoch_time, rcvd_time);
+ time_period sent_period(boot_time_, sent_time);
+ time_period rcvd_period(boot_time_, rcvd_time);
// Print timestamps for sent and received packet.
std::cout << "sent / received: "
<< to_iso_string(sent_period.length())
@@ -719,7 +727,7 @@ public:
/// \brief Private default constructor.
///
/// Default constructor is private because we want the client
- /// class to specify exchange type explicitely.
+ /// class to specify exchange type explicitly.
ExchangeStats();
/// \brief Erase packet from the list of sent packets.
@@ -803,6 +811,7 @@ public:
uint64_t sent_packets_num_; ///< Total number of sent packets.
uint64_t rcvd_packets_num_; ///< Total number of received packets.
+ boost::posix_time::ptime boot_time_; ///< Time when test is started.
};
/// Pointer to ExchangeStats.
@@ -853,7 +862,8 @@ public:
exchanges_[xchg_type] =
ExchangeStatsPtr(new ExchangeStats(xchg_type,
drop_time,
- archive_enabled_));
+ archive_enabled_,
+ boot_time_));
}
/// \brief Add named custom uint64 counter.
@@ -905,7 +915,7 @@ public:
/// \brief Increment specified counter.
///
- /// Increement counter value by one.
+ /// Increment counter value by one.
///
/// \param counter_key key poiting to the counter in the counters map.
/// \param value value to increment counter by.
@@ -988,7 +998,7 @@ public:
return(xchg_stats->getMaxDelay());
}
- /// \brief Return avarage packet delay.
+ /// \brief Return average packet delay.
///
/// Method returns average packet delay for specified
/// exchange type.
diff --git a/tests/tools/perfdhcp/test_control.cc b/tests/tools/perfdhcp/test_control.cc
index e51065f..925e9b6 100644
--- a/tests/tools/perfdhcp/test_control.cc
+++ b/tests/tools/perfdhcp/test_control.cc
@@ -57,7 +57,7 @@ TestControl::TestControlSocket::TestControlSocket(const int socket) :
}
TestControl::TestControlSocket::~TestControlSocket() {
- IfaceMgr::Iface* iface = IfaceMgr::instance().getIface(ifindex_);
+ Iface* iface = IfaceMgr::instance().getIface(ifindex_);
if (iface) {
iface->delSocket(sockfd_);
}
@@ -70,9 +70,9 @@ TestControl::TestControlSocket::initSocketData() {
for (IfaceMgr::IfaceCollection::const_iterator it = ifaces.begin();
it != ifaces.end();
++it) {
- const IfaceMgr::SocketCollection& socket_collection =
+ const Iface::SocketCollection& socket_collection =
it->getSockets();
- for (IfaceMgr::SocketCollection::const_iterator s =
+ for (Iface::SocketCollection::const_iterator s =
socket_collection.begin();
s != socket_collection.end();
++s) {
@@ -592,19 +592,27 @@ TestControl::openSocket() const {
std::string localname = options.getLocalName();
std::string servername = options.getServerName();
uint16_t port = options.getLocalPort();
- uint8_t family = AF_INET;
int sock = 0;
+
+ uint8_t family = (options.getIpVersion() == 6) ? AF_INET6 : AF_INET;
IOAddress remoteaddr(servername);
+
+ // Check for mismatch between IP option and server address
+ if (family != remoteaddr.getFamily()) {
+ isc_throw(InvalidParameter,
+ "Values for IP version: " <<
+ static_cast<unsigned int>(options.getIpVersion()) <<
+ " and server address: " << servername << " are mismatched.");
+ }
+
if (port == 0) {
- if (options.getIpVersion() == 6) {
+ if (family == AF_INET6) {
port = DHCP6_CLIENT_PORT;
} else if (options.getIpVersion() == 4) {
port = 67; // TODO: find out why port 68 is wrong here.
}
}
- if (options.getIpVersion() == 6) {
- family = AF_INET6;
- }
+
// Local name is specified along with '-l' option.
// It may point to interface name or local address.
if (!localname.empty()) {
@@ -653,7 +661,7 @@ TestControl::openSocket() const {
// If user specified interface name with '-l' the
// IPV6_MULTICAST_IF has to be set.
if ((ret >= 0) && options.isInterface()) {
- IfaceMgr::Iface* iface =
+ Iface* iface =
IfaceMgr::instance().getIface(options.getLocalName());
if (iface == NULL) {
isc_throw(Unexpected, "unknown interface "
@@ -905,8 +913,8 @@ TestControl::readPacketTemplate(const std::string& file_name) {
hex_digits.push_back(file_contents[i]);
} else if (!isxdigit(file_contents[i]) &&
!isspace(file_contents[i])) {
- isc_throw(BadValue, "the '" << file_contents[i] << "' is not a"
- " heaxadecimal digit");
+ isc_throw(BadValue, "'" << file_contents[i] << "' is not a"
+ " hexadecimal digit");
}
}
// Expect even number of digits.
@@ -1791,7 +1799,7 @@ TestControl::setDefaults4(const TestControlSocket& socket,
const Pkt4Ptr& pkt) {
CommandOptions& options = CommandOptions::instance();
// Interface name.
- IfaceMgr::Iface* iface = IfaceMgr::instance().getIface(socket.ifindex_);
+ Iface* iface = IfaceMgr::instance().getIface(socket.ifindex_);
if (iface == NULL) {
isc_throw(BadValue, "unable to find interface with given index");
}
@@ -1804,7 +1812,7 @@ TestControl::setDefaults4(const TestControlSocket& socket,
pkt->setRemotePort(DHCP4_SERVER_PORT);
// The remote server's name or IP.
pkt->setRemoteAddr(IOAddress(options.getServerName()));
- // Set local addresss.
+ // Set local address.
pkt->setLocalAddr(IOAddress(socket.addr_));
// Set relay (GIADDR) address to local address.
pkt->setGiaddr(IOAddress(socket.addr_));
@@ -1817,7 +1825,7 @@ TestControl::setDefaults6(const TestControlSocket& socket,
const Pkt6Ptr& pkt) {
CommandOptions& options = CommandOptions::instance();
// Interface name.
- IfaceMgr::Iface* iface = IfaceMgr::instance().getIface(socket.ifindex_);
+ Iface* iface = IfaceMgr::instance().getIface(socket.ifindex_);
if (iface == NULL) {
isc_throw(BadValue, "unable to find interface with given index");
}
@@ -1864,7 +1872,7 @@ TestControl::updateSendDue() {
// timer resolution.
duration = time_duration::ticks_per_second() / rate;
}
- // Calculate due time to initate next chunk of exchanges.
+ // Calculate due time to initiate next chunk of exchanges.
send_due_ = last_sent_ + time_duration(0, 0, 0, duration);
// Check if it is already due.
ptime now(microsec_clock::universal_time());
diff --git a/tests/tools/perfdhcp/test_control.h b/tests/tools/perfdhcp/test_control.h
index 245cf6d..3983fa6 100644
--- a/tests/tools/perfdhcp/test_control.h
+++ b/tests/tools/perfdhcp/test_control.h
@@ -85,7 +85,7 @@ static const size_t DHCPV6_IA_NA_OFFSET = 40;
/// - calculate how many packets must be send to satisfy desired rate,
/// - receive incoming packets from the server,
/// - check the exit conditions - terminate the program if the exit criteria
-/// are fulfiled, e.g. reached maximum number of packet drops,
+/// are fulfilled, e.g. reached maximum number of packet drops,
/// - send the number of packets appropriate to satisfy the desired rate,
/// - optionally print intermediate reports,
/// - print statistics, e.g. achieved rate,
@@ -143,7 +143,7 @@ public:
/// when exception occurs). This structure extends parent
/// structure with new field ifindex_ that holds interface
/// index where socket is bound to.
- struct TestControlSocket : public dhcp::IfaceMgr::SocketInfo {
+ struct TestControlSocket : public dhcp::SocketInfo {
/// Interface index.
uint16_t ifindex_;
/// Is socket valid. It will not be valid if the provided socket
@@ -245,7 +245,7 @@ public:
/// throw exception.
///
/// \throw isc::InvalidOperation if command line options are not parsed.
- /// \throw isc::Unexpected if internal Test Controler error occured.
+ /// \throw isc::Unexpected if internal Test Controller error occured.
/// \return error_code, 3 if number of received packets is not equal
/// to number of sent packets, 0 if everything is ok.
int run();
@@ -282,14 +282,14 @@ protected:
/// \brief Check if test exit condtitions fulfilled.
///
- /// Method checks if the test exit conditions are fulfiled.
+ /// Method checks if the test exit conditions are fulfilled.
/// Exit conditions are checked periodically from the
/// main loop. Program should break the main loop when
/// this method returns true. It is calling function
/// responsibility to break main loop gracefully and
/// cleanup after test execution.
///
- /// \return true if any of the exit conditions is fulfiled.
+ /// \return true if any of the exit conditions is fulfilled.
bool checkExitConditions() const;
/// \brief Factory function to create DHCPv6 ELAPSED_TIME option.
@@ -483,7 +483,7 @@ protected:
/// \throw isc::BadValue if socket can't be created for given
/// interface, local address or remote address.
/// \throw isc::InvalidOperation if broadcast option can't be
- /// set for the v4 socket or if multicast option cat't be set
+ /// set for the v4 socket or if multicast option can't be set
/// for the v6 socket.
/// \throw isc::Unexpected if interal unexpected error occured.
/// \return socket descriptor.
@@ -643,7 +643,7 @@ protected:
/// \brief Send DHCPv4 DISCOVER message from template.
///
/// Method sends DHCPv4 DISCOVER message from template. The
- /// template data is exepcted to be in binary format. Provided
+ /// template data is expected to be in binary format. Provided
/// buffer is copied and parts of it are replaced with actual
/// data (e.g. MAC address, transaction id etc.).
/// Copy of sent packet is stored in the stats_mgr4_ object to
@@ -909,7 +909,7 @@ private:
/// \brief Handle interrupt signal.
///
/// Function sets flag indicating that program has been
- /// interupted.
+ /// interrupted.
///
/// \param sig signal (ignored)
static void handleInterrupt(int sig);
@@ -970,7 +970,7 @@ private:
NumberGeneratorPtr transid_gen_; ///< Transaction id generator.
NumberGeneratorPtr macaddr_gen_; ///< Numbers generator for MAC address.
- /// Buffer holiding server id received in first packet
+ /// Buffer holding server id received in first packet
dhcp::OptionBuffer first_packet_serverid_;
/// Packet template buffers.
diff --git a/tests/tools/perfdhcp/tests/command_options_helper.h b/tests/tools/perfdhcp/tests/command_options_helper.h
index 7436bd7..253fe12 100644
--- a/tests/tools/perfdhcp/tests/command_options_helper.h
+++ b/tests/tools/perfdhcp/tests/command_options_helper.h
@@ -53,7 +53,7 @@ public:
/// \brief Destructor.
///
- /// Dealocates wrapped array of C-strings.
+ /// Deallocates wrapped array of C-strings.
~ArgvPtr() {
if (argv_ != NULL) {
for(int i = 0; i < argc_; ++i) {
diff --git a/tests/tools/perfdhcp/tests/command_options_unittest.cc b/tests/tools/perfdhcp/tests/command_options_unittest.cc
index 360178f..7a109fb 100644
--- a/tests/tools/perfdhcp/tests/command_options_unittest.cc
+++ b/tests/tools/perfdhcp/tests/command_options_unittest.cc
@@ -546,7 +546,7 @@ TEST_F(CommandOptionsTest, Interface) {
// In order to make this test portable we need to know
// at least one interface name on OS where test is run.
// Interface Manager has ability to detect interfaces.
- // Altough we don't call initIsInterface explicitely
+ // Although we don't call initIsInterface explicitly
// here it is called by CommandOptions object interally
// so this function is covered by the test.
dhcp::IfaceMgr& iface_mgr = dhcp::IfaceMgr::instance();
diff --git a/tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc b/tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc
index de134cc..02c009e 100644
--- a/tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc
+++ b/tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc
@@ -111,8 +111,8 @@ TEST_F(PerfPkt6Test, Constructor) {
// Test constructor to be used for incoming messages.
// Use default (1) offset value and don't specify transaction id.
boost::scoped_ptr<PerfPkt6> pkt1(new PerfPkt6(data, sizeof(data)));
- EXPECT_EQ(sizeof(data), pkt1->getData().size());
- EXPECT_EQ(0, memcmp(&pkt1->getData()[0], data, sizeof(data)));
+ EXPECT_EQ(sizeof(data), pkt1->data_.size());
+ EXPECT_EQ(0, memcmp(&pkt1->data_[0], data, sizeof(data)));
EXPECT_EQ(1, pkt1->getTransidOffset());
// Test constructor to be used for outgoing messages.
@@ -121,8 +121,8 @@ TEST_F(PerfPkt6Test, Constructor) {
const uint32_t transid = 0x010203;
boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(data, sizeof(data),
offset_transid, transid));
- EXPECT_EQ(sizeof(data), pkt2->getData().size());
- EXPECT_EQ(0, memcmp(&pkt2->getData()[0], data, sizeof(data)));
+ EXPECT_EQ(sizeof(data), pkt2->data_.size());
+ EXPECT_EQ(0, memcmp(&pkt2->data_[0], data, sizeof(data)));
EXPECT_EQ(0x010203, pkt2->getTransid());
EXPECT_EQ(10, pkt2->getTransidOffset());
}
@@ -163,7 +163,7 @@ TEST_F(PerfPkt6Test, RawPackUnpack) {
// Get output buffer from packet 1 to create new packet
// that will be later validated.
util::OutputBuffer pkt1_output = pkt1->getBuffer();
- ASSERT_EQ(pkt1_output.getLength(), pkt1->getData().size());
+ ASSERT_EQ(pkt1_output.getLength(), pkt1->data_.size());
const uint8_t* pkt1_output_data = static_cast<const uint8_t*>
(pkt1_output.getData());
boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(pkt1_output_data,
diff --git a/tests/tools/perfdhcp/tests/stats_mgr_unittest.cc b/tests/tools/perfdhcp/tests/stats_mgr_unittest.cc
index 8a83cac..ebb4f34 100644
--- a/tests/tools/perfdhcp/tests/stats_mgr_unittest.cc
+++ b/tests/tools/perfdhcp/tests/stats_mgr_unittest.cc
@@ -262,7 +262,7 @@ TEST_F(StatsMgrTest, SendReceiveSimple) {
boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER,
common_transid));
stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
- // The following attempt is expected to pass becase the right
+ // The following attempt is expected to pass because the right
// exchange type is used.
ASSERT_NO_THROW(
stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet)
diff --git a/tests/tools/perfdhcp/tests/test_control_unittest.cc b/tests/tools/perfdhcp/tests/test_control_unittest.cc
index ae67e6e..6aff71c 100644
--- a/tests/tools/perfdhcp/tests/test_control_unittest.cc
+++ b/tests/tools/perfdhcp/tests/test_control_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -241,8 +241,9 @@ public:
size_t matched_num = 0;
for (size_t i = 0; i < buf.size(); i += 2) {
for (int j = 0; j < requested_options.size(); j += 2) {
- uint16_t opt_i = buf[i + 1] << 8 + buf[i] & 0xFF;
- uint16_t opt_j = requested_options[j + 1] << 8 + requested_options[j] & 0xFF;
+ uint16_t opt_i = (buf[i + 1] << 8) + (buf[i] & 0xFF);
+ uint16_t opt_j = (requested_options[j + 1] << 8)
+ + (requested_options[j] & 0xFF);
if (opt_i == opt_j) {
// Requested option has been found.
++matched_num;
@@ -263,7 +264,7 @@ public:
/// If number of clients is between 257 and 65536 they can differ
/// on two last positions so the returned value will be 2 and so on.
///
- /// \param clients_num number of simulated clinets
+ /// \param clients_num number of simulated clients
/// \return maximum mismatch position
int unequalOctetPosition(int clients_num) const {
if (!clients_num) {
@@ -672,6 +673,18 @@ TEST_F(TestControlTest, GenerateDuid) {
testDuid();
}
+TEST_F(TestControlTest, MisMatchVerionServer) {
+ NakedTestControl tc;
+
+ // make sure we catch -6 paired with v4 address
+ ASSERT_NO_THROW(processCmdLine("perfdhcp -l 127.0.0.1 -6 192.168.1.1"));
+ EXPECT_THROW(tc.openSocket(), isc::InvalidParameter);
+
+ // make sure we catch -4 paired with v6 address
+ ASSERT_NO_THROW(processCmdLine("perfdhcp -l 127.0.0.1 -4 ff02::1:2"));
+ EXPECT_THROW(tc.openSocket(), isc::InvalidParameter);
+}
+
TEST_F(TestControlTest, GenerateMacAddress) {
// Simulate one client only. Always the same MAC address will be
// generated.
@@ -805,10 +818,12 @@ TEST_F(TestControlTest, Options6) {
// Prepare the reference buffer with requested options.
const uint8_t requested_options[] = {
0, D6O_NAME_SERVERS,
- 0, D6O_DOMAIN_SEARCH,
+ 0, D6O_DOMAIN_SEARCH
};
- int requested_options_num =
- sizeof(requested_options) / sizeof(requested_options[0]);
+ // Each option code in ORO is 2 bytes long. We calculate the number of
+ // requested options by dividing the size of the buffer holding options
+ // by the size of each individual option.
+ int requested_options_num = sizeof(requested_options) / sizeof(uint16_t);
OptionBuffer
requested_options_ref(requested_options,
requested_options + sizeof(requested_options));
diff --git a/tools/query_cmp/src/lib/compare_rrset.py b/tools/query_cmp/src/lib/compare_rrset.py
index 737d761..d7b3425 100755
--- a/tools/query_cmp/src/lib/compare_rrset.py
+++ b/tools/query_cmp/src/lib/compare_rrset.py
@@ -242,7 +242,7 @@ def resp_casecmp(msg1, msg2, num):
msg1.get_section(Message.SECTION_ADDITIONAL), \
msg2.get_section(Message.SECTION_ADDITIONAL))
- # If there are any differnt comparisons in the sections above, print the details
+ # If there are any different comparisons in the sections above, print the details
# contained in buf formattedly.
if not res_hdr or not res_ques or not res_ans or not res_auth or not res_addi:
print('=' * 30, ' BEGIN QUERY %s ' % num, '=' * 30, sep='')
diff --git a/tools/reorder_message_file.py b/tools/reorder_message_file.py
index 2ba4d7c..ce5934f 100644
--- a/tools/reorder_message_file.py
+++ b/tools/reorder_message_file.py
@@ -142,7 +142,7 @@ def print_dict(dictionary):
Prints the dictionary with a blank line between entries.
Parameters:
- dicitionary - Map holding the message dictionary
+ dictionary - Map holding the message dictionary
"""
count = 0
for msgid in sorted(dictionary):
diff --git a/tools/system_messages.py b/tools/system_messages.py
index 08a35e1..869a164 100644
--- a/tools/system_messages.py
+++ b/tools/system_messages.py
@@ -268,7 +268,7 @@ def addToDictionary(msgid, msgtext, desc, filename):
name of the message file in the messages manual.
"""
- # If the ID is in the dictionary, append a "(n)" to the name - this wil
+ # If the ID is in the dictionary, append a "(n)" to the name - this will
# flag that there are multiple instances. (However, this is an error -
# each ID should be unique in BIND-10.)
if msgid in dictionary:
More information about the bind10-changes
mailing list