BIND 10 bind10-1.1.0-release, updated. da3b1a5b6b1858a40d07df62f71005abe274d5f3 [bind10-1.1.0-release]rebranching from master
BIND 10 source code commits
bind10-changes at lists.isc.org
Fri May 3 01:13:26 UTC 2013
The branch, bind10-1.1.0-release has been updated
via da3b1a5b6b1858a40d07df62f71005abe274d5f3 (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 05504fc9e53521a9e5f55f6dbfc619813a08c733 (commit)
via 70994c681382b8c8b8c220af97999c887e01a423 (commit)
via 339c94b4664819a817eda8008778efd2c114ae93 (commit)
via 6d3e0f4b36a754248f8a03a29e2c36aef644cdcc (commit)
via 8d6493eb98994ac01c4305d12a148d78d048b681 (commit)
via f182b85e7ab01bae5c9c7a181a2b9db7977a9948 (commit)
via 5c82cc617522160d1c3d248aa215d84031b2de9c (commit)
via 75235e6f7c1b9b646d68c6d6238b2c38df9814d8 (commit)
via b5b6b43f34a3901dff343367515ea08f09a9d6ce (commit)
via 4f3776c2b1d06bca0e7e02feeba79caba01ac865 (commit)
via bf4f7d8e7a3404deafa3901720e58a524e178a18 (commit)
via 03cdf37097aac9e1f10f0f349e6c0aa83f6debe9 (commit)
via fa72716d183569839306fa8bde1d7ec451ce8919 (commit)
via 1a7685ff5054b4a51178ae24064d8cf41e817ced (commit)
via d3896075acb7d3b5dcfd8441d9ec9c40f3c44f4b (commit)
via dc507ec0a035a393304313f21184c9a9c804e46e (commit)
via 6df5353aa49ff281af8c5931b8b556595cd70c6b (commit)
via e4390d4f07ed2af66782abfa41ecfdbd7b1966cd (commit)
via 7ba811ceb844372d04d8f0ca62f112a377889ae9 (commit)
via 63e9d90b093a7938f0b7cdb34e9e71379a15d5de (commit)
via 7ff403a459b34457e433a4cf87ee9fdc64ba5af1 (commit)
via bb0d8b35c3f52e2900abccda0f52995683eee4ab (commit)
via 33e10c84c8b6ab274c157816ba81e6d2fc8ba628 (commit)
via 3a382331f1308099930d6b653dd6d11b70da8e22 (commit)
via e9fa118cd1b9d4d1c47ddb94244ddf78266c9dbd (commit)
via 7d6c238e01f66ff90e073c458312e732b9ce3f72 (commit)
via 719a1cb238f32588f5b891dc3f5d1461efff6905 (commit)
via 9f0934c0e2ca79fb5b1c03c57cc850fb9bdc22f8 (commit)
via 73bdf354a07a3e7ad4585881c434fd71c36ca775 (commit)
via e825b22e4ea06a8f0ecc8645fbd42af230d05151 (commit)
via 4933a1c6284498e2656909d15a84657b5b6ecb8b (commit)
via bf06401cd681a172444c7a07b8f99083be86a508 (commit)
via fa392e8eb391a17d30550d4b290c975710651d98 (commit)
via 27b1c2767aa9f7e7b74a640b548aa099605b28c4 (commit)
via 0c84643032d36c4b28fcc730872f8866dacebe2c (commit)
via de20a4c519712d5076b3e26c6eb1e4a40f689ed8 (commit)
via b47cd4c09f84751609f52a4a989889a7aa475b2a (commit)
via 88e8eec059a9e91725f8a12ff76e6f9c3af40155 (commit)
via 69dfb4544d9ded3c10cffbbfd573ae05fdeb771f (commit)
via a354d4ed95f1368c961efabea0eb2e1db95675c3 (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 7b3ab876a7fb697245c81d03bd477f81136355e8 (commit)
via 4963031b007d9e9f15cacfd058edc20a6d33bf37 (commit)
via b4e44a1c5f4f1fa5c16af5bcb2a3ed48a29c1da6 (commit)
via 73feee77d8a05f458c90990c8ff95f3338c54d72 (commit)
via d2b40a13c798fbbc64ef8aa8d02f6572405ab160 (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 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 2b67b0507c180a32d14f843d26b99614cb1a88ae (commit)
via eab6ead64d7f7d78b024988bc29e95bb28426a37 (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 c16ff5e78c1a0f80c57ce8fb7235674b839fef81 (commit)
via 9036a1857725ba935a78b4aeb9750741df9f1287 (commit)
via 430bc7504e0b6d439440376bc6b1c56fc69280f7 (commit)
via c6c92db7bae905d428a876996d8414ee1d278fa1 (commit)
via f00928ec99591b2cabde3d213f7756e90b1e6f50 (commit)
via 33ffc9a750cd3fb34158ef676aab6b05df0302e2 (commit)
via ff74508f934161e722d3a9d64158c061b46039fc (commit)
via ba0d6139cadca933ebbaaa7f5016480154331ae6 (commit)
via 9c36d2e22389ed7c9ae61fc91eb3f2946b10c169 (commit)
via 00b7af245214cfdc54f522d719f23406d80d68ad (commit)
via d17b3c006757eecc1fab20cec90ead3e23b5962b (commit)
via 08a77e0ef2096158eff12b5d20e9af9e46c93c45 (commit)
via e01ee176ce2071c076833c842a9cd946a5008130 (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 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 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 fa304f6d472075939f45a1e59a45c6a6c82d8ac7 (commit)
via ad304b978c110bee6e97ef3010776bbd8733e813 (commit)
via 44890bb7db4879313473787c726ae25dd6eac335 (commit)
via aef76a4ff2d463f09a2d82e304d6003025646a5a (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 3b6cd417ea4ade9d616b12e7cb0577f892bf18b6 (commit)
via 63df2f7cdb5672540d176054f5fda0d3a93707e2 (commit)
via 8bc0766533f5f78c4ef8bcd9438f816e58c3aa83 (commit)
via dd0c9ba580d2b87324e3063de6f69d2bc8be5687 (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 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 c3c0784a907aca8b915221c63e9228a81e93c346 (commit)
via 785ade580637cc0d78139edbda05ed13e1675dea (commit)
via 85655172ba34a2d3c44731880f0e439d8933d277 (commit)
via 49316890b86159c3ed72ef0913c5a58d4e8e5657 (commit)
via 2630be5587b6548b0217f16fb78bc80935013a69 (commit)
via 43fa9db5aecf34e77a211c43c4cc2cc6e27e7530 (commit)
via 492b1ef54094f220280d58b42ee0749ef829a045 (commit)
via 7c0534d4b3dcd4b318dca8ebdcb1f1424c14931b (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 dfe737706fed6ca7aac475fb91d988222799b21b (commit)
via 3c941835b81c1ad7869f38bbf74d8e0a7fd567a7 (commit)
via 4778557e13c231f1bd2cde5d0950b68de920d8cd (commit)
via 7b2ee20fb35a475916a817c30668d31d15fc8907 (commit)
via 61dcaac0cfc5b764e0d5ca144128b1aa7d5a311f (commit)
via ff8f84b695de0e5350af325c675ab2bdbab79cc0 (commit)
via 281c6ab13bf40a06055a64b3e5f69c0406d33db0 (commit)
via 8b106d20e3afa459ef9ca13401145395d8a6dcc3 (commit)
via 8117b69a1c1c8c569032951254e5a094763d74e9 (commit)
via ea8936112837ab22263edabe5c44f1f40fa0ef6a (commit)
via e42c4c83835acbb17075759f9dd206dcff4a8d37 (commit)
via 8e3eb1576a4f04736aeb7d44d9b13d19729a7062 (commit)
via edd462bc9e8f727b1ebdf14b3f7cb6c844dee35a (commit)
via 7e9c676a0a800322d77ecac81bf223405011442c (commit)
via 147db3ecd8be5a6fe692c07c3d4d7597f8f2fceb (commit)
via 84076d913d1e974e504ddac1c7b8a22c9172bc9b (commit)
via 122da90997b11cee73dd63291216aa4c6c6ecd11 (commit)
via 400ddf1d85976eb07188e6fa20ae0a83274895fc (commit)
via 479c0eeabb8cdb874385fd9ef2160bddf8a0a80f (commit)
via 29ec8dce0f9312be9cddb3712fbd107fb62e33d3 (commit)
via c70bba06b9157645d6493b16ef0ee414277ce7b1 (commit)
via ad6d16107655d744062af1e0570abc2023ee1ee2 (commit)
via b5d7336d2353cd552c2b253a358f612b54b56e54 (commit)
via aaace1a1f1579d5cb2a657c11115e601ef1d4b7e (commit)
via a852fc05305f5c062ecf9d3c9ff6a5b836370d6d (commit)
via 53e18a35628a7a595d0de637ae4a045e41fe6b64 (commit)
via a1ce8481248f5859f8319ae85d9e25eaa0ace2db (commit)
via 2d0fc10cb714c3e34bc670a86ba645b42d3bd777 (commit)
via 393534ac4698e6dfeee32ab4bdf99e25eade4a76 (commit)
via 98b10b7662c77074dc26b27a4db1023b18821942 (commit)
via ed58ec0a82ec0b9df0e0f9bd53f1a32123c9fff6 (commit)
via 0ecc1e01cde7133bfcb183ced46b011b4eb9558a (commit)
via 09fff7c3f6d538b80e8a1760673bc4a3ac9f62d2 (commit)
via 2045020b58739c4f1ba8c6d39ca118513f8b2e35 (commit)
via 945103ccf34fe1140877c1dda05a98eb4ee79d98 (commit)
via 2f3d1fe8c582c75c98b739e0f7b1486b0a9141ca (commit)
via fbfd9a49b562e3f5532e87ad801baf82c6b7a912 (commit)
via ba20d4a58b6d7ea93c69681372bd6f1064491d1a (commit)
via 02fb3337a339f2043183ae66062a774ae6375e83 (commit)
via ab624f466e084edecb4a6299a56f8d33282a1ba0 (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 4a6f54f71484ebc8531371da819d4f98f59418e9 (commit)
via 14023798bdf7385a96500cf8bb7d93d485def18a (commit)
via 8e75dec19bbdb3ae1e01558ed5bb8cd89ad45582 (commit)
via d124a5b6f1fb3fb55918e3b75343c6bb70023980 (commit)
via bb5479f56b9bdab1eff81e8ecca1d8e703a9ef50 (commit)
via 2fb93c012e0c3f8a5e8e0fff208abe5685b7d4ce (commit)
via 27ae48b404de59c031c7f97f64edd4056c5e62dc (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 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 c2d22d81551f2309538404b52615d04394a3b9c3 (commit)
via a3077227a49f8cd0ae4e7c1f20cc87c4844ef0fe (commit)
via 685c9b375d73134e0c59ba8c7623f74487f69348 (commit)
via 4cfe351f170077546b2187f9d33d41b4148aa6a0 (commit)
via 042bfd80328dd3c5888ac70630f1545aba82f125 (commit)
via d46357aadcf46d1be0be68fd510d9065ecba2a57 (commit)
via 167b544c41361a7cf316b042baf057afb9002d3b (commit)
via 00d53a32412c50c20c573f40d895e8ad7f74ad4f (commit)
via 8674224a8d108e92075436de2656e8ba912f7df6 (commit)
via 3de4ecaf232d8a75e9da6e54384da948807a0659 (commit)
via f43b44eea132aa6ed092a787d0933d5b119a4025 (commit)
via 7c41946c022b31c44c9e8b76b4c511c195c6743a (commit)
via 859e0b3e82d4cc5270d8fb557f0120dc35edd1a3 (commit)
via 3511c6e6512c0004d9332ea85d1d3d4c03a414e0 (commit)
from c1ddd32cc4f96620fda3a1fa96025262cce2e6cd (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit da3b1a5b6b1858a40d07df62f71005abe274d5f3
Merge: c1ddd32 9f92c87
Author: Jeremy C. Reed <jreed at isc.org>
Date: Thu May 2 20:13:06 2013 -0500
[bind10-1.1.0-release]rebranching from master
fix conflicts
and also increase version to beta2
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 103 ++++
Makefile.am | 4 +-
configure.ac | 25 +-
doc/design/ipc-high.txt | 382 ++++++++++++
doc/guide/bind10-guide.xml | 4 +-
m4macros/Makefile.am | 2 +
m4macros/ax_boost_for_bind10.m4 | 49 +-
m4macros/ax_sqlite3_for_bind10.m4 | 25 +
src/bin/auth/auth_srv.cc | 2 +-
src/bin/auth/datasrc_clients_mgr.h | 2 +-
src/bin/auth/tests/config_unittest.cc | 2 +-
src/bin/bind10/init.py.in | 2 +-
src/bin/bind10/tests/init_test.py.in | 2 +-
src/bin/cfgmgr/plugins/datasrc.spec.pre.in | 6 +
src/bin/cfgmgr/plugins/tests/tsig_keys_test.py | 2 +-
src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in | 2 +-
src/bin/cmdctl/cmdctl.py.in | 66 ++-
src/bin/cmdctl/tests/cmdctl_test.py | 49 +-
src/bin/dbutil/tests/Makefile.am | 6 +
src/bin/dhcp4/config_parser.cc | 93 ++-
src/bin/dhcp4/config_parser.h | 3 +-
src/bin/dhcp4/ctrl_dhcp4_srv.h | 2 +-
src/bin/dhcp4/dhcp4_messages.mes | 2 +-
src/bin/dhcp4/dhcp4_srv.cc | 34 +-
src/bin/dhcp4/dhcp4_srv.h | 6 +-
src/bin/dhcp4/tests/config_parser_unittest.cc | 13 +-
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc | 361 ++++++------
src/bin/dhcp6/config_parser.cc | 98 ++--
src/bin/dhcp6/ctrl_dhcp6_srv.h | 2 +-
src/bin/dhcp6/dhcp6_srv.cc | 2 +-
src/bin/dhcp6/dhcp6_srv.h | 4 +-
src/bin/dhcp6/tests/config_parser_unittest.cc | 2 +-
src/bin/msgq/msgq.py.in | 2 +-
src/bin/msgq/tests/msgq_run_test.py | 17 +-
src/bin/msgq/tests/msgq_test.py | 6 +-
src/bin/resolver/Makefile.am | 2 +-
src/bin/resolver/bench/Makefile.am | 25 +
.../logger.cc => bin/resolver/bench/dummy_work.cc} | 13 +-
.../resolver/bench/dummy_work.h} | 33 +-
src/bin/resolver/bench/fake_resolution.cc | 172 ++++++
src/bin/resolver/bench/fake_resolution.h | 221 +++++++
.../resolver/bench/main.cc} | 28 +-
src/bin/resolver/bench/naive_resolver.cc | 66 +++
.../resolver/bench/naive_resolver.h} | 39 +-
src/bin/stats/stats.py.in | 2 +-
src/bin/stats/stats_httpd.py.in | 2 +-
src/bin/stats/tests/b10-stats_test.py | 2 +-
src/bin/xfrin/tests/xfrin_test.py | 2 +-
src/bin/xfrin/xfrin.py.in | 4 +-
src/bin/zonemgr/zonemgr.py.in | 2 +-
src/lib/acl/loader.h | 4 +-
src/lib/asiodns/asiodns_messages.mes | 89 +++
src/lib/asiodns/dns_service.cc | 22 +-
src/lib/asiodns/sync_udp_server.cc | 91 +--
src/lib/asiodns/sync_udp_server.h | 57 +-
src/lib/asiodns/tcp_server.cc | 133 +++--
src/lib/asiodns/tests/dns_server_unittest.cc | 106 +++-
src/lib/asiodns/udp_server.cc | 30 +-
src/lib/asiolink/io_service.cc | 10 +-
src/lib/asiolink/io_service.h | 15 +-
src/lib/asiolink/tests/Makefile.am | 1 +
src/lib/asiolink/tests/io_service_unittest.cc | 48 ++
src/lib/bench/benchmark.h | 4 +-
.../tests/testdata/message_nxdomain_large_ttl.wire | 2 +-
src/lib/config/ccsession.cc | 2 +-
src/lib/config/ccsession.h | 4 +-
src/lib/cryptolink/cryptolink.h | 2 +-
src/lib/datasrc/Makefile.am | 6 +-
src/lib/datasrc/cache_config.cc | 87 ++-
src/lib/datasrc/cache_config.h | 58 +-
src/lib/datasrc/client.h | 2 +-
src/lib/datasrc/client_list.cc | 163 ++---
src/lib/datasrc/client_list.h | 12 +-
src/lib/datasrc/data_source.h | 68 ---
src/lib/datasrc/database.cc | 24 +-
src/lib/datasrc/database.h | 51 +-
src/lib/datasrc/datasrc_messages.mes | 41 +-
src/lib/datasrc/exceptions.h | 20 +
src/lib/datasrc/factory.cc | 2 +-
src/lib/datasrc/factory.h | 2 +-
src/lib/datasrc/memory/domaintree.h | 2 +-
src/lib/datasrc/memory/memory_client.cc | 122 +---
src/lib/datasrc/memory/memory_client.h | 79 +--
src/lib/datasrc/memory/memory_messages.mes | 6 +-
src/lib/datasrc/memory/rdata_serialization.h | 2 +-
src/lib/datasrc/memory/rdataset.cc | 10 +-
src/lib/datasrc/memory/zone_data_loader.cc | 6 +
src/lib/datasrc/memory/zone_finder.cc | 2 +-
src/lib/datasrc/memory/zone_table.cc | 15 +-
src/lib/datasrc/memory/zone_table.h | 7 +
src/lib/datasrc/sqlite3_accessor.cc | 22 +-
src/lib/datasrc/sqlite3_accessor.h | 6 +-
src/lib/datasrc/tests/Makefile.am | 1 +
src/lib/datasrc/tests/cache_config_unittest.cc | 69 +++
src/lib/datasrc/tests/client_list_unittest.cc | 83 ++-
src/lib/datasrc/tests/database_unittest.cc | 12 +-
src/lib/datasrc/tests/factory_unittest.cc | 2 +-
src/lib/datasrc/tests/memory/Makefile.am | 1 +
.../datasrc/tests/memory/memory_client_unittest.cc | 276 ++++-----
.../datasrc/tests/memory/zone_finder_unittest.cc | 21 +-
src/lib/datasrc/tests/memory/zone_loader_util.cc | 93 +++
src/lib/datasrc/tests/memory/zone_loader_util.h | 57 ++
.../datasrc/tests/memory/zone_table_unittest.cc | 8 +
src/lib/datasrc/tests/mock_client.cc | 2 +-
src/lib/datasrc/tests/mock_client.h | 5 +
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc | 16 +-
.../datasrc/tests/zone_finder_context_unittest.cc | 24 +-
src/lib/datasrc/tests/zone_loader_unittest.cc | 32 +-
.../datasrc/tests/zone_table_accessor_unittest.cc | 112 ++++
src/lib/datasrc/zone_finder.cc | 3 +-
src/lib/datasrc/zone_loader.cc | 2 +-
src/lib/datasrc/zone_loader.h | 2 +-
src/lib/datasrc/zone_table_accessor.h | 212 +++++++
src/lib/datasrc/zone_table_accessor_cache.cc | 60 ++
src/lib/datasrc/zone_table_accessor_cache.h | 76 +++
src/lib/dhcp/Makefile.am | 3 +
src/lib/dhcp/dhcp6.h | 2 +-
src/lib/dhcp/hwaddr.cc | 11 +-
src/lib/dhcp/hwaddr.h | 2 +
src/lib/dhcp/iface_mgr.cc | 287 ++++-----
src/lib/dhcp/iface_mgr.h | 504 +++++++++-------
src/lib/dhcp/iface_mgr_bsd.cc | 7 +-
src/lib/dhcp/iface_mgr_linux.cc | 70 +--
src/lib/dhcp/iface_mgr_sun.cc | 7 +-
src/lib/dhcp/libdhcp++.cc | 19 +-
src/lib/dhcp/libdhcp++.h | 21 +-
src/lib/dhcp/option_custom.cc | 11 +-
src/lib/dhcp/option_definition.h | 4 +-
src/lib/dhcp/pkt6.cc | 235 +++++++-
src/lib/dhcp/pkt6.h | 106 +++-
src/lib/dhcp/pkt_filter.h | 84 +++
src/lib/dhcp/pkt_filter_inet.cc | 264 +++++++++
src/lib/dhcp/pkt_filter_inet.h | 76 +++
src/lib/dhcp/pkt_filter_lpf.cc | 45 ++
src/lib/dhcp/pkt_filter_lpf.h | 72 +++
src/lib/dhcp/tests/hwaddr_unittest.cc | 11 +-
src/lib/dhcp/tests/iface_mgr_unittest.cc | 144 ++++-
src/lib/dhcp/tests/option_custom_unittest.cc | 18 +-
src/lib/dhcp/tests/option_int_unittest.cc | 12 +-
src/lib/dhcp/tests/pkt6_unittest.cc | 247 +++++++-
src/lib/dhcpsrv/Makefile.am | 9 +-
src/lib/dhcpsrv/alloc_engine.cc | 2 -
src/lib/dhcpsrv/dhcp_config_parser.h | 103 +++-
src/lib/dhcpsrv/lease_mgr.h | 2 -
src/lib/dhcpsrv/mysql_lease_mgr.cc | 16 +-
src/lib/dhcpsrv/subnet.h | 2 +-
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc | 121 ++++
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc | 54 +-
src/lib/dns/gen-rdatacode.py.in | 3 +
src/lib/dns/master_loader.cc | 2 +-
src/lib/dns/nsec3hash.cc | 2 +-
src/lib/dns/python/pydnspp_common.h | 2 +-
src/lib/dns/python/rrset_python.cc | 10 +-
src/lib/dns/python/tests/nsec3hash_python_test.py | 2 +-
src/lib/dns/python/tests/rrset_python_test.py | 7 +-
.../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/dnskey_48.cc | 187 ++++--
src/lib/dns/rdata/generic/dnskey_48.h | 9 +
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 | 104 +++-
src/lib/dns/rdata/generic/nsec3_50.h | 3 +
src/lib/dns/rdata/generic/nsec3param_51.cc | 81 ++-
src/lib/dns/rdata/generic/nsec3param_51.h | 4 +
src/lib/dns/rdata/generic/nsec_47.cc | 6 +-
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/rdata_dnskey_unittest.cc | 166 ++++--
src/lib/dns/tests/rdata_nsec3_unittest.cc | 140 +++--
src/lib/dns/tests/rdata_nsec3param_unittest.cc | 129 +++-
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/tsigrecord.cc | 7 +-
src/lib/log/log_dbglevels.h | 2 +-
src/lib/log/logger_manager_impl.h | 2 +-
src/lib/log/tests/Makefile.am | 4 +
.../log/tests/message_initializer_1_unittest.cc | 2 +-
src/lib/nsas/nameserver_entry.cc | 4 +-
src/lib/nsas/tests/nameserver_entry_unittest.cc | 6 +-
src/lib/nsas/tests/zone_entry_unittest.cc | 2 +-
src/lib/nsas/zone_entry.cc | 8 +-
src/lib/python/bind10_config.py.in | 2 +-
src/lib/python/isc/bind10/component.py | 2 +-
.../python/isc/bind10/tests/sockcreator_test.py | 2 +-
.../python/isc/bind10/tests/socket_cache_test.py | 2 +-
src/lib/python/isc/cc/session.py | 4 +-
src/lib/python/isc/config/ccsession.py | 2 +-
src/lib/python/isc/config/cfgmgr.py | 2 +-
src/lib/python/isc/config/tests/cfgmgr_test.py | 4 +-
src/lib/python/isc/datasrc/client_inc.cc | 2 +-
src/lib/python/isc/datasrc/client_python.cc | 4 +-
src/lib/python/isc/datasrc/finder_python.cc | 2 +-
src/lib/python/isc/datasrc/updater_python.cc | 2 +-
src/lib/python/isc/ddns/session.py | 2 +-
src/lib/python/isc/ddns/tests/session_tests.py | 2 +-
src/lib/python/isc/notify/notify_out.py | 61 +-
src/lib/python/isc/notify/notify_out_messages.mes | 2 +-
src/lib/python/isc/notify/tests/notify_out_test.py | 137 ++++-
src/lib/python/isc/server_common/dns_tcp.py | 2 +-
src/lib/python/isc/sysinfo/sysinfo.py | 2 +-
src/lib/python/isc/util/socketserver_mixin.py | 2 +-
src/lib/resolve/tests/recursive_query_unittest.cc | 2 +-
src/lib/server_common/portconfig.cc | 2 +-
src/lib/util/Makefile.am | 15 +
src/lib/util/memory_segment.h | 253 +++++++-
src/lib/util/memory_segment_local.cc | 23 +-
src/lib/util/memory_segment_local.h | 32 +
src/lib/util/memory_segment_mapped.cc | 382 ++++++++++++
src/lib/util/memory_segment_mapped.h | 261 ++++++++
src/lib/util/python/gen_wiredata.py.in | 47 +-
src/lib/util/tests/Makefile.am | 6 +
src/lib/util/tests/fd_share_tests.cc | 2 +-
.../util/tests/interprocess_sync_file_unittest.cc | 38 +-
.../tests/interprocess_util.cc} | 43 +-
.../tests/interprocess_util.h} | 28 +-
.../util/tests/memory_segment_common_unittest.cc | 92 +++
.../tests/memory_segment_common_unittest.h} | 27 +-
.../util/tests/memory_segment_local_unittest.cc | 9 +-
.../util/tests/memory_segment_mapped_unittest.cc | 620 ++++++++++++++++++++
src/lib/util/unittests/resolver.h | 2 +-
src/lib/xfr/tests/client_test.cc | 2 +-
tests/lettuce/features/auth_badzone.feature | 6 +-
tests/lettuce/features/bindctl_commands.feature | 15 +
tests/lettuce/features/example.feature | 2 +-
tests/lettuce/features/nsec3_auth.feature | 2 +-
tests/lettuce/features/terrain/bind10_control.py | 20 +-
.../lettuce/features/xfrin_notify_handling.feature | 8 +-
tests/tools/badpacket/option_info.h | 2 +-
tests/tools/dhcp-ubench/dhcp-perf-guide.xml | 2 +-
tests/tools/perfdhcp/command_options.cc | 2 +-
tests/tools/perfdhcp/pkt_transform.h | 2 +-
tests/tools/perfdhcp/stats_mgr.h | 19 +-
tests/tools/perfdhcp/test_control.cc | 36 +-
tests/tools/perfdhcp/test_control.h | 6 +-
.../perfdhcp/tests/command_options_unittest.cc | 2 +-
.../tools/perfdhcp/tests/test_control_unittest.cc | 12 +
244 files changed, 8174 insertions(+), 2419 deletions(-)
create mode 100644 doc/design/ipc-high.txt
create mode 100644 m4macros/Makefile.am
create mode 100644 m4macros/ax_sqlite3_for_bind10.m4
create mode 100644 src/bin/resolver/bench/Makefile.am
copy src/{lib/datasrc/memory/logger.cc => bin/resolver/bench/dummy_work.cc} (78%)
copy src/{lib/dns/python/zone_checker_python.h => bin/resolver/bench/dummy_work.h} (63%)
create mode 100644 src/bin/resolver/bench/fake_resolution.cc
create mode 100644 src/bin/resolver/bench/fake_resolution.h
copy src/{lib/dns/python/zone_checker_python.h => bin/resolver/bench/main.cc} (67%)
create mode 100644 src/bin/resolver/bench/naive_resolver.cc
copy src/{lib/dns/python/zone_checker_python.h => bin/resolver/bench/naive_resolver.h} (56%)
create mode 100644 src/lib/asiolink/tests/io_service_unittest.cc
delete mode 100644 src/lib/datasrc/data_source.h
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/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/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/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/util/memory_segment_mapped.cc
create mode 100644 src/lib/util/memory_segment_mapped.h
copy src/lib/{dns/python/zone_checker_python.h => util/tests/interprocess_util.cc} (60%)
copy src/lib/{dns/python/zone_checker_python.h => util/tests/interprocess_util.h} (67%)
create mode 100644 src/lib/util/tests/memory_segment_common_unittest.cc
copy src/lib/{dns/python/zone_checker_python.h => util/tests/memory_segment_common_unittest.h} (59%)
create mode 100644 src/lib/util/tests/memory_segment_mapped_unittest.cc
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index f6ce7a3..5552772 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,106 @@
+bind10-1.0.0beta2 released on May 3, 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-dchp4 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.0.0beta1 released on April 4, 2013
598. [func]* jinmei
diff --git a/Makefile.am b/Makefile.am
index 8e01f4a..10ef321 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
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" ; \
diff --git a/configure.ac b/configure.ac
index 72eec7e..06bb040 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, 1.1.0beta1, bind10-dev at isc.org)
+AC_INIT(bind10, 1.1.0beta2, 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
@@ -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
@@ -884,6 +882,17 @@ 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
+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])
+AC_SUBST(BOOST_MAPPED_FILE_CXXFLAG)
+
# Add some default CPP flags needed for Boost, identified by the AX macro.
CPPFLAGS="$CPPFLAGS $CPPFLAGS_BOOST_THREADCONF"
@@ -1032,12 +1041,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.
@@ -1190,6 +1203,7 @@ AC_CONFIG_FILES([Makefile
src/bin/dhcp4/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
@@ -1311,6 +1325,7 @@ AC_CONFIG_FILES([Makefile
tests/tools/perfdhcp/Makefile
tests/tools/perfdhcp/tests/Makefile
tests/tools/perfdhcp/tests/testdata/Makefile
+ m4macros/Makefile
dns++.pc
])
AC_OUTPUT([doc/version.ent
diff --git a/doc/design/ipc-high.txt b/doc/design/ipc-high.txt
new file mode 100644
index 0000000..3f46b5c
--- /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/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 27e6ebf..d0d1d4c 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -4110,7 +4110,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
@@ -4822,7 +4822,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
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..577af6b 100644
--- a/m4macros/ax_boost_for_bind10.m4
+++ b/m4macros/ax_boost_for_bind10.m4
@@ -23,7 +23,11 @@ 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.
AC_DEFUN([AX_BOOST_FOR_BIND10], [
AC_LANG_SAVE
@@ -101,10 +105,49 @@ if test "X$GXX" = "Xyes"; then
CXXFLAGS="$CXXFLAGS_SAVED"
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
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
+
+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/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index 3fe9b9c..90efee7 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -42,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>
diff --git a/src/bin/auth/datasrc_clients_mgr.h b/src/bin/auth/datasrc_clients_mgr.h
index 5bbdb99..f75b394 100644
--- a/src/bin/auth/datasrc_clients_mgr.h
+++ b/src/bin/auth/datasrc_clients_mgr.h
@@ -25,7 +25,7 @@
#include <cc/data.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <datasrc/client_list.h>
#include <datasrc/memory/zone_writer.h>
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/bind10/init.py.in b/src/bin/bind10/init.py.in
index e57971a..efc0b04 100755
--- a/src/bin/bind10/init.py.in
+++ b/src/bin/bind10/init.py.in
@@ -223,7 +223,7 @@ 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
+ # 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
diff --git a/src/bin/bind10/tests/init_test.py.in b/src/bin/bind10/tests/init_test.py.in
index f384865..8ac6458 100644
--- a/src/bin/bind10/tests/init_test.py.in
+++ b/src/bin/bind10/tests/init_test.py.in
@@ -1595,7 +1595,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 = []
diff --git a/src/bin/cfgmgr/plugins/datasrc.spec.pre.in b/src/bin/cfgmgr/plugins/datasrc.spec.pre.in
index eac14c4..3f16758 100644
--- a/src/bin/cfgmgr/plugins/datasrc.spec.pre.in
+++ b/src/bin/cfgmgr/plugins/datasrc.spec.pre.in
@@ -68,6 +68,12 @@
"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/tsig_keys_test.py b/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py
index 8c1639c..575879d 100644
--- a/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py
+++ b/src/bin/cfgmgr/plugins/tests/tsig_keys_test.py
@@ -92,7 +92,7 @@ class TSigKeysTest(unittest.TestCase):
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/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/cmdctl.py.in b/src/bin/cmdctl/cmdctl.py.in
index 219dc53..b1ee903 100755
--- a/src/bin/cmdctl/cmdctl.py.in
+++ b/src/bin/cmdctl/cmdctl.py.in
@@ -247,6 +247,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()
@@ -326,7 +327,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
@@ -362,6 +382,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):
@@ -402,7 +423,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.
@@ -424,31 +445,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:
diff --git a/src/bin/cmdctl/tests/cmdctl_test.py b/src/bin/cmdctl/tests/cmdctl_test.py
index dd1a6f2..4a6b0e3 100644
--- a/src/bin/cmdctl/tests/cmdctl_test.py
+++ b/src/bin/cmdctl/tests/cmdctl_test.py
@@ -373,7 +373,30 @@ class TestSecureHTTPRequestHandler(unittest.TestCase):
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 {}
@@ -390,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):
@@ -502,8 +531,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', 'print_settings',
+ None)
+ self.assertEqual(2, len(self.cmdctl.sent_messages))
+ self.assertEqual(({'command': ['print_settings']}, 'Cmdctl'),
+ self.cmdctl.sent_messages[-1])
+ self.assertEqual(1, rcode)
class MySecureHTTPServer(SecureHTTPServer):
def server_bind(self):
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/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index d8a586b..3a54c28 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -56,15 +56,6 @@ 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;
@@ -198,7 +189,7 @@ public:
/// @brief Put a parsed value to the storage.
virtual void commit() {
if (storage_ != NULL && !param_name_.empty()) {
- (*storage_)[param_name_] = value_;
+ storage_->setParam(param_name_, value_);
}
}
@@ -292,7 +283,7 @@ public:
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_;
+ storage_->setParam(param_name_, value_);
}
}
@@ -364,7 +355,7 @@ public:
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_;
+ storage_->setParam(param_name_, value_);
}
}
@@ -735,7 +726,7 @@ private:
/// 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
+ /// is initialized but this check is not needed here because it is done
/// in the \ref build function.
///
/// @throw DhcpConfigError if parameters provided in the configuration
@@ -744,7 +735,7 @@ private:
// 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_);
+ 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.");
@@ -753,9 +744,10 @@ private:
<< "', 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_);
+ // 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");
@@ -764,7 +756,7 @@ private:
<< "', space character is not allowed");
}
- std::string option_space = getParam<std::string>("space", string_values_);
+ 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 '"
@@ -805,8 +797,8 @@ private:
}
// 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_);
+ 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;
@@ -838,7 +830,7 @@ private:
<< " does not have a definition.");
}
- // @todo We have a limited set of option definitions intiialized at the moment.
+ // @todo We have a limited set of option definitions initialized 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
@@ -1080,8 +1072,9 @@ 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_);
+ std::string space = string_values_.getParam("space");
if (!OptionSpace::validateName(space)) {
isc_throw(DhcpConfigError, "invalid option space name '"
<< space << "'");
@@ -1089,12 +1082,11 @@ private:
// 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_);
+ 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;
@@ -1124,8 +1116,8 @@ private:
}
// 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_);
+ 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, ",");
@@ -1422,13 +1414,16 @@ private:
///
/// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
void createSubnet() {
- StringStorage::const_iterator it = string_values_.find("subnet");
- if (it == string_values_.end()) {
+ std::string subnet_txt;
+ try {
+ subnet_txt = string_values_.getParam("subnet");
+ } catch (DhcpConfigError) {
+ // Rethrow with precise error.
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");
@@ -1440,7 +1435,7 @@ private:
size_t pos = subnet_txt.find("/");
if (pos == string::npos) {
isc_throw(DhcpConfigError,
- "Invalid subnet syntax (prefix/len expected):" << it->second);
+ "Invalid subnet syntax (prefix/len expected):" << subnet_txt);
}
// Try to create the address object. It also validates that
@@ -1540,7 +1535,6 @@ private:
/// @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;
@@ -1571,26 +1565,21 @@ private:
/// @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
+ try {
+ // look for local value
+ value = uint32_values_.getParam(name);
+ } catch (DhcpConfigError) {
+ try {
+ // no local, use global value
+ value = uint32_defaults.getParam(name);
+ } catch (DhcpConfigError) {
+ isc_throw(DhcpConfigError, "Mandatory parameter " << name
<< " missing (no global default and no subnet-"
<< "specific value)");
+ }
}
+
+ return (Triplet<uint32_t>(value));
}
/// storage for subnet-specific uint32 values
@@ -1859,7 +1848,7 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
return (answer);
}
-const std::map<std::string, uint32_t>& getUint32Defaults() {
+const Uint32Storage& getUint32Defaults() {
return (uint32_defaults);
}
diff --git a/src/bin/dhcp4/config_parser.h b/src/bin/dhcp4/config_parser.h
index 4f1ea32..51a7556 100644
--- a/src/bin/dhcp4/config_parser.h
+++ b/src/bin/dhcp4/config_parser.h
@@ -13,6 +13,7 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <exceptions/exceptions.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <cc/data.h>
#include <stdint.h>
#include <string>
@@ -66,7 +67,7 @@ configureDhcp4Server(Dhcpv4Srv&,
/// Uint32Parser works as expected.
///
/// @return a reference to a global uint32 values storage.
-const std::map<std::string, uint32_t>& getUint32Defaults();
+const Uint32Storage& getUint32Defaults();
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h
index e45deff..8f14f4b 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.h
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h
@@ -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
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index 2f49ac8..8b3e255 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -74,7 +74,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
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 261e213..6f119ed 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -57,7 +57,7 @@ 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) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
try {
// First call to instance() will create IfaceMgr (it's a singleton)
@@ -67,7 +67,7 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
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);
@@ -287,9 +287,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 +317,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 +343,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
@@ -517,6 +517,28 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
answer->setYiaddr(lease->addr_);
+ // If remote address is not set, we are dealing with a directly
+ // connected client requesting new lease. We can send response to
+ // the address assigned in the lease, but first we have to make sure
+ // that IfaceMgr supports responding directly to the client when
+ // client doesn't have address assigned to its interface yet.
+ if (answer->getRemoteAddr().toText() == "0.0.0.0") {
+ if (IfaceMgr::instance().isDirectResponseSupported()) {
+ answer->setRemoteAddr(lease->addr_);
+ } else {
+ // Since IfaceMgr does not support direct responses to
+ // clients not having IP addresses, we have to send response
+ // to broadcast. We don't check whether the use_bcast flag
+ // was set in the constructor, because this flag is only used
+ // by unit tests to prevent opening broadcast sockets, as
+ // it requires root privileges. If this function is invoked by
+ // unit tests, we expect that it sets broadcast address if
+ // direct response is not supported, so as a test can verify
+ // function's behavior, regardless of the use_bcast flag's value.
+ answer->setRemoteAddr(IOAddress("255.255.255.255"));
+ }
+ }
+
// IP Address Lease time (type 51)
opt = OptionPtr(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
opt->setUint32(lease->valid_lft_);
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index 1c988b1..bc8851e 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -66,8 +66,10 @@ class Dhcpv4Srv : public boost::noncopyable {
/// @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.
Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT,
- const char* dbconfig = "type=memfile");
+ const char* dbconfig = "type=memfile",
+ const bool use_bcast = true);
/// @brief Destructor. Used during DHCPv4 service shutdown.
~Dhcpv4Srv();
@@ -216,7 +218,7 @@ protected:
/// @param msg_type specifies message type
void appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type);
- /// @brief Returns server-intentifier option
+ /// @brief Returns server-identifier option
///
/// @return server-id option
OptionPtr getServerID() { return serverid_; }
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index 80b304c..dd11ec3 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -52,15 +52,14 @@ public:
// 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 Uint32Storage& uint32_defaults = getUint32Defaults();
+ 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
@@ -83,7 +82,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.
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index c938155..18e3ee3 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -17,6 +17,7 @@
#include <asiolink/io_address.h>
#include <dhcp/dhcp4.h>
+#include <dhcp/iface_mgr.h>
#include <dhcp/option.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_custom.h>
@@ -44,7 +45,16 @@ namespace {
class NakedDhcpv4Srv: public Dhcpv4Srv {
// "Naked" DHCPv4 server, exposes internal fields
public:
- NakedDhcpv4Srv(uint16_t port = 0):Dhcpv4Srv(port) { }
+
+ /// @brief Constructor.
+ ///
+ /// It disables configuration of broadcast options on
+ /// sockets that are opened by the Dhcpv4Srv constructor.
+ /// Setting broadcast options requires root privileges
+ /// which is not the case when running unit tests.
+ NakedDhcpv4Srv(uint16_t port = 0)
+ : Dhcpv4Srv(port, "type=memfile", false) {
+ }
using Dhcpv4Srv::processDiscover;
using Dhcpv4Srv::processRequest;
@@ -116,10 +126,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() {
@@ -170,6 +180,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,6 +357,120 @@ public:
EXPECT_TRUE(expected_clientid->getData() == opt->getData());
}
+ /// @brief Tests if Discover or Request message is processed correctly
+ ///
+ /// @param msg_type DHCPDISCOVER or DHCPREQUEST
+ /// @param client_addr client address
+ /// @param relay_addr relay address
+ void testDiscoverRequest(const uint8_t msg_type,
+ const IOAddress& client_addr,
+ const IOAddress& relay_addr) {
+
+ boost::scoped_ptr<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(msg_type, 1234));
+ boost::shared_ptr<Pkt4> rsp;
+
+ req->setIface("eth0");
+ req->setIndex(17);
+ req->setHWAddr(1, 6, mac);
+ req->setRemoteAddr(IOAddress(client_addr));
+ req->setGiaddr(relay_addr);
+
+ // 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());
+
+ }
+
+ if (relay_addr.toText() != "0.0.0.0") {
+ // This is relayed message. It should be sent brsp to relay address.
+ EXPECT_EQ(req->getGiaddr().toText(),
+ rsp->getRemoteAddr().toText());
+
+ } else if (client_addr.toText() != "0.0.0.0") {
+ // This is a message from a client having an IP address.
+ EXPECT_EQ(req->getRemoteAddr().toText(),
+ rsp->getRemoteAddr().toText());
+
+ } else {
+ // This is a message from a client having no IP address yet.
+ // If IfaceMgr supports direct traffic the response should
+ // be sent to the new address assigned to the client.
+ if (IfaceMgr::instance().isDirectResponseSupported()) {
+ EXPECT_EQ(rsp->getYiaddr(),
+ rsp->getRemoteAddr().toText());
+
+ // If direct response to the client having no IP address is
+ // not supported, response should go to broadcast.
+ } else {
+ EXPECT_EQ("255.255.255.255", rsp->getRemoteAddr().toText());
+
+ }
+
+ }
+
+ 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);
+
+ }
+
~Dhcpv4SrvTest() {
CfgMgr::instance().deleteSubnets4();
@@ -389,7 +515,7 @@ TEST_F(Dhcpv4SrvTest, basic) {
delete naked_srv;
}
-// Verifies that received DISCOVER can be processed correctly,
+// Verifies that DISCOVER received via relay can be processed correctly,
// that the OFFER message generated in response is valid and
// contains necessary options.
//
@@ -397,203 +523,56 @@ TEST_F(Dhcpv4SrvTest, basic) {
// 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());
-
- messageCheck(pkt, offer);
-
- // Check that the requested options are returned.
- optionsCheck(offer);
-
- // Now repeat the test for directly sent message
- pkt->setHops(0);
- pkt->setGiaddr(IOAddress("0.0.0.0"));
- pkt->setRemotePort(DHCP4_CLIENT_PORT);
-
- EXPECT_NO_THROW(
- offer = srv->processDiscover(pkt);
- );
-
- // Should return something
- ASSERT_TRUE(offer);
-
- EXPECT_EQ(DHCPOFFER, offer->getType());
-
- // This is direct message. It should be sent back to origin, not
- // to relay.
- EXPECT_EQ(pkt->getRemoteAddr(), offer->getRemoteAddr());
-
- messageCheck(pkt, offer);
+TEST_F(Dhcpv4SrvTest, processDiscoverRelay) {
+ testDiscoverRequest(DHCPDISCOVER,
+ IOAddress("192.0.2.56"),
+ IOAddress("192.0.2.67"));
+}
- // Check that the requested options are returned.
- optionsCheck(offer);
+// Verifies that the non-relayed DISCOVER is processed correctly when
+// client source address is specified.
+TEST_F(Dhcpv4SrvTest, processDiscoverNoRelay) {
+ testDiscoverRequest(DHCPDISCOVER,
+ IOAddress("0.0.0.0"),
+ IOAddress("192.0.2.67"));
+}
- delete srv;
+// Verified that the non-relayed DISCOVER is processed correctly when
+// client source address is not specified.
+TEST_F(Dhcpv4SrvTest, processDiscoverNoClientAddr) {
+ testDiscoverRequest(DHCPDISCOVER,
+ IOAddress("0.0.0.0"),
+ IOAddress("0.0.0.0"));
}
-// Verifies that received REQUEST can be processed correctly,
-// that the ACK message generated in response is valid and
+// Verifies that REQUEST received via relay 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.
-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);
+// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
+// and DiscoverInvalidHint.
+TEST_F(Dhcpv4SrvTest, processRequestRelay) {
+ testDiscoverRequest(DHCPREQUEST,
+ IOAddress("192.0.2.56"),
+ IOAddress("192.0.2.67"));
+}
- // Check that the requested options are returned.
- optionsCheck(ack);
+// Verifies that the non-relayed REQUEST is processed correctly when
+// client source address is specified.
+TEST_F(Dhcpv4SrvTest, processRequestNoRelay) {
+ testDiscoverRequest(DHCPREQUEST,
+ IOAddress("0.0.0.0"),
+ IOAddress("192.0.2.67"));
+}
- delete srv;
+// Verified that the non-relayed REQUEST is processed correctly when
+// client source address is not specified.
+TEST_F(Dhcpv4SrvTest, processRequestNoClientAddr) {
+ testDiscoverRequest(DHCPREQUEST,
+ IOAddress("0.0.0.0"),
+ IOAddress("0.0.0.0"));
}
TEST_F(Dhcpv4SrvTest, processRelease) {
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 76ed228..0c09361 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -66,15 +66,6 @@ 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;
@@ -209,7 +200,7 @@ public:
/// @brief Put a parsed value to the storage.
virtual void commit() {
if (storage_ != NULL && !param_name_.empty()) {
- (*storage_)[param_name_] = value_;
+ storage_->setParam(param_name_, value_);
}
}
@@ -317,7 +308,7 @@ public:
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_;
+ storage_->setParam(param_name_, value_);
}
}
@@ -393,7 +384,7 @@ public:
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_;
+ storage_->setParam(param_name_, value_);
}
}
@@ -764,7 +755,7 @@ private:
/// 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
+ /// is initialized but this check is not needed here because it is done
/// in the \ref build function.
///
/// @throw DhcpConfigError if parameters provided in the configuration
@@ -774,7 +765,7 @@ private:
// 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_);
+ 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 DHCPv6.");
@@ -785,7 +776,7 @@ private:
}
// 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_);
+ 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");
@@ -794,7 +785,7 @@ private:
<< "', space character is not allowed");
}
- std::string option_space = getParam<std::string>("space", string_values_);
+ 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 '"
@@ -835,8 +826,8 @@ private:
}
// 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_);
+ 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;
@@ -868,7 +859,7 @@ private:
<< " does not have a definition.");
}
- // @todo We have a limited set of option definitions intiialized at the moment.
+ // @todo We have a limited set of option definitions initialized 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
@@ -1109,7 +1100,7 @@ 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_);
+ std::string space = string_values_.getParam("space");
if (!OptionSpace::validateName(space)) {
isc_throw(DhcpConfigError, "invalid option space name '"
<< space << "'");
@@ -1117,12 +1108,11 @@ private:
// 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_);
+ 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;
@@ -1153,8 +1143,7 @@ private:
// 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_);
+ 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, ",");
@@ -1448,17 +1437,19 @@ private:
///
/// @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()) {
+ std::string subnet_txt;
+ try {
+ subnet_txt = string_values_.getParam("subnet");
+ } catch (DhcpConfigError) {
+ // rethrow with precise error
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
@@ -1467,7 +1458,7 @@ private:
size_t pos = subnet_txt.find("/");
if (pos == string::npos) {
isc_throw(DhcpConfigError,
- "Invalid subnet syntax (prefix/len expected):" << it->second);
+ "Invalid subnet syntax (prefix/len expected):" << subnet_txt);
}
// Try to create the address object. It also validates that
@@ -1487,11 +1478,11 @@ private:
// Get interface name. If it is defined, then the subnet is available
// directly over specified network interface.
-
- string iface;
- StringStorage::const_iterator iface_iter = string_values_.find("interface");
- if (iface_iter != string_values_.end()) {
- iface = iface_iter->second;
+ std::string iface;
+ try {
+ iface = string_values_.getParam("interface");
+ } catch (DhcpConfigError) {
+ // iface not mandatory so swallow the exception
}
/// @todo: Convert this to logger once the parser is working reliably
@@ -1624,26 +1615,21 @@ private:
/// @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
+ try {
+ // look for local value
+ value = uint32_values_.getParam(name);
+ } catch (DhcpConfigError) {
+ try {
+ // no local, use global value
+ value = uint32_defaults.getParam(name);
+ } catch (DhcpConfigError) {
+ isc_throw(DhcpConfigError, "Mandatory parameter " << name
<< " missing (no global default and no subnet-"
<< "specific value)");
+ }
}
+
+ return (Triplet<uint32_t>(value));
}
/// storage for subnet-specific uint32 values
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h
index 908304d..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
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 4c19e74..75a5337 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -329,7 +329,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...
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index bdcb560..c7b1f0f 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -40,7 +40,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 +69,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_; }
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index 63fc980..f4ebf0c 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -88,7 +88,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.
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index 8cf6191..efa3cbd 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -790,7 +790,7 @@ 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)
diff --git a/src/bin/msgq/tests/msgq_run_test.py b/src/bin/msgq/tests/msgq_run_test.py
index 8e8cb65..95173e0 100644
--- a/src/bin/msgq/tests/msgq_run_test.py
+++ b/src/bin/msgq/tests/msgq_run_test.py
@@ -72,12 +72,21 @@ class MsgqRunTest(unittest.TestCase):
# Start msgq
self.__msgq = subprocess.Popen([MSGQ_PATH, '-s', SOCKET_PATH],
close_fds=True)
- # Wait for it to become ready (up to the alarm-set timeout)
- while not os.path.exists(SOCKET_PATH):
- # Just a short wait, so we don't hog CPU, but don't wait too long
- time.sleep(0.01)
# 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):
"""
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index 98705bf..e5a5656 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -186,7 +186,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 +338,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):
@@ -834,7 +834,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
diff --git a/src/bin/resolver/Makefile.am b/src/bin/resolver/Makefile.am
index 47e242c..a549e6a 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
diff --git a/src/bin/resolver/bench/Makefile.am b/src/bin/resolver/bench/Makefile.am
new file mode 100644
index 0000000..e4689fb
--- /dev/null
+++ b/src/bin/resolver/bench/Makefile.am
@@ -0,0 +1,25 @@
+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 = $(GTEST_LDADD)
+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..c08e45a
--- /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 <cstdlib>
+
+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_->outstanding_ = false;
+ 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..c2011d3
--- /dev/null
+++ b/src/bin/resolver/bench/fake_resolution.h
@@ -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.
+
+#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;
+ }
+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/stats/stats.py.in b/src/bin/stats/stats.py.in
index 577afe6..7ec530b 100755
--- a/src/bin/stats/stats.py.in
+++ b/src/bin/stats/stats.py.in
@@ -194,7 +194,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.
'''
diff --git a/src/bin/stats/stats_httpd.py.in b/src/bin/stats/stats_httpd.py.in
index 82b9dcc..c3cdb76 100755
--- a/src/bin/stats/stats_httpd.py.in
+++ b/src/bin/stats/stats_httpd.py.in
@@ -407,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/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py
index f807168..540c707 100644
--- a/src/bin/stats/tests/b10-stats_test.py
+++ b/src/bin/stats/tests/b10-stats_test.py
@@ -376,7 +376,7 @@ class TestStats(unittest.TestCase):
'report_time': 42})),
('update_module', ())], call_log)
- # Then update faked timestamp so the intial polling will happen, and
+ # Then update faked timestamp so the initial polling will happen, and
# confirm that.
call_log = []
stats.get_timestamp = lambda: 10
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index 68158db..5759bb1 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -2193,7 +2193,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
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index 0a67718..52d230b 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -187,7 +187,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
@@ -269,7 +269,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):
diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
index 619e017..fcb929a 100755
--- a/src/bin/zonemgr/zonemgr.py.in
+++ b/src/bin/zonemgr/zonemgr.py.in
@@ -191,7 +191,7 @@ class ZonemgrRefresh:
self._set_zone_retry_timer(zone_name_class)
def zone_handle_notify(self, zone_name_class, master):
- """Handle an incomding NOTIFY message via the Auth module.
+ """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.
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/asiodns_messages.mes b/src/lib/asiodns/asiodns_messages.mes
index db2902e..5f2a264 100644
--- a/src/lib/asiodns/asiodns_messages.mes
+++ b/src/lib/asiodns/asiodns_messages.mes
@@ -53,6 +53,78 @@ 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_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 +136,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_service.cc b/src/lib/asiodns/dns_service.cc
index f72d24b..0324980 100644
--- a/src/lib/asiodns/dns_service.cc
+++ b/src/lib/asiodns/dns_service.cc
@@ -58,9 +58,15 @@ public:
template<class Ptr, class Server> void addServerFromFD(int fd, int af) {
Ptr server(new Server(io_service_.get_io_service(), fd, af, checkin_,
lookup_, answer_));
- server->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(new SyncUDPServer(io_service_.get_io_service(),
+ fd, af, lookup_));
+ startServer(server);
}
void setTCPRecvTimeout(size_t timeout) {
@@ -72,6 +78,13 @@ 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,
@@ -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/sync_udp_server.cc b/src/lib/asiodns/sync_udp_server.cc
index d5d2a7a..9a06691 100644
--- a/src/lib/asiodns/sync_udp_server.cc
+++ b/src/lib/asiodns/sync_udp_server.cc
@@ -39,18 +39,21 @@ namespace isc {
namespace asiodns {
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),
+ recv_callback_(boost::bind(&SyncUDPServer::handleRead, this, _1, _2))
{
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 +64,36 @@ 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_, recv_callback_);
}
void
SyncUDPServer::handleRead(const asio::error_code& ec, const size_t length) {
- // Abort on fatal errors
if (ec) {
using namespace asio::error;
- if (ec.value() != would_block && ec.value() != try_again &&
- ec.value() != interrupted) {
+ 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 +101,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 +115,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());
}
}
@@ -181,13 +148,13 @@ SyncUDPServer::stop() {
/// for it won't be scheduled by io service not matter it is
/// submit to io service before or after close call. And we will
/// get bad_descriptor error.
- socket_->close();
+ 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..becbb2e 100644
--- a/src/lib/asiodns/sync_udp_server.h
+++ b/src/lib/asiodns/sync_udp_server.h
@@ -25,10 +25,14 @@
#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 <stdint.h>
@@ -39,29 +43,39 @@ namespace asiodns {
///
/// That means, the lookup handler must provide the answer right away.
/// This allows for implementation with less overhead, compared with
-/// the UDPClass.
+/// the \c UDPServer class.
class SyncUDPServer : public DNSServer, public boost::noncopyable {
public:
/// \brief Constructor
+ ///
+ /// Due to the nature of this server, it's meaningless if the lookup
+ /// callback is NULL. So the constructor 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);
+ 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 +128,39 @@ 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_;
+ // 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_;
+ // The callback functor for internal asynchronous read event. This is
+ // stateless (and it will be copied in the ASIO library anyway), so
+ // can be const
+ const boost::function<void(const asio::error_code&, size_t)>
+ recv_callback_;
// Auxiliary functions
@@ -144,3 +175,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..32a43ce 100644
--- a/src/lib/asiodns/tcp_server.cc
+++ b/src/lib/asiodns/tcp_server.cc
@@ -14,13 +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>
@@ -32,6 +25,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;
@@ -100,41 +101,58 @@ 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
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));
@@ -144,29 +162,22 @@ TCPServer::operator()(asio::error_code ec, size_t 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,7 +199,8 @@ 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_));
+ 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).
@@ -198,8 +215,7 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
// 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 +226,24 @@ 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_) {
// TODO: should we keep the connection open for a short time
// to see if new requests come in?
- socket_->close();
- CORO_YIELD return;
+ 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 +255,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 +283,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 +308,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/tests/dns_server_unittest.cc b/src/lib/asiodns/tests/dns_server_unittest.cc
index 51fb6b8..dd92dcb 100644
--- a/src/lib/asiodns/tests/dns_server_unittest.cc
+++ b/src/lib/asiodns/tests/dns_server_unittest.cc
@@ -117,7 +117,7 @@ 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,6 +147,24 @@ 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 can't get response, there is a timer wait
// for specified seconds (the value is just a estimate since server process logic is quite
@@ -353,6 +371,7 @@ class DNSServerTestBase : public::testing::Test {
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_,
@@ -375,6 +394,7 @@ class DNSServerTestBase : public::testing::Test {
}
delete checker_;
delete lookup_;
+ delete sync_lookup_;
delete answer_;
delete udp_server_;
delete udp_client_;
@@ -421,7 +441,8 @@ 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_;
@@ -482,19 +503,34 @@ 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,
+ 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_ = new TCPServer(this->service, fd_tcp, AF_INET6,
this->checker_, this->lookup_,
this->answer_);
}
+
+ // A helper factory of the tested UDP server class: allow customization
+ // by template specialization.
+ UDPServerClass* createServer(int fd, int af) {
+ return (new UDPServerClass(this->service, fd, af,
+ this->checker_, this->lookup_,
+ this->answer_));
+ }
};
+// Specialization for SyncUDPServer. It needs to use SyncDummyLookup.
+template<>
+SyncUDPServer*
+FdInit<SyncUDPServer>::createServer(int fd, int af) {
+ delete this->lookup_;
+ this->lookup_ = new SyncDummyLookup;
+ return (new SyncUDPServer(this->service, fd, af, this->lookup_));
+}
+
// This makes it the template as gtest wants it.
template<class Parent>
class DNSServerTest : public Parent { };
@@ -503,6 +539,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,7 +554,7 @@ 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_,
@@ -532,8 +573,10 @@ TYPED_TEST(DNSServerTest, stopUDPServerBeforeItStartServing) {
}
-// Test whether udp server stopped successfully during message check
-TYPED_TEST(DNSServerTest, stopUDPServerDuringMessageCheck) {
+// Test whether udp server stopped successfully during message check.
+// This only works for non-sync server; SyncUDPServer doesn't use checkin
+// callback.
+TEST_F(AsyncServerTest, stopUDPServerDuringMessageCheck) {
this->testStopServerByStopper(this->udp_server_, this->udp_client_,
this->checker_);
EXPECT_EQ(std::string(""), this->udp_client_->getReceivedData());
@@ -548,12 +591,13 @@ TYPED_TEST(DNSServerTest, stopUDPServerDuringQueryLookup) {
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,7 +609,7 @@ 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);
@@ -668,14 +712,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_,
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
@@ -694,7 +739,7 @@ TYPED_TEST(DNSServerTestBase, invalidTCPFD) {
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 +747,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);
@@ -723,4 +763,10 @@ TEST_F(SyncServerTest, mustResume) {
isc::Unexpected);
}
+// SyncUDPServer doesn't allow NULL lookup callback.
+TEST_F(SyncServerTest, nullLookupCallback) {
+ EXPECT_THROW(SyncUDPServer(service, 0, AF_INET, NULL),
+ isc::InvalidParameter);
+}
+
}
diff --git a/src/lib/asiodns/udp_server.cc b/src/lib/asiodns/udp_server.cc
index f84a4d6..7221296 100644
--- a/src/lib/asiodns/udp_server.cc
+++ b/src/lib/asiodns/udp_server.cc
@@ -82,8 +82,8 @@ struct UDPServer::Data {
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 {
@@ -212,14 +212,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);
@@ -270,7 +275,7 @@ UDPServer::operator()(asio::error_code ec, size_t length) {
// 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 +292,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,6 +327,8 @@ UDPServer::asyncLookup() {
/// Stop the UDPServer
void
UDPServer::stop() {
+ asio::error_code ec;
+
/// Using close instead of cancel, because cancel
/// will only cancel the asynchronized event already submitted
/// to io service, the events post to io service after
@@ -330,7 +337,10 @@ UDPServer::stop() {
/// for it won't be scheduled by io service not matter it is
/// submit to io service before or after close call. And we will
// get bad_descriptor error.
- data_->socket_->close();
+ 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 +349,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/asiolink/io_service.cc b/src/lib/asiolink/io_service.cc
index 15fad0c..0d791a1 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
@@ -63,6 +63,9 @@ 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) {
+ io_service_.post(callback);
+ }
private:
asio::io_service io_service_;
asio::io_service::work work_;
@@ -96,5 +99,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/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index b401834..70b94dc 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -33,6 +33,7 @@ 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_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/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/config/ccsession.cc b/src/lib/config/ccsession.cc
index f65831d..d094ab9 100644
--- a/src/lib/config/ccsession.cc
+++ b/src/lib/config/ccsession.cc
@@ -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,
diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h
index 15290e6..995a5cd 100644
--- a/src/lib/config/ccsession.h
+++ b/src/lib/config/ccsession.h
@@ -313,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.
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/Makefile.am b/src/lib/datasrc/Makefile.am
index d7c5978..5422f7d 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -24,8 +24,7 @@ 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
@@ -40,6 +39,9 @@ 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
diff --git a/src/lib/datasrc/cache_config.cc b/src/lib/datasrc/cache_config.cc
index 9cfe3b1..3896414 100644
--- a/src/lib/datasrc/cache_config.cc
+++ b/src/lib/datasrc/cache_config.cc
@@ -15,10 +15,19 @@
#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>
@@ -37,7 +46,7 @@ getEnabledFromConf(const Element& conf) {
std::string
getSegmentTypeFromConf(const Element& conf) {
- // If cache-zones is not explicitly configured, use the default type.
+ // 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");
@@ -108,6 +117,82 @@ CacheConfig::CacheConfig(const std::string& datasrc_type,
}
}
+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,
+ // DataSourceError 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
index a615b8a..7781f49 100644
--- a/src/lib/datasrc/cache_config.h
+++ b/src/lib/datasrc/cache_config.h
@@ -17,7 +17,7 @@
#include <exceptions/exceptions.h>
-#include <dns/name.h>
+#include <dns/dns_fwd.h>
#include <cc/data.h>
#include <datasrc/memory/load_action.h>
@@ -53,7 +53,6 @@ public:
/// 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.
-/// NOTE: this part will be done in #2834.
///
/// This class is publicly defined so it can be tested directly, but
/// it's essentially private to the \c ConfigurableClientList class.
@@ -144,12 +143,56 @@ public:
/// \throw None
const std::string& getSegmentType() const { return (segment_type_); }
- /// \todo the following definition is tentative, mainly for tests.
- /// In #2834 we'll (probably) extend it to be a custom iterator so
- /// the caller can iterate over the whole set of zones, loading the
- /// content in memory.
+ /// \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 DataSourceError error happens in the underlying data source
+ /// storing the cache data. Most commonly it's because the specified zone
+ /// doesn't exist there.
+ /// \throw Unexpected Unexpected error happens in the underlying data
+ /// source storing the cache data. 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:
@@ -158,6 +201,9 @@ private:
// 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_;
};
diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h
index 9c5d262..6929946 100644
--- a/src/lib/datasrc/client.h
+++ b/src/lib/datasrc/client.h
@@ -206,7 +206,7 @@ public:
///
/// 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).
diff --git a/src/lib/datasrc/client_list.cc b/src/lib/datasrc/client_list.cc
index d366ac3..c9bdee0 100644
--- a/src/lib/datasrc/client_list.cc
+++ b/src/lib/datasrc/client_list.cc
@@ -31,6 +31,7 @@
#include <set>
#include <boost/foreach.hpp>
#include <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
using namespace isc::data;
using namespace isc::dns;
@@ -119,6 +120,9 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
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));
@@ -127,60 +131,38 @@ ConfigurableClientList::configure(const ConstElementPtr& config,
cache_conf, rrclass_,
name));
- if (cache_conf->isEnabled()) {
- // 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 cache is disabled we are done for this data source.
+ // Otherwise load zones into the in-memory cache.
+ if (!cache_conf->isEnabled()) {
+ continue;
+ }
+ 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;
+ memory::LoadAction load_action;
+ try {
+ load_action = cache_conf->getLoadAction(rrclass_, zname);
+ } catch (const DataSourceError&) {
+ isc_throw(ConfigurationError, "Data source error for "
+ "loading a zone (possibly non-existent) "
+ << zname << "/" << rrclass_);
}
-
- 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());
- }
- }
+ assert(load_action); // in this loop this should be always true
+ boost::scoped_ptr<memory::ZoneWriter> writer;
+ try {
+ writer.reset(new_data_sources.back().ztable_segment_->
+ getZoneWriter(load_action, zname, rrclass_));
+ writer->load();
+ writer->install();
+ writer->cleanup();
+ } catch (const ZoneLoaderException& e) {
+ LOG_ERROR(logger, DATASRC_LOAD_ZONE_ERROR)
+ .arg(zname).arg(rrclass_).arg(name).arg(e.what());
}
}
}
@@ -344,46 +326,6 @@ ConfigurableClientList::reload(const Name& name) {
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)
-{
- return (memory::loadZoneData(segment, rrclass, name, filename));
-}
-
-}
-
ConfigurableClientList::ZoneWriterPair
ConfigurableClientList::getCachedZoneWriter(const Name& name) {
if (!allow_cache_) {
@@ -395,36 +337,15 @@ ConfigurableClientList::getCachedZoneWriter(const Name& name) {
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_) {
+
+ // Then get the appropriate load action and create a zone writer.
+ // Note that getCacheConfig() must return non NULL in this module (only
+ // tests could set it to a bogus value).
+ const memory::LoadAction load_action =
+ result.info->getCacheConfig()->getLoadAction(rrclass_, name);
+ if (!load_action) {
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);
- }
- // 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");
- }
- // boost::bind is enough here.
- load_action = boost::bind(loadZoneDataFromFile, _1, rrclass_, name,
- filename);
- }
return (ZoneWriterPair(ZONE_SUCCESS,
ZoneWriterPtr(
result.info->ztable_segment_->
diff --git a/src/lib/datasrc/client_list.h b/src/lib/datasrc/client_list.h
index cd19c10..a5a7488 100644
--- a/src/lib/datasrc/client_list.h
+++ b/src/lib/datasrc/client_list.h
@@ -174,7 +174,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),
@@ -342,8 +342,10 @@ public:
/// \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_NOT_CACHED, ///< Zone is served directly, not from cache
+ /// (including the case cache is disabled for
+ /// the specific data source).
+ ZONE_NOT_FOUND, ///< Zone does not exist in this list.
ZONE_SUCCESS ///< The zone was successfully reloaded or
/// the writer provided.
};
@@ -418,6 +420,10 @@ public:
boost::shared_ptr<memory::InMemoryClient> cache_;
boost::shared_ptr<memory::ZoneTableSegment> ztable_segment_;
std::string name_;
+
+ const internal::CacheConfig* getCacheConfig() const {
+ return (cache_conf_.get());
+ }
private:
// this is kept private for now. When it needs to be accessed,
// we'll add a read-only getter method.
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..70f4df0 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>
@@ -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 19c28eb..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
@@ -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 46d13a4..2c345c2 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -83,11 +83,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.
@@ -328,15 +354,12 @@ 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_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
-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_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. 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
diff --git a/src/lib/datasrc/exceptions.h b/src/lib/datasrc/exceptions.h
index 749b955..f9c5655 100644
--- a/src/lib/datasrc/exceptions.h
+++ b/src/lib/datasrc/exceptions.h
@@ -20,6 +20,26 @@
namespace isc {
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) {}
+};
+
/// 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..73f7e4a 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"
diff --git a/src/lib/datasrc/factory.h b/src/lib/datasrc/factory.h
index 45e4f9b..4e669ec 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>
diff --git a/src/lib/datasrc/memory/domaintree.h b/src/lib/datasrc/memory/domaintree.h
index 6e8b062..d503a11 100644
--- a/src/lib/datasrc/memory/domaintree.h
+++ b/src/lib/datasrc/memory/domaintree.h
@@ -1684,7 +1684,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);
diff --git a/src/lib/datasrc/memory/memory_client.cc b/src/lib/datasrc/memory/memory_client.cc
index 66e61a2..d49359a 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
@@ -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 {
@@ -369,7 +257,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 1b312bb..cf51706 100644
--- a/src/lib/datasrc/memory/memory_messages.mes
+++ b/src/lib/datasrc/memory/memory_messages.mes
@@ -126,7 +126,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
diff --git a/src/lib/datasrc/memory/rdata_serialization.h b/src/lib/datasrc/memory/rdata_serialization.h
index a6146a5..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.
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/zone_data_loader.cc b/src/lib/datasrc/memory/zone_data_loader.cc
index de3a749..d66fb3b 100644
--- a/src/lib/datasrc/memory/zone_data_loader.cc
+++ b/src/lib/datasrc/memory/zone_data_loader.cc
@@ -253,6 +253,9 @@ loadZoneData(util::MemorySegment& mem_sgmt,
const isc::dns::Name& zone_name,
const std::string& zone_file)
{
+ 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(),
@@ -266,6 +269,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_finder.cc b/src/lib/datasrc/memory/zone_finder.cc
index 4f9183e..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>
diff --git a/src/lib/datasrc/memory/zone_table.cc b/src/lib/datasrc/memory/zone_table.cc
index c0237f5..77071f4 100644
--- a/src/lib/datasrc/memory/zone_table.cc
+++ b/src/lib/datasrc/memory/zone_table.cc
@@ -12,14 +12,15 @@
// 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 <util/memory_segment.h>
+
+#include <dns/name.h>
#include <boost/function.hpp>
#include <boost/bind.hpp>
@@ -70,6 +71,9 @@ ZoneTable::AddResult
ZoneTable::addZone(util::MemorySegment& mem_sgmt, RRClass zone_class,
const Name& zone_name, ZoneData* content)
{
+ LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEMORY_MEM_ADD_ZONE).
+ arg(zone_name).arg(rrclass_);
+
if (content == NULL) {
isc_throw(isc::BadValue, "Zone content must not be NULL");
}
@@ -94,6 +98,7 @@ ZoneTable::addZone(util::MemorySegment& mem_sgmt, RRClass zone_class,
if (old != NULL) {
return (AddResult(result::EXIST, old));
} else {
+ ++zone_count_;
return (AddResult(result::SUCCESS, NULL));
}
}
diff --git a/src/lib/datasrc/memory/zone_table.h b/src/lib/datasrc/memory/zone_table.h
index 1b369b9..2774df3 100644
--- a/src/lib/datasrc/memory/zone_table.h
+++ b/src/lib/datasrc/memory/zone_table.h
@@ -104,6 +104,7 @@ private:
/// It never throws an exception otherwise.
ZoneTable(const dns::RRClass& rrclass, ZoneTableTree* zones) :
rrclass_(rrclass),
+ zone_count_(0),
zones_(zones)
{}
@@ -139,6 +140,11 @@ public:
/// is undefined if this condition isn't met).
static void destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable);
+ /// \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.
///
/// This method adds a given zone data to the internal table.
@@ -187,6 +193,7 @@ public:
private:
const dns::RRClass rrclass_;
+ size_t zone_count_;
boost::interprocess::offset_ptr<ZoneTableTree> zones_;
};
}
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index 11a5eb5..93f468c 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -25,7 +25,7 @@
#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>
@@ -104,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:
@@ -1295,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/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 341d6eb..f9380d8 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -60,6 +60,7 @@ 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)
diff --git a/src/lib/datasrc/tests/cache_config_unittest.cc b/src/lib/datasrc/tests/cache_config_unittest.cc
index 8c266ec..e73b06d 100644
--- a/src/lib/datasrc/tests/cache_config_unittest.cc
+++ b/src/lib/datasrc/tests/cache_config_unittest.cc
@@ -13,10 +13,15 @@
// 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>
@@ -28,12 +33,15 @@ 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
};
@@ -50,9 +58,14 @@ protected:
" \"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
@@ -140,6 +153,28 @@ TEST_F(CacheConfigTest, badConstructMasterFiles) {
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
@@ -219,6 +254,40 @@ TEST_F(CacheConfigTest, badConstructWithMock) {
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")),
+ DataSourceError);
+
+ // 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",
diff --git a/src/lib/datasrc/tests/client_list_unittest.cc b/src/lib/datasrc/tests/client_list_unittest.cc
index ab553cb..8013f01 100644
--- a/src/lib/datasrc/tests/client_list_unittest.cc
+++ b/src/lib/datasrc/tests/client_list_unittest.cc
@@ -14,8 +14,9 @@
#include <datasrc/client_list.h>
#include <datasrc/client.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>
@@ -133,29 +134,50 @@ public:
}
// 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();
-
- // 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));
+ 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 =
+ Element::fromJSON("{\"type\": \"mock\","
+ " \"cache-enable\": " +
+ string(enabled ? "true," : "false,") +
+ " \"cache-zones\": "
+ " [\"" + zone.toText() + "\"]}");
+ 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) {
+ boost::scoped_ptr<memory::ZoneWriter> writer(
+ dsrc_info.ztable_segment_->getZoneWriter(
+ cache_conf->getLoadAction(rrclass_, zone),
+ zone, rrclass_));
+ writer->load();
+ writer->install();
+ writer->cleanup(); // not absolutely necessary, but just in case
+ }
- 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,
@@ -874,7 +896,7 @@ TYPED_TEST(ReloadTest, reloadSuccess) {
}
// The cache is not enabled. The load should be rejected.
-TYPED_TEST(ReloadTest, reloadNotEnabled) {
+TYPED_TEST(ReloadTest, reloadNotAllowed) {
this->list_->configure(this->config_elem_zones_, false);
const Name name("example.org");
// We put the cache in even when not enabled. This won't confuse the thing.
@@ -893,6 +915,17 @@ TYPED_TEST(ReloadTest, reloadNotEnabled) {
RRType::A())->code);
}
+// Similar to the previous case, but the cache is disabled in config.
+TYPED_TEST(ReloadTest, reloadNotEnabled) {
+ this->list_->configure(this->config_elem_zones_, true);
+ const Name name("example.org");
+ // We put the cache, actually disabling it.
+ this->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, this->doReload(name));
+}
+
// Test several cases when the zone does not exist
TYPED_TEST(ReloadTest, reloadNoSuchZone) {
this->list_->configure(this->config_elem_zones_, true);
@@ -926,7 +959,7 @@ TYPED_TEST(ReloadTest, reloadNoSuchZone) {
// 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);
+ this->list_->configure(this->config_elem_zones_, true);
const Name name("example.org");
// 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
@@ -936,6 +969,10 @@ TYPED_TEST(ReloadTest, reloadZoneGone) {
EXPECT_EQ(ZoneFinder::SUCCESS,
this->list_->find(name).finder_->find(name,
RRType::SOA())->code);
+ // Remove the zone from the data source.
+ static_cast<MockDataSourceClient*>(
+ this->list_->getDataSources()[0].data_src_client_)->eraseZone(name);
+
// The zone is not there, so abort the reload.
EXPECT_THROW(this->doReload(name), DataSourceError);
// The (cached) zone is not hurt.
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index cac5e33..16b0627 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);
}
diff --git a/src/lib/datasrc/tests/factory_unittest.cc b/src/lib/datasrc/tests/factory_unittest.cc
index 58d6029..5a01a27 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>
diff --git a/src/lib/datasrc/tests/memory/Makefile.am b/src/lib/datasrc/tests/memory/Makefile.am
index f77d212..e0fc0f5 100644
--- a/src/lib/datasrc/tests/memory/Makefile.am
+++ b/src/lib/datasrc/tests/memory/Makefile.am
@@ -21,6 +21,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
diff --git a/src/lib/datasrc/tests/memory/memory_client_unittest.cc b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
index 5984de5..f5b72a0 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>
@@ -42,6 +44,7 @@
#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 {
@@ -169,26 +173,22 @@ protected:
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_;
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);
}
@@ -196,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
}
@@ -206,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")));
@@ -281,21 +276,23 @@ TEST_F(MemoryClientTest, loadFromIterator) {
// Iterating past the end should result in an exception
EXPECT_THROW(iterator->getNextRRset(), isc::Unexpected);
+ // 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.
- client_->load(Name("example.org"),
- *MockIterator::makeIterator(
- rrset_data_separated));
+ loadZoneIntoTable(*ztable_segment_, Name("example.org"), zclass_,
+ *MockIterator::makeIterator(rrset_data_separated));
// Similar to the previous case, but with separated RRSIGs.
- client_->load(Name("example.org"),
- *MockIterator::makeIterator(
- rrset_data_sigseparated));
+ 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);
}
@@ -316,16 +313,16 @@ TEST_F(MemoryClientTest, loadMemoryAllocationFailures) {
// fail (due to MemorySegmentTest 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"));
@@ -441,15 +438,14 @@ TEST_F(MemoryClientTest, loadReloadZone) {
TEST_F(MemoryClientTest, loadDuplicateType) {
// This should not result in any exceptions (multiple records of the
// same name, type are present, one after another in sequence).
- client_->load(Name("example.org"),
- TEST_DATA_DIR "/example.org-duplicate-type.zone");
+ 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).
- client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-duplicate-type-bad.zone");
+ 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"));
@@ -479,104 +475,116 @@ TEST_F(MemoryClientTest, loadDuplicateType) {
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
@@ -584,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());
}
@@ -642,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);
}
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
@@ -693,8 +695,8 @@ TEST_F(MemoryClientTest, getIterator) {
}
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")));
@@ -746,8 +748,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();
@@ -764,7 +766,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.
@@ -780,16 +783,17 @@ 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, 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/zone_finder_unittest.cc b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
index 02c8a09..0350ed9 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_test.h>
+#include <datasrc/tests/memory/zone_table_segment_test.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>
@@ -1610,12 +1611,13 @@ 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_));
+ 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
@@ -1771,16 +1773,17 @@ TEST_F(InMemoryZoneFinderNSEC3Test, findNSEC3MissingOrigin) {
DefaultNSEC3HashCreator creator;
setNSEC3HashCreator(&creator);
+ const Name name("example.com.");
shared_ptr<ZoneTableSegment> ztable_segment(
new ZoneTableSegmentTest(class_, mem_sgmt_));
+ 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..1bf9cfa
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/zone_loader_util.cc
@@ -0,0 +1,93 @@
+// 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 <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#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)
+{
+ const isc::datasrc::internal::CacheConfig cache_conf(
+ "MasterFiles", NULL, *data::Element::fromJSON(
+ "{\"cache-enable\": true,"
+ " \"params\": {\"" + zname.toText() + "\": \"" + zone_file +
+ "\"}}"), true);
+ boost::scoped_ptr<memory::ZoneWriter> writer(
+ zt_sgmt.getZoneWriter(cache_conf.getLoadAction(zclass, zname),
+ zname, zclass));
+ 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)
+{
+ boost::scoped_ptr<memory::ZoneWriter> writer(
+ zt_sgmt.getZoneWriter(IteratorLoader(zclass, zname, iterator),
+ zname, zclass));
+ 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..06eba87
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/zone_loader_util.h
@@ -0,0 +1,57 @@
+// 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.
+void
+loadZoneIntoTable(ZoneTableSegment& zt_sgmt, const dns::Name& zname,
+ const dns::RRClass& zclass, const std::string& zone_file);
+
+/// \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_unittest.cc b/src/lib/datasrc/tests/memory/zone_table_unittest.cc
index 3c53a59..0109793 100644
--- a/src/lib/datasrc/tests/memory/zone_table_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_table_unittest.cc
@@ -70,9 +70,13 @@ TEST_F(ZoneTableTest, create) {
}
TEST_F(ZoneTableTest, addZone) {
+ // By default there's no zone contained.
+ EXPECT_EQ(0, zone_table->getZoneCount());
+
// It doesn't accept empty (NULL) zones
EXPECT_THROW(zone_table->addZone(mem_sgmt_, zclass_, zname1, NULL),
isc::BadValue);
+ EXPECT_EQ(0, zone_table->getZoneCount()); // count is still 0
SegmentObjectHolder<ZoneData, RRClass> holder1(
mem_sgmt_, ZoneData::create(mem_sgmt_, zname1), zclass_);
@@ -85,6 +89,7 @@ TEST_F(ZoneTableTest, addZone) {
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(
@@ -99,6 +104,7 @@ 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")),
@@ -115,11 +121,13 @@ TEST_F(ZoneTableTest, addZone) {
EXPECT_EQ(result::SUCCESS,
zone_table->addZone(mem_sgmt_, zclass_, zname2,
holder4.release()).code);
+ EXPECT_EQ(2, zone_table->getZoneCount());
SegmentObjectHolder<ZoneData, RRClass> holder5(
mem_sgmt_, ZoneData::create(mem_sgmt_, zname3), zclass_);
EXPECT_EQ(result::SUCCESS,
zone_table->addZone(mem_sgmt_, zclass_, 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
diff --git a/src/lib/datasrc/tests/mock_client.cc b/src/lib/datasrc/tests/mock_client.cc
index 90e0a66..fd3916d 100644
--- a/src/lib/datasrc/tests/mock_client.cc
+++ b/src/lib/datasrc/tests/mock_client.cc
@@ -16,7 +16,7 @@
#include <datasrc/client.h>
#include <datasrc/result.h>
#include <datasrc/zone_iterator.h>
-#include <datasrc/data_source.h>
+#include <datasrc/exceptions.h>
#include <dns/name.h>
#include <dns/rrclass.h>
diff --git a/src/lib/datasrc/tests/mock_client.h b/src/lib/datasrc/tests/mock_client.h
index ae7a0bf..c51e9a1 100644
--- a/src/lib/datasrc/tests/mock_client.h
+++ b/src/lib/datasrc/tests/mock_client.h
@@ -52,7 +52,12 @@ public:
}
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);
+ }
const std::string type_;
const data::ConstElementPtr configuration_;
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/zone_finder_context_unittest.cc b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
index 614b1be..f541fd8 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>
@@ -32,6 +36,7 @@
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
#include <fstream>
#include <sstream>
@@ -39,11 +44,13 @@
using namespace std;
using boost::shared_ptr;
+using boost::scoped_ptr;
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;
@@ -64,11 +71,22 @@ typedef DataSourceClientPtr (*ClientCreator)(RRClass, const Name&);
// Creator for the in-memory client to be tested
DataSourceClientPtr
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(zclass, "local"));
+ ZoneTableSegment::create(zclass, cache_conf.getSegmentType()));
+ scoped_ptr<memory::ZoneWriter> writer(
+ ztable_segment->getZoneWriter(cache_conf.getLoadAction(zclass, zname),
+ zname, zclass));
+ writer->load();
+ writer->install();
+ writer->cleanup();
shared_ptr<InMemoryClient> client(new InMemoryClient(ztable_segment,
zclass));
- client->load(zname, TEST_ZONE_FILE);
return (client);
}
@@ -105,7 +123,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 eabcd65..4cf9e9a 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,6 +28,8 @@
#include <util/memory_segment_local.h>
#include <exceptions/exceptions.h>
+#include <cc/data.h>
+
#include <gtest/gtest.h>
#include <boost/shared_ptr.hpp>
@@ -37,6 +41,7 @@
using namespace isc::dns;
using namespace isc::datasrc;
+using isc::data::Element;
using boost::shared_ptr;
using std::string;
using std::vector;
@@ -301,13 +306,28 @@ protected:
// (re)configure zone table, then (re)construct the in-memory client
// with it.
- ztable_segment_.reset(memory::ZoneTableSegment::create(rrclass_,
- "local"));
- source_client_.reset(new memory::InMemoryClient(ztable_segment_,
- rrclass_));
+ string param_data;
if (filename) {
- source_client_->load(zone, string(TEST_DATA_DIR) + "/" + 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(
+ ztable_segment_->getZoneWriter(cache_conf.getLoadAction(
+ rrclass_, zone),
+ zone, rrclass_));
+ writer->load();
+ writer->install();
+ writer->cleanup();
+ }
+ source_client_.reset(new memory::InMemoryClient(ztable_segment_,
+ rrclass_));
}
private:
const RRClass rrclass_;
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_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..378c7eb
--- /dev/null
+++ b/src/lib/datasrc/zone_table_accessor.h
@@ -0,0 +1,212 @@
+// 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;
+};
+
+}
+}
+
+#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..1e292bd 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -35,6 +35,9 @@ libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
libb10_dhcp___la_SOURCES += option_space.cc option_space.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
+libb10_dhcp___la_SOURCES += pkt_filter_lpf.cc pkt_filter_lpf.h
libb10_dhcp___la_SOURCES += std_option_defs.h
libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
diff --git a/src/lib/dhcp/dhcp6.h b/src/lib/dhcp/dhcp6.h
index 874ee46..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
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..13a16bf 100644
--- a/src/lib/dhcp/hwaddr.h
+++ b/src/lib/dhcp/hwaddr.h
@@ -27,6 +27,8 @@ namespace dhcp {
/// @brief Hardware type that represents information from DHCPv4 packet
struct HWAddr {
public:
+ /// @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..74f5fe8 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,7 +48,7 @@ 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)
@@ -56,7 +57,7 @@ IfaceMgr::Iface::Iface(const std::string& name, int ifindex)
}
void
-IfaceMgr::Iface::closeSockets() {
+Iface::closeSockets() {
for (SocketCollection::iterator sock = sockets_.begin();
sock != sockets_.end(); ++sock) {
close(sock->sockfd_);
@@ -65,14 +66,14 @@ IfaceMgr::Iface::closeSockets() {
}
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 +87,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 +109,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 +125,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 {
@@ -193,10 +195,23 @@ 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) {
@@ -207,8 +222,8 @@ bool IfaceMgr::openSockets4(const uint16_t port) {
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 +232,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++;
@@ -242,8 +288,8 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
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 +350,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 +364,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 +372,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 +384,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 +397,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 +430,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 +467,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 +556,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 +630,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 +748,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(getSocket(*pkt), pkt));
}
@@ -792,8 +782,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 +838,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 +856,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 +880,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 +936,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 +1057,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 +1080,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..2085b97 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 IfaceMgr 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) { };
+};
+
/// @brief IfaceMgr exception thrown thrown when socket opening
/// or configuration failed.
class SocketConfigError : public Exception {
@@ -62,6 +70,219 @@ public:
isc::Exception(file, line, what) { };
};
+/// 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;
+
+ /// type that holds a list of socket informations
+ /// @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 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_;
+};
+
/// @brief handles network interfaces, transmission and reception
///
/// IfaceMgr is an interface manager class that detects available network
@@ -70,15 +291,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
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,211 +303,6 @@ 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)
@@ -306,6 +316,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();
+
/// @brief Returns interface with specified interface index
///
/// @param ifindex index of searched interface
@@ -434,6 +454,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 +466,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,18 +530,20 @@ 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 Closes all open sockets.
+ /// Is used in destructor, but also from Dhcpv4_srv and Dhcpv6_srv classes.
+ void closeSockets();
/// @brief returns number of detected interfaces
///
@@ -534,6 +562,24 @@ 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.
+ ///
+ /// @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.
+ void setPacketFilter(const boost::shared_ptr<PktFilter>& packet_filter) {
+ if (!packet_filter) {
+ isc_throw(InvalidPacketFilter, "NULL packet filter object specified");
+ }
+ packet_filter_ = packet_filter;
+ }
+
/// A value of socket descriptor representing "not specified" state.
static const int INVALID_SOCKET = -1;
@@ -557,9 +603,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 +635,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 +644,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();
@@ -674,10 +724,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).
+ boost::shared_ptr<PktFilter> 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..afd97bb 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
@@ -34,6 +34,11 @@ IfaceMgr::detectIfaces() {
stubDetectIfaces();
}
+bool
+IfaceMgr::isDirectResponseSupported() {
+ return (false);
+}
+
void IfaceMgr::os_send4(struct msghdr& /*m*/,
boost::scoped_array<char>& /*control_buf*/,
size_t /*control_buf_len*/,
diff --git a/src/lib/dhcp/iface_mgr_linux.cc b/src/lib/dhcp/iface_mgr_linux.cc
index e7d5048..71a32d8 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
@@ -67,7 +67,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 +103,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 +277,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 +476,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]));
@@ -494,13 +494,18 @@ void IfaceMgr::detectIfaces() {
nl.release_list(addr_info);
}
+bool
+IfaceMgr::isDirectResponseSupported() {
+ return (false);
+}
+
/// @brief sets flag_*_ fields.
///
/// This implementation is OS-specific as bits have different meaning
/// 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 +515,15 @@ 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;
-
- 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);
+void IfaceMgr::os_send4(struct msghdr&, boost::scoped_array<char>&,
+ size_t, const Pkt4Ptr&) {
+ return;
- 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.
-
- // 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..1556b70 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
@@ -34,6 +34,11 @@ IfaceMgr::detectIfaces() {
stubDetectIfaces();
}
+bool
+IfaceMgr::isDirectResponseSupported() {
+ return (false);
+}
+
void IfaceMgr::os_send4(struct msghdr& /*m*/,
boost::scoped_array<char>& /*control_buf*/,
size_t /*control_buf_len*/,
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++.h b/src/lib/dhcp/libdhcp++.h
index c242611..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
@@ -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_custom.cc b/src/lib/dhcp/option_custom.cc
index 3d2a1a9..e807928 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
@@ -230,7 +230,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 +238,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
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index 233f778..0aa0e17 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.
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
index c3a98bf..c97281e 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,61 @@ 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::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();
@@ -82,6 +141,50 @@ Pkt6::pack() {
bool
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
@@ -127,12 +230,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 +277,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.");
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index afb85d2..0bf4192 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
@@ -32,15 +32,40 @@ namespace dhcp {
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 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, ...)
@@ -89,7 +114,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
@@ -160,6 +184,23 @@ 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 Returns all instances of specified type.
///
/// Returns all instances of options of the specified type. DHCPv6 protocol
@@ -246,7 +287,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,8 +300,18 @@ 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
///
+ /// @todo: Text mentions protected, but this is really public
+ ///
/// @warning This protected member is accessed by derived
/// classes directly. One of such derived classes is
/// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
@@ -305,6 +356,15 @@ public:
/// be freed by the caller.
const char* getName() const;
+ /// 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_;
protected:
/// Builds on wire packet for TCP transmission.
///
@@ -340,6 +400,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_;
diff --git a/src/lib/dhcp/pkt_filter.h b/src/lib/dhcp/pkt_filter.h
new file mode 100644
index 0000000..946bd14
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter.h
@@ -0,0 +1,84 @@
+// 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 <asiolink/io_address.h>
+
+namespace isc {
+namespace dhcp {
+
+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 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 sockfd socket descriptor
+ /// @param pkt packet to be sent
+ ///
+ /// @return result of sending the packet. It is 0 if successful.
+ virtual int send(uint16_t sockfd, const Pkt4Ptr& pkt) = 0;
+};
+
+} // 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..a6360aa
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_inet.cc
@@ -0,0 +1,264 @@
+// 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_])
+{
+}
+
+// iface is only used when SO_BINDTODEVICE is defined and thus
+// the code section using this variable is compiled.
+#ifdef SO_BINDTODEVICE
+int PktFilterInet::openSocket(const Iface& iface,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast) {
+
+#else
+int PktFilterInet::openSocket(const Iface&,
+ const isc::asiolink::IOAddress& addr,
+ const uint16_t port,
+ const bool receive_bcast,
+ const bool send_bcast) {
+
+
+#endif
+
+ 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) {
+ 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) {
+ // 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) {
+ // 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(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..4e98612
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_inet.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 PKT_FILTER_INET_H
+#define PKT_FILTER_INET_H
+
+#include <dhcp/pkt_filter.h>
+
+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 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 sockfd socket descriptor
+ /// @param pkt packet to be sent
+ ///
+ /// @return result of sending a packet. It is 0 if successful.
+ virtual int send(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..ef75426
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_lpf.cc
@@ -0,0 +1,45 @@
+// 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_lpf.h>
+
+namespace isc {
+namespace dhcp {
+
+int
+PktFilterLPF::openSocket(const Iface&, const isc::asiolink::IOAddress&,
+ const uint16_t, const bool,
+ const bool) {
+ isc_throw(isc::NotImplemented,
+ "Linux Packet Filtering is not implemented yet");
+}
+
+Pkt4Ptr
+PktFilterLPF::receive(const Iface&, const SocketInfo&) {
+ isc_throw(isc::NotImplemented,
+ "Linux Packet Filtering is not implemented yet");
+}
+
+int
+PktFilterLPF::send(uint16_t, const Pkt4Ptr&) {
+ isc_throw(isc::NotImplemented,
+ "Linux Packet Filtering is not implemented yet");
+}
+
+
+} // 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..67b190f
--- /dev/null
+++ b/src/lib/dhcp/pkt_filter_lpf.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 PKT_FILTER_LPF_H
+#define PKT_FILTER_LPF_H
+
+#include <dhcp/pkt_filter.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 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 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(uint16_t sockfd, const Pkt4Ptr& pkt);
+
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // PKT_FILTER_LPF_H
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 a7f338c..31b9300 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>
@@ -51,6 +52,53 @@ const uint16_t PORT2 = 10548; // V4 socket
// For such cases we set the tolerance of 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) {
+ }
+
+ /// Pretends to open socket. Only records a call to this function.
+ virtual int openSocket(const Iface&,
+ const isc::asiolink::IOAddress&,
+ const uint16_t,
+ const bool,
+ const bool) {
+ open_socket_called_ = true;
+ return (1024);
+ }
+
+ /// Does nothing
+ virtual Pkt4Ptr receive(const Iface&,
+ const SocketInfo&) {
+ return (Pkt4Ptr());
+ }
+
+ /// Does nothing
+ virtual int send(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
@@ -166,7 +214,7 @@ TEST_F(IfaceMgrTest, basic) {
TEST_F(IfaceMgrTest, ifaceClass) {
// basic tests for Iface inner class
- IfaceMgr::Iface* iface = new IfaceMgr::Iface("eth5", 7);
+ Iface* iface = new Iface("eth5", 7);
EXPECT_STREQ("eth5/7", iface->getFullName().c_str());
@@ -181,10 +229,10 @@ TEST_F(IfaceMgrTest, getIface) {
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);
+ 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;
@@ -205,7 +253,7 @@ TEST_F(IfaceMgrTest, getIface) {
// check that interface can be retrieved by ifindex
- IfaceMgr::Iface* tmp = ifacemgr->getIface(102);
+ Iface* tmp = ifacemgr->getIface(102);
ASSERT_TRUE(tmp != NULL);
EXPECT_EQ("en3", tmp->getName());
@@ -354,7 +402,7 @@ TEST_F(IfaceMgrTest, multipleSockets) {
// 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 +410,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 +427,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.
@@ -759,6 +807,38 @@ TEST_F(IfaceMgrTest, sendReceive4) {
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 1024.
+ EXPECT_EQ(1024, socket1);
+}
+
TEST_F(IfaceMgrTest, socket4) {
@@ -788,9 +868,9 @@ TEST_F(IfaceMgrTest, socket4) {
// Test the Iface structure itself
TEST_F(IfaceMgrTest, iface) {
- IfaceMgr::Iface* iface = NULL;
+ Iface* iface = NULL;
EXPECT_NO_THROW(
- iface = new IfaceMgr::Iface("eth0",1);
+ iface = new Iface("eth0",1);
);
EXPECT_EQ("eth0", iface->getName());
@@ -798,7 +878,7 @@ TEST_F(IfaceMgrTest, iface) {
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());
@@ -828,13 +908,13 @@ TEST_F(IfaceMgrTest, iface) {
}
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 +923,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,11 +931,11 @@ 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()));
}
@@ -863,14 +943,14 @@ TEST_F(IfaceMgrTest, iface_methods) {
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);
+ 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);
+ 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_);
@@ -878,7 +958,7 @@ TEST_F(IfaceMgrTest, socketInfo) {
// now let's test if IfaceMgr handles socket info properly
NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
- IfaceMgr::Iface* loopback = ifacemgr->getIface(LOOPBACK);
+ Iface* loopback = ifacemgr->getIface(LOOPBACK);
ASSERT_TRUE(loopback);
loopback->addSocket(sock1);
loopback->addSocket(sock2);
@@ -955,7 +1035,7 @@ TEST_F(IfaceMgrTest, socketInfo) {
/// 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
@@ -1045,7 +1125,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 +1137,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);
}
}
@@ -1165,8 +1245,8 @@ 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() << " ";
}
@@ -1208,13 +1288,13 @@ 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();
+ 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;
diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc
index e0ae727..2a9f771 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));
@@ -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
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/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc
index cdaad3b..b5a745e 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,15 @@
#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 <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <util/encode/hex.h>
#include <gtest/gtest.h>
#include <iostream>
@@ -99,6 +105,67 @@ 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();
@@ -306,5 +373,183 @@ 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) {
+
+ boost::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(pair<int, boost::shared_ptr<Option> >(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_TRUE(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
+ boost::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)));
+}
}
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index db82513..f646dd6 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)
@@ -74,3 +76,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 2a1bfc2..9c5bdeb 100644
--- a/src/lib/dhcpsrv/alloc_engine.cc
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -303,7 +303,6 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
// Check if there's existing lease for that subnet/clientid/hwaddr combination.
Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(*hwaddr, subnet->getID());
if (existing) {
- std::cout << "Got lease using HWADdr" << std::endl;
// We have a lease already. This is a returning client, probably after
// its reboot.
existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
@@ -318,7 +317,6 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
if (clientid) {
existing = LeaseMgrFactory::instance().getLease4(*clientid, subnet->getID());
if (existing) {
- std::cout << "Got lease using Clientid" << std::endl;
// we have a lease already. This is a returning client, probably after
// its reboot.
existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
diff --git a/src/lib/dhcpsrv/dhcp_config_parser.h b/src/lib/dhcpsrv/dhcp_config_parser.h
index cb419f8..8abdfc8 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 {
@@ -122,38 +128,83 @@ 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 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 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 << "'");
+ /// @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 <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);
}
- ReturnType value = param->second;
- return (value);
- }
+ /// @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_;
};
-} // end of isc::dhcp namespace
-} // end of isc namespace
+/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
+typedef ValueStorage<uint32_t> Uint32Storage;
+
+/// @brief a collection of elements that store string values
+typedef ValueStorage<std::string> StringStorage;
+
+/// @brief Storage for parsed boolean values.
+typedef ValueStorage<bool> BooleanStorage;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
#endif // DHCP_CONFIG_PARSER_H
+
diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h
index 7d328e7..02e517e 100644
--- a/src/lib/dhcpsrv/lease_mgr.h
+++ b/src/lib/dhcpsrv/lease_mgr.h
@@ -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
///
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc
index b9706e4..9828085 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc
@@ -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
@@ -533,7 +530,7 @@ 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
@@ -1109,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,
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index 5a16647..4c7cbcc 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -47,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 {
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index 9b3d61b..be31bab 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -15,6 +15,7 @@
#include <config.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <exceptions/exceptions.h>
#include <gtest/gtest.h>
@@ -36,6 +37,126 @@ 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 paramaters.
+ 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() {
diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
index 226afe9..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>
@@ -883,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
@@ -921,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);
@@ -957,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
@@ -979,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
diff --git a/src/lib/dns/gen-rdatacode.py.in b/src/lib/dns/gen-rdatacode.py.in
index 0d946b9..e265d56 100755
--- a/src/lib/dns/gen-rdatacode.py.in
+++ b/src/lib/dns/gen-rdatacode.py.in
@@ -37,12 +37,15 @@ new_rdata_factory_users = [('a', 'in'), ('aaaa', 'in'),
('cname', 'generic'),
('dlv', 'generic'),
('dname', 'generic'),
+ ('dnskey', 'generic'),
('ds', 'generic'),
('hinfo', 'generic'),
('naptr', 'generic'),
('mx', 'generic'),
('ns', 'generic'),
('nsec', 'generic'),
+ ('nsec3', 'generic'),
+ ('nsec3param', 'generic'),
('ptr', 'generic'),
('soa', 'generic'),
('spf', 'generic'),
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/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.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/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_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/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/dnskey_48.cc b/src/lib/dns/rdata/generic/dnskey_48.cc
index 054ac18..2e9a9f3 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) :
@@ -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/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..fd8f78d 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);
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..494746d 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);
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..ffa2c97 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) :
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/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_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/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/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/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_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/tests/Makefile.am b/src/lib/log/tests/Makefile.am
index 3b10cf6..306d5f9 100644
--- a/src/lib/log/tests/Makefile.am
+++ b/src/lib/log/tests/Makefile.am
@@ -25,6 +25,7 @@ logger_example_CPPFLAGS = $(AM_CPPFLAGS)
logger_example_LDFLAGS = $(AM_LDFLAGS)
logger_example_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
logger_example_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+logger_example_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
logger_example_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
logger_example_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
@@ -34,6 +35,7 @@ init_logger_test_CPPFLAGS = $(AM_CPPFLAGS)
init_logger_test_LDFLAGS = $(AM_LDFLAGS)
init_logger_test_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
init_logger_test_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+init_logger_test_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
init_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
init_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
@@ -43,6 +45,7 @@ buffer_logger_test_CPPFLAGS = $(AM_CPPFLAGS)
buffer_logger_test_LDFLAGS = $(AM_LDFLAGS)
buffer_logger_test_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
buffer_logger_test_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+buffer_logger_test_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
buffer_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
buffer_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
@@ -53,6 +56,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)
diff --git a/src/lib/log/tests/message_initializer_1_unittest.cc b/src/lib/log/tests/message_initializer_1_unittest.cc
index fea6033..761231d 100644
--- a/src/lib/log/tests/message_initializer_1_unittest.cc
+++ b/src/lib/log/tests/message_initializer_1_unittest.cc
@@ -98,7 +98,7 @@ TEST(MessageInitializerTest1, Duplicates) {
MessageInitializer::clearDuplicates();
ASSERT_EQ(0, MessageInitializer::getDuplicates().size());
- // Do it again to make sure, let's explicitely provide false now
+ // 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());
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/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 74b0c8a..8754906 100644
--- a/src/lib/nsas/tests/zone_entry_unittest.cc
+++ b/src/lib/nsas/tests/zone_entry_unittest.cc
@@ -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));
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/python/bind10_config.py.in b/src/lib/python/bind10_config.py.in
index 7ef660b..e56e908 100644
--- a/src/lib/python/bind10_config.py.in
+++ b/src/lib/python/bind10_config.py.in
@@ -33,7 +33,7 @@ def reload():
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.
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/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 f713a5c..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)
diff --git a/src/lib/python/isc/cc/session.py b/src/lib/python/isc/cc/session.py
index fced424..636dd08 100644
--- a/src/lib/python/isc/cc/session.py
+++ b/src/lib/python/isc/cc/session.py
@@ -67,7 +67,9 @@ class Session:
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):
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index c6d9791..e801309 100644
--- a/src/lib/python/isc/config/ccsession.py
+++ b/src/lib/python/isc/config/ccsession.py
@@ -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
diff --git a/src/lib/python/isc/config/cfgmgr.py b/src/lib/python/isc/config/cfgmgr.py
index fe4924c..d100aa8 100644
--- a/src/lib/python/isc/config/cfgmgr.py
+++ b/src/lib/python/isc/config/cfgmgr.py
@@ -442,7 +442,7 @@ 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 committed already).
if module_name in self.virtual_modules:
diff --git a/src/lib/python/isc/config/tests/cfgmgr_test.py b/src/lib/python/isc/config/tests/cfgmgr_test.py
index c1fe970..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)
@@ -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/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/finder_python.cc b/src/lib/python/isc/datasrc/finder_python.cc
index 05c44c9..ef1bcc1 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>
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/ddns/session.py b/src/lib/python/isc/ddns/session.py
index 3368523..febdfdc 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(),
diff --git a/src/lib/python/isc/ddns/tests/session_tests.py b/src/lib/python/isc/ddns/tests/session_tests.py
index bc25310..4880d72 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):
diff --git a/src/lib/python/isc/notify/notify_out.py b/src/lib/python/isc/notify/notify_out.py
index 7ae5665..a030a51 100644
--- a/src/lib/python/isc/notify/notify_out.py
+++ b/src/lib/python/isc/notify/notify_out.py
@@ -41,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
@@ -211,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.
@@ -421,20 +421,45 @@ 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, AddressFormatter(tgt))
+ 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:
@@ -444,8 +469,9 @@ class NotifyOut:
_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)
@@ -537,9 +563,12 @@ 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)
@@ -574,7 +603,7 @@ class NotifyOut:
logger.debug(logger.DBGLVL_TRACE_BASIC, NOTIFY_OUT_REPLY_RECEIVED,
zone_notify_info.zone_name, zone_notify_info.zone_class,
- from_addr[0], from_addr[1], msg.get_rcode())
+ AddressFormatter(from_addr), msg.get_rcode())
return _REPLY_OK
diff --git a/src/lib/python/isc/notify/notify_out_messages.mes b/src/lib/python/isc/notify/notify_out_messages.mes
index 30fb087..fd08f43 100644
--- a/src/lib/python/isc/notify/notify_out_messages.mes
+++ b/src/lib/python/isc/notify/notify_out_messages.mes
@@ -60,7 +60,7 @@ 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: %5
+% 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.
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..e2b8d27 100644
--- a/src/lib/python/isc/notify/tests/notify_out_test.py
+++ b/src/lib/python/isc/notify/tests/notify_out_test.py
@@ -25,10 +25,32 @@ from isc.dns import *
TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
+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 +66,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 +75,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 +111,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())
@@ -105,14 +137,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 +257,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))
@@ -328,39 +364,86 @@ class TestNotifyOut(unittest.TestCase):
'zones', '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/server_common/dns_tcp.py b/src/lib/python/isc/server_common/dns_tcp.py
index 2ab5c8f..2cbd47a 100644
--- a/src/lib/python/isc/server_common/dns_tcp.py
+++ b/src/lib/python/isc/server_common/dns_tcp.py
@@ -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
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/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/resolve/tests/recursive_query_unittest.cc b/src/lib/resolve/tests/recursive_query_unittest.cc
index e5e46e1..acbbb03 100644
--- a/src/lib/resolve/tests/recursive_query_unittest.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest.cc
@@ -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/server_common/portconfig.cc b/src/lib/server_common/portconfig.cc
index 122c740..7bc6876 100644
--- a/src/lib/server_common/portconfig.cc
+++ b/src/lib/server_common/portconfig.cc
@@ -135,7 +135,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/util/Makefile.am b/src/lib/util/Makefile.am
index 3960a8b..32a9341 100644
--- a/src/lib/util/Makefile.am
+++ b/src/lib/util/Makefile.am
@@ -6,6 +6,18 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/lib/exceptions -I$(top_builddir)/src/lib/exce
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
@@ -18,6 +30,9 @@ 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
diff --git a/src/lib/util/memory_segment.h b/src/lib/util/memory_segment.h
index 664bd3c..e62c9df 100644
--- a/src/lib/util/memory_segment.h
+++ b/src/lib/util/memory_segment.h
@@ -15,27 +15,107 @@
#ifndef MEMORY_SEGMENT_H
#define MEMORY_SEGMENT_H
+#include <exceptions/exceptions.h>
+
#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.
///
- /// Throws <code>std::bad_alloc</code> if the implementation cannot
- /// allocate the requested storage.
+ /// The source of the memory is dependent on the implementation used.
+ ///
+ /// 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.
///
/// \param size The size of the memory requested in bytes.
/// \return Returns pointer to the memory allocated.
@@ -50,6 +130,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 +150,155 @@ 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).
+ ///
+ /// \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 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.
+ /// \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.
+ if (!name) {
+ isc_throw(InvalidParameter,
+ "NULL name is given to setNamedAddress");
+ }
+ return (setNamedAddressImpl(name, addr));
+ }
+
+ /// \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.
+ ///
+ /// 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.
+ ///
+ /// \param name A C string of which the segment memory address is to be
+ /// returned. Must not be NULL.
+ /// \return The address associated with the name, or NULL if not found.
+ void* getNamedAddress(const char* name) {
+ // This public method implements common validation. The actual
+ // work specific to the derived segment is delegated to the
+ // corresponding protected method.
+ if (!name) {
+ isc_throw(InvalidParameter,
+ "NULL name is given to getNamedAddress");
+ }
+ 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.
+ ///
+ /// See \c getNamedAddress() about exception consideration.
+ ///
+ /// \throw InvalidParameter name is NULL.
+ /// \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.
+ if (!name) {
+ isc_throw(InvalidParameter,
+ "NULL name is given to clearNamedAddress");
+ }
+ return (clearNamedAddressImpl(name));
+ }
+
+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 void* getNamedAddressImpl(const char* name) = 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..81548fd 100644
--- a/src/lib/util/memory_segment_local.cc
+++ b/src/lib/util/memory_segment_local.cc
@@ -48,7 +48,28 @@ MemorySegmentLocal::deallocate(void* ptr, size_t size) {
bool
MemorySegmentLocal::allMemoryDeallocated() const {
- return (allocated_size_ == 0);
+ return (allocated_size_ == 0 && named_addrs_.empty());
+}
+
+void*
+MemorySegmentLocal::getNamedAddressImpl(const char* name) {
+ std::map<std::string, void*>::iterator found = named_addrs_.find(name);
+ if (found != named_addrs_.end()) {
+ return (found->second);
+ }
+ return (0);
+}
+
+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..1db55a0 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 void* getNamedAddressImpl(const char* name);
+
+ /// \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..e2ac944
--- /dev/null
+++ b/src/lib/util/memory_segment_mapped.cc
@@ -0,0 +1,382 @@
+// 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>
+
+// 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 {
+// 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();
+ }
+
+ // 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();
+ }
+
+ // 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();
+ }
+ }
+
+ // 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_.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);
+
+ if (!BaseSegment::grow(filename_.c_str(), new_size - prev_size)) {
+ throw std::bad_alloc();
+ }
+
+ try {
+ // Remap the grown 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.
+ base_sgmt_.reset(new BaseSegment(open_only, filename_.c_str()));
+ } catch (const boost::interprocess::interprocess_exception& ex) {
+ 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_->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 {
+ return (impl_->base_sgmt_->all_memory_deallocated());
+}
+
+void*
+MemorySegmentMapped::getNamedAddressImpl(const char* name) {
+ offset_ptr<void>* storage =
+ impl_->base_sgmt_->find<offset_ptr<void> >(name).first;
+ if (storage) {
+ return (storage->get());
+ }
+ return (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);
+ }
+
+ bool grown = false;
+ while (true) {
+ offset_ptr<void>* storage =
+ impl_->base_sgmt_->find_or_construct<offset_ptr<void> >(
+ name, std::nothrow)();
+ if (storage) {
+ *storage = addr;
+ 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 and) close the underlying file.
+ 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.
+ 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..7685e30
--- /dev/null
+++ b/src/lib/util/memory_segment_mapped.h
@@ -0,0 +1,261 @@
+// 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.
+ ///
+ /// 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 void* getNamedAddressImpl(const char* name);
+
+ /// \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/gen_wiredata.py.in b/src/lib/util/python/gen_wiredata.py.in
index c219e25..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
diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am
index 105322f..3ee16f9 100644
--- a/src/lib/util/tests/Makefile.am
+++ b/src/lib/util/tests/Makefile.am
@@ -34,6 +34,11 @@ 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
@@ -41,6 +46,7 @@ run_unittests_SOURCES += socketsession_unittest.cc
run_unittests_SOURCES += strutil_unittest.cc
run_unittests_SOURCES += time_utilities_unittest.cc
run_unittests_SOURCES += range_utilities_unittest.cc
+run_unittests_SOURCES += interprocess_util.h interprocess_util.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
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
index 6f23558..38d9026 100644
--- a/src/lib/util/tests/interprocess_sync_file_unittest.cc
+++ b/src/lib/util/tests/interprocess_sync_file_unittest.cc
@@ -12,48 +12,20 @@
// 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/interprocess_sync_file.h>
#include <util/unittests/check_valgrind.h>
+#include <util/tests/interprocess_util.h>
#include <gtest/gtest.h>
#include <unistd.h>
using namespace std;
+using isc::util::test::parentReadState;
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);
@@ -99,7 +71,7 @@ TEST(InterprocessSyncFileTest, TestLock) {
// Parent reads from pipe
close(fds[1]);
- const unsigned char locked = parentReadLockedState(fds[0]);
+ const unsigned char locked = parentReadState(fds[0]);
close(fds[0]);
@@ -163,7 +135,7 @@ TEST(InterprocessSyncFileTest, TestMultipleFilesForked) {
// Parent reads from pipe
close(fds[1]);
- const unsigned char locked = parentReadLockedState(fds[0]);
+ const unsigned char locked = parentReadState(fds[0]);
close(fds[0]);
diff --git a/src/lib/util/tests/interprocess_util.cc b/src/lib/util/tests/interprocess_util.cc
new file mode 100644
index 0000000..dfb04b7
--- /dev/null
+++ b/src/lib/util/tests/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 test {
+
+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/tests/interprocess_util.h b/src/lib/util/tests/interprocess_util.h
new file mode 100644
index 0000000..286f9cf
--- /dev/null
+++ b/src/lib/util/tests/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 test {
+/// \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/tests/memory_segment_common_unittest.cc b/src/lib/util/tests/memory_segment_common_unittest.cc
new file mode 100644
index 0000000..3810e0a
--- /dev/null
+++ b/src/lib/util/tests/memory_segment_common_unittest.cc
@@ -0,0 +1,92 @@
+// 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, NULL should be returned.
+ EXPECT_EQ(static_cast<void*>(NULL),
+ segment.getNamedAddress("test address"));
+
+ // 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);
+
+ // we can now get it; the stored value should be intact.
+ EXPECT_EQ(ptr32, segment.getNamedAddress("test address"));
+ EXPECT_EQ(test_val, *static_cast<const uint32_t*>(ptr32));
+
+ // 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));
+ EXPECT_EQ(ptr16, segment.getNamedAddress("test address"));
+ EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(ptr16));
+
+ // Clear it. Then we won't be able to find it any more.
+ EXPECT_TRUE(segment.clearNamedAddress("test address"));
+ EXPECT_EQ(static_cast<void*>(NULL),
+ segment.getNamedAddress("test address"));
+
+ // 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));
+ EXPECT_EQ(static_cast<void*>(NULL),
+ segment.getNamedAddress("null address"));
+
+ // 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..1d9979d
--- /dev/null
+++ b/src/lib/util/tests/memory_segment_mapped_unittest.cc
@@ -0,0 +1,620 @@
+// 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/tests/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::test::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) {
+ // Make the mapped file non-writable; managed_mapped_file::grow() will
+ // fail, resulting in std::bad_alloc
+ const int ret = chmod(mapped_file, 0444);
+ ASSERT_EQ(0, ret);
+
+ EXPECT_THROW(segment_->allocate(DEFAULT_INITIAL_SIZE * 2), std::bad_alloc);
+}
+
+// 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)
+{
+ void* dp = sgmt.getNamedAddress(name.c_str());
+ ASSERT_TRUE(dp);
+ EXPECT_EQ(0, std::memcmp(dp, &data[0], data.size()));
+
+ if (delete_after_check) {
+ sgmt.deallocate(dp, 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));
+ EXPECT_NE(static_cast<void*>(NULL),
+ segment_->getNamedAddress("test address"));
+ EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(
+ segment_->getNamedAddress("test address")));
+
+ // 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));
+ EXPECT_EQ(static_cast<void*>(NULL),
+ segment_->getNamedAddress(long_name.c_str()));
+
+ // 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);
+ void* ptr_child = sgmt.getNamedAddress("test address");
+ EXPECT_TRUE(ptr_child);
+ if (ptr_child) {
+ const uint32_t val = *static_cast<const uint32_t*>(ptr_child);
+ 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));
+ ptr = segment_->getNamedAddress("test address");
+ ASSERT_TRUE(ptr);
+ EXPECT_EQ(424242, *static_cast<const uint32_t*>(ptr));
+ 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 = segment_->allocate(sizeof(uint32_t));
+ 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);
+ EXPECT_TRUE(segment_ro.getNamedAddress("test address"));
+ *static_cast<uint32_t*>(
+ segment_ro.getNamedAddress("test address")) = 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);
+ ptr = segment_ro.getNamedAddress("test address");
+ EXPECT_NE(static_cast<void*>(NULL), ptr);
+
+ EXPECT_THROW(segment_ro.deallocate(ptr, 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/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/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/lettuce/features/auth_badzone.feature b/tests/lettuce/features/auth_badzone.feature
index edc1a64..5448b6e 100644
--- a/tests/lettuce/features/auth_badzone.feature
+++ b/tests/lettuce/features/auth_badzone.feature
@@ -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
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/example.feature b/tests/lettuce/features/example.feature
index f2df6d4..86d20d3 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
diff --git a/tests/lettuce/features/nsec3_auth.feature b/tests/lettuce/features/nsec3_auth.feature
index 4e5ed5b..6d3a556 100644
--- a/tests/lettuce/features/nsec3_auth.feature
+++ b/tests/lettuce/features/nsec3_auth.feature
@@ -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)
diff --git a/tests/lettuce/features/terrain/bind10_control.py b/tests/lettuce/features/terrain/bind10_control.py
index 5aab431..d32cd20 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
@@ -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):
diff --git a/tests/lettuce/features/xfrin_notify_handling.feature b/tests/lettuce/features/xfrin_notify_handling.feature
index a17ce42..c14254f 100644
--- a/tests/lettuce/features/xfrin_notify_handling.feature
+++ b/tests/lettuce/features/xfrin_notify_handling.feature
@@ -81,8 +81,8 @@ Feature: Xfrin incoming notify handling
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 notifyoutv6 for the zone _SERVER_ should be 1
+ Then the statistics counter notifyoutv6 for the zone example.org. should be 1
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
@@ -184,8 +184,8 @@ Feature: Xfrin incoming notify handling
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 notifyoutv6 for the zone _SERVER_ should be 1
+ Then the statistics counter notifyoutv6 for the zone example.org. should be 1
# 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
diff --git a/tests/tools/badpacket/option_info.h b/tests/tools/badpacket/option_info.h
index 944f60e..8cc0fe9 100644
--- a/tests/tools/badpacket/option_info.h
+++ b/tests/tools/badpacket/option_info.h
@@ -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/dhcp-perf-guide.xml b/tests/tools/dhcp-ubench/dhcp-perf-guide.xml
index 417968d..0115cd6 100644
--- a/tests/tools/dhcp-ubench/dhcp-perf-guide.xml
+++ b/tests/tools/dhcp-ubench/dhcp-perf-guide.xml
@@ -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
diff --git a/tests/tools/perfdhcp/command_options.cc b/tests/tools/perfdhcp/command_options.cc
index 9169600..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
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 5e143e6..c51ba6d 100644
--- a/tests/tools/perfdhcp/stats_mgr.h
+++ b/tests/tools/perfdhcp/stats_mgr.h
@@ -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();
}
@@ -699,9 +701,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 +720,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 +804,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 +855,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 +908,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.
diff --git a/tests/tools/perfdhcp/test_control.cc b/tests/tools/perfdhcp/test_control.cc
index 4a3075a..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");
}
@@ -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 d3196f7..3983fa6 100644
--- a/tests/tools/perfdhcp/test_control.h
+++ b/tests/tools/perfdhcp/test_control.h
@@ -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
@@ -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_unittest.cc b/tests/tools/perfdhcp/tests/command_options_unittest.cc
index bda256f..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.
- // Although 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/test_control_unittest.cc b/tests/tools/perfdhcp/tests/test_control_unittest.cc
index f7666ae..3e0145c 100644
--- a/tests/tools/perfdhcp/tests/test_control_unittest.cc
+++ b/tests/tools/perfdhcp/tests/test_control_unittest.cc
@@ -672,6 +672,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.
More information about the bind10-changes
mailing list