BIND 10 trac2467, updated. 315d2846dec8b2f36df1aa8ed23feddfc621a356 [2467] Merge branch 'master' into trac2467

BIND 10 source code commits bind10-changes at lists.isc.org
Wed May 8 11:06:45 UTC 2013


The branch, trac2467 has been updated
       via  315d2846dec8b2f36df1aa8ed23feddfc621a356 (commit)
       via  7ece6fb69d2791ea263c080083a725c36e7a12ea (commit)
       via  d2861efce4cf71c924de470c48594727e0eba80f (commit)
       via  70066be6e51afc479dc485dfdf9ab79169a9d3c1 (commit)
       via  c630ff8a3861c5597a5c77a81eee52c09aaef643 (commit)
       via  45e7d86237a1b483d27819c5f5fe0d5c6ebb6dc4 (commit)
       via  e1a0ea8ef5c51b9b25afa111fbfe9347afbe5413 (commit)
       via  d36c276d69d8897175964b8a2002405c53630663 (commit)
       via  7ccddd64de7f7928d20f7eff23053b64c644349d (commit)
       via  5bdee1a5056c8e3517d2dd3de954d40323027ee1 (commit)
       via  034e1d3b1490054f265c4e4ac20cfa43127a5625 (commit)
       via  b308ba3bf71ccc768f302022e7ab571ff2cfbdfd (commit)
       via  72604e2508ea78f0f0fcce74b6e34838e75675a2 (commit)
       via  0475216923e0c60294c0901ae3e6af09f5e961ae (commit)
       via  8f4a354b90fbf0be2ddc5c54f72325d52f0eff2e (commit)
       via  b9608c20b42ed6db6592a1f7851c91efce974b32 (commit)
       via  214b2747a3213b7267a4a8a597d07851f00fb1c8 (commit)
       via  fd6c9be84e460ead7e1fd3e0888828854adccb4f (commit)
       via  71be4dc12e4ffee2641ea54f70bd26f16bdc1460 (commit)
       via  673bbeb9b8b1ecd0629c0807578624705c58085b (commit)
       via  fc4e4e5c2c469c8c464146bfe56cda99effd4593 (commit)
       via  e3c5c430e69e4b3b2211421be2dc23723c4c40eb (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  ac3da26ee0505f828be081d284b4216f5ada820a (commit)
       via  79d4b470181b50699125d1cf3394e11071f6178d (commit)
       via  b6de5342bd648c4f38e892df6c93177c243ec993 (commit)
       via  7ff403a459b34457e433a4cf87ee9fdc64ba5af1 (commit)
       via  bb0d8b35c3f52e2900abccda0f52995683eee4ab (commit)
       via  33e10c84c8b6ab274c157816ba81e6d2fc8ba628 (commit)
       via  3a382331f1308099930d6b653dd6d11b70da8e22 (commit)
       via  0a89b921bb8cb268721e8ca38d99f7ea967fdf8c (commit)
       via  c0acd591b18839863e5c46be32daeebc07c162c4 (commit)
       via  5ee122cfa69f8bd6be3c4861407a3da35cb72bdb (commit)
       via  5ee0c3b143458e52d59e3e162c5d9cd4e11e0d1f (commit)
       via  2a8863be72c4fdf0b9aba1e05e525de7fe18d4b4 (commit)
       via  861a1055e8d48f682b5aed9942f1eb1963d93a55 (commit)
       via  e9fa118cd1b9d4d1c47ddb94244ddf78266c9dbd (commit)
       via  a6328cdc2907b9becde3d261373521dc1a3b963f (commit)
       via  1c295702731e5fb76d8ea39c14104347fb0a8b91 (commit)
       via  51be92846c3bf4881701f17e869eb9f12f9cd239 (commit)
       via  35df226d16838f37390270d9d3d1a5e736cfcd12 (commit)
       via  2aaa162fd5f5921aac58baeeb0dd026b7a0a2fbb (commit)
       via  db5ddcf7b62eb21d9cf0f057ee45ec31adc832f3 (commit)
       via  125dd1132b09275fbb528ebdd4358f5977f17402 (commit)
       via  f82f47b299e9f48c86c6c8f7967f29c381d5df06 (commit)
       via  ca3018c0273b41dac1f67892cdf59e3673a183f5 (commit)
       via  7d6c238e01f66ff90e073c458312e732b9ce3f72 (commit)
       via  719a1cb238f32588f5b891dc3f5d1461efff6905 (commit)
       via  9f0934c0e2ca79fb5b1c03c57cc850fb9bdc22f8 (commit)
       via  73bdf354a07a3e7ad4585881c434fd71c36ca775 (commit)
       via  e825b22e4ea06a8f0ecc8645fbd42af230d05151 (commit)
       via  8250e0313cf3e7f81646e8dd978f4a16f0578222 (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  199beea87baee4dd3d4339ec20c32d0d2cb47f40 (commit)
       via  a354d4ed95f1368c961efabea0eb2e1db95675c3 (commit)
       via  0a2277b4be0ef6e326d170ad7a0836a17b82c0f4 (commit)
       via  6b3b78fa009e557f65deee688302c573106a3ef1 (commit)
       via  b154498887ecdedb12bcde74d91d2e76a3f57e1a (commit)
       via  db2e9d7c08423014a3aba97ffe3d48d23b3ec28a (commit)
       via  4ee02ef54b1056af032347aca528dd1d78c610e9 (commit)
       via  65bbffe7c89169fc958fad642185684154c85aa4 (commit)
       via  abd65c5b6ee4df38adc9dd03207525b805067544 (commit)
       via  a6e56d56d0a24ba1b8ce4c77f3ae7eddf56c7d24 (commit)
       via  e2d04f2d4343bf4c9bf8384f44187145c76d1717 (commit)
       via  f0f403fc8e950db97ddb71a0fe1ba2fc9ab13b3f (commit)
       via  c66811b7635660c28041c947cbd75e6dcc322168 (commit)
       via  4c45f29f28ae766a9f7dc3142859f1d0000284e1 (commit)
       via  9a2bcae912446f9ebe821c1e226f9629f89fa8fd (commit)
       via  d6cae3d58234c7fabfc8d1c9bc5b1b59c7c1e3cf (commit)
       via  91a4343fe608ab91dda3f8caf05fef277a2eb215 (commit)
       via  7b3ab876a7fb697245c81d03bd477f81136355e8 (commit)
       via  4963031b007d9e9f15cacfd058edc20a6d33bf37 (commit)
       via  b4e44a1c5f4f1fa5c16af5bcb2a3ed48a29c1da6 (commit)
       via  73feee77d8a05f458c90990c8ff95f3338c54d72 (commit)
       via  d2b40a13c798fbbc64ef8aa8d02f6572405ab160 (commit)
       via  31f138b96f8713469feb16ff361e40334ec8b83d (commit)
       via  4b79543220fa1f81ffaf16bdcfb79845587f51cd (commit)
       via  c7fd8cf0745b7a13c3ddf0e8309b64c7bdb18f8c (commit)
       via  11d850685bf9e0d6a46b434fde322463a056c95a (commit)
       via  bf60255cc1f000a7251436f2461cd8e9b31e52ff (commit)
       via  bf807435010663e517215f69a5777b76f903927b (commit)
       via  6287fdfff0892a1a71312e0cd2109a03c00cdaea (commit)
       via  6700ecd585dac224bd92008fef7f6ee8a275d04b (commit)
       via  e7f74b8e4b4b8b63d1ebddac464cf713f18e8a15 (commit)
       via  953b4dcd1d40790aafa513ed9be7409da2128ae9 (commit)
       via  e0f000ed71e1eb4a83b8db1b28ff96b6d13b6481 (commit)
       via  a16cb0089b11bb4a2cbc3230ee6870693c469fc7 (commit)
       via  104820a94bef55a4fa1de729723cffc0e3801ac5 (commit)
       via  81d23e305105f215c3d6142f750445f7e2ce0fe2 (commit)
       via  1e763b3d4808e0f722e2f971b726cae5aefde463 (commit)
       via  146f2e6c679ee84783d3b260cf96dac35de9511b (commit)
       via  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  8544c8e88d5519acf996ef6c4659b75b52ce6839 (commit)
       via  5db914470d01eeb3ebde4b71f13647b093df3138 (commit)
       via  1cef6595d167a14adcec8674ec51d6059baa06fc (commit)
       via  04e248bf10707b003b4584a2c06e9733f6729d28 (commit)
       via  61ec72fa83146fe30e2165e02639eab082531712 (commit)
       via  084f1f59a6da39c03e5de0ef079216ef94e277dc (commit)
       via  a50e0a4671586daa8142b17d4f1a7d2ecd9e44bf (commit)
       via  07326330d220e74b0f84572d601d50d83ce48945 (commit)
       via  71e0b2b77c475c41ff73668455d1dc447f01e8e5 (commit)
       via  ff74508f934161e722d3a9d64158c061b46039fc (commit)
       via  ba0d6139cadca933ebbaaa7f5016480154331ae6 (commit)
       via  9c36d2e22389ed7c9ae61fc91eb3f2946b10c169 (commit)
       via  00b7af245214cfdc54f522d719f23406d80d68ad (commit)
       via  d17b3c006757eecc1fab20cec90ead3e23b5962b (commit)
       via  08a77e0ef2096158eff12b5d20e9af9e46c93c45 (commit)
       via  e01ee176ce2071c076833c842a9cd946a5008130 (commit)
       via  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  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  eacf5511e4a005c343b78430c9f749ba46a84c3a (commit)
       via  fdb64790c2d2e104630ffe208439048f0abc50d4 (commit)
       via  b215e58d58791c27f6116f65b66243b7a54ec14b (commit)
       via  58825bde4cbb00eb52ff5bc31a1d170594a26d87 (commit)
       via  5d1b066a7702c075d7934a3ce88181b1c02da878 (commit)
       via  b610eee2708d9c1783384c850896c6203ccc3dd2 (commit)
       via  58f99ec6977a07a6df8f7053d2e03201245491fd (commit)
       via  3b1551b28913ae83e62997de0c8c4dbd28c25217 (commit)
       via  23af78de3f2162aa27920e0ccc2784d76e0def8d (commit)
       via  22eb335a0216d642194450947b28ad2ac3f771f0 (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  5305956b8ed2e5983eb4255e959ffc26c6017693 (commit)
       via  5dc9b2f5c86bab3a7aec205a2f5cf91b2b6398e1 (commit)
       via  23fa01352842de0a16314734ac061f87c81b662a (commit)
       via  21ef42e572becd4001baa3fcd509b955e077c656 (commit)
       via  493b6d66358602da6281641df2c80833f2b10b94 (commit)
       via  1b437a9f90cf46154bd7cc3c0c63315293d8860c (commit)
       via  2ca8aec80f6a9dcb4da01a9057e9abb9717ab93e (commit)
       via  9a160a14fcb4d4b781ee79fdd4aa60f8e56918b3 (commit)
       via  69127fa628cf8cf44362b9334ae1c0e6c6ee5475 (commit)
       via  639532ca50d317fdee78374be0e05c0c43ee2a0d (commit)
       via  e1126a32146e14b8816b7a3fb733f3836ca61379 (commit)
       via  252e7844e40e29ba6a7a30498bf525b75deb8219 (commit)
       via  43ad3049d20a10c2a96ae85b167694aaaaadb6be (commit)
       via  9d00667f84006fc001b04953519e78b1ee5f0010 (commit)
       via  d7885ecddb52be49de729021a70b42e60aca2cea (commit)
       via  63df2f7cdb5672540d176054f5fda0d3a93707e2 (commit)
       via  8bc0766533f5f78c4ef8bcd9438f816e58c3aa83 (commit)
       via  1f578c9b208b0fb2e58b101d537a9d07f66ad690 (commit)
       via  4f63e23b7aac425bafd4e0c6e5c4099b157206c5 (commit)
       via  a4b1c8d4bb37b273c9027f41b00eeae54ad2f83d (commit)
       via  4a8316ddc1bff361a289ed8c406bbe90fde79e16 (commit)
       via  dd0c9ba580d2b87324e3063de6f69d2bc8be5687 (commit)
       via  5c0c1cb8f090edf516c21cf03506075bd866915c (commit)
       via  67d05b954c47821d6a29ba093632604c49fb794c (commit)
       via  2f6c7ab36c4c2c88bc19503a2bf5577d148cb71b (commit)
       via  8a09c89d47dd66c641809fadca16d29eb79c25f4 (commit)
       via  229916f0c84ee62b3c0f3c204d6b20dd3e8d3062 (commit)
       via  ba37fe42bf0e97653780bddc8ecb47a090c3ccc9 (commit)
       via  41a8bec7700a7792e1f24883add4e542977c111e (commit)
       via  c37383f57aa3d6b6cd4911279a143091912c0e32 (commit)
       via  d253c3c481194eff6ae52861624d0d0fdf44b4b3 (commit)
       via  854d608fd26a2b9c1cee43f5ce48d75ac7223e22 (commit)
       via  83652c539b9e0fc04202bef8c6abe6fffad1f58c (commit)
       via  bb5f0f9677e89c5fa45a6e54c2176a2496cfa6a8 (commit)
       via  97f58d9418e14b7076850c529619dcd1efa41145 (commit)
       via  4a27055866a0ec36350d20422713174d61543015 (commit)
       via  a5ec1487b162ef818186ae92b54e5c73d8757bd1 (commit)
       via  e7971923e90e58921feac425b7ec9073f4a22c95 (commit)
       via  962447aa16c4f4accd42fc326d9408cdf75d2dde (commit)
       via  b7a8ae05f15fb10c0e2215565f160889390c3c8d (commit)
       via  e346959d031b0ce05939f801e83f653892757e49 (commit)
       via  60b242c2d161e30a05236fb6dae3ff21daa0b6b8 (commit)
       via  307e3c7a946a593945050b9ea8159df85f5cf28d (commit)
       via  2e97db1d7db5a65bc19f6909269ae930d2e8da18 (commit)
       via  4d5c20e29fd4a74db31024bdcdaa99d431af3219 (commit)
       via  20df0b0ce702241ce8265690388483c5ece3eb16 (commit)
       via  ff8d95f4e0afc3d2b8fc5f3e75eb41edee1f969e (commit)
       via  c3c0784a907aca8b915221c63e9228a81e93c346 (commit)
       via  2630be5587b6548b0217f16fb78bc80935013a69 (commit)
       via  43fa9db5aecf34e77a211c43c4cc2cc6e27e7530 (commit)
       via  d919490c1df6f34147d38e3d0af91d8b836fe25f (commit)
       via  d9b1da77a397df06221431acaa3ba3caba40bb7a (commit)
       via  f5a9aeba7e7b55411834315f192c77d98d3d82d5 (commit)
       via  f0f75ecda5cb7e51fbebbcf35ca40f3d38b4b535 (commit)
       via  70a919f029b159119a50ac6d6fded6b843167ef1 (commit)
       via  dfe737706fed6ca7aac475fb91d988222799b21b (commit)
       via  3c941835b81c1ad7869f38bbf74d8e0a7fd567a7 (commit)
       via  678965be5a2ca3ba12981884e48911028953f853 (commit)
       via  7e9c676a0a800322d77ecac81bf223405011442c (commit)
       via  147db3ecd8be5a6fe692c07c3d4d7597f8f2fceb (commit)
       via  84076d913d1e974e504ddac1c7b8a22c9172bc9b (commit)
       via  122da90997b11cee73dd63291216aa4c6c6ecd11 (commit)
       via  400ddf1d85976eb07188e6fa20ae0a83274895fc (commit)
       via  479c0eeabb8cdb874385fd9ef2160bddf8a0a80f (commit)
       via  836fabb60589aaf7b901176892b6c28ab464e8d6 (commit)
       via  eb8aaf5c3b048047cd1cc138ee4951405bb59ce3 (commit)
       via  c97e9105e1c25d4708d2f153c93c1a29a73c9537 (commit)
       via  30c5683babdc96906117dfd677c3313d3a82fea9 (commit)
       via  2d0fc10cb714c3e34bc670a86ba645b42d3bd777 (commit)
       via  acbf4d8c9e3deb825a7c193a26738b12ef870166 (commit)
       via  fbfd9a49b562e3f5532e87ad801baf82c6b7a912 (commit)
       via  14ee53dc65a150d46f51e4cf6d666c38187ce075 (commit)
       via  9344c41c186e020cddf191880ff5bef4e653b461 (commit)
       via  b448dabbe9de31366ae5a20c0aae423f6257d953 (commit)
       via  c11f99307c27f07ad5b8ab9abb80f6ee5be32efc (commit)
       via  0eec3b5ce47348b8fdf8eae9ffc61dcb4db0ccb0 (commit)
       via  c37fd56ccccd540bde166eb7f29c2c691bbff154 (commit)
       via  be426ec46dfd7aa3573872059ac9420dd4fd7d75 (commit)
       via  208c318edbba5b98ebbd865f15c31bd77626328c (commit)
       via  bae4e4960422f2f86af1fc48d1f1d1a8f23a1b95 (commit)
       via  b0f2acac75969ee9773b3ec3eb584e017cbfb117 (commit)
       via  5ac7de770cd112d0bc3842dd79c612bae3acc611 (commit)
       via  ba20d4a58b6d7ea93c69681372bd6f1064491d1a (commit)
       via  dc2960e9845fe08a27d49dcf9d6474ad8b825fb1 (commit)
       via  d1d37e198212833b5ca1e5c943f3f8aeef805a17 (commit)
       via  ad8e7ea08e814913ff6fadbf593a3fba44d4bbbc (commit)
       via  26a9ee9fd3b8d2cf0c9643ac5441bcedfa3c2ab3 (commit)
       via  d6d04321180adf453ecb64c28e03b2c43591e521 (commit)
       via  be67ff2306c8aa98efd9ae3b017a2926dd3fd46e (commit)
       via  cb5eb7ddeb879f1658e446d06e9556aa075d4e3a (commit)
       via  d69fb646ed39dafe4277334a28b52d948e6348ae (commit)
       via  655179cfb251cd07a03469524e91c4ed81b57803 (commit)
       via  aca3456d32de52b95c0ec0986c041cc9ab63e132 (commit)
       via  55a49c67cdff76b0dca55d425ae977fb3feda9c2 (commit)
       via  85aef712019c4b0293e1726c705b6bfc749ad537 (commit)
       via  fcf8b9df5fb8511e9bd78f42bd797f2a8718c6e6 (commit)
       via  7fe200c92eb411c554a82fedb9b4126686961f0c (commit)
       via  0ef3b7adcd45bbd3127fc3aef69cf6798e69c807 (commit)
       via  936b3ffaaaca125fe6a87ea3ad3910aa2d45c3d5 (commit)
       via  695a3119423907a059435465f98669976a969f28 (commit)
       via  4d94e850cc95e491ce268c9782c0eae335e7f7ea (commit)
       via  2cfdda6be3038b9318c9f72da1b7c20488f2ae99 (commit)
       via  01b2a2fa9d86fbc5796626461b282581e46d2979 (commit)
       via  4a6f54f71484ebc8531371da819d4f98f59418e9 (commit)
       via  14023798bdf7385a96500cf8bb7d93d485def18a (commit)
       via  8e75dec19bbdb3ae1e01558ed5bb8cd89ad45582 (commit)
       via  bb5479f56b9bdab1eff81e8ecca1d8e703a9ef50 (commit)
       via  2fb93c012e0c3f8a5e8e0fff208abe5685b7d4ce (commit)
       via  27ae48b404de59c031c7f97f64edd4056c5e62dc (commit)
       via  4de51f893dc92b863a9d3baa747153c2a011923c (commit)
       via  a1b3cfd353802b33b1ed51b51c9d98f6fe56d2bc (commit)
       via  75445a1c6e208d27ab8d85166ebd079be947f98b (commit)
       via  643e32a402aa9dcfbf9d7df47495c77474eeffe8 (commit)
       via  a2ea0fb9c996d174936122809275cc464cd2e17c (commit)
       via  2bc9bc74ea4450a1581ffcaf5f1598a4c03d3721 (commit)
       via  0fccc0c84fb7706ff71039896564c5496f0cb7ed (commit)
       via  d7647f9c62f62e2115947ec0f1aaea57cc266b0b (commit)
       via  111e2c83f6584bc452e5658f17779b30d2959c99 (commit)
       via  c2d22d81551f2309538404b52615d04394a3b9c3 (commit)
       via  a3077227a49f8cd0ae4e7c1f20cc87c4844ef0fe (commit)
       via  685c9b375d73134e0c59ba8c7623f74487f69348 (commit)
       via  4cfe351f170077546b2187f9d33d41b4148aa6a0 (commit)
       via  042bfd80328dd3c5888ac70630f1545aba82f125 (commit)
       via  d46357aadcf46d1be0be68fd510d9065ecba2a57 (commit)
       via  4b74e2550ba3030f666d03bf57f65f1d6d1a30f7 (commit)
       via  e42a6c969cda1e3676c9884e9697349e18db92b7 (commit)
       via  f4486e178f04944792e169c1a7a0c5098dd2c223 (commit)
       via  857b8cbf4ad057e50232eb152bd1ec4903da807f (commit)
       via  e4cf4a7222b006c5534edaecb23614fb224585ff (commit)
       via  fc40317d31b6c76327ff83d81c9ee3598e05cf3d (commit)
       via  63808a73aa582e3bac75c70bc332e3eaca8152ba (commit)
       via  986925540226c118c63b33803914f9e5d44c0576 (commit)
       via  68a52f5e6e31895b350bb049b7eee9f262efbb1a (commit)
       via  df40284b1ee1bd833f9cc79f0da0c1202d38d951 (commit)
       via  feae5f317ee4c0940fb49c92f08228e21b720a8e (commit)
       via  cef5bd992ace60f4e7af50ba9e46d7e27aa9e39e (commit)
       via  6c13b0c102f7716cb70b381b5f615e1b29a05ce3 (commit)
       via  8add7a15c72e218d7a954c4cb2765206806e1237 (commit)
       via  29f51829c08e4248ee79f073c466bad761d07f5d (commit)
       via  0d7421b938c0017f364daf2bd4ef5d27aa1c66fa (commit)
       via  605921ab9a037b9361ac4eafeefe82c9b0d83a9d (commit)
       via  35edc2d075c899f9da26bf6644c8c113826aa16b (commit)
       via  e2f1735572f26c3f8003ec17f6b9be333ac4320d (commit)
       via  d9749e1f6d660696c07c2e3474fce9f291d5b959 (commit)
       via  6cd568fb20e3714a77e98d31f0387e941702c327 (commit)
       via  64cdf20648e3a3aa50334c4d555838213901602e (commit)
       via  6655139a125d4f9032bef3dbbc0a49f9ae2b87d7 (commit)
       via  0e92ed020eecca48e4fb045221d732d591d1afad (commit)
      from  55dd08057c52c3f0baf3f1b34f23f87c2ee7ff35 (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 315d2846dec8b2f36df1aa8ed23feddfc621a356
Merge: 55dd080 7ece6fb
Author: Stephen Morris <stephen at isc.org>
Date:   Wed May 8 11:54:45 2013 +0100

    [2467] Merge branch 'master' into trac2467
    
    Conflicts:
    	src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
    	src/lib/dhcp/tests/iface_mgr_unittest.cc

-----------------------------------------------------------------------

Summary of changes:
 ChangeLog                                          |   87 ++-
 Makefile.am                                        |    4 +-
 configure.ac                                       |   25 +-
 doc/design/ipc-high.txt                            |  382 ++++++++++++
 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/cfgmgr/plugins/datasrc.spec.pre.in         |    6 +
 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/dhcp4_srv.cc                         |   32 +-
 src/bin/dhcp4/dhcp4_srv.h                          |    4 +-
 src/bin/dhcp4/tests/dhcp4_srv_unittest.cc          |  353 ++++++-----
 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           |  228 +++++++
 .../resolver/bench/main.cc}                        |   28 +-
 src/bin/resolver/bench/naive_resolver.cc           |   66 +++
 .../resolver/bench/naive_resolver.h}               |   39 +-
 src/bin/stats/tests/Makefile.am                    |    2 +-
 ...b10-stats-httpd_test.py => stats-httpd_test.py} |  229 +++++---
 .../tests/{b10-stats_test.py => stats_test.py}     |  348 ++++++-----
 src/bin/stats/tests/test_utils.py                  |  363 ++++--------
 src/bin/xfrin/b10-xfrin.xml                        |  126 ++++
 src/bin/xfrin/tests/xfrin_test.py                  |  212 ++++++-
 src/bin/xfrin/xfrin.py.in                          |   56 +-
 src/bin/xfrin/xfrin.spec                           |  113 ++++
 src/bin/xfrin/xfrin_messages.mes                   |    3 +
 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                  |   70 ++-
 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                     |   28 +-
 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 +-
 src/lib/datasrc/Makefile.am                        |    6 +-
 src/lib/datasrc/cache_config.cc                    |   87 ++-
 src/lib/datasrc/cache_config.h                     |   58 +-
 src/lib/datasrc/client_list.cc                     |  163 ++---
 src/lib/datasrc/client_list.h                      |   10 +-
 src/lib/datasrc/data_source.h                      |   68 ---
 src/lib/datasrc/database.cc                        |   13 +-
 src/lib/datasrc/database.h                         |   37 +-
 src/lib/datasrc/datasrc_messages.mes               |   15 +-
 src/lib/datasrc/exceptions.h                       |   20 +
 src/lib/datasrc/factory.cc                         |    2 +-
 src/lib/datasrc/factory.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/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                |   14 +-
 src/lib/datasrc/sqlite3_accessor.h                 |    4 +-
 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 |   13 +-
 .../datasrc/tests/zone_finder_context_unittest.cc  |   22 +-
 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/iface_mgr.cc                          |  287 ++++-----
 src/lib/dhcp/iface_mgr.h                           |  498 +++++++++-------
 src/lib/dhcp/iface_mgr_bsd.cc                      |    7 +-
 src/lib/dhcp/iface_mgr_linux.cc                    |   66 +--
 src/lib/dhcp/iface_mgr_sun.cc                      |    7 +-
 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/iface_mgr_unittest.cc           |  142 ++++-
 src/lib/dhcpsrv/Makefile.am                        |    9 +-
 src/lib/dhcpsrv/alloc_engine.cc                    |    2 -
 src/lib/dns/python/rrset_python.cc                 |   10 +-
 src/lib/dns/python/tests/rrset_python_test.py      |    7 +-
 src/lib/dns/tsigrecord.cc                          |    7 +-
 src/lib/python/isc/datasrc/client_python.cc        |    2 +-
 src/lib/python/isc/datasrc/finder_python.cc        |    2 +-
 src/lib/python/isc/datasrc/updater_python.cc       |    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/statistics/counters.py          |    8 +-
 .../python/isc/statistics/tests/counters_test.py   |    4 +-
 .../isc/statistics/tests/testdata/test_spec3.spec  |   16 +-
 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/tests/Makefile.am                     |    6 +
 .../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 ++++++++++++++++++++
 .../xfrin/retransfer_master.conf.orig              |    3 -
 ...er.conf.orig => retransfer_master_v4.conf.orig} |    9 +-
 .../xfrin/retransfer_slave_notify.conf.orig        |    1 +
 ...y.conf.orig => retransfer_slave_notify_v4.conf} |    7 +-
 tests/lettuce/features/auth_badzone.feature        |    6 +-
 tests/lettuce/features/bindctl_commands.feature    |   15 +
 tests/lettuce/features/terrain/bind10_control.py   |  138 +++--
 tests/lettuce/features/terrain/terrain.py          |    2 +
 .../lettuce/features/xfrin_notify_handling.feature |  416 +++++++++----
 tests/tools/perfdhcp/stats_mgr.h                   |   15 +-
 tests/tools/perfdhcp/test_control.cc               |   12 +-
 tests/tools/perfdhcp/test_control.h                |    2 +-
 144 files changed, 7544 insertions(+), 2432 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%)
 rename src/bin/stats/tests/{b10-stats-httpd_test.py => stats-httpd_test.py} (87%)
 rename src/bin/stats/tests/{b10-stats_test.py => stats_test.py} (87%)
 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/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
 copy tests/lettuce/configurations/xfrin/{retransfer_master.conf.orig => retransfer_master_v4.conf.orig} (89%)
 copy tests/lettuce/configurations/xfrin/{retransfer_slave_notify.conf.orig => retransfer_slave_notify_v4.conf} (85%)

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index f5bb09c..b2717a8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,13 +1,90 @@
+611.	[func]		naokikambe
+	Added Xfrin statistics items such as the number of successful
+	transfers.  These are per-zone type counters.  Their values can be
+	obtained with zone names by invoking "Stats show Xfrin" via bindctl
+	while Xfrin is running.
+	(Trac #2252, git e1a0ea8ef5c51b9b25afa111fbfe9347afbe5413)
+
+bind10-1.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
-	Perdhcp 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.
+	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 do that the parameter includes reversed name in
+	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
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 eff05ec..af63958 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
 # Process this file with autoconf to produce a configure script.
 
 AC_PREREQ([2.59])
-AC_INIT(bind10, 20130221, bind10-dev at isc.org)
+AC_INIT(bind10, 20130503, 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/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/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/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/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index c651275..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()");
@@ -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 31d4794..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();
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index 46b588a..cabd8b7 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>
@@ -46,7 +47,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;
@@ -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();
 
@@ -381,7 +507,7 @@ TEST_F(Dhcpv4SrvTest, basic) {
     EXPECT_TRUE(naked_srv->getServerID());
 }
 
-// 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.
 //
@@ -389,199 +515,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) {
-    boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
-    vector<uint8_t> mac(6);
-    for (int i = 0; i < 6; i++) {
-        mac[i] = 255 - i;
-    }
-
-    Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 1234));
-    Pkt4Ptr 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());
+TEST_F(Dhcpv4SrvTest, processDiscoverRelay) {
+    testDiscoverRequest(DHCPDISCOVER,
+                        IOAddress("192.0.2.56"),
+                        IOAddress("192.0.2.67"));
+}
 
-    messageCheck(pkt, 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"));
+}
 
-    // Check that the requested options are returned.
-    optionsCheck(offer);
+// 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) {
-    boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
-    vector<uint8_t> mac(6);
-    for (int i = 0; i < 6; i++) {
-        mac[i] = i * 10;
-    }
-
-    Pkt4Ptr req(new Pkt4(DHCPREQUEST, 1234));
-    Pkt4Ptr 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());
+// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
+// and DiscoverInvalidHint.
+TEST_F(Dhcpv4SrvTest, processRequestRelay) {
+    testDiscoverRequest(DHCPREQUEST,
+                        IOAddress("192.0.2.56"),
+                        IOAddress("192.0.2.67"));
+}
 
-    messageCheck(req, 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"));
+}
 
-    // Check that the requested options are returned.
-    optionsCheck(ack);
+// 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/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..e3d54a9
--- /dev/null
+++ b/src/bin/resolver/bench/fake_resolution.cc
@@ -0,0 +1,172 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <resolver/bench/fake_resolution.h>
+#include <resolver/bench/dummy_work.h>
+
+#include <asiolink/interval_timer.h>
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <algorithm>
+#include <stdlib.h> // not cstdlib, which doesn't officially have random()
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+// Parameters of the generated queries.
+// How much work is each operation?
+const size_t parse_size = 100000;
+const size_t render_size = 100000;
+const size_t send_size = 1000;
+const size_t cache_read_size = 10000;
+const size_t cache_write_size = 10000;
+// How large a change is to terminate in this iteration (either by getting
+// the complete answer, or by finding it in the cache). With 0.5, half the
+// queries are found in the cache directly. Half of the rest needs just one
+// upstream query. Etc.
+const float chance_complete = 0.5;
+// Number of milliseconds an upstream query can take. It picks a random number
+// in between.
+const size_t upstream_time_min = 2;
+const size_t upstream_time_max = 50;
+
+FakeQuery::FakeQuery(FakeInterface& interface) :
+    interface_(&interface),
+    outstanding_(false)
+{
+    // Schedule what tasks are needed.
+    // First, parse the query
+    steps_.push_back(Step(Compute, parse_size));
+    // Look into the cache if it is there
+    steps_.push_back(Step(CacheRead, cache_read_size));
+    while ((1.0 * random()) / RAND_MAX > chance_complete) {
+        // Needs another step of recursion. Render the upstream query.
+        steps_.push_back(Step(Compute, render_size));
+        // Send it and wait for the answer.
+        steps_.push_back(Step(Upstream, upstream_time_min +
+                              (random() *
+                               (upstream_time_max - upstream_time_min) /
+                               RAND_MAX)));
+        // After it comes, parse the answer and store it in the cache.
+        steps_.push_back(Step(Compute, parse_size));
+        steps_.push_back(Step(CacheWrite, cache_write_size));
+    }
+    // Last, render the answer and send it.
+    steps_.push_back(Step(Compute, render_size));
+    steps_.push_back(Step(Send, send_size));
+    // Reverse it, so we can pop_back the tasks as we work on them.
+    std::reverse(steps_.begin(), steps_.end());
+}
+
+void
+FakeQuery::performTask(const StepCallback& callback) {
+    // nextTask also does all the sanity checking we need.
+    if (nextTask() == Upstream) {
+        outstanding_ = true;
+        interface_->scheduleUpstreamAnswer(this, callback,
+                                           steps_.back().second);
+        steps_.pop_back();
+    } else {
+        for (size_t i = 0; i < steps_.back().second; ++i) {
+            dummy_work();
+        }
+        steps_.pop_back();
+        callback();
+    }
+}
+
+FakeInterface::FakeInterface(size_t query_count) :
+    queries_(query_count)
+{
+    BOOST_FOREACH(FakeQueryPtr& query, queries_) {
+        query = FakeQueryPtr(new FakeQuery(*this));
+    }
+}
+
+void
+FakeInterface::processEvents() {
+    service_.run_one();
+}
+
+namespace {
+
+void
+processDone(bool* flag) {
+    *flag = true;
+}
+
+}
+
+FakeQueryPtr
+FakeInterface::receiveQuery() {
+    // Handle all the events that are already scheduled.
+    // As processEvents blocks until an event happens and we want to terminate
+    // if there are no events, we do a small trick. We post an event to the end
+    // of the queue and work until it is found. This should process all the
+    // events that were there already.
+    bool processed = false;
+    service_.post(boost::bind(&processDone, &processed));
+    while (!processed) {
+        processEvents();
+    }
+
+    // Now, look if there are more queries to return.
+    if (queries_.empty()) {
+        return (FakeQueryPtr());
+    } else {
+        // Take from the back. The order doesn't matter and it's faster from
+        // there.
+        FakeQueryPtr result(queries_.back());
+        queries_.pop_back();
+        return (result);
+    }
+}
+
+class FakeInterface::UpstreamQuery {
+public:
+    UpstreamQuery(FakeQuery* query, const FakeQuery::StepCallback& callback,
+                  const boost::shared_ptr<asiolink::IntervalTimer> timer) :
+        query_(query),
+        callback_(callback),
+        timer_(timer)
+    {}
+    void trigger() {
+        query_->answerReceived();
+        callback_();
+        // We are not needed any more.
+        delete this;
+    }
+private:
+    FakeQuery* const query_;
+    const FakeQuery::StepCallback callback_;
+    // Just to hold it alive before the callback is called.
+    const boost::shared_ptr<asiolink::IntervalTimer> timer_;
+};
+
+void
+FakeInterface::scheduleUpstreamAnswer(FakeQuery* query,
+                                      const FakeQuery::StepCallback& callback,
+                                      size_t msec)
+{
+    const boost::shared_ptr<asiolink::IntervalTimer>
+        timer(new asiolink::IntervalTimer(service_));
+    UpstreamQuery* q(new UpstreamQuery(query, callback, timer));
+    timer->setup(boost::bind(&UpstreamQuery::trigger, q), msec);
+}
+
+}
+}
+}
diff --git a/src/bin/resolver/bench/fake_resolution.h b/src/bin/resolver/bench/fake_resolution.h
new file mode 100644
index 0000000..cf2219c
--- /dev/null
+++ b/src/bin/resolver/bench/fake_resolution.h
@@ -0,0 +1,228 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef FAKE_RESOLUTION_H
+#define FAKE_RESOLUTION_H
+
+#include <exceptions/exceptions.h>
+#include <asiolink/io_service.h>
+
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <utility>
+#include <vector>
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+/// \brief The kind of task a FakeQuery might want to perform.
+///
+/// The benchmark should examine which kind of task the query needs to perform
+/// to progress forward. According to the task, some resources might need to be
+/// locked, something re-scheduled, or such.
+enum Task {
+    /// \brief Some CPU-bound computation.
+    ///
+    /// The query needs to do some computation without any shared resources.
+    /// This might be parsing or rendering of the query, verification of
+    /// signatures, etc.
+    Compute,
+    /// \brief The query needs to read data from cache.
+    CacheRead,
+    /// \brief The query needs to modify the cache.
+    CacheWrite,
+    /// \brief A response is to be sent.
+    ///
+    /// This needs to access the interface/socket. If the socket is shared
+    /// between threads, it might need to lock it.
+    Send,
+    /// \brief An answer from upstream server is needed.
+    ///
+    /// The query needs to send a query to some authoritative server and wait
+    /// for the answer. Something might need to be locked (or not, depending
+    /// on the architecture of the thing that sends and receives). Also, the
+    /// task will not complete immediately, the callback of performTask
+    /// will be called at later time.
+    Upstream
+};
+
+class FakeInterface;
+
+/// \brief Imitation of the work done to resolve a query.
+///
+/// An object of this class represents some fake work that should look like
+/// the work needed to perform resolution of one query. No real work is done,
+/// but several steps are scheduled, with characteristics hopefully
+/// corresponding to steps of the real query.
+///
+/// The idea is that benchmark will repeatedly check if the query is done.
+/// If not, it examines the next task by calling nextTask(). Depending on
+/// the result, it'd lock or prepare any shared resources. After that, it'd
+/// call performTask() to do the task. Once the query calls the callback
+/// passed, it can proceed to the next step.
+///
+/// See naive_resolver.cc for example code how this could be done.
+class FakeQuery {
+private:
+    // The queries come only through an interface. Don't let others create.
+    friend class FakeInterface;
+    /// \brief Constructor
+    FakeQuery(FakeInterface& interface);
+public:
+    /// \brief Is work on the query completely done?
+    ///
+    /// If this returns true, do not call performTask or nextTask any more.
+    /// The resolution is done.
+    ///
+    /// \throw isc::InvalidOperation if upstream query is still in progress.
+    bool done() const {
+        if (outstanding_) {
+            isc_throw(isc::InvalidOperation, "Upstream query outstanding");
+        }
+        return (steps_.empty());
+    }
+    /// \brief Callback to signify a task has been performed.
+    typedef boost::function<void()> StepCallback;
+    /// \brief Perform next step in the resolution.
+    ///
+    /// Do whatever is needed to be done for the next step of resolution.
+    /// Once the step is done, the callback is called.
+    ///
+    /// The callback is usually called from within this call. However, in
+    /// the case when the nextTask() returned `Upstream`, the call to the
+    /// callback is delayed for some period of time after the method
+    /// returns.
+    ///
+    /// \throw isc::InvalidOperation if it is called when done() is true, or
+    ///     if an upstream query is still in progress (performTask was called
+    ///     before and the callback was not called by the query yet).
+    void performTask(const StepCallback& callback);
+    /// \brief Examine the kind of the next resolution process.
+    ///
+    /// Call this to know what kind of task will performTask do next.
+    ///
+    /// \throw isc::InvalidOperation if it is called when done() is true, or
+    ///     if an upstream query is still in progress (performTask was called
+    ///     before and the callback was not called by the query yet).
+    Task nextTask() const {
+        // Will check for outstanding_ internally too
+        if (done()) {
+            isc_throw(isc::InvalidOperation, "We are done, no more tasks");
+        }
+        return (steps_.back().first);
+    }
+    /// \brief Move network communication to different interface.
+    ///
+    /// By default, a query does all the "communication" on the interface
+    /// it was born on. This may be used to move a query from one interface
+    /// to another.
+    ///
+    /// You don't have to lock either of the interfaces to do so, this
+    /// only switches the data in the query.
+    ///
+    /// \throw isc::InvalidOperation if it is called while an upstream query
+    ///     is in progress.
+    void migrateTo(FakeInterface& dst_interface) {
+        if (outstanding_) {
+            isc_throw(isc::InvalidOperation,
+                      "Can't migrate in the middle of query");
+        }
+        interface_ = &dst_interface;
+    }
+    /// \brief The answer for upstream query was received
+    ///
+    /// This should be called from within the FakeInterface only.
+    /// It marks that the query from upstream was answered.
+    void answerReceived() {
+        outstanding_ = false;
+    }
+private:
+    // The scheduled steps for this task.
+    typedef std::pair<Task, size_t> Step;
+    // The scheduled steps. Reversed (first to be done at the end), so we can
+    // pop_back() the completed steps.
+    std::vector<Step> steps_;
+    // The interface to schedule timeouts on.
+    FakeInterface* interface_;
+    // Is an upstream query outstanding?
+    bool outstanding_;
+};
+
+typedef boost::shared_ptr<FakeQuery> FakeQueryPtr;
+
+/// \brief An imitation of interface for receiving queries.
+///
+/// This is effectively a little bit smarter factory for queries. You can
+/// request a new query from it, or let process events (incoming answers).
+///
+/// It contains its own event loop. If the benchmark has more threads, have
+/// one in each of the threads (if the threads ever handles network
+/// communication -- if it accepts queries, sends answers or does upstream
+/// queries).
+///
+/// If the model simulated would share the same interface between multiple
+/// threads, it is better to have one in each thread as well, but lock
+/// access to receiveQuery() so only one is used at once (no idea what happens
+/// if ASIO loop is accessed from multiple threads).
+///
+/// Note that the creation of the queries is not thread safe (due to
+/// the random() function inside). The interface generates all its queries
+/// in advance, on creation time. But you need to create all the needed
+/// interfaces from single thread and then distribute them to your threads.
+class FakeInterface {
+public:
+    /// \brief Constructor
+    ///
+    /// Initiarile the interface and create query_count queries for the
+    /// benchmark. They will be handed out one by one with receiveQuery().
+    FakeInterface(size_t query_count);
+    /// \brief Wait for answers from upstream servers.
+    ///
+    /// Wait until at least one "answer" comes from the remote server. This
+    /// will effectively block the calling thread until it is time to call
+    /// a callback of performTask.
+    ///
+    /// It is not legal to call it without any outstanding upstream queries
+    /// on this interface. However, the situation is not explicitly checked.
+    ///
+    /// \note Due to internal implementation, it is not impossible no or more
+    ///    than one callbacks to be called from within this method.
+    void processEvents();
+    /// \brief Accept another query.
+    ///
+    /// Generate a new fake query to resolve.
+    ///
+    /// This method might call callbacks of other queries waiting for upstream
+    /// answer.
+    ///
+    /// This returns a NULL pointer when there are no more queries to answer
+    /// (the number designated for the benchmark was reached).
+    FakeQueryPtr receiveQuery();
+private:
+    class UpstreamQuery;
+    friend class FakeQuery;
+    void scheduleUpstreamAnswer(FakeQuery* query,
+                                const FakeQuery::StepCallback& callback,
+                                size_t msec);
+    asiolink::IOService service_;
+    std::vector<FakeQueryPtr> queries_;
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/bin/resolver/bench/main.cc b/src/bin/resolver/bench/main.cc
new file mode 100644
index 0000000..3007c40
--- /dev/null
+++ b/src/bin/resolver/bench/main.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <resolver/bench/naive_resolver.h>
+
+#include <bench/benchmark.h>
+
+const size_t count = 1000; // TODO: We may want to read this from argv.
+
+int main(int, const char**) {
+    // Run the naive implementation
+    isc::resolver::bench::NaiveResolver naive_resolver(count);
+    isc::bench::BenchMark<isc::resolver::bench::NaiveResolver>
+        (1, naive_resolver, true);
+    return 0;
+}
diff --git a/src/bin/resolver/bench/naive_resolver.cc b/src/bin/resolver/bench/naive_resolver.cc
new file mode 100644
index 0000000..1654496
--- /dev/null
+++ b/src/bin/resolver/bench/naive_resolver.cc
@@ -0,0 +1,66 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <resolver/bench/naive_resolver.h>
+
+#include <cassert>
+#include <boost/bind.hpp>
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+NaiveResolver::NaiveResolver(size_t query_count) :
+    interface_(query_count),
+    processed_(false)
+{}
+
+namespace {
+
+void
+stepDone(bool* flag) {
+    *flag = true;
+}
+
+}
+
+size_t
+NaiveResolver::run() {
+    assert(!processed_);
+    size_t count = 0;
+    FakeQueryPtr query;
+    // Process a query at a time. As the previous is already handled, the
+    // receiveQuery may never trigger other events.
+    while ((query = interface_.receiveQuery())) {
+        // Handle each step
+        while (!query->done()) {
+            bool done = false; // This step is not yet done.
+            // If there were more queries/threads/whatever, we would examine
+            // the query->nextTask() and lock or prepare resources accordingly.
+            // But as there's just one, we simply do the task, without caring.
+            query->performTask(boost::bind(&stepDone, &done));
+            // We may need to wait for the upstream query.
+            while (!done) {
+                interface_.processEvents();
+            }
+        }
+        count ++;
+    }
+    processed_ = true;
+    return (count);
+}
+
+}
+}
+}
diff --git a/src/bin/resolver/bench/naive_resolver.h b/src/bin/resolver/bench/naive_resolver.h
new file mode 100644
index 0000000..e1deff1
--- /dev/null
+++ b/src/bin/resolver/bench/naive_resolver.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RESOLVER_BENCH_NAIVE_H
+#define RESOLVER_BENCH_NAIVE_H
+
+#include <resolver/bench/fake_resolution.h>
+
+namespace isc {
+namespace resolver {
+namespace bench {
+
+/// \brief Naive implementation of resolver for the benchmark
+///
+/// This is here mostly to show how to implement the other benchmark
+/// implementations. Look at the code inside how to use the fake
+/// resolution.
+class NaiveResolver {
+public:
+    /// \brief Constructor. Initializes the data.
+    NaiveResolver(size_t query_count);
+    /// \brief Run the resolution.
+    size_t run();
+private:
+    FakeInterface interface_;
+    bool processed_;
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/bin/stats/tests/Makefile.am b/src/bin/stats/tests/Makefile.am
index a5ff4e5..06d11a1 100644
--- a/src/bin/stats/tests/Makefile.am
+++ b/src/bin/stats/tests/Makefile.am
@@ -1,7 +1,7 @@
 SUBDIRS = testdata .
 
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-PYTESTS = b10-stats_test.py b10-stats-httpd_test.py
+PYTESTS = stats_test.py stats-httpd_test.py
 EXTRA_DIST = $(PYTESTS) test_utils.py
 CLEANFILES = test_utils.pyc
 
diff --git a/src/bin/stats/tests/b10-stats-httpd_test.py b/src/bin/stats/tests/b10-stats-httpd_test.py
deleted file mode 100644
index 9a463ab..0000000
--- a/src/bin/stats/tests/b10-stats-httpd_test.py
+++ /dev/null
@@ -1,1082 +0,0 @@
-# Copyright (C) 2011-2012  Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""
-In each of these tests we start several virtual components. They are
-not the real components, no external processes are started. They are
-just simple mock objects running each in its own thread and pretending
-to be bind10 modules. This helps testing the stats http server in a
-close to real environment.
-"""
-
-import unittest
-import os
-import imp
-import socket
-import errno
-import select
-import string
-import time
-import threading
-import http.client
-import xml.etree.ElementTree
-import random
-import urllib.parse
-import sys
-# load this module for xml validation with xsd. For this test, an
-# installation of lxml is required in advance. See http://lxml.de/.
-try:
-    from lxml import etree as lxml_etree
-except ImportError:
-    lxml_etree = None
-
-import isc
-import isc.log
-import stats_httpd
-import stats
-from test_utils import BaseModules, ThreadingServerManager, MyStats,\
-                       MyStatsHttpd, SignalHandler,\
-                       send_command, CONST_BASETIME
-from isc.testutils.ccsession_mock import MockModuleCCSession
-
-# This test suite uses xml.etree.ElementTree.XMLParser via
-# xml.etree.ElementTree.parse. On the platform where expat isn't
-# installed, ImportError is raised and it's failed. Check expat is
-# available before the test invocation. Skip this test if it's
-# unavailable.
-try:
-    # ImportError raised if xpat is unavailable
-    xml_parser = xml.etree.ElementTree.XMLParser()
-except ImportError:
-    xml_parser = None
-
-# set XML Namespaces for testing
-XMLNS_XSL = "http://www.w3.org/1999/XSL/Transform"
-XMLNS_XHTML = "http://www.w3.org/1999/xhtml"
-XMLNS_XSD = "http://www.w3.org/2001/XMLSchema"
-XMLNS_XSI = stats_httpd.XMLNS_XSI
-
-DUMMY_DATA = {
-    'Init' : {
-        "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME)
-        },
-    'Auth' : {
-        "queries.tcp": 6,
-        "queries.udp": 4,
-        "queries.perzone": [{
-                "zonename": "test1.example",
-                "queries.tcp": 10,
-                "queries.udp": 8
-                }, {
-                "zonename": "test2.example",
-                "queries.tcp": 8,
-                "queries.udp": 6
-                }],
-        "nds_queries.perzone": {
-                "test10.example": {
-                    "queries.tcp": 10,
-                    "queries.udp": 8
-                  },
-                "test20.example": {
-                    "queries.tcp": 8,
-                    "queries.udp": 6
-                  }
-                }
-        },
-    'Stats' : {
-        "report_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
-        "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
-        "last_update_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
-        "lname": "4d70d40a_c at host",
-        "timestamp": time.mktime(CONST_BASETIME)
-        }
-    }
-
-def get_availaddr(address='127.0.0.1', port=8001):
-    """returns a tuple of address and port which is available to
-    listen on the platform. The first argument is a address for
-    search. The second argument is a port for search. If a set of
-    address and port is failed on the search for the availability, the
-    port number is increased and it goes on the next trial until the
-    available set of address and port is looked up. If the port number
-    reaches over 65535, it may stop the search and raise a
-    OverflowError exception."""
-    while True:
-        for addr in socket.getaddrinfo(
-            address, port, 0,
-            socket.SOCK_STREAM, socket.IPPROTO_TCP):
-            sock = socket.socket(addr[0], socket.SOCK_STREAM)
-            try:
-                sock.bind((address, port))
-                return (address, port)
-            except socket.error:
-                continue
-            finally:
-                if sock: sock.close()
-        # This address and port number are already in use.
-        # next port number is added
-        port = port + 1
-
-def is_ipv6_enabled(address='::1', port=8001):
-    """checks IPv6 enabled on the platform. address for check is '::1'
-    and port for check is random number between 8001 and
-    65535. Retrying is 3 times even if it fails. The built-in socket
-    module provides a 'has_ipv6' parameter, but it's not used here
-    because there may be a situation where the value is True on an
-    environment where the IPv6 config is disabled."""
-    for p in random.sample(range(port, 65535), 3):
-        try:
-            sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
-            sock.bind((address, p))
-            return True
-        except socket.error:
-            continue
-        finally:
-            if sock: sock.close()
-    return False
-
-class TestItemNameList(unittest.TestCase):
-
-    def test_item_name_list(self):
-        # for a one-element list
-        self.assertEqual(['a'],
-                         stats_httpd.item_name_list({'a':1}, 'a'))
-        # for a dict under a dict
-        self.assertEqual(['a','a/b'],
-                         stats_httpd.item_name_list({'a':{'b':1}}, 'a'))
-        self.assertEqual(['a/b'],
-                         stats_httpd.item_name_list({'a':{'b':1}}, 'a/b'))
-        self.assertEqual(['a','a/b','a/b/c'],
-                         stats_httpd.item_name_list({'a':{'b':{'c':1}}}, 'a'))
-        self.assertEqual(['a/b','a/b/c'],
-                         stats_httpd.item_name_list({'a':{'b':{'c':1}}},
-                                                    'a/b'))
-        self.assertEqual(['a/b/c'],
-                         stats_httpd.item_name_list({'a':{'b':{'c':1}}},
-                                                    'a/b/c'))
-        # for a list under a dict
-        self.assertEqual(['a[2]'],
-                         stats_httpd.item_name_list({'a':[1,2,3]}, 'a[2]'))
-        self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
-                         stats_httpd.item_name_list({'a':[1,2,3]}, 'a'))
-        self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
-                         stats_httpd.item_name_list({'a':[1,2,3]}, ''))
-        # for a list under a dict under a dict
-        self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
-                         stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a'))
-        self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
-                         stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, ''))
-        self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
-                         stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a/b'))
-        # for a mixed case of the above
-        self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]', 'a/c'],
-                         stats_httpd.item_name_list(
-                {'a':{'b':[1,2,3], 'c':1}}, 'a'))
-        self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
-                         stats_httpd.item_name_list(
-                {'a':{'b':[1,2,3], 'c':1}}, 'a/b'))
-        self.assertEqual(['a/c'],
-                         stats_httpd.item_name_list(
-                {'a':{'b':[1,2,3], 'c':1}}, 'a/c'))
-        # for specifying a wrong identifier which is not found in
-        # element
-        self.assertRaises(isc.cc.data.DataNotFoundError,
-                         stats_httpd.item_name_list, {'x':1}, 'a')
-        # for specifying a string in element and an empty string in
-        # identifier
-        self.assertEqual([],
-                         stats_httpd.item_name_list('a', ''))
-        # for specifying empty strings in element and identifier
-        self.assertEqual([],
-                         stats_httpd.item_name_list('', ''))
-        # for specifying wrong element, which is an non-empty string,
-        # and an non-empty string in identifier
-        self.assertRaises(isc.cc.data.DataTypeError,
-                         stats_httpd.item_name_list, 'a', 'a')
-        # for specifying None in element and identifier
-        self.assertRaises(isc.cc.data.DataTypeError,
-                         stats_httpd.item_name_list, None, None)
-        # for specifying non-dict in element
-        self.assertRaises(isc.cc.data.DataTypeError,
-                         stats_httpd.item_name_list, [1,2,3], 'a')
-        self.assertRaises(isc.cc.data.DataTypeError,
-                         stats_httpd.item_name_list, [1,2,3], '')
-        # for checking key names sorted which consist of element
-        num = 11
-        keys = [ 'a', 'aa', 'b' ]
-        keys.sort(reverse=True)
-        dictlist = dict([ (k, list(range(num))) for k in keys ])
-        keys.sort()
-        ans = []
-        for k in keys:
-            ans += [k] + [ '%s[%d]' % (k, i) for i in range(num) ]
-        self.assertEqual(ans,
-                         stats_httpd.item_name_list(dictlist, ''))
-
-class TestHttpHandler(unittest.TestCase):
-    """Tests for HttpHandler class"""
-    def setUp(self):
-        # set the signal handler for deadlock
-        self.sig_handler = SignalHandler(self.fail)
-        self.base = BaseModules()
-        self.stats_server = ThreadingServerManager(MyStats)
-        self.stats = self.stats_server.server
-        DUMMY_DATA['Stats']['lname'] = self.stats.cc_session.lname
-        self.stats_server.run()
-        (self.address, self.port) = get_availaddr()
-        self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, (self.address, self.port))
-        self.stats_httpd = self.stats_httpd_server.server
-        self.stats_httpd_server.run()
-        self.client = http.client.HTTPConnection(self.address, self.port)
-        self.client._http_vsn_str = 'HTTP/1.0\n'
-        self.client.connect()
-
-    def tearDown(self):
-        self.client.close()
-        self.stats_httpd_server.shutdown()
-        self.stats_server.shutdown()
-        self.base.shutdown()
-        # reset the signal handler
-        self.sig_handler.reset()
-
-    @unittest.skipIf(sys.version_info >= (3, 3), "Unsupported in Python 3.3 or higher")
-    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
-    def test_do_GET(self):
-        self.assertTrue(type(self.stats_httpd.httpd) is list)
-        self.assertEqual(len(self.stats_httpd.httpd), 1)
-        self.assertEqual((self.address, self.port), self.stats_httpd.http_addrs[0])
-
-        def check_XML_URL_PATH(path=''):
-            url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
-            url_path = urllib.parse.quote(url_path)
-            self.client.putrequest('GET', url_path)
-            self.client.endheaders()
-            response = self.client.getresponse()
-            self.assertEqual(response.getheader("Content-type"), "text/xml")
-            self.assertGreater(int(response.getheader("Content-Length")), 0)
-            self.assertEqual(response.status, 200)
-            xml_doctype = response.readline().decode()
-            xsl_doctype = response.readline().decode()
-            self.assertGreater(len(xml_doctype), 0)
-            self.assertGreater(len(xsl_doctype), 0)
-            root = xml.etree.ElementTree.parse(response).getroot()
-            self.assertGreater(root.tag.find('statistics'), 0)
-            schema_loc = '{%s}schemaLocation' % XMLNS_XSI
-            # check the path of XSD
-            self.assertEqual(root.attrib[schema_loc],
-                             stats_httpd.XSD_NAMESPACE + ' '
-                             + stats_httpd.XSD_URL_PATH)
-            # check the path of XSL
-            self.assertTrue(xsl_doctype.startswith(
-                    '<?xml-stylesheet type="text/xsl" href="' +
-                    stats_httpd.XSL_URL_PATH
-                    + '"?>'))
-            # check whether the list of 'identifier' attributes in
-            # root is same as the list of item names in DUMMY_DATA
-            id_list = [ elm.attrib['identifier'] for elm in root ]
-            item_list = [ it for it in \
-                              stats_httpd.item_name_list(DUMMY_DATA, path) \
-                              if len(it.split('/')) > 1 ]
-            self.assertEqual(id_list, item_list)
-            for elem in root:
-                attr = elem.attrib
-                value = isc.cc.data.find(DUMMY_DATA, attr['identifier'])
-                # No 'value' attribute should be found in the 'item'
-                # element when datatype of the value is list or dict.
-                if type(value) is list or type(value) is dict:
-                    self.assertFalse('value' in attr)
-                # The value of the 'value' attribute should be checked
-                # after casting it to string type if datatype of the
-                # value is int or float. Because attr['value'] returns
-                # string type even if its value is int or float.
-                elif type(value) is int or type(value) is float:
-                    self.assertEqual(attr['value'], str(value))
-                else:
-                    self.assertEqual(attr['value'], value)
-
-        # URL is '/bind10/statistics/xml'
-        check_XML_URL_PATH()
-        for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
-            check_XML_URL_PATH(path)
-
-        def check_XSD_URL_PATH():
-            url_path = stats_httpd.XSD_URL_PATH
-            url_path = urllib.parse.quote(url_path)
-            self.client.putrequest('GET', url_path)
-            self.client.endheaders()
-            response = self.client.getresponse()
-            self.assertEqual(response.getheader("Content-type"), "text/xml")
-            self.assertGreater(int(response.getheader("Content-Length")), 0)
-            self.assertEqual(response.status, 200)
-            root = xml.etree.ElementTree.parse(response).getroot()
-            url_xmlschema = '{%s}' % XMLNS_XSD
-            self.assertGreater(root.tag.find('schema'), 0)
-            self.assertTrue(hasattr(root, 'attrib'))
-            self.assertTrue('targetNamespace' in root.attrib)
-            self.assertEqual(root.attrib['targetNamespace'],
-                             stats_httpd.XSD_NAMESPACE)
-
-        # URL is '/bind10/statistics/xsd'
-        check_XSD_URL_PATH()
-
-        def check_XSL_URL_PATH():
-            url_path = stats_httpd.XSL_URL_PATH
-            url_path = urllib.parse.quote(url_path)
-            self.client.putrequest('GET', url_path)
-            self.client.endheaders()
-            response = self.client.getresponse()
-            self.assertEqual(response.getheader("Content-type"), "text/xml")
-            self.assertGreater(int(response.getheader("Content-Length")), 0)
-            self.assertEqual(response.status, 200)
-            root = xml.etree.ElementTree.parse(response).getroot()
-            url_trans = '{%s}' % XMLNS_XSL
-            url_xhtml = '{%s}' % XMLNS_XHTML
-            self.assertEqual(root.tag, url_trans + 'stylesheet')
-
-        # URL is '/bind10/statistics/xsl'
-        check_XSL_URL_PATH()
-
-        # 302 redirect
-        self.client._http_vsn_str = 'HTTP/1.1'
-        self.client.putrequest('GET', '/')
-        self.client.putheader('Host', self.address)
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 302)
-        self.assertEqual(response.getheader('Location'),
-                         "http://%s:%d%s/" % (self.address, self.port, stats_httpd.XML_URL_PATH))
-
-        # 404 NotFound (random path)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', '/path/to/foo/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', '/bind10/foo')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', '/bind10/statistics/foo')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + 'Auth') # with no slash
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-
-        # 200 ok
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/#foo')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/?foo=bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-
-        # 404 NotFound (too long path)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Init/boot_time/a')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-
-        # 404 NotFound (nonexistent module name)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-
-        # 404 NotFound (nonexistent item name)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-
-        # 404 NotFound (existent module but nonexistent item name)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Auth/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Auth/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-        self.client._http_vsn_str = 'HTTP/1.0'
-        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Auth/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-
-    def test_do_GET_failed1(self):
-        # checks status
-        self.assertEqual(send_command("status", "Stats"),
-                         (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-        # failure case(Stats is down)
-        self.assertTrue(self.stats.running)
-        self.assertEqual(send_command("shutdown", "Stats"),
-                         (0, None)) # Stats is down
-        self.assertFalse(self.stats.running)
-        self.stats_httpd.cc_session.set_timeout(milliseconds=100)
-
-        # request XML
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 500)
-
-        # request XSD
-        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-
-        # request XSL
-        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-
-    def test_do_GET_failed2(self):
-        # failure case(Stats replies an error)
-        self.stats.mccs.set_command_handler(
-            lambda cmd, args: \
-                isc.config.ccsession.create_answer(1, "specified arguments are incorrect: I have an error.")
-            )
-
-        # request XML
-        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-
-        # request XSD
-        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-
-        # request XSL
-        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-
-    def test_do_HEAD(self):
-        self.client.putrequest('HEAD', stats_httpd.XML_URL_PATH + '/')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 200)
-
-        self.client.putrequest('HEAD', '/path/to/foo/bar')
-        self.client.endheaders()
-        response = self.client.getresponse()
-        self.assertEqual(response.status, 404)
-
-    @unittest.skipUnless(lxml_etree, "skipping XML validation with XSD")
-    def test_xml_validation_with_xsd(self):
-        """Tests for XML validation with XSD. If lxml is not
-        installed, this tests would be skipped."""
-        def request_xsd():
-            url_path = stats_httpd.XSD_URL_PATH
-            url_path = urllib.parse.quote(url_path)
-            self.client.putrequest('GET', url_path)
-            self.client.endheaders()
-            xsd_doc = self.client.getresponse()
-            xsd_doc = lxml_etree.parse(xsd_doc)
-            return lxml_etree.XMLSchema(xsd_doc)
-
-        def request_xmldoc(path=''):
-            url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
-            url_path = urllib.parse.quote(url_path)
-            self.client.putrequest('GET', url_path)
-            self.client.endheaders()
-            xml_doc = self.client.getresponse()
-            return lxml_etree.parse(xml_doc)
-
-        # request XSD and XML
-        xsd = request_xsd()
-        xml_doc = request_xmldoc()
-        # do validation
-        self.assertTrue(xsd.validate(xml_doc))
-
-        # validate each paths in DUMMY_DATA
-        for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
-            # request XML
-            xml_doc = request_xmldoc(path)
-            # do validation
-            self.assertTrue(xsd.validate(xml_doc))
-
-class TestHttpServerError(unittest.TestCase):
-    """Tests for HttpServerError exception"""
-    def test_raises(self):
-        try:
-            raise stats_httpd.HttpServerError('Nothing')
-        except stats_httpd.HttpServerError as err:
-            self.assertEqual(str(err), 'Nothing')
-
-class TestHttpServer(unittest.TestCase):
-    """Tests for HttpServer class"""
-    def setUp(self):
-        # set the signal handler for deadlock
-        self.sig_handler = SignalHandler(self.fail)
-        self.base = BaseModules()
-
-    def tearDown(self):
-        if hasattr(self, "stats_httpd"):
-            self.stats_httpd.stop()
-        self.base.shutdown()
-        # reset the signal handler
-        self.sig_handler.reset()
-
-    def test_httpserver(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        self.assertEqual(type(self.stats_httpd.httpd), list)
-        self.assertEqual(len(self.stats_httpd.httpd), 1)
-        for httpd in self.stats_httpd.httpd:
-            self.assertTrue(isinstance(httpd, stats_httpd.HttpServer))
-
-class TestStatsHttpdError(unittest.TestCase):
-    """Tests for StatsHttpdError exception"""
-
-    def test_raises1(self):
-        try:
-            raise stats_httpd.StatsHttpdError('Nothing')
-        except stats_httpd.StatsHttpdError as err:
-            self.assertEqual(str(err), 'Nothing')
-
-    def test_raises2(self):
-        try:
-            raise stats_httpd.StatsHttpdDataError('Nothing')
-        except stats_httpd.StatsHttpdDataError as err:
-            self.assertEqual(str(err), 'Nothing')
-
-class TestStatsHttpd(unittest.TestCase):
-    """Tests for StatsHttpd class"""
-
-    def setUp(self):
-        # set the signal handler for deadlock
-        self.sig_handler = SignalHandler(self.fail)
-        self.base = BaseModules()
-        self.stats_server = ThreadingServerManager(MyStats)
-        self.stats_server.run()
-        # checking IPv6 enabled on this platform
-        self.ipv6_enabled = is_ipv6_enabled()
-        # instantiation of StatsHttpd indirectly calls gethostbyaddr(), which
-        # can block for an uncontrollable period, leading many undesirable
-        # results.  We should rather eliminate the reliance, but until we
-        # can make such fundamental cleanup we replace it with a faked method;
-        # in our test scenario the return value doesn't matter.
-        self.__gethostbyaddr_orig = socket.gethostbyaddr
-        socket.gethostbyaddr = lambda x: ('test.example.', [], None)
-
-    def tearDown(self):
-        socket.gethostbyaddr = self.__gethostbyaddr_orig
-        if hasattr(self, "stats_httpd"):
-            self.stats_httpd.stop()
-        self.stats_server.shutdown()
-        self.base.shutdown()
-        # reset the signal handler
-        self.sig_handler.reset()
-
-    def test_init(self):
-        server_address = get_availaddr()
-        self.stats_httpd = MyStatsHttpd(server_address)
-        self.assertEqual(self.stats_httpd.running, False)
-        self.assertEqual(self.stats_httpd.poll_intval, 0.5)
-        self.assertNotEqual(len(self.stats_httpd.httpd), 0)
-        self.assertEqual(type(self.stats_httpd.mccs), isc.config.ModuleCCSession)
-        self.assertEqual(type(self.stats_httpd.cc_session), isc.cc.Session)
-        self.assertEqual(len(self.stats_httpd.config), 2)
-        self.assertTrue('listen_on' in self.stats_httpd.config)
-        self.assertEqual(len(self.stats_httpd.config['listen_on']), 1)
-        self.assertTrue('address' in self.stats_httpd.config['listen_on'][0])
-        self.assertTrue('port' in self.stats_httpd.config['listen_on'][0])
-        self.assertTrue(server_address in set(self.stats_httpd.http_addrs))
-        ans = send_command(
-            isc.config.ccsession.COMMAND_GET_MODULE_SPEC,
-            "ConfigManager", {"module_name":"StatsHttpd"})
-        # assert StatsHttpd is added to ConfigManager
-        self.assertNotEqual(ans, (0,{}))
-        self.assertTrue(ans[1]['module_name'], 'StatsHttpd')
-
-    def test_init_hterr(self):
-        orig_open_httpd = stats_httpd.StatsHttpd.open_httpd
-        def err_open_httpd(arg): raise stats_httpd.HttpServerError
-        stats_httpd.StatsHttpd.open_httpd = err_open_httpd
-        self.assertRaises(stats_httpd.HttpServerError, stats_httpd.StatsHttpd)
-        ans = send_command(
-            isc.config.ccsession.COMMAND_GET_MODULE_SPEC,
-            "ConfigManager", {"module_name":"StatsHttpd"})
-        # assert StatsHttpd is removed from ConfigManager
-        self.assertEqual(ans, (0,{}))
-        stats_httpd.StatsHttpd.open_httpd = orig_open_httpd
-
-    def test_openclose_mccs(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        mccs = MockModuleCCSession()
-        self.stats_httpd.mccs = mccs
-        self.assertFalse(self.stats_httpd.mccs.stopped)
-        self.assertFalse(self.stats_httpd.mccs.closed)
-        self.stats_httpd.close_mccs()
-        self.assertTrue(mccs.stopped)
-        self.assertTrue(mccs.closed)
-        self.assertEqual(self.stats_httpd.mccs, None)
-        self.stats_httpd.open_mccs()
-        self.assertIsNotNone(self.stats_httpd.mccs)
-        self.stats_httpd.mccs = None
-        self.assertEqual(self.stats_httpd.mccs, None)
-        self.assertEqual(self.stats_httpd.close_mccs(), None)
-
-    def test_mccs(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        self.assertIsNotNone(self.stats_httpd.mccs.get_socket())
-        self.assertTrue(
-            isinstance(self.stats_httpd.mccs.get_socket(), socket.socket))
-        self.assertTrue(
-            isinstance(self.stats_httpd.cc_session, isc.cc.session.Session))
-        statistics_spec = self.stats_httpd.get_stats_spec()
-        for mod in DUMMY_DATA:
-            self.assertTrue(mod in statistics_spec)
-            for cfg in statistics_spec[mod]:
-                self.assertTrue('item_name' in cfg)
-                self.assertTrue(cfg['item_name'] in DUMMY_DATA[mod])
-            self.assertTrue(len(statistics_spec[mod]), len(DUMMY_DATA[mod]))
-        self.stats_httpd.close_mccs()
-        self.assertIsNone(self.stats_httpd.mccs)
-
-    def test_httpd(self):
-        # dual stack (addresses is ipv4 and ipv6)
-        if self.ipv6_enabled:
-            server_addresses = (get_availaddr('::1'), get_availaddr())
-            self.stats_httpd = MyStatsHttpd(*server_addresses)
-            for ht in self.stats_httpd.httpd:
-                self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
-                self.assertTrue(ht.address_family in set([socket.AF_INET, socket.AF_INET6]))
-                self.assertTrue(isinstance(ht.socket, socket.socket))
-
-        # dual stack (address is ipv6)
-        if self.ipv6_enabled:
-            server_addresses = get_availaddr('::1')
-            self.stats_httpd = MyStatsHttpd(server_addresses)
-            for ht in self.stats_httpd.httpd:
-                self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
-                self.assertEqual(ht.address_family, socket.AF_INET6)
-                self.assertTrue(isinstance(ht.socket, socket.socket))
-
-        # dual/single stack (address is ipv4)
-        server_addresses = get_availaddr()
-        self.stats_httpd = MyStatsHttpd(server_addresses)
-        for ht in self.stats_httpd.httpd:
-            self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
-            self.assertEqual(ht.address_family, socket.AF_INET)
-            self.assertTrue(isinstance(ht.socket, socket.socket))
-
-    def test_httpd_anyIPv4(self):
-        # any address (IPv4)
-        server_addresses = get_availaddr(address='0.0.0.0')
-        self.stats_httpd = MyStatsHttpd(server_addresses)
-        for ht in self.stats_httpd.httpd:
-            self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
-            self.assertEqual(ht.address_family,socket.AF_INET)
-            self.assertTrue(isinstance(ht.socket, socket.socket))
-
-    def test_httpd_anyIPv6(self):
-        # any address (IPv6)
-        if self.ipv6_enabled:
-            server_addresses = get_availaddr(address='::')
-            self.stats_httpd = MyStatsHttpd(server_addresses)
-            for ht in self.stats_httpd.httpd:
-                self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
-                self.assertEqual(ht.address_family,socket.AF_INET6)
-                self.assertTrue(isinstance(ht.socket, socket.socket))
-
-    def test_httpd_failed(self):
-        # existent hostname
-        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
-                          get_availaddr(address='localhost'))
-
-        # nonexistent hostname
-        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('my.host.domain', 8000))
-
-        # over flow of port number
-        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', 80000))
-
-        # negative
-        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', -8000))
-
-        # alphabet
-        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, ('127.0.0.1', 'ABCDE'))
-
-        # Address already in use
-        server_addresses = get_availaddr()
-        self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, server_addresses)
-        self.stats_httpd_server.run()
-        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd, server_addresses)
-        send_command("shutdown", "StatsHttpd")
-
-    def test_running(self):
-        self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, get_availaddr())
-        self.stats_httpd = self.stats_httpd_server.server
-        self.assertFalse(self.stats_httpd.running)
-        self.stats_httpd_server.run()
-        self.assertEqual(send_command("status", "StatsHttpd"),
-                         (0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
-        self.assertTrue(self.stats_httpd.running)
-        self.assertEqual(send_command("shutdown", "StatsHttpd"), (0, None))
-        self.assertFalse(self.stats_httpd.running)
-        self.stats_httpd_server.shutdown()
-
-        # failure case
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        self.stats_httpd.cc_session.close()
-        self.assertRaises(ValueError, self.stats_httpd.start)
-
-    def test_failure_with_a_select_error (self):
-        """checks select.error is raised if the exception except
-        errno.EINTR is raised while it's selecting"""
-        def raise_select_except(*args):
-            raise select.error('dummy error')
-        orig_select = stats_httpd.select.select
-        stats_httpd.select.select = raise_select_except
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        self.assertRaises(select.error, self.stats_httpd.start)
-        stats_httpd.select.select = orig_select
-
-    def test_nofailure_with_errno_EINTR(self):
-        """checks no exception is raised if errno.EINTR is raised
-        while it's selecting"""
-        def raise_select_except(*args):
-            raise select.error(errno.EINTR)
-        orig_select = stats_httpd.select.select
-        stats_httpd.select.select = raise_select_except
-        self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd, get_availaddr())
-        self.stats_httpd_server.run()
-        self.stats_httpd_server.shutdown()
-        stats_httpd.select.select = orig_select
-
-    def test_open_template(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        # successful conditions
-        tmpl = self.stats_httpd.open_template(stats_httpd.XML_TEMPLATE_LOCATION)
-        self.assertTrue(isinstance(tmpl, string.Template))
-        opts = dict(
-            xml_string="<dummy></dummy>",
-            xsl_url_path="/path/to/")
-        lines = tmpl.substitute(opts)
-        for n in opts:
-            self.assertGreater(lines.find(opts[n]), 0)
-        tmpl = self.stats_httpd.open_template(stats_httpd.XSD_TEMPLATE_LOCATION)
-        self.assertTrue(isinstance(tmpl, string.Template))
-        opts = dict(xsd_namespace="http://host/path/to/")
-        lines = tmpl.substitute(opts)
-        for n in opts:
-            self.assertGreater(lines.find(opts[n]), 0)
-        tmpl = self.stats_httpd.open_template(stats_httpd.XSL_TEMPLATE_LOCATION)
-        self.assertTrue(isinstance(tmpl, string.Template))
-        opts = dict(xsd_namespace="http://host/path/to/")
-        lines = tmpl.substitute(opts)
-        for n in opts:
-            self.assertGreater(lines.find(opts[n]), 0)
-        # unsuccessful condition
-        self.assertRaises(
-            stats_httpd.StatsHttpdDataError,
-            self.stats_httpd.open_template, '/path/to/foo/bar')
-
-    def test_commands(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        self.assertEqual(self.stats_httpd.command_handler("status", None),
-                         isc.config.ccsession.create_answer(
-                0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
-        self.stats_httpd.running = True
-        self.assertEqual(self.stats_httpd.command_handler("shutdown", None),
-                         isc.config.ccsession.create_answer(0))
-        self.assertFalse(self.stats_httpd.running)
-        self.assertEqual(
-            self.stats_httpd.command_handler("__UNKNOWN_COMMAND__", None),
-            isc.config.ccsession.create_answer(
-                1, "Unknown command: __UNKNOWN_COMMAND__"))
-
-    def test_config(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        self.assertEqual(
-            self.stats_httpd.config_handler(dict(_UNKNOWN_KEY_=None)),
-            isc.config.ccsession.create_answer(
-                1, "unknown item _UNKNOWN_KEY_"))
-
-        addresses = get_availaddr()
-        self.assertEqual(
-            self.stats_httpd.config_handler(
-                dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
-            isc.config.ccsession.create_answer(0))
-        self.assertTrue("listen_on" in self.stats_httpd.config)
-        for addr in self.stats_httpd.config["listen_on"]:
-            self.assertTrue("address" in addr)
-            self.assertTrue("port" in addr)
-            self.assertTrue(addr["address"] == addresses[0])
-            self.assertTrue(addr["port"] == addresses[1])
-
-        if self.ipv6_enabled:
-            addresses = get_availaddr("::1")
-            self.assertEqual(
-                self.stats_httpd.config_handler(
-                dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
-                isc.config.ccsession.create_answer(0))
-            self.assertTrue("listen_on" in self.stats_httpd.config)
-            for addr in self.stats_httpd.config["listen_on"]:
-                self.assertTrue("address" in addr)
-                self.assertTrue("port" in addr)
-                self.assertTrue(addr["address"] == addresses[0])
-                self.assertTrue(addr["port"] == addresses[1])
-
-        addresses = get_availaddr()
-        self.assertEqual(
-            self.stats_httpd.config_handler(
-                dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
-            isc.config.ccsession.create_answer(0))
-        self.assertTrue("listen_on" in self.stats_httpd.config)
-        for addr in self.stats_httpd.config["listen_on"]:
-            self.assertTrue("address" in addr)
-            self.assertTrue("port" in addr)
-            self.assertTrue(addr["address"] == addresses[0])
-            self.assertTrue(addr["port"] == addresses[1])
-        (ret, arg) = isc.config.ccsession.parse_answer(
-            self.stats_httpd.config_handler(
-                dict(listen_on=[dict(address="1.2.3.4",port=543210)]))
-            )
-        self.assertEqual(ret, 1)
-
-    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
-    def test_xml_handler(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        module_name = 'Dummy'
-        stats_spec = \
-            { module_name :
-                  [{
-                        "item_name": "foo",
-                        "item_type": "string",
-                        "item_optional": False,
-                        "item_default": "bar",
-                        "item_description": "foo is bar",
-                        "item_title": "Foo"
-                        },
-                   {
-                        "item_name": "foo2",
-                        "item_type": "list",
-                        "item_optional": False,
-                        "item_default": [
-                            {
-                                "zonename" : "test1",
-                                "queries.udp" : 1,
-                                "queries.tcp" : 2
-                                },
-                            {
-                                "zonename" : "test2",
-                                "queries.udp" : 3,
-                                "queries.tcp" : 4
-                                }
-                        ],
-                        "item_title": "Foo bar",
-                        "item_description": "Foo bar",
-                        "list_item_spec": {
-                            "item_name": "foo2-1",
-                            "item_type": "map",
-                            "item_optional": False,
-                            "item_default": {},
-                            "map_item_spec": [
-                                {
-                                    "item_name": "foo2-1-1",
-                                    "item_type": "string",
-                                    "item_optional": False,
-                                    "item_default": "",
-                                    "item_title": "Foo2 1 1",
-                                    "item_description": "Foo bar"
-                                    },
-                                {
-                                    "item_name": "foo2-1-2",
-                                    "item_type": "integer",
-                                    "item_optional": False,
-                                    "item_default": 0,
-                                    "item_title": "Foo2 1 2",
-                                    "item_description": "Foo bar"
-                                    },
-                                {
-                                    "item_name": "foo2-1-3",
-                                    "item_type": "integer",
-                                    "item_optional": False,
-                                    "item_default": 0,
-                                    "item_title": "Foo2 1 3",
-                                    "item_description": "Foo bar"
-                                    }
-                                ]
-                            }
-                        }]
-              }
-        stats_data = \
-            { module_name : { 'foo':'bar',
-                          'foo2': [
-                            {
-                                "foo2-1-1" : "bar1",
-                                "foo2-1-2" : 10,
-                                "foo2-1-3" : 9
-                                },
-                            {
-                                "foo2-1-1" : "bar2",
-                                "foo2-1-2" : 8,
-                                "foo2-1-3" : 7
-                                }
-                            ] } }
-        self.stats_httpd.get_stats_spec = lambda x,y: stats_spec
-        self.stats_httpd.get_stats_data = lambda x,y: stats_data
-        xml_string = self.stats_httpd.xml_handler()
-        stats_xml = xml.etree.ElementTree.fromstring(xml_string)
-        schema_loc = '{%s}schemaLocation' % XMLNS_XSI
-        self.assertEqual(stats_xml.attrib[schema_loc],
-                         stats_httpd.XML_ROOT_ATTRIB['xsi:schemaLocation'])
-        stats_data = stats_data[module_name]
-        stats_spec = stats_spec[module_name]
-        names = stats_httpd.item_name_list(stats_data, '')
-        for i in range(0, len(names)):
-            self.assertEqual('%s/%s' % (module_name, names[i]), stats_xml[i].attrib['identifier'])
-            value = isc.cc.data.find(stats_data, names[i])
-            if type(value) is int:
-                value = str(value)
-            if type(value) is dict or type(value) is list:
-                self.assertFalse('value' in stats_xml[i].attrib)
-            else:
-                self.assertEqual(value, stats_xml[i].attrib['value'])
-            self.assertEqual(module_name, stats_xml[i].attrib['owner'])
-            self.assertEqual(urllib.parse.quote('%s/%s/%s' % (stats_httpd.XML_URL_PATH,
-                                                              module_name, names[i])),
-                             stats_xml[i].attrib['uri'])
-            spec = isc.config.find_spec_part(stats_spec, names[i])
-            self.assertEqual(spec['item_name'], stats_xml[i].attrib['name'])
-            self.assertEqual(spec['item_type'], stats_xml[i].attrib['type'])
-            self.assertEqual(spec['item_description'], stats_xml[i].attrib['description'])
-            self.assertEqual(spec['item_title'], stats_xml[i].attrib['title'])
-            self.assertEqual(str(spec['item_optional']).lower(), stats_xml[i].attrib['optional'])
-            default = spec['item_default']
-            if type(default) is int:
-                default = str(default)
-            if type(default) is dict or type(default) is list:
-                self.assertFalse('default' in stats_xml[i].attrib)
-            else:
-                self.assertEqual(default, stats_xml[i].attrib['default'])
-            self.assertFalse('item_format' in spec)
-            self.assertFalse('format' in stats_xml[i].attrib)
-
-    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
-    def test_xsd_handler(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        xsd_string = self.stats_httpd.xsd_handler()
-        stats_xsd = xml.etree.ElementTree.fromstring(xsd_string)
-        ns = '{%s}' % XMLNS_XSD
-        stats_xsd = stats_xsd[1].find('%scomplexType/%ssequence/%selement' % ((ns,)*3))
-        self.assertEqual('item', stats_xsd.attrib['name'])
-        stats_xsd = stats_xsd.find('%scomplexType' % ns)
-        type_types = ('boolean', 'integer', 'real', 'string', 'map', \
-                          'list', 'named_set', 'any')
-        attribs = [('identifier', 'string', 'required'),
-                   ('value', 'string', 'optional'),
-                   ('owner', 'string', 'required'),
-                   ('uri', 'anyURI', 'required'),
-                   ('name', 'string', 'required'),
-                   ('type', type_types, 'required'),
-                   ('description', 'string', 'optional'),
-                   ('title', 'string', 'optional'),
-                   ('optional', 'boolean', 'optional'),
-                   ('default', 'string', 'optional'),
-                   ('format', 'string', 'optional')]
-        for i in range(0, len(attribs)):
-            self.assertEqual(attribs[i][0], stats_xsd[i].attrib['name'])
-            if attribs[i][0] == 'type':
-                stats_xsd_types = \
-                    stats_xsd[i].find('%ssimpleType/%srestriction' % \
-                                          ((ns,)*2))
-                for j in range(0, len(attribs[i][1])):
-                    self.assertEqual(attribs[i][1][j], \
-                                         stats_xsd_types[j].attrib['value'])
-            else:
-                self.assertEqual(attribs[i][1], stats_xsd[i].attrib['type'])
-            self.assertEqual(attribs[i][2], stats_xsd[i].attrib['use'])
-
-    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
-    def test_xsl_handler(self):
-        self.stats_httpd = MyStatsHttpd(get_availaddr())
-        xsl_string = self.stats_httpd.xsl_handler()
-        stats_xsl = xml.etree.ElementTree.fromstring(xsl_string)
-        nst = '{%s}' % XMLNS_XSL
-        nsx = '{%s}' % XMLNS_XHTML
-        self.assertEqual("bind10:statistics", stats_xsl[2].attrib['match'])
-        stats_xsl = stats_xsl[2].find('%stable' % nsx)
-        self.assertEqual('item', stats_xsl[1].attrib['select'])
-        stats_xsl = stats_xsl[1].find('%str' % nsx)
-        self.assertEqual('@uri', stats_xsl[0].find(
-                '%selement/%sattribute/%svalue-of' % ((nst,)*3)).attrib['select'])
-        self.assertEqual('@identifier', stats_xsl[0].find(
-                '%selement/%svalue-of' % ((nst,)*2)).attrib['select'])
-        self.assertEqual('@value', stats_xsl[1].find('%sif' % nst).attrib['test'])
-        self.assertEqual('@value', stats_xsl[1].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
-        self.assertEqual('@description', stats_xsl[2].find('%sif' % nst).attrib['test'])
-        self.assertEqual('@description', stats_xsl[2].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
-
-    def test_for_without_B10_FROM_SOURCE(self):
-        # just lets it go through the code without B10_FROM_SOURCE env
-        # variable
-        if "B10_FROM_SOURCE" in os.environ:
-            tmppath = os.environ["B10_FROM_SOURCE"]
-            os.environ.pop("B10_FROM_SOURCE")
-            imp.reload(stats_httpd)
-            os.environ["B10_FROM_SOURCE"] = tmppath
-            imp.reload(stats_httpd)
-
-if __name__ == "__main__":
-    isc.log.resetUnitTestRootLogger()
-    unittest.main()
diff --git a/src/bin/stats/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py
deleted file mode 100644
index 540c707..0000000
--- a/src/bin/stats/tests/b10-stats_test.py
+++ /dev/null
@@ -1,1379 +0,0 @@
-# Copyright (C) 2010, 2011, 2012  Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""
-In each of these tests we start several virtual components. They are
-not the real components, no external processes are started. They are
-just simple mock objects running each in its own thread and pretending
-to be bind10 modules. This helps testing the stats module in a close
-to real environment.
-"""
-
-import unittest
-import os
-import threading
-import io
-import time
-import imp
-import sys
-
-import stats
-import isc.log
-import isc.cc.session
-from test_utils import BaseModules, ThreadingServerManager, MyStats, \
-    SimpleStats, SignalHandler, MyModuleCCSession, send_command
-from isc.testutils.ccsession_mock import MockModuleCCSession
-
-class TestUtilties(unittest.TestCase):
-    items = [
-        { 'item_name': 'test_int1',  'item_type': 'integer', 'item_default': 12345      },
-        { 'item_name': 'test_real1', 'item_type': 'real',    'item_default': 12345.6789 },
-        { 'item_name': 'test_bool1', 'item_type': 'boolean', 'item_default': True       },
-        { 'item_name': 'test_str1',  'item_type': 'string',  'item_default': 'ABCD'     },
-        { 'item_name': 'test_list1', 'item_type': 'list',    'item_default': [1,2,3],
-          'list_item_spec' : { 'item_name': 'number',   'item_type': 'integer' } },
-        { 'item_name': 'test_map1',  'item_type': 'map',     'item_default': {'a':1,'b':2,'c':3},
-          'map_item_spec'  : [ { 'item_name': 'a',   'item_type': 'integer'},
-                               { 'item_name': 'b',   'item_type': 'integer'},
-                               { 'item_name': 'c', 'item_type': 'integer'} ] },
-        { 'item_name': 'test_int2',  'item_type': 'integer' },
-        { 'item_name': 'test_real2', 'item_type': 'real'    },
-        { 'item_name': 'test_bool2', 'item_type': 'boolean' },
-        { 'item_name': 'test_str2',  'item_type': 'string'  },
-        { 'item_name': 'test_list2', 'item_type': 'list',
-          'list_item_spec' : { 'item_name': 'number',   'item_type': 'integer' } },
-        { 'item_name': 'test_map2',  'item_type': 'map',
-          'map_item_spec'  : [ { 'item_name': 'A', 'item_type': 'integer'},
-                               { 'item_name': 'B', 'item_type': 'integer'},
-                               { 'item_name': 'C', 'item_type': 'integer'} ] },
-        { 'item_name': 'test_none',  'item_type': 'none'    },
-        { 'item_name': 'test_list3', 'item_type': 'list',    'item_default': ["one","two","three"],
-          'list_item_spec' : { 'item_name': 'number', 'item_type': 'string' } },
-        { 'item_name': 'test_map3',  'item_type': 'map',     'item_default': {'a':'one','b':'two','c':'three'},
-          'map_item_spec'  : [ { 'item_name': 'a', 'item_type': 'string'},
-                               { 'item_name': 'b', 'item_type': 'string'},
-                               { 'item_name': 'c', 'item_type': 'string'} ] },
-        {
-          'item_name': 'test_named_set',
-          'item_type': 'named_set',
-          'item_default': { },
-          'named_set_item_spec': {
-            'item_name': 'name',
-            'item_type': 'map',
-            'item_default': { },
-            'map_item_spec': [
-              {
-                'item_name': 'number1',
-                'item_type': 'integer'
-                },
-              {
-                'item_name': 'number2',
-                'item_type': 'integer'
-                }
-              ]
-            }
-          }
-        ]
-
-    def setUp(self):
-        self.const_timestamp = 1308730448.965706
-        self.const_timetuple = (2011, 6, 22, 8, 14, 8, 2, 173, 0)
-        self.const_datetime = '2011-06-22T08:14:08Z'
-        stats.time = lambda : self.const_timestamp
-        stats.gmtime = lambda : self.const_timetuple
-
-    def test_get_spec_defaults(self):
-        self.assertEqual(
-            stats.get_spec_defaults(self.items), {
-                'test_int1'  : 12345              ,
-                'test_real1' : 12345.6789         ,
-                'test_bool1' : True               ,
-                'test_str1'  : 'ABCD'             ,
-                'test_list1' : [1,2,3]            ,
-                'test_map1'  : {'a':1,'b':2,'c':3},
-                'test_int2'  : 0 ,
-                'test_real2' : 0.0,
-                'test_bool2' : False,
-                'test_str2'  : "",
-                'test_list2' : [0],
-                'test_map2'  : { 'A' : 0, 'B' : 0, 'C' : 0 },
-                'test_none'  : None,
-                'test_list3' : [ "one", "two", "three" ],
-                'test_map3'  : { 'a' : 'one', 'b' : 'two', 'c' : 'three' },
-                'test_named_set' : {} })
-        self.assertEqual(stats.get_spec_defaults(None), {})
-        self.assertRaises(KeyError, stats.get_spec_defaults, [{'item_name':'Foo'}])
-
-    def test_get_timestamp(self):
-        self.assertEqual(stats.get_timestamp(), self.const_timestamp)
-
-    def test_get_datetime(self):
-        self.assertEqual(stats.get_datetime(), self.const_datetime)
-        self.assertNotEqual(stats.get_datetime(
-                (2011, 6, 22, 8, 23, 40, 2, 173, 0)), self.const_datetime)
-
-    def test__accum(self):
-        self.assertEqual(stats._accum(None, None), None)
-        self.assertEqual(stats._accum(None, "b"), "b")
-        self.assertEqual(stats._accum("a", None), "a")
-        self.assertEqual(stats._accum(1, 2), 3)
-        self.assertEqual(stats._accum(0.5, 0.3), 0.8)
-        self.assertEqual(stats._accum('aa','bb'), 'bb')
-        self.assertEqual(stats._accum('1970-01-01T09:00:00Z','2012-08-09T09:33:31Z'),
-                         '2012-08-09T09:33:31Z')
-        self.assertEqual(stats._accum(
-                [1, 2, 3], [4, 5]), [5, 7, 3])
-        self.assertEqual(stats._accum(
-                [4, 5], [1, 2, 3]), [5, 7, 3])
-        self.assertEqual(stats._accum(
-                [1, 2, 3], [None, 5, 6]), [1, 7, 9])
-        self.assertEqual(stats._accum(
-                [None, 5, 6], [1, 2, 3]), [1, 7, 9])
-        self.assertEqual(stats._accum(
-                [1, 2, 3], [None, None, None, None]), [1,2,3,None])
-        self.assertEqual(stats._accum(
-                [[1,2],3],[[],5,6]), [[1,2],8,6])
-        self.assertEqual(stats._accum(
-                {'one': 1, 'two': 2, 'three': 3},
-                {'one': 4, 'two': 5}),
-                         {'one': 5, 'two': 7, 'three': 3})
-        self.assertEqual(stats._accum(
-                {'one': 1, 'two': 2, 'three': 3},
-                {'four': 4, 'five': 5}),
-                         {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
-        self.assertEqual(stats._accum(
-                {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
-                {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
-                         {'one':[3,2], 'two':[7,5,5], 'three':[None,3,None], 'four': 'FOUR'})
-        self.assertEqual(stats._accum(
-                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
-                [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
-                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 5, 'five': 7, 'six': 9} ])
-
-    def test_merge_oldnre(self):
-        self.assertEqual(stats.merge_oldnew(1, 2), 2)
-        self.assertEqual(stats.merge_oldnew(0.5, 0.3), 0.3)
-        self.assertEqual(stats.merge_oldnew('aa','bb'), 'bb')
-        self.assertEqual(stats.merge_oldnew(
-                [1, 2, 3], [4, 5]), [4, 5, 3])
-        self.assertEqual(stats.merge_oldnew(
-                [4, 5], [1, 2, 3]), [1, 2, 3])
-        self.assertEqual(stats.merge_oldnew(
-                [1, 2, 3], [None, 5, 6]), [None, 5, 6])
-        self.assertEqual(stats.merge_oldnew(
-                [None, 5, 6], [1, 2, 3]), [1, 2, 3])
-        self.assertEqual(stats.merge_oldnew(
-                [1, 2, 3], [None, None, None, None]), [None, None, None, None])
-        self.assertEqual(stats.merge_oldnew(
-                [[1,2],3],[[],5,6]), [[1,2],5,6])
-        self.assertEqual(stats.merge_oldnew(
-                {'one': 1, 'two': 2, 'three': 3},
-                {'one': 4, 'two': 5}),
-                         {'one': 4, 'two': 5, 'three': 3})
-        self.assertEqual(stats.merge_oldnew(
-                {'one': 1, 'two': 2, 'three': 3},
-                {'four': 4, 'five': 5}),
-                         {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
-        self.assertEqual(stats.merge_oldnew(
-                {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
-                {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
-                         {'one':[2,2], 'two':[4,5,5], 'three':[None,None,None], 'four': 'FOUR'})
-        self.assertEqual(stats.merge_oldnew(
-                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
-                [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
-                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 1, 'five': 2, 'six': 3} ])
-
-class TestCallback(unittest.TestCase):
-    def setUp(self):
-        self.dummy_func = lambda *x, **y : (x, y)
-        self.dummy_args = (1,2,3)
-        self.dummy_kwargs = {'a':1,'b':2,'c':3}
-        self.cback1 = stats.Callback(
-            command=self.dummy_func,
-            args=self.dummy_args,
-            kwargs=self.dummy_kwargs
-            )
-        self.cback2 = stats.Callback(
-            args=self.dummy_args,
-            kwargs=self.dummy_kwargs
-            )
-        self.cback3 = stats.Callback(
-            command=self.dummy_func,
-            kwargs=self.dummy_kwargs
-            )
-        self.cback4 = stats.Callback(
-            command=self.dummy_func,
-            args=self.dummy_args
-            )
-
-    def test_init(self):
-        self.assertEqual((self.cback1.command, self.cback1.args, self.cback1.kwargs),
-                         (self.dummy_func, self.dummy_args, self.dummy_kwargs))
-        self.assertEqual((self.cback2.command, self.cback2.args, self.cback2.kwargs),
-                         (None, self.dummy_args, self.dummy_kwargs))
-        self.assertEqual((self.cback3.command, self.cback3.args, self.cback3.kwargs),
-                         (self.dummy_func, (), self.dummy_kwargs))
-        self.assertEqual((self.cback4.command, self.cback4.args, self.cback4.kwargs),
-                         (self.dummy_func, self.dummy_args, {}))
-
-    def test_call(self):
-        self.assertEqual(self.cback1(), (self.dummy_args, self.dummy_kwargs))
-        self.assertEqual(self.cback1(100, 200), ((100, 200), self.dummy_kwargs))
-        self.assertEqual(self.cback1(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
-        self.assertEqual(self.cback2(), None)
-        self.assertEqual(self.cback3(), ((), self.dummy_kwargs))
-        self.assertEqual(self.cback3(100, 200), ((100, 200), self.dummy_kwargs))
-        self.assertEqual(self.cback3(a=100, b=200), ((), {'a':100, 'b':200}))
-        self.assertEqual(self.cback4(), (self.dummy_args, {}))
-        self.assertEqual(self.cback4(100, 200), ((100, 200), {}))
-        self.assertEqual(self.cback4(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
-
-class TestStats(unittest.TestCase):
-    def setUp(self):
-        # set the signal handler for deadlock
-        self.sig_handler = SignalHandler(self.fail)
-        self.base = BaseModules()
-        self.const_timestamp = 1308730448.965706
-        self.const_datetime = '2011-06-22T08:14:08Z'
-        self.const_default_datetime = '1970-01-01T00:00:00Z'
-        # Record original module-defined functions in case we replace them
-        self.__orig_timestamp = stats.get_timestamp
-        self.__orig_get_datetime = stats.get_datetime
-
-    def tearDown(self):
-        self.base.shutdown()
-        # reset the signal handler
-        self.sig_handler.reset()
-        # restore the stored original function in case we replaced them
-        stats.get_timestamp = self.__orig_timestamp
-        stats.get_datetime = self.__orig_get_datetime
-
-    def test_init(self):
-        self.stats = stats.Stats()
-        self.assertEqual(self.stats.module_name, 'Stats')
-        self.assertFalse(self.stats.running)
-        self.assertTrue('command_show' in self.stats.callbacks)
-        self.assertTrue('command_status' in self.stats.callbacks)
-        self.assertTrue('command_shutdown' in self.stats.callbacks)
-        self.assertTrue('command_show' in self.stats.callbacks)
-        self.assertTrue('command_showschema' in self.stats.callbacks)
-        self.assertEqual(self.stats.config['poll-interval'], 60)
-
-    def test_init_undefcmd(self):
-        spec_str = """\
-{
-  "module_spec": {
-    "module_name": "Stats",
-    "module_description": "Stats daemon",
-    "config_data": [],
-    "commands": [
-      {
-        "command_name": "_undef_command_",
-        "command_description": "a undefined command in stats",
-        "command_args": []
-      }
-    ],
-    "statistics": []
-  }
-}
-"""
-        orig_spec_location = stats.SPECFILE_LOCATION
-        stats.SPECFILE_LOCATION = io.StringIO(spec_str)
-        self.assertRaises(stats.StatsError, stats.Stats)
-        stats.SPECFILE_LOCATION = orig_spec_location
-
-    def __send_command(self, stats, command_name, params=None):
-        '''Emulate a command arriving to stats by directly calling callback'''
-        return isc.config.ccsession.parse_answer(
-            stats.command_handler(command_name, params))
-
-    def test_start(self):
-        # Define a separate exception class so we can be sure that's actually
-        # the one raised in __check_start() below
-        class CheckException(Exception):
-            pass
-
-        def __check_start(tested_stats):
-            self.assertTrue(tested_stats.running)
-            raise CheckException # terminate the loop
-
-        # start without err
-        stats = SimpleStats()
-        self.assertFalse(stats.running)
-        stats._check_command = lambda: __check_start(stats)
-        # We are going to confirm start() will set running to True, avoiding
-        # to fall into a loop with the exception trick.
-        self.assertRaises(CheckException, stats.start)
-        self.assertEqual(self.__send_command(stats, "status"),
-                         (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-
-    def test_shutdown(self):
-        def __check_shutdown(tested_stats):
-            self.assertTrue(tested_stats.running)
-            self.assertEqual(self.__send_command(tested_stats, "shutdown"),
-                             (0, None))
-            self.assertFalse(tested_stats.running)
-            # override get_interval() so it won't go poll statistics
-            tested_stats.get_interval = lambda : 0
-
-        stats = SimpleStats()
-        stats._check_command = lambda: __check_shutdown(stats)
-        stats.start()
-        self.assertTrue(stats.mccs.stopped)
-
-    def test_handlers(self):
-        """Test command_handler"""
-
-        __stats = SimpleStats()
-
-        # 'show' command.  We're going to check the expected methods are
-        # called in the expected order, and check the resulting response.
-        # Details of each method are tested separately.
-        call_log = []
-        def __steal_method(fn_name, *arg):
-            call_log.append((fn_name, arg))
-            if fn_name == 'update_stat':
-                return False        # "no error"
-            if fn_name == 'showschema':
-                return isc.config.create_answer(0, 'no error')
-
-        # Fake some methods and attributes for inspection
-        __stats.do_polling = lambda: __steal_method('polling')
-        __stats.update_statistics_data = \
-            lambda x, y, z: __steal_method('update_stat', x, y, z)
-        __stats.update_modules = lambda: __steal_method('update_module')
-        __stats.mccs.lname = 'test lname'
-        __stats.statistics_data = {'Init': {'boot_time': self.const_datetime}}
-
-        # skip initial polling
-        stats.get_timestamp = lambda: 0
-        __stats._lasttime_poll = 0
-
-        stats.get_datetime = lambda: 42 # make the result predictable
-
-        # now send the command
-        self.assertEqual(
-            self.__send_command(
-                __stats, 'show',
-                params={ 'owner' : 'Init', 'name'  : 'boot_time' }),
-            (0, {'Init': {'boot_time': self.const_datetime}}))
-        # Check if expected methods are called
-        self.assertEqual([('update_stat',
-                           ('Stats', 'test lname',
-                            {'timestamp': 0,
-                             'report_time': 42})),
-                          ('update_module', ())], call_log)
-
-        # Then update faked timestamp so the initial polling will happen, and
-        # confirm that.
-        call_log = []
-        stats.get_timestamp = lambda: 10
-        self.assertEqual(
-            self.__send_command(
-                __stats, 'show',
-                params={ 'owner' : 'Init', 'name'  : 'boot_time' }),
-            (0, {'Init': {'boot_time': self.const_datetime}}))
-        self.assertEqual([('polling', ()),
-                          ('update_stat',
-                           ('Stats', 'test lname',
-                            {'timestamp': 10,
-                             'report_time': 42})),
-                          ('update_module', ())], call_log)
-
-        # 'status' command.  We can confirm the behavior without any fake
-        self.assertEqual(
-            self.__send_command(__stats, 'status'),
-            (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-
-        # 'showschema' command.  update_modules() will be called, which
-        # (implicitly) confirms the correct method is called; further details
-        # are tested separately.
-        call_log = []
-        (rcode, value) = self.__send_command(__stats, 'showschema')
-        self.assertEqual([('update_module', ())], call_log)
-
-        # Unknown command.  Error should be returned
-        self.assertEqual(
-            self.__send_command(__stats, '__UNKNOWN__'),
-            (1, "Unknown command: '__UNKNOWN__'"))
-
-    def test_update_modules(self):
-        """Confirm the behavior of Stats.update_modules().
-
-        It checks whether the expected command is sent to ConfigManager,
-        and whether the answer from ConfigManager is handled as expected.
-
-        """
-
-        def __check_rpc_call(command, group):
-            self.assertEqual('ConfigManager', group)
-            self.assertEqual(command,
-                             isc.config.ccsession.COMMAND_GET_STATISTICS_SPEC)
-            answer_value = {'Init': [{
-                        "item_name": "boot_time",
-                        "item_type": "string",
-                        "item_optional": False,
-                        # Use a different default so we can check it below
-                        "item_default": "2013-01-01T00:00:01Z",
-                        "item_title": "Boot time",
-                        "item_description": "dummy desc",
-                        "item_format": "date-time"
-                        }]}
-            return answer_value
-
-        self.stats = SimpleStats()
-        self.stats.cc_session.rpc_call = __check_rpc_call
-
-        self.stats.update_modules()
-
-        # Stats is always incorporated.  For others, only the ones returned
-        # by group_recvmsg() above is available.
-        self.assertTrue('Stats' in self.stats.modules)
-        self.assertTrue('Init' in self.stats.modules)
-        self.assertFalse('Dummy' in self.stats.modules)
-
-        my_statistics_data = stats.get_spec_defaults(
-            self.stats.modules['Stats'].get_statistics_spec())
-        self.assertTrue('report_time' in my_statistics_data)
-        self.assertTrue('boot_time' in my_statistics_data)
-        self.assertTrue('last_update_time' in my_statistics_data)
-        self.assertTrue('timestamp' in my_statistics_data)
-        self.assertTrue('lname' in my_statistics_data)
-        self.assertEqual(my_statistics_data['report_time'],
-                         self.const_default_datetime)
-        self.assertEqual(my_statistics_data['boot_time'],
-                         self.const_default_datetime)
-        self.assertEqual(my_statistics_data['last_update_time'],
-                         self.const_default_datetime)
-        self.assertEqual(my_statistics_data['timestamp'], 0.0)
-        self.assertEqual(my_statistics_data['lname'], "")
-        my_statistics_data = stats.get_spec_defaults(
-            self.stats.modules['Init'].get_statistics_spec())
-        self.assertTrue('boot_time' in my_statistics_data)
-        self.assertEqual(my_statistics_data['boot_time'],
-                         "2013-01-01T00:00:01Z")
-
-        # Error case
-        def __raise_on_rpc_call(x, y):
-            raise isc.config.RPCError(99, 'error')
-        orig_parse_answer = stats.isc.config.ccsession.parse_answer
-        self.stats.cc_session.rpc_call = __raise_on_rpc_call
-        self.assertRaises(stats.StatsError, self.stats.update_modules)
-
-    def test_get_statistics_data(self):
-        """Confirm the behavior of Stats.get_statistics_data().
-
-        It should first call update_modules(), and then retrieve the requested
-        data from statistics_data.  We confirm this by fake update_modules()
-        where we set the expected data in statistics_data.
-
-        """
-        self.stats = SimpleStats()
-        def __faked_update_modules():
-            self.stats.statistics_data = { \
-                'Stats': {
-                    'report_time': self.const_default_datetime,
-                    'boot_time': None,
-                    'last_update_time': None,
-                    'timestamp': 0.0,
-                    'lname': 'dummy name'
-                    },
-                'Init': { 'boot_time': None }
-                }
-
-        self.stats.update_modules = __faked_update_modules
-
-        my_statistics_data = self.stats.get_statistics_data()
-        self.assertTrue('Stats' in my_statistics_data)
-        self.assertTrue('Init' in my_statistics_data)
-        self.assertTrue('boot_time' in my_statistics_data['Init'])
-
-        my_statistics_data = self.stats.get_statistics_data(owner='Stats')
-        self.assertTrue('Stats' in my_statistics_data)
-        self.assertTrue('report_time' in my_statistics_data['Stats'])
-        self.assertTrue('boot_time' in my_statistics_data['Stats'])
-        self.assertTrue('last_update_time' in my_statistics_data['Stats'])
-        self.assertTrue('timestamp' in my_statistics_data['Stats'])
-        self.assertTrue('lname' in my_statistics_data['Stats'])
-        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
-                          owner='Foo')
-
-        my_statistics_data = self.stats.get_statistics_data(
-            owner='Stats', name='report_time')
-        self.assertEqual(my_statistics_data['Stats']['report_time'],
-                         self.const_default_datetime)
-
-        my_statistics_data = self.stats.get_statistics_data(
-            owner='Stats', name='boot_time')
-        self.assertTrue('boot_time' in my_statistics_data['Stats'])
-
-        my_statistics_data = self.stats.get_statistics_data(
-            owner='Stats', name='last_update_time')
-        self.assertTrue('last_update_time' in my_statistics_data['Stats'])
-
-        my_statistics_data = self.stats.get_statistics_data(
-            owner='Stats', name='timestamp')
-        self.assertEqual(my_statistics_data['Stats']['timestamp'], 0.0)
-
-        my_statistics_data = self.stats.get_statistics_data(
-            owner='Stats', name='lname')
-        self.assertTrue(len(my_statistics_data['Stats']['lname']) >0)
-        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
-                          owner='Stats', name='Bar')
-        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
-                          owner='Foo', name='Bar')
-        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
-                          name='Bar')
-
-    def test_update_statistics_data(self):
-        """test for list-type statistics"""
-        self.stats = SimpleStats()
-        _test_exp1 = {
-              'zonename': 'test1.example',
-              'queries.tcp': 5,
-              'queries.udp': 4
-            }
-        _test_exp2 = {
-              'zonename': 'test2.example',
-              'queries.tcp': 3,
-              'queries.udp': 2
-            }
-        _test_exp3 = {}
-        _test_exp4 = {
-              'queries.udp': 4
-            }
-        _test_exp5_1 = {
-              'queries.perzone': [
-                { },
-                {
-                  'queries.udp': 9876
-                }
-              ]
-            }
-        _test_exp5_2 = {
-              'queries.perzone[1]/queries.udp':
-                  isc.cc.data.find(_test_exp5_1,
-                                   'queries.perzone[1]/queries.udp')
-            }
-        # Success cases
-        self.assertEqual(self.stats.statistics_data['Stats']['lname'],
-                         self.stats.cc_session.lname)
-        self.stats.update_statistics_data(
-            'Stats', self.stats.cc_session.lname,
-            {'lname': 'foo at bar'})
-        self.assertEqual(self.stats.statistics_data['Stats']['lname'],
-                         'foo at bar')
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'queries.perzone': [_test_exp1]}))
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['queries.perzone'],\
-                             [_test_exp1])
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'queries.perzone': [_test_exp2]}))
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['queries.perzone'],\
-                             [_test_exp2])
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'queries.perzone': [_test_exp1,_test_exp2]}))
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['queries.perzone'],
-                         [_test_exp1,_test_exp2])
-        # differential update
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'queries.perzone': [_test_exp3,_test_exp4]}))
-        _new_data = stats.merge_oldnew(_test_exp2,_test_exp4)
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['queries.perzone'], \
-                             [_test_exp1,_new_data])
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', _test_exp5_2))
-        _new_data = stats.merge_oldnew(_new_data,
-                                       _test_exp5_1['queries.perzone'][1])
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['queries.perzone'], \
-                             [_test_exp1,_new_data])
-        # Error cases
-        self.assertEqual(self.stats.update_statistics_data('Stats', None,
-                                                           {'lname': 0.0}),
-                         ['0.0 should be a string'])
-        self.assertEqual(self.stats.update_statistics_data('Dummy', None,
-                                                           {'foo': 'bar'}),
-                         ['unknown module name: Dummy'])
-        self.assertEqual(self.stats.update_statistics_data(
-                'Auth', 'foo1', {'queries.perzone': [None]}), ['None should be a map'])
-
-    def test_update_statistics_data_pt2(self):
-        """test for named_set-type statistics"""
-        self.stats = SimpleStats()
-        _test_exp1 = \
-            { 'test10.example': { 'queries.tcp': 5, 'queries.udp': 4 } }
-        _test_exp2 = \
-            { 'test20.example': { 'queries.tcp': 3, 'queries.udp': 2 } }
-        _test_exp3 = {}
-        _test_exp4 = { 'test20.example': { 'queries.udp': 4 } }
-        _test_exp5_1 = { 'test10.example': { 'queries.udp': 5432 } }
-        _test_exp5_2 ={
-              'nds_queries.perzone/test10.example/queries.udp':
-                  isc.cc.data.find(_test_exp5_1, 'test10.example/queries.udp')
-            }
-        _test_exp6 = { 'foo/bar':  'brabra' }
-        _test_exp7 = { 'foo[100]': 'bar' }
-        # Success cases
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'nds_queries.perzone': _test_exp1}))
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['nds_queries.perzone'],\
-                             _test_exp1)
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'nds_queries.perzone': _test_exp2}))
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['nds_queries.perzone'],\
-                         dict(_test_exp1,**_test_exp2))
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'nds_queries.perzone':
-                                 dict(_test_exp1, **_test_exp2)}))
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['nds_queries.perzone'],
-                         dict(_test_exp1, **_test_exp2))
-        # differential update
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', {'nds_queries.perzone':
-                                 dict(_test_exp3, **_test_exp4)}))
-        _new_val = dict(_test_exp1,
-                        **stats.merge_oldnew(_test_exp2,_test_exp4))
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['nds_queries.perzone'],\
-                             _new_val)
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo1', _test_exp5_2))
-        _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['nds_queries.perzone'],\
-                             _new_val)
-        self.assertIsNone(self.stats.update_statistics_data(
-            'Auth', 'foo2', _test_exp5_2))
-        _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo2']['nds_queries.perzone'],\
-                             _test_exp5_1)
-        # Error cases
-        self.assertEqual(self.stats.update_statistics_data(
-                'Auth', 'foo1', {'nds_queries.perzone': None}),
-                         ['None should be a map'])
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['nds_queries.perzone'],\
-                             _new_val)
-        self.assertEqual(self.stats.update_statistics_data(
-                'Auth', 'foo1', _test_exp6), ['unknown item foo'])
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
-                             ['foo1']['nds_queries.perzone'],\
-                             _new_val)
-        self.assertEqual(self.stats.update_statistics_data(
-                'Init', 'bar1', _test_exp7), ["KeyError: 'foo'"])
-        self.assertEqual(self.stats.update_statistics_data(
-                'Foo', 'foo1', _test_exp6), ['unknown module name: Foo'])
-
-    def test_update_statistics_data_withmid(self):
-        self.stats = SimpleStats()
-
-        # This test relies on existing statistics data at the Stats object.
-        # This version of test prepares the data using the do_polling() method;
-        # that's a bad practice because a unittest for a method
-        # (update_statistics_data) would heavily depend on details of another
-        # method (do_polling).  However, there's currently no direct test
-        # for do_polling (which is also bad), so we still keep that approach,
-        # partly for testing do_polling indirectly.  #2781 should provide
-        # direct test for do_polling, with which this test scenario should
-        # also be changed to be more stand-alone.
-
-        # We use the knowledge of what kind of messages are sent via
-        # do_polling, and return the following faked answer directly.
-        create_answer = isc.config.ccsession.create_answer # shortcut
-        self.stats._answers = [\
-            # Answer for "show_processes"
-            (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
-                               [1035, 'b10-auth-2', 'Auth']]),  None),
-            # Answers for "getstats".  2 for Auth instances and 1 for Init.
-            # we return some bogus values for Init, but the rest of the test
-            # doesn't need it, so it's okay.
-            (create_answer(0, self.stats._auth_sdata), {'from': 'auth1'}),
-            (create_answer(0, self.stats._auth_sdata), {'from': 'auth2'}),
-            (create_answer(0, self.stats._auth_sdata), {'from': 'auth3'})
-            ]
-        # do_polling calls update_modules internally; in our scenario there's
-        # no change in modules, so we make it no-op.
-        self.stats.update_modules = lambda: None
-        # Now call do_polling.
-        self.stats.do_polling()
-
-        # samples of query number
-        bar1_tcp = 1001
-        bar2_tcp = 2001
-        bar3_tcp = 1002
-        bar3_udp = 1003
-        # two auth instances invoked, so we double the pre-set stat values
-        sum_qtcp = self.stats._queries_tcp * 2
-        sum_qudp = self.stats._queries_udp * 2
-        self.stats.update_statistics_data('Auth', "bar1 at foo",
-                                          {'queries.tcp': bar1_tcp})
-        self.assertTrue('Auth' in self.stats.statistics_data)
-        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
-                         bar1_tcp + sum_qtcp)
-        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
-        self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
-        self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid
-                        ['Auth']['bar1 at foo'])
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
-                         {'queries.tcp': bar1_tcp})
-        # check consolidation of statistics data even if there is
-        # non-existent mid of Auth
-        self.stats.update_statistics_data('Auth', "bar2 at foo",
-                                          {'queries.tcp': bar2_tcp})
-        self.assertTrue('Auth' in self.stats.statistics_data)
-        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
-                         bar1_tcp + bar2_tcp + sum_qtcp)
-        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
-        self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
-        self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
-                         {'queries.tcp': bar1_tcp})
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar2 at foo'],
-                         {'queries.tcp': bar2_tcp})
-        # kill running Auth but the statistics data doesn't change
-        self.base.auth2.server.shutdown()
-        self.stats.update_statistics_data()
-        self.assertTrue('Auth' in self.stats.statistics_data)
-        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
-        self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
-                         bar1_tcp + bar2_tcp + sum_qtcp)
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
-                         sum_qudp)
-        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
-        # restore statistics data of killed auth
-        # self.base.b10_init.server.pid_list = [ killed ] + self.base.b10_init.server.pid_list[:]
-        self.stats.update_statistics_data('Auth',
-                                          "bar1 at foo",
-                                          {'queries.tcp': bar1_tcp})
-        # set another mid of Auth
-        self.stats.update_statistics_data('Auth',
-                                          "bar3 at foo",
-                                          {'queries.tcp':bar3_tcp,
-                                           'queries.udp':bar3_udp})
-        self.assertTrue('Auth' in self.stats.statistics_data)
-        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
-        self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
-                         bar1_tcp + bar2_tcp + bar3_tcp + sum_qtcp)
-        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
-                         bar3_udp + sum_qudp)
-        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
-        self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
-        self.assertTrue('bar3 at foo' in self.stats.statistics_data_bymid['Auth'])
-        self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
-        self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
-        self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo']['queries.tcp'], bar1_tcp)
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.tcp'], bar3_tcp)
-        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.udp'], bar3_udp)
-
-    def test_config(self):
-        orig_get_timestamp = stats.get_timestamp
-        stats.get_timestamp = lambda : self.const_timestamp
-        stat = SimpleStats()
-
-        # test updating poll-interval
-        self.assertEqual(stat.config['poll-interval'], 60)
-        self.assertEqual(stat.get_interval(), 60)
-        self.assertEqual(stat.next_polltime, self.const_timestamp + 60)
-        self.assertEqual(stat.config_handler({'poll-interval': 120}),
-                         isc.config.create_answer(0))
-        self.assertEqual(stat.config['poll-interval'], 120)
-        self.assertEqual(stat.get_interval(), 120)
-        self.assertEqual(stat.next_polltime, self.const_timestamp + 120)
-        stats.get_timestamp = orig_get_timestamp
-        self.assertEqual(stat.config_handler({'poll-interval': "foo"}),
-                         isc.config.create_answer(1, 'foo should be an integer'))
-        self.assertEqual(stat.config_handler({'poll-interval': -1}),
-                         isc.config.create_answer(1, 'Negative integer ignored'))
-        # unknown item
-        self.assertEqual(
-            stat.config_handler({'_UNKNOWN_KEY_': None}),
-            isc.config.ccsession.create_answer(
-                1, "unknown item _UNKNOWN_KEY_"))
-        # test no change if zero interval time
-        self.assertEqual(stat.config_handler({'poll-interval': 0}),
-                         isc.config.create_answer(0))
-        self.assertEqual(stat.config['poll-interval'], 0)
-
-        # see the comment for test_update_statistics_data_withmid.  We abuse
-        # do_polling here, too.  With #2781 we should make it more direct.
-        create_answer = isc.config.ccsession.create_answer # shortcut
-        stat._answers = [\
-            # Answer for "show_processes"
-            (create_answer(0, []),  None),
-            # Answers for "getstats" for Init (the other one for Auth, but
-            # that doesn't matter for this test)
-            (create_answer(0, stat._init_sdata), {'from': 'init'}),
-            (create_answer(0, stat._init_sdata), {'from': 'init'})
-            ]
-        stat.update_modules = lambda: None
-
-        self.assertEqual(
-            self.__send_command(
-                stat, 'show',
-                params={ 'owner' : 'Init', 'name'  : 'boot_time' }),
-            (0, {'Init': {'boot_time': self.const_datetime}}))
-
-    def test_commands(self):
-        self.stats = stats.Stats()
-
-        # status
-        self.assertEqual(self.stats.command_status(),
-                isc.config.create_answer(
-                0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-
-        # shutdown
-        self.stats.running = True
-        self.assertEqual(self.stats.command_shutdown(),
-                         isc.config.create_answer(0))
-        self.assertFalse(self.stats.running)
-
-    @unittest.skipIf(sys.version_info >= (3, 3), "Unsupported in Python 3.3 or higher")
-    def test_command_show(self):
-        # two auth instances invoked
-        list_auth = [ self.base.auth.server,
-                      self.base.auth2.server ]
-        sum_qtcp = 0
-        sum_qudp = 0
-        sum_qtcp_perzone1 = 0
-        sum_qudp_perzone1 = 0
-        sum_qtcp_perzone2 = 4 * len(list_auth)
-        sum_qudp_perzone2 = 3 * len(list_auth)
-        sum_qtcp_nds_perzone10 = 0
-        sum_qudp_nds_perzone10 = 0
-        sum_qtcp_nds_perzone20 = 4 * len(list_auth)
-        sum_qudp_nds_perzone20 = 3 * len(list_auth)
-        self.stats = stats.Stats()
-        self.assertEqual(self.stats.command_show(owner='Foo', name=None),
-                         isc.config.create_answer(
-                1, "specified arguments are incorrect: owner: Foo, name: None"))
-        self.assertEqual(self.stats.command_show(owner='Foo', name='_bar_'),
-                         isc.config.create_answer(
-                1, "specified arguments are incorrect: owner: Foo, name: _bar_"))
-        self.assertEqual(self.stats.command_show(owner='Foo', name='bar'),
-                         isc.config.create_answer(
-                1, "specified arguments are incorrect: owner: Foo, name: bar"))
-
-        for a in list_auth:
-            sum_qtcp += a.queries_tcp
-            sum_qudp += a.queries_udp
-            sum_qtcp_perzone1 += a.queries_per_zone[0]['queries.tcp']
-            sum_qudp_perzone1 += a.queries_per_zone[0]['queries.udp']
-            sum_qtcp_nds_perzone10 += a.nds_queries_per_zone['test10.example']['queries.tcp']
-            sum_qudp_nds_perzone10 += a.nds_queries_per_zone['test10.example']['queries.udp']
-
-        self.assertEqual(self.stats.command_show(owner='Auth'),
-                         isc.config.create_answer(
-                0, {'Auth':{ 'queries.udp': sum_qudp,
-                     'queries.tcp': sum_qtcp,
-                     'queries.perzone': [{ 'zonename': 'test1.example',
-                                           'queries.udp': sum_qudp_perzone1,
-                                           'queries.tcp': sum_qtcp_perzone1 },
-                                         { 'zonename': 'test2.example',
-                                           'queries.udp': sum_qudp_perzone2,
-                                           'queries.tcp': sum_qtcp_perzone2 }
-                                         ],
-                     'nds_queries.perzone': { 'test10.example' : {
-                                              'queries.udp': sum_qudp_nds_perzone10,
-                                              'queries.tcp': sum_qtcp_nds_perzone10 },
-                                              'test20.example' : {
-                                              'queries.udp': sum_qudp_nds_perzone20,
-                                              'queries.tcp': sum_qtcp_nds_perzone20 }
-                             }}}))
-        self.assertEqual(self.stats.command_show(owner='Auth', name='queries.udp'),
-                         isc.config.create_answer(
-                0, {'Auth': {'queries.udp': sum_qudp}}))
-        self.assertEqual(self.stats.command_show(owner='Auth', name='queries.perzone'),
-                         isc.config.create_answer(
-                0, {'Auth': {'queries.perzone': [
-                            { 'zonename': 'test1.example',
-                              'queries.udp': sum_qudp_perzone1,
-                              'queries.tcp': sum_qtcp_perzone1 },
-                            { 'zonename': 'test2.example',
-                              'queries.udp': sum_qudp_perzone2,
-                              'queries.tcp': sum_qtcp_perzone2 }]}}))
-        self.assertEqual(self.stats.command_show(owner='Auth', name='nds_queries.perzone'),
-                         isc.config.create_answer(
-                0, {'Auth': {'nds_queries.perzone': {
-                            'test10.example': {
-                                'queries.udp': sum_qudp_nds_perzone10,
-                                'queries.tcp': sum_qtcp_nds_perzone10 },
-                            'test20.example': {
-                                'queries.udp': sum_qudp_nds_perzone20,
-                                'queries.tcp': sum_qtcp_nds_perzone20 }}}}))
-        orig_get_datetime = stats.get_datetime
-        orig_get_timestamp = stats.get_timestamp
-        stats.get_datetime = lambda x=None: self.const_datetime
-        stats.get_timestamp = lambda : self.const_timestamp
-        self.assertEqual(self.stats.command_show(owner='Stats', name='report_time'),
-                         isc.config.create_answer(
-                0, {'Stats': {'report_time':self.const_datetime}}))
-        self.assertEqual(self.stats.command_show(owner='Stats', name='timestamp'),
-                         isc.config.create_answer(
-                0, {'Stats': {'timestamp':self.const_timestamp}}))
-        stats.get_datetime = orig_get_datetime
-        stats.get_timestamp = orig_get_timestamp
-        self.stats.modules[self.stats.module_name] = isc.config.module_spec.ModuleSpec(
-            { "module_name": self.stats.module_name,
-              "statistics": [] } )
-        self.assertRaises(
-            stats.StatsError, self.stats.command_show, owner=self.stats.module_name, name='bar')
-
-    def test_command_showchema(self):
-        self.stats = stats.Stats()
-        (rcode, value) = isc.config.ccsession.parse_answer(
-            self.stats.command_showschema())
-        self.assertEqual(rcode, 0)
-        self.assertEqual(len(value), 3)
-        self.assertTrue('Stats' in value)
-        self.assertTrue('Init' in value)
-        self.assertTrue('Auth' in value)
-        self.assertFalse('__Dummy__' in value)
-        schema = value['Stats']
-        self.assertEqual(len(schema), 5)
-        for item in schema:
-            self.assertTrue(len(item) == 6 or len(item) == 7)
-            self.assertTrue('item_name' in item)
-            self.assertTrue('item_type' in item)
-            self.assertTrue('item_optional' in item)
-            self.assertTrue('item_default' in item)
-            self.assertTrue('item_title' in item)
-            self.assertTrue('item_description' in item)
-            if len(item) == 7:
-                self.assertTrue('item_format' in item)
-
-        schema = value['Init']
-        self.assertEqual(len(schema), 1)
-        for item in schema:
-            self.assertTrue(len(item) == 7)
-            self.assertTrue('item_name' in item)
-            self.assertTrue('item_type' in item)
-            self.assertTrue('item_optional' in item)
-            self.assertTrue('item_default' in item)
-            self.assertTrue('item_title' in item)
-            self.assertTrue('item_description' in item)
-            self.assertTrue('item_format' in item)
-
-        schema = value['Auth']
-        self.assertEqual(len(schema), 4)
-        for item in schema:
-            if item['item_type'] == 'list' or item['item_type'] == 'named_set':
-                self.assertEqual(len(item), 7)
-            else:
-                self.assertEqual(len(item), 6)
-            self.assertTrue('item_name' in item)
-            self.assertTrue('item_type' in item)
-            self.assertTrue('item_optional' in item)
-            self.assertTrue('item_default' in item)
-            self.assertTrue('item_title' in item)
-            self.assertTrue('item_description' in item)
-
-        (rcode, value) = isc.config.ccsession.parse_answer(
-            self.stats.command_showschema(owner='Stats'))
-        self.assertEqual(rcode, 0)
-        self.assertTrue('Stats' in value)
-        self.assertFalse('Init' in value)
-        self.assertFalse('Auth' in value)
-        for item in value['Stats']:
-            self.assertTrue(len(item) == 6 or len(item) == 7)
-            self.assertTrue('item_name' in item)
-            self.assertTrue('item_type' in item)
-            self.assertTrue('item_optional' in item)
-            self.assertTrue('item_default' in item)
-            self.assertTrue('item_title' in item)
-            self.assertTrue('item_description' in item)
-            if len(item) == 7:
-                self.assertTrue('item_format' in item)
-
-        (rcode, value) = isc.config.ccsession.parse_answer(
-            self.stats.command_showschema(owner='Stats', name='report_time'))
-        self.assertEqual(rcode, 0)
-        self.assertTrue('Stats' in value)
-        self.assertFalse('Init' in value)
-        self.assertFalse('Auth' in value)
-        self.assertEqual(len(value['Stats'][0]), 7)
-        self.assertTrue('item_name' in value['Stats'][0])
-        self.assertTrue('item_type' in value['Stats'][0])
-        self.assertTrue('item_optional' in value['Stats'][0])
-        self.assertTrue('item_default' in value['Stats'][0])
-        self.assertTrue('item_title' in value['Stats'][0])
-        self.assertTrue('item_description' in value['Stats'][0])
-        self.assertTrue('item_format' in value['Stats'][0])
-        self.assertEqual(value['Stats'][0]['item_name'], 'report_time')
-        self.assertEqual(value['Stats'][0]['item_format'], 'date-time')
-
-        self.assertEqual(self.stats.command_showschema(owner='Foo'),
-                         isc.config.create_answer(
-                1, "specified arguments are incorrect: owner: Foo, name: None"))
-        self.assertEqual(self.stats.command_showschema(owner='Foo', name='bar'),
-                         isc.config.create_answer(
-                1, "specified arguments are incorrect: owner: Foo, name: bar"))
-        self.assertEqual(self.stats.command_showschema(owner='Auth'),
-                         isc.config.create_answer(
-                0, {'Auth': [{
-                        "item_default": 0,
-                        "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
-                        "item_name": "queries.tcp",
-                        "item_optional": False,
-                        "item_title": "Queries TCP",
-                        "item_type": "integer"
-                        },
-                    {
-                        "item_default": 0,
-                        "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially",
-                        "item_name": "queries.udp",
-                        "item_optional": False,
-                        "item_title": "Queries UDP",
-                        "item_type": "integer"
-                        },
-                    {
-                        "item_name": "queries.perzone",
-                        "item_type": "list",
-                        "item_optional": False,
-                        "item_default": [
-                            {
-                                "zonename" : "test1.example",
-                                "queries.udp" : 1,
-                                "queries.tcp" : 2
-                                },
-                            {
-                                "zonename" : "test2.example",
-                                "queries.udp" : 3,
-                                "queries.tcp" : 4
-                                }
-                        ],
-                        "item_title": "Queries per zone",
-                        "item_description": "Queries per zone",
-                        "list_item_spec": {
-                            "item_name": "zones",
-                            "item_type": "map",
-                            "item_optional": False,
-                            "item_default": {},
-                            "map_item_spec": [
-                                {
-                                    "item_name": "zonename",
-                                    "item_type": "string",
-                                    "item_optional": False,
-                                    "item_default": "",
-                                    "item_title": "Zonename",
-                                    "item_description": "Zonename"
-                                    },
-                                {
-                                    "item_name": "queries.udp",
-                                    "item_type": "integer",
-                                    "item_optional": False,
-                                    "item_default": 0,
-                                    "item_title": "Queries UDP per zone",
-                                    "item_description": "A number of UDP query counts per zone"
-                                    },
-                                {
-                                    "item_name": "queries.tcp",
-                                    "item_type": "integer",
-                                    "item_optional": False,
-                                    "item_default": 0,
-                                    "item_title": "Queries TCP per zone",
-                                    "item_description": "A number of TCP query counts per zone"
-                                    }
-                                ]
-                            }
-                        },
-                    {
-                        "item_name": "nds_queries.perzone",
-                        "item_type": "named_set",
-                        "item_optional": False,
-                        "item_default": {
-                            "test10.example" : {
-                                "queries.udp" : 1,
-                                "queries.tcp" : 2
-                            },
-                            "test20.example" : {
-                                "queries.udp" : 3,
-                                "queries.tcp" : 4
-                            }
-                        },
-                        "item_title": "Queries per zone",
-                        "item_description": "Queries per zone",
-                        "named_set_item_spec": {
-                            "item_name": "zonename",
-                            "item_type": "map",
-                            "item_optional": False,
-                            "item_default": {},
-                            "item_title": "Zonename",
-                            "item_description": "Zonename",
-                            "map_item_spec": [
-                                {
-                                    "item_name": "queries.udp",
-                                    "item_type": "integer",
-                                    "item_optional": False,
-                                    "item_default": 0,
-                                    "item_title": "Queries UDP per zone",
-                                    "item_description": "A number of UDP query counts per zone"
-                                    },
-                                {
-                                    "item_name": "queries.tcp",
-                                    "item_type": "integer",
-                                    "item_optional": False,
-                                    "item_default": 0,
-                                    "item_title": "Queries TCP per zone",
-                                    "item_description": "A number of TCP query counts per zone"
-                                    }
-                                ]
-                            }
-                        }]}))
-        self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.tcp'),
-                         isc.config.create_answer(
-                0, {'Auth': [{
-                    "item_default": 0,
-                    "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
-                    "item_name": "queries.tcp",
-                    "item_optional": False,
-                    "item_title": "Queries TCP",
-                    "item_type": "integer"
-                    }]}))
-        self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.perzone'),
-                         isc.config.create_answer(
-                0, {'Auth':[{
-                    "item_name": "queries.perzone",
-                    "item_type": "list",
-                    "item_optional": False,
-                    "item_default": [
-                        {
-                            "zonename" : "test1.example",
-                            "queries.udp" : 1,
-                            "queries.tcp" : 2
-                            },
-                        {
-                            "zonename" : "test2.example",
-                            "queries.udp" : 3,
-                            "queries.tcp" : 4
-                            }
-                    ],
-                    "item_title": "Queries per zone",
-                    "item_description": "Queries per zone",
-                    "list_item_spec": {
-                        "item_name": "zones",
-                        "item_type": "map",
-                        "item_optional": False,
-                        "item_default": {},
-                        "map_item_spec": [
-                            {
-                                "item_name": "zonename",
-                                "item_type": "string",
-                                "item_optional": False,
-                                "item_default": "",
-                                "item_title": "Zonename",
-                                "item_description": "Zonename"
-                                },
-                            {
-                                "item_name": "queries.udp",
-                                "item_type": "integer",
-                                "item_optional": False,
-                                "item_default": 0,
-                                "item_title": "Queries UDP per zone",
-                                "item_description": "A number of UDP query counts per zone"
-                                },
-                            {
-                                "item_name": "queries.tcp",
-                                "item_type": "integer",
-                                "item_optional": False,
-                                "item_default": 0,
-                                "item_title": "Queries TCP per zone",
-                                "item_description": "A number of TCP query counts per zone"
-                                }
-                            ]
-                         }
-                     }]}))
-        self.assertEqual(self.stats.command_showschema(owner='Auth', name='nds_queries.perzone'),
-                         isc.config.create_answer(
-                0, {'Auth':[{
-                    "item_name": "nds_queries.perzone",
-                    "item_type": "named_set",
-                    "item_optional": False,
-                    "item_default": {
-                        "test10.example" : {
-                            "queries.udp" : 1,
-                            "queries.tcp" : 2
-                        },
-                        "test20.example" : {
-                            "queries.udp" : 3,
-                            "queries.tcp" : 4
-                        }
-                    },
-                    "item_title": "Queries per zone",
-                    "item_description": "Queries per zone",
-                    "named_set_item_spec": {
-                        "item_name": "zonename",
-                        "item_type": "map",
-                        "item_optional": False,
-                        "item_default": {},
-                        "item_title": "Zonename",
-                        "item_description": "Zonename",
-                        "map_item_spec": [
-                            {
-                                "item_name": "queries.udp",
-                                "item_type": "integer",
-                                "item_optional": False,
-                                "item_default": 0,
-                                "item_title": "Queries UDP per zone",
-                                "item_description": "A number of UDP query counts per zone"
-                                },
-                            {
-                                "item_name": "queries.tcp",
-                                "item_type": "integer",
-                                "item_optional": False,
-                                "item_default": 0,
-                                "item_title": "Queries TCP per zone",
-                                "item_description": "A number of TCP query counts per zone"
-                                }
-                            ]
-                        }
-                    }]}))
-
-        self.assertEqual(self.stats.command_showschema(owner='Stats', name='bar'),
-                         isc.config.create_answer(
-                1, "specified arguments are incorrect: owner: Stats, name: bar"))
-        self.assertEqual(self.stats.command_showschema(name='bar'),
-                         isc.config.create_answer(
-                1, "module name is not specified"))
-
-    @unittest.skipIf(sys.version_info >= (3, 3), "Unsupported in Python 3.3 or higher")
-    def test_polling(self):
-        stats_server = ThreadingServerManager(MyStats)
-        stat = stats_server.server
-        stats_server.run()
-        self.assertEqual(
-            send_command('show', 'Stats'),
-            (0, stat.statistics_data))
-        # check statistics data of 'Init'
-        b10_init = self.base.b10_init.server
-        self.assertEqual(
-            stat.statistics_data_bymid['Init'][b10_init.cc_session.lname],
-            {'boot_time': self.const_datetime})
-        self.assertEqual(
-            len(stat.statistics_data_bymid['Init']), 1)
-        self.assertEqual(
-            stat.statistics_data['Init'],
-            {'boot_time': self.const_datetime})
-        # check statistics data of each 'Auth' instances
-        list_auth = ['', '2']
-        for i in list_auth:
-            auth = getattr(self.base,"auth"+i).server
-            for s in stat.statistics_data_bymid['Auth'].values():
-                self.assertEqual(
-                    s, {'queries.perzone': auth.queries_per_zone,
-                        'nds_queries.perzone': auth.nds_queries_per_zone,
-                        'queries.tcp': auth.queries_tcp,
-                        'queries.udp': auth.queries_udp})
-            n = len(stat.statistics_data_bymid['Auth'])
-            self.assertEqual(n, len(list_auth))
-            # check consolidation of statistics data of the auth
-            # instances
-            self.assertEqual(
-                stat.statistics_data['Auth'],
-                {'queries.perzone': [
-                        {'zonename':
-                             auth.queries_per_zone[0]['zonename'],
-                         'queries.tcp':
-                             auth.queries_per_zone[0]['queries.tcp']*n,
-                         'queries.udp':
-                             auth.queries_per_zone[0]['queries.udp']*n},
-                        {'zonename': "test2.example",
-                         'queries.tcp': 4*n,
-                         'queries.udp': 3*n },
-                        ],
-                 'nds_queries.perzone': {
-                         'test10.example': {
-                             'queries.tcp':
-                                 auth.nds_queries_per_zone['test10.example']['queries.tcp']*n,
-                             'queries.udp':
-                                 auth.nds_queries_per_zone['test10.example']['queries.udp']*n},
-                         'test20.example': {
-                             'queries.tcp':
-                                 4*n,
-                             'queries.udp':
-                                 3*n},
-                         },
-                 'queries.tcp': auth.queries_tcp*n,
-                 'queries.udp': auth.queries_udp*n})
-        # check statistics data of 'Stats'
-        self.assertEqual(
-            len(stat.statistics_data['Stats']), 5)
-        self.assertTrue('boot_time' in
-            stat.statistics_data['Stats'])
-        self.assertTrue('last_update_time' in
-            stat.statistics_data['Stats'])
-        self.assertTrue('report_time' in
-            stat.statistics_data['Stats'])
-        self.assertTrue('timestamp' in
-            stat.statistics_data['Stats'])
-        self.assertEqual(
-            stat.statistics_data['Stats']['lname'],
-            stat.mccs._session.lname)
-        stats_server.shutdown()
-
-    def test_polling2(self):
-        # set invalid statistics
-        b10_init = self.base.b10_init.server
-        b10_init.statistics_data = {'boot_time':1}
-        stats_server = ThreadingServerManager(MyStats)
-        stat = stats_server.server
-        stats_server.run()
-        self.assertEqual(
-            send_command('status', 'Stats'),
-            (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
-        # check default statistics data of 'Init'
-        self.assertEqual(
-            stat.statistics_data['Init'],
-            {'boot_time': self.const_default_datetime})
-        stats_server.shutdown()
-
-class TestOSEnv(unittest.TestCase):
-    def test_osenv(self):
-        """
-        test for the environ variable "B10_FROM_SOURCE"
-        "B10_FROM_SOURCE" is set in Makefile
-        """
-        # test case having B10_FROM_SOURCE
-        self.assertTrue("B10_FROM_SOURCE" in os.environ)
-        self.assertEqual(stats.SPECFILE_LOCATION, \
-                             os.environ["B10_FROM_SOURCE"] + os.sep + \
-                             "src" + os.sep + "bin" + os.sep + "stats" + \
-                             os.sep + "stats.spec")
-        # test case not having B10_FROM_SOURCE
-        path = os.environ["B10_FROM_SOURCE"]
-        os.environ.pop("B10_FROM_SOURCE")
-        self.assertFalse("B10_FROM_SOURCE" in os.environ)
-        # import stats again
-        imp.reload(stats)
-        # revert the changes
-        os.environ["B10_FROM_SOURCE"] = path
-        imp.reload(stats)
-
-if __name__ == "__main__":
-    isc.log.resetUnitTestRootLogger()
-    unittest.main()
diff --git a/src/bin/stats/tests/stats-httpd_test.py b/src/bin/stats/tests/stats-httpd_test.py
new file mode 100644
index 0000000..a61ee47
--- /dev/null
+++ b/src/bin/stats/tests/stats-httpd_test.py
@@ -0,0 +1,1127 @@
+# Copyright (C) 2011-2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+In each of these tests we start several virtual components. They are
+not the real components, no external processes are started. They are
+just simple mock objects running each in its own thread and pretending
+to be bind10 modules. This helps testing the stats http server in a
+close to real environment.
+"""
+
+import unittest
+import os
+import imp
+import socket
+import errno
+import select
+import string
+import time
+import threading
+import http.client
+import xml.etree.ElementTree
+import random
+import urllib.parse
+import sys
+# load this module for xml validation with xsd. For this test, an
+# installation of lxml is required in advance. See http://lxml.de/.
+try:
+    from lxml import etree as lxml_etree
+except ImportError:
+    lxml_etree = None
+
+import isc
+import isc.log
+import stats_httpd
+import stats
+from test_utils import ThreadingServerManager, SignalHandler, \
+    MyStatsHttpd, CONST_BASETIME
+from isc.testutils.ccsession_mock import MockModuleCCSession
+from isc.config import RPCRecipientMissing, RPCError
+
+# This test suite uses xml.etree.ElementTree.XMLParser via
+# xml.etree.ElementTree.parse. On the platform where expat isn't
+# installed, ImportError is raised and it's failed. Check expat is
+# available before the test invocation. Skip this test if it's
+# unavailable.
+try:
+    # ImportError raised if xpat is unavailable
+    xml_parser = xml.etree.ElementTree.XMLParser()
+except ImportError:
+    xml_parser = None
+
+# set XML Namespaces for testing
+XMLNS_XSL = "http://www.w3.org/1999/XSL/Transform"
+XMLNS_XHTML = "http://www.w3.org/1999/xhtml"
+XMLNS_XSD = "http://www.w3.org/2001/XMLSchema"
+XMLNS_XSI = stats_httpd.XMLNS_XSI
+
+DUMMY_DATA = {
+    'Init' : {
+        "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME)
+        },
+    'Auth' : {
+        "queries.tcp": 6,
+        "queries.udp": 4,
+        "queries.perzone": [{
+                "zonename": "test1.example",
+                "queries.tcp": 10,
+                "queries.udp": 8
+                }, {
+                "zonename": "test2.example",
+                "queries.tcp": 8,
+                "queries.udp": 6
+                }],
+        "nds_queries.perzone": {
+                "test10.example": {
+                    "queries.tcp": 10,
+                    "queries.udp": 8
+                  },
+                "test20.example": {
+                    "queries.tcp": 8,
+                    "queries.udp": 6
+                  }
+                }
+        },
+    'Stats' : {
+        "report_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+        "boot_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+        "last_update_time": time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+        "lname": "4d70d40a_c at host",
+        "timestamp": time.mktime(CONST_BASETIME)
+        }
+    }
+
+# Bad practice: this should be localized
+stats._BASETIME = CONST_BASETIME
+stats.get_timestamp = lambda: time.mktime(CONST_BASETIME)
+stats.get_datetime = lambda x=None: time.strftime("%Y-%m-%dT%H:%M:%SZ", CONST_BASETIME)
+
+def get_availaddr(address='127.0.0.1', port=8001):
+    """returns a tuple of address and port which is available to
+    listen on the platform. The first argument is a address for
+    search. The second argument is a port for search. If a set of
+    address and port is failed on the search for the availability, the
+    port number is increased and it goes on the next trial until the
+    available set of address and port is looked up. If the port number
+    reaches over 65535, it may stop the search and raise a
+    OverflowError exception."""
+    while True:
+        for addr in socket.getaddrinfo(
+            address, port, 0,
+            socket.SOCK_STREAM, socket.IPPROTO_TCP):
+            sock = socket.socket(addr[0], socket.SOCK_STREAM)
+            try:
+                sock.bind((address, port))
+                return (address, port)
+            except socket.error:
+                continue
+            finally:
+                if sock: sock.close()
+        # This address and port number are already in use.
+        # next port number is added
+        port = port + 1
+
+def is_ipv6_enabled(address='::1', port=8001):
+    """checks IPv6 enabled on the platform. address for check is '::1'
+    and port for check is random number between 8001 and
+    65535. Retrying is 3 times even if it fails. The built-in socket
+    module provides a 'has_ipv6' parameter, but it's not used here
+    because there may be a situation where the value is True on an
+    environment where the IPv6 config is disabled."""
+    for p in random.sample(range(port, 65535), 3):
+        try:
+            sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+            sock.bind((address, p))
+            return True
+        except socket.error:
+            continue
+        finally:
+            if sock: sock.close()
+    return False
+
+class TestItemNameList(unittest.TestCase):
+
+    def test_item_name_list(self):
+        # for a one-element list
+        self.assertEqual(['a'],
+                         stats_httpd.item_name_list({'a':1}, 'a'))
+        # for a dict under a dict
+        self.assertEqual(['a','a/b'],
+                         stats_httpd.item_name_list({'a':{'b':1}}, 'a'))
+        self.assertEqual(['a/b'],
+                         stats_httpd.item_name_list({'a':{'b':1}}, 'a/b'))
+        self.assertEqual(['a','a/b','a/b/c'],
+                         stats_httpd.item_name_list({'a':{'b':{'c':1}}}, 'a'))
+        self.assertEqual(['a/b','a/b/c'],
+                         stats_httpd.item_name_list({'a':{'b':{'c':1}}},
+                                                    'a/b'))
+        self.assertEqual(['a/b/c'],
+                         stats_httpd.item_name_list({'a':{'b':{'c':1}}},
+                                                    'a/b/c'))
+        # for a list under a dict
+        self.assertEqual(['a[2]'],
+                         stats_httpd.item_name_list({'a':[1,2,3]}, 'a[2]'))
+        self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
+                         stats_httpd.item_name_list({'a':[1,2,3]}, 'a'))
+        self.assertEqual(['a', 'a[0]', 'a[1]', 'a[2]'],
+                         stats_httpd.item_name_list({'a':[1,2,3]}, ''))
+        # for a list under a dict under a dict
+        self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+                         stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a'))
+        self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+                         stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, ''))
+        self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+                         stats_httpd.item_name_list({'a':{'b':[1,2,3]}}, 'a/b'))
+        # for a mixed case of the above
+        self.assertEqual(['a', 'a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]', 'a/c'],
+                         stats_httpd.item_name_list(
+                {'a':{'b':[1,2,3], 'c':1}}, 'a'))
+        self.assertEqual(['a/b', 'a/b[0]', 'a/b[1]', 'a/b[2]'],
+                         stats_httpd.item_name_list(
+                {'a':{'b':[1,2,3], 'c':1}}, 'a/b'))
+        self.assertEqual(['a/c'],
+                         stats_httpd.item_name_list(
+                {'a':{'b':[1,2,3], 'c':1}}, 'a/c'))
+        # for specifying a wrong identifier which is not found in
+        # element
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                         stats_httpd.item_name_list, {'x':1}, 'a')
+        # for specifying a string in element and an empty string in
+        # identifier
+        self.assertEqual([],
+                         stats_httpd.item_name_list('a', ''))
+        # for specifying empty strings in element and identifier
+        self.assertEqual([],
+                         stats_httpd.item_name_list('', ''))
+        # for specifying wrong element, which is an non-empty string,
+        # and an non-empty string in identifier
+        self.assertRaises(isc.cc.data.DataTypeError,
+                         stats_httpd.item_name_list, 'a', 'a')
+        # for specifying None in element and identifier
+        self.assertRaises(isc.cc.data.DataTypeError,
+                         stats_httpd.item_name_list, None, None)
+        # for specifying non-dict in element
+        self.assertRaises(isc.cc.data.DataTypeError,
+                         stats_httpd.item_name_list, [1,2,3], 'a')
+        self.assertRaises(isc.cc.data.DataTypeError,
+                         stats_httpd.item_name_list, [1,2,3], '')
+        # for checking key names sorted which consist of element
+        num = 11
+        keys = [ 'a', 'aa', 'b' ]
+        keys.sort(reverse=True)
+        dictlist = dict([ (k, list(range(num))) for k in keys ])
+        keys.sort()
+        ans = []
+        for k in keys:
+            ans += [k] + [ '%s[%d]' % (k, i) for i in range(num) ]
+        self.assertEqual(ans,
+                         stats_httpd.item_name_list(dictlist, ''))
+
+class TestHttpHandler(unittest.TestCase):
+    """Tests for HttpHandler class"""
+    def setUp(self):
+        # set the signal handler for deadlock
+        self.sig_handler = SignalHandler(self.fail)
+        DUMMY_DATA['Stats']['lname'] = 'test-lname'
+        (self.address, self.port) = get_availaddr()
+        self.stats_httpd_server = ThreadingServerManager(MyStatsHttpd,
+                                                         (self.address,
+                                                          self.port))
+        self.stats_httpd = self.stats_httpd_server.server
+        self.stats_httpd_server.run()
+        self.client = http.client.HTTPConnection(self.address, self.port)
+        self.client._http_vsn_str = 'HTTP/1.0\n'
+        self.client.connect()
+
+    def tearDown(self):
+        self.client.close()
+        # reset the signal handler
+        self.sig_handler.reset()
+
+    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+    def test_do_GET(self):
+        self.assertTrue(type(self.stats_httpd.httpd) is list)
+        self.assertEqual(len(self.stats_httpd.httpd), 1)
+        self.assertEqual((self.address, self.port), self.stats_httpd.http_addrs[0])
+
+        def check_XML_URL_PATH(path=''):
+            url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
+            url_path = urllib.parse.quote(url_path)
+            self.client.putrequest('GET', url_path)
+            self.client.endheaders()
+            response = self.client.getresponse()
+            self.assertEqual(response.getheader("Content-type"), "text/xml")
+            self.assertGreater(int(response.getheader("Content-Length")), 0)
+            self.assertEqual(response.status, 200)
+            xml_doctype = response.readline().decode()
+            xsl_doctype = response.readline().decode()
+            self.assertGreater(len(xml_doctype), 0)
+            self.assertGreater(len(xsl_doctype), 0)
+            root = xml.etree.ElementTree.parse(response).getroot()
+            self.assertGreater(root.tag.find('statistics'), 0)
+            schema_loc = '{%s}schemaLocation' % XMLNS_XSI
+            # check the path of XSD
+            self.assertEqual(root.attrib[schema_loc],
+                             stats_httpd.XSD_NAMESPACE + ' '
+                             + stats_httpd.XSD_URL_PATH)
+            # check the path of XSL
+            self.assertTrue(xsl_doctype.startswith(
+                    '<?xml-stylesheet type="text/xsl" href="' +
+                    stats_httpd.XSL_URL_PATH
+                    + '"?>'))
+            # check whether the list of 'identifier' attributes in
+            # root is same as the list of item names in DUMMY_DATA
+            id_list = [ elm.attrib['identifier'] for elm in root ]
+            item_list = [ it for it in \
+                              stats_httpd.item_name_list(DUMMY_DATA, path) \
+                              if len(it.split('/')) > 1 ]
+            self.assertEqual(id_list, item_list)
+            for elem in root:
+                attr = elem.attrib
+                value = isc.cc.data.find(DUMMY_DATA, attr['identifier'])
+                # No 'value' attribute should be found in the 'item'
+                # element when datatype of the value is list or dict.
+                if type(value) is list or type(value) is dict:
+                    self.assertFalse('value' in attr)
+                # The value of the 'value' attribute should be checked
+                # after casting it to string type if datatype of the
+                # value is int or float. Because attr['value'] returns
+                # string type even if its value is int or float.
+                elif type(value) is int or type(value) is float:
+                    self.assertEqual(attr['value'], str(value))
+                else:
+                    self.assertEqual(attr['value'], value)
+
+        # URL is '/bind10/statistics/xml'
+        check_XML_URL_PATH()
+        for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
+            check_XML_URL_PATH(path)
+
+        def check_XSD_URL_PATH():
+            url_path = stats_httpd.XSD_URL_PATH
+            url_path = urllib.parse.quote(url_path)
+            self.client.putrequest('GET', url_path)
+            self.client.endheaders()
+            response = self.client.getresponse()
+            self.assertEqual(response.getheader("Content-type"), "text/xml")
+            self.assertGreater(int(response.getheader("Content-Length")), 0)
+            self.assertEqual(response.status, 200)
+            root = xml.etree.ElementTree.parse(response).getroot()
+            url_xmlschema = '{%s}' % XMLNS_XSD
+            self.assertGreater(root.tag.find('schema'), 0)
+            self.assertTrue(hasattr(root, 'attrib'))
+            self.assertTrue('targetNamespace' in root.attrib)
+            self.assertEqual(root.attrib['targetNamespace'],
+                             stats_httpd.XSD_NAMESPACE)
+
+        # URL is '/bind10/statistics/xsd'
+        check_XSD_URL_PATH()
+
+        def check_XSL_URL_PATH():
+            url_path = stats_httpd.XSL_URL_PATH
+            url_path = urllib.parse.quote(url_path)
+            self.client.putrequest('GET', url_path)
+            self.client.endheaders()
+            response = self.client.getresponse()
+            self.assertEqual(response.getheader("Content-type"), "text/xml")
+            self.assertGreater(int(response.getheader("Content-Length")), 0)
+            self.assertEqual(response.status, 200)
+            root = xml.etree.ElementTree.parse(response).getroot()
+            url_trans = '{%s}' % XMLNS_XSL
+            url_xhtml = '{%s}' % XMLNS_XHTML
+            self.assertEqual(root.tag, url_trans + 'stylesheet')
+
+        # URL is '/bind10/statistics/xsl'
+        check_XSL_URL_PATH()
+
+        # 302 redirect
+        self.client._http_vsn_str = 'HTTP/1.1'
+        self.client.putrequest('GET', '/')
+        self.client.putheader('Host', self.address)
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 302)
+        self.assertEqual(response.getheader('Location'),
+                         "http://%s:%d%s/" % (self.address, self.port, stats_httpd.XML_URL_PATH))
+
+        # 404 NotFound (random path)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', '/path/to/foo/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', '/bind10/foo')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', '/bind10/statistics/foo')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + 'Auth') # with no slash
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+
+        # 200 ok
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/#foo')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/?foo=bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+
+        # 404 NotFound (too long path)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Init/boot_time/a')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+
+        # 404 NotFound (nonexistent module name)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+
+        # 404 NotFound (nonexistent item name)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Foo/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Foo/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Foo/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+
+        # 404 NotFound (existent module but nonexistent item name)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/Auth/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH + '/Auth/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+        self.client._http_vsn_str = 'HTTP/1.0'
+        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH + '/Auth/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+
+    def test_do_GET_failed1(self):
+        # failure case (Stats is down, so rpc_call() results in an exception)
+        # Note: this should eventually be RPCRecipientMissing.
+        self.stats_httpd._rpc_answers.append(
+            isc.cc.session.SessionTimeout('timeout'))
+
+        # request XML
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 500)
+
+        # request XSD
+        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+
+        # request XSL
+        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+
+    def test_do_GET_failed2(self):
+        # failure case(Stats replies an error)
+        self.stats_httpd._rpc_answers.append(
+            RPCError(1, "specified arguments are incorrect: I have an error."))
+
+        # request XML
+        self.client.putrequest('GET', stats_httpd.XML_URL_PATH + '/')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+
+        # request XSD
+        self.stats_httpd._rpc_answers.append(
+            RPCError(1, "specified arguments are incorrect: I have an error."))
+        self.client.putrequest('GET', stats_httpd.XSD_URL_PATH)
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+
+        # request XSL
+        self.stats_httpd._rpc_answers.append(
+            RPCError(1, "specified arguments are incorrect: I have an error."))
+        self.client.putrequest('GET', stats_httpd.XSL_URL_PATH)
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+
+    def test_do_HEAD(self):
+        self.client.putrequest('HEAD', stats_httpd.XML_URL_PATH + '/')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 200)
+
+        self.client.putrequest('HEAD', '/path/to/foo/bar')
+        self.client.endheaders()
+        response = self.client.getresponse()
+        self.assertEqual(response.status, 404)
+
+    @unittest.skipUnless(lxml_etree, "skipping XML validation with XSD")
+    def test_xml_validation_with_xsd(self):
+        """Tests for XML validation with XSD. If lxml is not
+        installed, this tests would be skipped."""
+        def request_xsd():
+            url_path = stats_httpd.XSD_URL_PATH
+            url_path = urllib.parse.quote(url_path)
+            self.client.putrequest('GET', url_path)
+            self.client.endheaders()
+            xsd_doc = self.client.getresponse()
+            xsd_doc = lxml_etree.parse(xsd_doc)
+            return lxml_etree.XMLSchema(xsd_doc)
+
+        def request_xmldoc(path=''):
+            url_path = '%s/%s' % (stats_httpd.XML_URL_PATH, path)
+            url_path = urllib.parse.quote(url_path)
+            self.client.putrequest('GET', url_path)
+            self.client.endheaders()
+            xml_doc = self.client.getresponse()
+            return lxml_etree.parse(xml_doc)
+
+        # request XSD and XML
+        xsd = request_xsd()
+        xml_doc = request_xmldoc()
+        # do validation
+        self.assertTrue(xsd.validate(xml_doc))
+
+        # validate each paths in DUMMY_DATA
+        for path in stats_httpd.item_name_list(DUMMY_DATA, ''):
+            # request XML
+            xml_doc = request_xmldoc(path)
+            # do validation
+            self.assertTrue(xsd.validate(xml_doc))
+
+class TestHttpServerError(unittest.TestCase):
+    """Tests for HttpServerError exception"""
+    def test_raises(self):
+        try:
+            raise stats_httpd.HttpServerError('Nothing')
+        except stats_httpd.HttpServerError as err:
+            self.assertEqual(str(err), 'Nothing')
+
+class TestHttpServer(unittest.TestCase):
+    """Tests for HttpServer class"""
+    def setUp(self):
+        # set the signal handler for deadlock
+        self.sig_handler = SignalHandler(self.fail)
+
+    def tearDown(self):
+        if hasattr(self, "stats_httpd"):
+            self.stats_httpd.stop()
+        # reset the signal handler
+        self.sig_handler.reset()
+
+    def test_httpserver(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.assertEqual(type(self.stats_httpd.httpd), list)
+        self.assertEqual(len(self.stats_httpd.httpd), 1)
+        for httpd in self.stats_httpd.httpd:
+            self.assertTrue(isinstance(httpd, stats_httpd.HttpServer))
+
+class TestStatsHttpdError(unittest.TestCase):
+    """Tests for StatsHttpdError exception"""
+
+    def test_raises1(self):
+        try:
+            raise stats_httpd.StatsHttpdError('Nothing')
+        except stats_httpd.StatsHttpdError as err:
+            self.assertEqual(str(err), 'Nothing')
+
+    def test_raises2(self):
+        try:
+            raise stats_httpd.StatsHttpdDataError('Nothing')
+        except stats_httpd.StatsHttpdDataError as err:
+            self.assertEqual(str(err), 'Nothing')
+
+class TestStatsHttpd(unittest.TestCase):
+    """Tests for StatsHttpd class"""
+
+    def setUp(self):
+        # set the signal handler for deadlock
+        self.sig_handler = SignalHandler(self.fail)
+        # checking IPv6 enabled on this platform
+        self.ipv6_enabled = is_ipv6_enabled()
+        # instantiation of StatsHttpd indirectly calls gethostbyaddr(), which
+        # can block for an uncontrollable period, leading many undesirable
+        # results.  We should rather eliminate the reliance, but until we
+        # can make such fundamental cleanup we replace it with a faked method;
+        # in our test scenario the return value doesn't matter.
+        self.__gethostbyaddr_orig = socket.gethostbyaddr
+        socket.gethostbyaddr = lambda x: ('test.example.', [], None)
+
+        # Some tests replace this library function.  Keep the original for
+        # restor
+        self.__orig_select_select = select.select
+
+    def tearDown(self):
+        socket.gethostbyaddr = self.__gethostbyaddr_orig
+        if hasattr(self, "stats_httpd"):
+            self.stats_httpd.stop()
+        # reset the signal handler
+        self.sig_handler.reset()
+
+        # restore original of replaced library
+        select.select = self.__orig_select_select
+
+    def test_init(self):
+        server_address = get_availaddr()
+        self.stats_httpd = MyStatsHttpd(server_address)
+        self.assertEqual(self.stats_httpd.running, False)
+        self.assertEqual(self.stats_httpd.poll_intval, 0.5)
+        self.assertNotEqual(len(self.stats_httpd.httpd), 0)
+        self.assertIsNotNone(self.stats_httpd.mccs)
+        self.assertIsNotNone(self.stats_httpd.cc_session)
+        # The real CfgMgr would return 'version', but our test mock omits it,
+        # so the len(config) should be 1
+        self.assertEqual(len(self.stats_httpd.config), 1)
+        self.assertTrue('listen_on' in self.stats_httpd.config)
+        self.assertEqual(len(self.stats_httpd.config['listen_on']), 1)
+        self.assertTrue('address' in self.stats_httpd.config['listen_on'][0])
+        self.assertTrue('port' in self.stats_httpd.config['listen_on'][0])
+        self.assertTrue(server_address in set(self.stats_httpd.http_addrs))
+        self.assertEqual('StatsHttpd', self.stats_httpd.mccs.\
+                             get_module_spec().get_module_name())
+
+    def test_init_hterr(self):
+        """Test the behavior of StatsHttpd constructor when open_httpd fails.
+
+        We specifically check the following two:
+        - close_mccs() is called (so stats-httpd tells ConfigMgr it's shutting
+          down)
+        - the constructor results in HttpServerError exception.
+
+        """
+        self.__mccs_closed = False
+        def call_checker():
+            self.__mccs_closed = True
+        class FailingStatsHttpd(MyStatsHttpd):
+            def open_httpd(self):
+                raise stats_httpd.HttpServerError
+            def close_mccs(self):
+                call_checker()
+        self.assertRaises(stats_httpd.HttpServerError, FailingStatsHttpd)
+        self.assertTrue(self.__mccs_closed)
+
+    def test_openclose_mccs(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        mccs = self.stats_httpd.mccs
+        self.assertFalse(self.stats_httpd.mccs.stopped)
+        self.assertFalse(self.stats_httpd.mccs.closed)
+        self.stats_httpd.close_mccs()
+        self.assertTrue(mccs.stopped)
+        self.assertTrue(mccs.closed)
+        self.assertIsNone(self.stats_httpd.mccs)
+        self.stats_httpd.open_mccs()
+        self.assertIsNotNone(self.stats_httpd.mccs)
+        self.stats_httpd.mccs = None
+        self.assertIsNone(self.stats_httpd.mccs)
+        self.assertIsNone(self.stats_httpd.close_mccs())
+
+    def test_mccs(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.assertIsNotNone(self.stats_httpd.mccs.get_socket())
+        self.assertTrue(
+            isinstance(self.stats_httpd.mccs.get_socket(), socket.socket))
+        self.assertIsNotNone(self.stats_httpd.cc_session)
+        statistics_spec = self.stats_httpd.get_stats_spec()
+        for mod in DUMMY_DATA:
+            self.assertTrue(mod in statistics_spec)
+            for cfg in statistics_spec[mod]:
+                self.assertTrue('item_name' in cfg)
+                self.assertTrue(cfg['item_name'] in DUMMY_DATA[mod])
+            self.assertTrue(len(statistics_spec[mod]), len(DUMMY_DATA[mod]))
+        self.stats_httpd.close_mccs()
+        self.assertIsNone(self.stats_httpd.mccs)
+
+    def test_httpd(self):
+        # dual stack (addresses is ipv4 and ipv6)
+        if self.ipv6_enabled:
+            server_addresses = (get_availaddr('::1'), get_availaddr())
+            self.stats_httpd = MyStatsHttpd(*server_addresses)
+            for ht in self.stats_httpd.httpd:
+                self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+                self.assertTrue(ht.address_family in set([socket.AF_INET,
+                                                          socket.AF_INET6]))
+                self.assertTrue(isinstance(ht.socket, socket.socket))
+                ht.socket.close() # to silence warning about resource leak
+            self.stats_httpd.close_mccs() # ditto
+
+        # dual stack (address is ipv6)
+        if self.ipv6_enabled:
+            server_addresses = get_availaddr('::1')
+            self.stats_httpd = MyStatsHttpd(server_addresses)
+            for ht in self.stats_httpd.httpd:
+                self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+                self.assertEqual(ht.address_family, socket.AF_INET6)
+                self.assertTrue(isinstance(ht.socket, socket.socket))
+                ht.socket.close()
+            self.stats_httpd.close_mccs() # ditto
+
+        # dual/single stack (address is ipv4)
+        server_addresses = get_availaddr()
+        self.stats_httpd = MyStatsHttpd(server_addresses)
+        for ht in self.stats_httpd.httpd:
+            self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+            self.assertEqual(ht.address_family, socket.AF_INET)
+            self.assertTrue(isinstance(ht.socket, socket.socket))
+            ht.socket.close()
+        self.stats_httpd.close_mccs()
+
+    def test_httpd_anyIPv4(self):
+        # any address (IPv4)
+        server_addresses = get_availaddr(address='0.0.0.0')
+        self.stats_httpd = MyStatsHttpd(server_addresses)
+        for ht in self.stats_httpd.httpd:
+            self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+            self.assertEqual(ht.address_family,socket.AF_INET)
+            self.assertTrue(isinstance(ht.socket, socket.socket))
+
+    def test_httpd_anyIPv6(self):
+        # any address (IPv6)
+        if self.ipv6_enabled:
+            server_addresses = get_availaddr(address='::')
+            self.stats_httpd = MyStatsHttpd(server_addresses)
+            for ht in self.stats_httpd.httpd:
+                self.assertTrue(isinstance(ht, stats_httpd.HttpServer))
+                self.assertEqual(ht.address_family,socket.AF_INET6)
+                self.assertTrue(isinstance(ht.socket, socket.socket))
+
+    def test_httpd_failed(self):
+        # existent hostname
+        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+                          get_availaddr(address='localhost'))
+
+        # nonexistent hostname
+        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+                          ('my.host.domain', 8000))
+
+        # over flow of port number
+        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+                          ('127.0.0.1', 80000))
+
+        # negative
+        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+                          ('127.0.0.1', -8000))
+
+        # alphabet
+        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+                          ('127.0.0.1', 'ABCDE'))
+
+        # Address already in use
+        server_addresses = get_availaddr()
+        server = MyStatsHttpd(server_addresses)
+        self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
+                          server_addresses)
+
+    def __faked_select(self, ex=None):
+        """A helper subroutine for tests using faked select.select.
+
+        See test_running() for basic features.  If ex is not None,
+        it's assumed to be an exception object and will be raised on the
+        first call.
+
+        """
+        self.assertTrue(self.stats_httpd.running)
+        self.__call_count += 1
+        if ex is not None and self.__call_count == 1:
+            raise ex
+        if self.__call_count == 2:
+            self.stats_httpd.running  = False
+        assert self.__call_count <= 2 # safety net to avoid infinite loop
+        return ([], [], [])
+
+    def test_running(self):
+        # Previous version of this test checks the result of "status" and
+        # "shutdown" commands; however, they are more explicitly tested
+        # in specific tests.  In this test we only have to check:
+        # - start() will set 'running' to True
+        # - as long as 'running' is True, it keeps calling select.select
+        # - when running becomes False, it exists from the loop and calls
+        #   stop()
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.assertFalse(self.stats_httpd.running)
+
+        # In this test we'll call select.select() 2 times: on the first call
+        # stats_httpd.running should be True; on the second call the faked
+        # select() will set it to False.
+        self.__call_count = 0
+        select.select = lambda r, w, x, t: self.__faked_select()
+        self.stats_httpd.start()
+        self.assertFalse(self.stats_httpd.running)
+        self.assertIsNone(self.stats_httpd.mccs) # stop() clears .mccs
+
+    def test_running_fail(self):
+        # A failure case of start(): we close the (real but dummy) socket for
+        # the CC session.  This breaks the select-loop due to exception
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.stats_httpd.mccs.get_socket().close()
+        self.assertRaises(ValueError, self.stats_httpd.start)
+
+    def test_failure_with_a_select_error (self):
+        """checks select.error is raised if the exception except
+        errno.EINTR is raised while it's selecting"""
+        def raise_select_except(*args):
+            raise select.error('dummy error')
+        select.select = raise_select_except
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.assertRaises(select.error, self.stats_httpd.start)
+
+    def test_nofailure_with_errno_EINTR(self):
+        """checks no exception is raised if errno.EINTR is raised
+        while it's selecting"""
+        self.__call_count = 0
+        select.select = lambda r, w, x, t: self.__faked_select(
+            select.error(errno.EINTR))
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.stats_httpd.start() # shouldn't leak the exception
+        self.assertFalse(self.stats_httpd.running)
+        self.assertIsNone(self.stats_httpd.mccs)
+
+    def test_open_template(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        # successful conditions
+        tmpl = self.stats_httpd.open_template(
+            stats_httpd.XML_TEMPLATE_LOCATION)
+        self.assertTrue(isinstance(tmpl, string.Template))
+        opts = dict(
+            xml_string="<dummy></dummy>",
+            xsl_url_path="/path/to/")
+        lines = tmpl.substitute(opts)
+        for n in opts:
+            self.assertGreater(lines.find(opts[n]), 0)
+        tmpl = self.stats_httpd.open_template(
+            stats_httpd.XSD_TEMPLATE_LOCATION)
+        self.assertTrue(isinstance(tmpl, string.Template))
+        opts = dict(xsd_namespace="http://host/path/to/")
+        lines = tmpl.substitute(opts)
+        for n in opts:
+            self.assertGreater(lines.find(opts[n]), 0)
+        tmpl = self.stats_httpd.open_template(
+            stats_httpd.XSL_TEMPLATE_LOCATION)
+        self.assertTrue(isinstance(tmpl, string.Template))
+        opts = dict(xsd_namespace="http://host/path/to/")
+        lines = tmpl.substitute(opts)
+        for n in opts:
+            self.assertGreater(lines.find(opts[n]), 0)
+        # unsuccessful condition
+        self.assertRaises(
+            stats_httpd.StatsHttpdDataError,
+            self.stats_httpd.open_template, '/path/to/foo/bar')
+
+    def test_commands(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.assertEqual(self.stats_httpd.command_handler("status", None),
+                         isc.config.ccsession.create_answer(
+                0, "Stats Httpd is up. (PID " + str(os.getpid()) + ")"))
+        self.stats_httpd.running = True
+        self.assertEqual(self.stats_httpd.command_handler("shutdown", None),
+                         isc.config.ccsession.create_answer(0))
+        self.assertFalse(self.stats_httpd.running)
+        self.assertEqual(
+            self.stats_httpd.command_handler("__UNKNOWN_COMMAND__", None),
+            isc.config.ccsession.create_answer(
+                1, "Unknown command: __UNKNOWN_COMMAND__"))
+
+    def test_config(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        self.assertEqual(
+            self.stats_httpd.config_handler(dict(_UNKNOWN_KEY_=None)),
+            isc.config.ccsession.create_answer(
+                1, "unknown item _UNKNOWN_KEY_"))
+
+        addresses = get_availaddr()
+        self.assertEqual(
+            self.stats_httpd.config_handler(
+                dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
+            isc.config.ccsession.create_answer(0))
+        self.assertTrue("listen_on" in self.stats_httpd.config)
+        for addr in self.stats_httpd.config["listen_on"]:
+            self.assertTrue("address" in addr)
+            self.assertTrue("port" in addr)
+            self.assertTrue(addr["address"] == addresses[0])
+            self.assertTrue(addr["port"] == addresses[1])
+
+        if self.ipv6_enabled:
+            addresses = get_availaddr("::1")
+            self.assertEqual(
+                self.stats_httpd.config_handler(
+                dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
+                isc.config.ccsession.create_answer(0))
+            self.assertTrue("listen_on" in self.stats_httpd.config)
+            for addr in self.stats_httpd.config["listen_on"]:
+                self.assertTrue("address" in addr)
+                self.assertTrue("port" in addr)
+                self.assertTrue(addr["address"] == addresses[0])
+                self.assertTrue(addr["port"] == addresses[1])
+
+        addresses = get_availaddr()
+        self.assertEqual(
+            self.stats_httpd.config_handler(
+                dict(listen_on=[dict(address=addresses[0],port=addresses[1])])),
+            isc.config.ccsession.create_answer(0))
+        self.assertTrue("listen_on" in self.stats_httpd.config)
+        for addr in self.stats_httpd.config["listen_on"]:
+            self.assertTrue("address" in addr)
+            self.assertTrue("port" in addr)
+            self.assertTrue(addr["address"] == addresses[0])
+            self.assertTrue(addr["port"] == addresses[1])
+        (ret, arg) = isc.config.ccsession.parse_answer(
+            self.stats_httpd.config_handler(
+                dict(listen_on=[dict(address="1.2.3.4",port=543210)]))
+            )
+        self.assertEqual(ret, 1)
+
+    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+    def test_xml_handler(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        module_name = 'Dummy'
+        stats_spec = \
+            { module_name :
+                  [{
+                        "item_name": "foo",
+                        "item_type": "string",
+                        "item_optional": False,
+                        "item_default": "bar",
+                        "item_description": "foo is bar",
+                        "item_title": "Foo"
+                        },
+                   {
+                        "item_name": "foo2",
+                        "item_type": "list",
+                        "item_optional": False,
+                        "item_default": [
+                            {
+                                "zonename" : "test1",
+                                "queries.udp" : 1,
+                                "queries.tcp" : 2
+                                },
+                            {
+                                "zonename" : "test2",
+                                "queries.udp" : 3,
+                                "queries.tcp" : 4
+                                }
+                        ],
+                        "item_title": "Foo bar",
+                        "item_description": "Foo bar",
+                        "list_item_spec": {
+                            "item_name": "foo2-1",
+                            "item_type": "map",
+                            "item_optional": False,
+                            "item_default": {},
+                            "map_item_spec": [
+                                {
+                                    "item_name": "foo2-1-1",
+                                    "item_type": "string",
+                                    "item_optional": False,
+                                    "item_default": "",
+                                    "item_title": "Foo2 1 1",
+                                    "item_description": "Foo bar"
+                                    },
+                                {
+                                    "item_name": "foo2-1-2",
+                                    "item_type": "integer",
+                                    "item_optional": False,
+                                    "item_default": 0,
+                                    "item_title": "Foo2 1 2",
+                                    "item_description": "Foo bar"
+                                    },
+                                {
+                                    "item_name": "foo2-1-3",
+                                    "item_type": "integer",
+                                    "item_optional": False,
+                                    "item_default": 0,
+                                    "item_title": "Foo2 1 3",
+                                    "item_description": "Foo bar"
+                                    }
+                                ]
+                            }
+                        }]
+              }
+        stats_data = \
+            { module_name : { 'foo':'bar',
+                          'foo2': [
+                            {
+                                "foo2-1-1" : "bar1",
+                                "foo2-1-2" : 10,
+                                "foo2-1-3" : 9
+                                },
+                            {
+                                "foo2-1-1" : "bar2",
+                                "foo2-1-2" : 8,
+                                "foo2-1-3" : 7
+                                }
+                            ] } }
+        self.stats_httpd.get_stats_spec = lambda x,y: stats_spec
+        self.stats_httpd.get_stats_data = lambda x,y: stats_data
+        xml_string = self.stats_httpd.xml_handler()
+        stats_xml = xml.etree.ElementTree.fromstring(xml_string)
+        schema_loc = '{%s}schemaLocation' % XMLNS_XSI
+        self.assertEqual(stats_xml.attrib[schema_loc],
+                         stats_httpd.XML_ROOT_ATTRIB['xsi:schemaLocation'])
+        stats_data = stats_data[module_name]
+        stats_spec = stats_spec[module_name]
+        names = stats_httpd.item_name_list(stats_data, '')
+        for i in range(0, len(names)):
+            self.assertEqual('%s/%s' % (module_name, names[i]), stats_xml[i].attrib['identifier'])
+            value = isc.cc.data.find(stats_data, names[i])
+            if type(value) is int:
+                value = str(value)
+            if type(value) is dict or type(value) is list:
+                self.assertFalse('value' in stats_xml[i].attrib)
+            else:
+                self.assertEqual(value, stats_xml[i].attrib['value'])
+            self.assertEqual(module_name, stats_xml[i].attrib['owner'])
+            self.assertEqual(urllib.parse.quote('%s/%s/%s' % (stats_httpd.XML_URL_PATH,
+                                                              module_name, names[i])),
+                             stats_xml[i].attrib['uri'])
+            spec = isc.config.find_spec_part(stats_spec, names[i])
+            self.assertEqual(spec['item_name'], stats_xml[i].attrib['name'])
+            self.assertEqual(spec['item_type'], stats_xml[i].attrib['type'])
+            self.assertEqual(spec['item_description'], stats_xml[i].attrib['description'])
+            self.assertEqual(spec['item_title'], stats_xml[i].attrib['title'])
+            self.assertEqual(str(spec['item_optional']).lower(), stats_xml[i].attrib['optional'])
+            default = spec['item_default']
+            if type(default) is int:
+                default = str(default)
+            if type(default) is dict or type(default) is list:
+                self.assertFalse('default' in stats_xml[i].attrib)
+            else:
+                self.assertEqual(default, stats_xml[i].attrib['default'])
+            self.assertFalse('item_format' in spec)
+            self.assertFalse('format' in stats_xml[i].attrib)
+
+    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+    def test_xsd_handler(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        xsd_string = self.stats_httpd.xsd_handler()
+        stats_xsd = xml.etree.ElementTree.fromstring(xsd_string)
+        ns = '{%s}' % XMLNS_XSD
+        stats_xsd = stats_xsd[1].find('%scomplexType/%ssequence/%selement' % ((ns,)*3))
+        self.assertEqual('item', stats_xsd.attrib['name'])
+        stats_xsd = stats_xsd.find('%scomplexType' % ns)
+        type_types = ('boolean', 'integer', 'real', 'string', 'map', \
+                          'list', 'named_set', 'any')
+        attribs = [('identifier', 'string', 'required'),
+                   ('value', 'string', 'optional'),
+                   ('owner', 'string', 'required'),
+                   ('uri', 'anyURI', 'required'),
+                   ('name', 'string', 'required'),
+                   ('type', type_types, 'required'),
+                   ('description', 'string', 'optional'),
+                   ('title', 'string', 'optional'),
+                   ('optional', 'boolean', 'optional'),
+                   ('default', 'string', 'optional'),
+                   ('format', 'string', 'optional')]
+        for i in range(0, len(attribs)):
+            self.assertEqual(attribs[i][0], stats_xsd[i].attrib['name'])
+            if attribs[i][0] == 'type':
+                stats_xsd_types = \
+                    stats_xsd[i].find('%ssimpleType/%srestriction' % \
+                                          ((ns,)*2))
+                for j in range(0, len(attribs[i][1])):
+                    self.assertEqual(attribs[i][1][j], \
+                                         stats_xsd_types[j].attrib['value'])
+            else:
+                self.assertEqual(attribs[i][1], stats_xsd[i].attrib['type'])
+            self.assertEqual(attribs[i][2], stats_xsd[i].attrib['use'])
+
+    @unittest.skipUnless(xml_parser, "skipping the test using XMLParser")
+    def test_xsl_handler(self):
+        self.stats_httpd = MyStatsHttpd(get_availaddr())
+        xsl_string = self.stats_httpd.xsl_handler()
+        stats_xsl = xml.etree.ElementTree.fromstring(xsl_string)
+        nst = '{%s}' % XMLNS_XSL
+        nsx = '{%s}' % XMLNS_XHTML
+        self.assertEqual("bind10:statistics", stats_xsl[2].attrib['match'])
+        stats_xsl = stats_xsl[2].find('%stable' % nsx)
+        self.assertEqual('item', stats_xsl[1].attrib['select'])
+        stats_xsl = stats_xsl[1].find('%str' % nsx)
+        self.assertEqual('@uri', stats_xsl[0].find(
+                '%selement/%sattribute/%svalue-of' % ((nst,)*3)).attrib['select'])
+        self.assertEqual('@identifier', stats_xsl[0].find(
+                '%selement/%svalue-of' % ((nst,)*2)).attrib['select'])
+        self.assertEqual('@value', stats_xsl[1].find('%sif' % nst).attrib['test'])
+        self.assertEqual('@value', stats_xsl[1].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
+        self.assertEqual('@description', stats_xsl[2].find('%sif' % nst).attrib['test'])
+        self.assertEqual('@description', stats_xsl[2].find('%sif/%svalue-of' % ((nst,)*2)).attrib['select'])
+
+class Z_TestOSEnv(unittest.TestCase):
+    def test_for_without_B10_FROM_SOURCE(self):
+        # Note: this test is sensitive due to its substantial side effect of
+        # reloading.  For exmaple, it affects tests that tweak module
+        # attributes (such as test_init_hterr).  It also breaks logging
+        # setting for unit tests.  To minimize these effects, we use
+        # workaround: make it very likely to run at the end of the tests
+        # by naming the test class "Z_".
+
+        # just lets it go through the code without B10_FROM_SOURCE env
+        # variable
+        if "B10_FROM_SOURCE" in os.environ:
+            tmppath = os.environ["B10_FROM_SOURCE"]
+            os.environ.pop("B10_FROM_SOURCE")
+            imp.reload(stats_httpd)
+            os.environ["B10_FROM_SOURCE"] = tmppath
+            imp.reload(stats_httpd)
+
+if __name__ == "__main__":
+    isc.log.resetUnitTestRootLogger()
+    unittest.main()
diff --git a/src/bin/stats/tests/stats_test.py b/src/bin/stats/tests/stats_test.py
new file mode 100644
index 0000000..e010c97
--- /dev/null
+++ b/src/bin/stats/tests/stats_test.py
@@ -0,0 +1,1431 @@
+# Copyright (C) 2010, 2011, 2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+In each of these tests we start several virtual components. They are
+not the real components, no external processes are started. They are
+just simple mock objects running each in its own thread and pretending
+to be bind10 modules. This helps testing the stats module in a close
+to real environment.
+"""
+
+import unittest
+import os
+import io
+import time
+import imp
+import sys
+
+import stats
+import isc.log
+from test_utils import MyStats
+
+class TestUtilties(unittest.TestCase):
+    items = [
+        { 'item_name': 'test_int1',  'item_type': 'integer', 'item_default': 12345      },
+        { 'item_name': 'test_real1', 'item_type': 'real',    'item_default': 12345.6789 },
+        { 'item_name': 'test_bool1', 'item_type': 'boolean', 'item_default': True       },
+        { 'item_name': 'test_str1',  'item_type': 'string',  'item_default': 'ABCD'     },
+        { 'item_name': 'test_list1', 'item_type': 'list',    'item_default': [1,2,3],
+          'list_item_spec' : { 'item_name': 'number',   'item_type': 'integer' } },
+        { 'item_name': 'test_map1',  'item_type': 'map',     'item_default': {'a':1,'b':2,'c':3},
+          'map_item_spec'  : [ { 'item_name': 'a',   'item_type': 'integer'},
+                               { 'item_name': 'b',   'item_type': 'integer'},
+                               { 'item_name': 'c', 'item_type': 'integer'} ] },
+        { 'item_name': 'test_int2',  'item_type': 'integer' },
+        { 'item_name': 'test_real2', 'item_type': 'real'    },
+        { 'item_name': 'test_bool2', 'item_type': 'boolean' },
+        { 'item_name': 'test_str2',  'item_type': 'string'  },
+        { 'item_name': 'test_list2', 'item_type': 'list',
+          'list_item_spec' : { 'item_name': 'number',   'item_type': 'integer' } },
+        { 'item_name': 'test_map2',  'item_type': 'map',
+          'map_item_spec'  : [ { 'item_name': 'A', 'item_type': 'integer'},
+                               { 'item_name': 'B', 'item_type': 'integer'},
+                               { 'item_name': 'C', 'item_type': 'integer'} ] },
+        { 'item_name': 'test_none',  'item_type': 'none'    },
+        { 'item_name': 'test_list3', 'item_type': 'list',    'item_default': ["one","two","three"],
+          'list_item_spec' : { 'item_name': 'number', 'item_type': 'string' } },
+        { 'item_name': 'test_map3',  'item_type': 'map',     'item_default': {'a':'one','b':'two','c':'three'},
+          'map_item_spec'  : [ { 'item_name': 'a', 'item_type': 'string'},
+                               { 'item_name': 'b', 'item_type': 'string'},
+                               { 'item_name': 'c', 'item_type': 'string'} ] },
+        {
+          'item_name': 'test_named_set',
+          'item_type': 'named_set',
+          'item_default': { },
+          'named_set_item_spec': {
+            'item_name': 'name',
+            'item_type': 'map',
+            'item_default': { },
+            'map_item_spec': [
+              {
+                'item_name': 'number1',
+                'item_type': 'integer'
+                },
+              {
+                'item_name': 'number2',
+                'item_type': 'integer'
+                }
+              ]
+            }
+          }
+        ]
+
+    def setUp(self):
+        self.const_timestamp = 1308730448.965706
+        self.const_timetuple = (2011, 6, 22, 8, 14, 8, 2, 173, 0)
+        self.const_datetime = '2011-06-22T08:14:08Z'
+        self.__orig_time = stats.time
+        self.__orig_gmtime = stats.gmtime
+        stats.time = lambda : self.const_timestamp
+        stats.gmtime = lambda : self.const_timetuple
+
+    def tearDown(self):
+        stats.time = self.__orig_time
+        stats.gmtime = self.__orig_gmtime
+
+    def test_get_spec_defaults(self):
+        self.assertEqual(
+            stats.get_spec_defaults(self.items), {
+                'test_int1'  : 12345              ,
+                'test_real1' : 12345.6789         ,
+                'test_bool1' : True               ,
+                'test_str1'  : 'ABCD'             ,
+                'test_list1' : [1,2,3]            ,
+                'test_map1'  : {'a':1,'b':2,'c':3},
+                'test_int2'  : 0 ,
+                'test_real2' : 0.0,
+                'test_bool2' : False,
+                'test_str2'  : "",
+                'test_list2' : [0],
+                'test_map2'  : { 'A' : 0, 'B' : 0, 'C' : 0 },
+                'test_none'  : None,
+                'test_list3' : [ "one", "two", "three" ],
+                'test_map3'  : { 'a' : 'one', 'b' : 'two', 'c' : 'three' },
+                'test_named_set' : {} })
+        self.assertEqual(stats.get_spec_defaults(None), {})
+        self.assertRaises(KeyError, stats.get_spec_defaults, [{'item_name':'Foo'}])
+
+    def test_get_timestamp(self):
+        self.assertEqual(stats.get_timestamp(), self.const_timestamp)
+
+    def test_get_datetime(self):
+        self.assertEqual(stats.get_datetime(), self.const_datetime)
+        self.assertNotEqual(stats.get_datetime(
+                (2011, 6, 22, 8, 23, 40, 2, 173, 0)), self.const_datetime)
+
+    def test__accum(self):
+        self.assertEqual(stats._accum(None, None), None)
+        self.assertEqual(stats._accum(None, "b"), "b")
+        self.assertEqual(stats._accum("a", None), "a")
+        self.assertEqual(stats._accum(1, 2), 3)
+        self.assertEqual(stats._accum(0.5, 0.3), 0.8)
+        self.assertEqual(stats._accum('aa','bb'), 'bb')
+        self.assertEqual(stats._accum('1970-01-01T09:00:00Z','2012-08-09T09:33:31Z'),
+                         '2012-08-09T09:33:31Z')
+        self.assertEqual(stats._accum(
+                [1, 2, 3], [4, 5]), [5, 7, 3])
+        self.assertEqual(stats._accum(
+                [4, 5], [1, 2, 3]), [5, 7, 3])
+        self.assertEqual(stats._accum(
+                [1, 2, 3], [None, 5, 6]), [1, 7, 9])
+        self.assertEqual(stats._accum(
+                [None, 5, 6], [1, 2, 3]), [1, 7, 9])
+        self.assertEqual(stats._accum(
+                [1, 2, 3], [None, None, None, None]), [1,2,3,None])
+        self.assertEqual(stats._accum(
+                [[1,2],3],[[],5,6]), [[1,2],8,6])
+        self.assertEqual(stats._accum(
+                {'one': 1, 'two': 2, 'three': 3},
+                {'one': 4, 'two': 5}),
+                         {'one': 5, 'two': 7, 'three': 3})
+        self.assertEqual(stats._accum(
+                {'one': 1, 'two': 2, 'three': 3},
+                {'four': 4, 'five': 5}),
+                         {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
+        self.assertEqual(stats._accum(
+                {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
+                {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
+                         {'one':[3,2], 'two':[7,5,5], 'three':[None,3,None], 'four': 'FOUR'})
+        self.assertEqual(stats._accum(
+                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
+                [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
+                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 5, 'five': 7, 'six': 9} ])
+
+    def test_merge_oldnre(self):
+        self.assertEqual(stats.merge_oldnew(1, 2), 2)
+        self.assertEqual(stats.merge_oldnew(0.5, 0.3), 0.3)
+        self.assertEqual(stats.merge_oldnew('aa','bb'), 'bb')
+        self.assertEqual(stats.merge_oldnew(
+                [1, 2, 3], [4, 5]), [4, 5, 3])
+        self.assertEqual(stats.merge_oldnew(
+                [4, 5], [1, 2, 3]), [1, 2, 3])
+        self.assertEqual(stats.merge_oldnew(
+                [1, 2, 3], [None, 5, 6]), [None, 5, 6])
+        self.assertEqual(stats.merge_oldnew(
+                [None, 5, 6], [1, 2, 3]), [1, 2, 3])
+        self.assertEqual(stats.merge_oldnew(
+                [1, 2, 3], [None, None, None, None]), [None, None, None, None])
+        self.assertEqual(stats.merge_oldnew(
+                [[1,2],3],[[],5,6]), [[1,2],5,6])
+        self.assertEqual(stats.merge_oldnew(
+                {'one': 1, 'two': 2, 'three': 3},
+                {'one': 4, 'two': 5}),
+                         {'one': 4, 'two': 5, 'three': 3})
+        self.assertEqual(stats.merge_oldnew(
+                {'one': 1, 'two': 2, 'three': 3},
+                {'four': 4, 'five': 5}),
+                         {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5})
+        self.assertEqual(stats.merge_oldnew(
+                {'one': [1, 2], 'two': [3, None, 5], 'three': [None, 3, None]},
+                {'one': [2], 'two': [4, 5], 'three': [None, None, None], 'four': 'FOUR'}),
+                         {'one':[2,2], 'two':[4,5,5], 'three':[None,None,None], 'four': 'FOUR'})
+        self.assertEqual(stats.merge_oldnew(
+                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 4, 'five': 5, 'six': 6} ],
+                [ {}, {'four': 1, 'five': 2, 'six': 3} ]),
+                [ {'one': 1, 'two': 2, 'three': 3}, {'four': 1, 'five': 2, 'six': 3} ])
+
+class TestCallback(unittest.TestCase):
+    def setUp(self):
+        self.dummy_func = lambda *x, **y : (x, y)
+        self.dummy_args = (1,2,3)
+        self.dummy_kwargs = {'a':1,'b':2,'c':3}
+        self.cback1 = stats.Callback(
+            command=self.dummy_func,
+            args=self.dummy_args,
+            kwargs=self.dummy_kwargs
+            )
+        self.cback2 = stats.Callback(
+            args=self.dummy_args,
+            kwargs=self.dummy_kwargs
+            )
+        self.cback3 = stats.Callback(
+            command=self.dummy_func,
+            kwargs=self.dummy_kwargs
+            )
+        self.cback4 = stats.Callback(
+            command=self.dummy_func,
+            args=self.dummy_args
+            )
+
+    def test_init(self):
+        self.assertEqual((self.cback1.command, self.cback1.args, self.cback1.kwargs),
+                         (self.dummy_func, self.dummy_args, self.dummy_kwargs))
+        self.assertEqual((self.cback2.command, self.cback2.args, self.cback2.kwargs),
+                         (None, self.dummy_args, self.dummy_kwargs))
+        self.assertEqual((self.cback3.command, self.cback3.args, self.cback3.kwargs),
+                         (self.dummy_func, (), self.dummy_kwargs))
+        self.assertEqual((self.cback4.command, self.cback4.args, self.cback4.kwargs),
+                         (self.dummy_func, self.dummy_args, {}))
+
+    def test_call(self):
+        self.assertEqual(self.cback1(), (self.dummy_args, self.dummy_kwargs))
+        self.assertEqual(self.cback1(100, 200), ((100, 200), self.dummy_kwargs))
+        self.assertEqual(self.cback1(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
+        self.assertEqual(self.cback2(), None)
+        self.assertEqual(self.cback3(), ((), self.dummy_kwargs))
+        self.assertEqual(self.cback3(100, 200), ((100, 200), self.dummy_kwargs))
+        self.assertEqual(self.cback3(a=100, b=200), ((), {'a':100, 'b':200}))
+        self.assertEqual(self.cback4(), (self.dummy_args, {}))
+        self.assertEqual(self.cback4(100, 200), ((100, 200), {}))
+        self.assertEqual(self.cback4(a=100, b=200), (self.dummy_args, {'a':100, 'b':200}))
+
+class TestStats(unittest.TestCase):
+    def setUp(self):
+        # set the signal handler for deadlock
+        self.const_timestamp = 1308730448.965706
+        self.const_datetime = '2011-06-22T08:14:08Z'
+        self.const_default_datetime = '1970-01-01T00:00:00Z'
+        # Record original module-defined functions in case we replace them
+        self.__orig_timestamp = stats.get_timestamp
+        self.__orig_get_datetime = stats.get_datetime
+
+    def tearDown(self):
+        # restore the stored original function in case we replaced them
+        stats.get_timestamp = self.__orig_timestamp
+        stats.get_datetime = self.__orig_get_datetime
+
+    def test_init(self):
+        self.stats = MyStats()
+        self.assertEqual(self.stats.module_name, 'Stats')
+        self.assertFalse(self.stats.running)
+        self.assertTrue('command_show' in self.stats.callbacks)
+        self.assertTrue('command_status' in self.stats.callbacks)
+        self.assertTrue('command_shutdown' in self.stats.callbacks)
+        self.assertTrue('command_show' in self.stats.callbacks)
+        self.assertTrue('command_showschema' in self.stats.callbacks)
+        self.assertEqual(self.stats.config['poll-interval'], 60)
+
+    def test_init_undefcmd(self):
+        spec_str = """\
+{
+  "module_spec": {
+    "module_name": "Stats",
+    "module_description": "Stats daemon",
+    "config_data": [],
+    "commands": [
+      {
+        "command_name": "_undef_command_",
+        "command_description": "a undefined command in stats",
+        "command_args": []
+      }
+    ],
+    "statistics": []
+  }
+}
+"""
+        orig_spec_location = stats.SPECFILE_LOCATION
+        stats.SPECFILE_LOCATION = io.StringIO(spec_str)
+        self.assertRaises(stats.StatsError, MyStats)
+        stats.SPECFILE_LOCATION = orig_spec_location
+
+    def __send_command(self, stats, command_name, params=None):
+        '''Emulate a command arriving to stats by directly calling callback'''
+        return isc.config.ccsession.parse_answer(
+            stats.command_handler(command_name, params))
+
+    def test_start(self):
+        # Define a separate exception class so we can be sure that's actually
+        # the one raised in __check_start() below
+        class CheckException(Exception):
+            pass
+
+        def __check_start(tested_stats):
+            self.assertTrue(tested_stats.running)
+            raise CheckException # terminate the loop
+
+        # start without err
+        self.stats = MyStats()
+        self.assertFalse(self.stats.running)
+        self.stats._check_command = lambda: __check_start(self.stats)
+        # We are going to confirm start() will set running to True, avoiding
+        # to fall into a loop with the exception trick.
+        self.assertRaises(CheckException, self.stats.start)
+        self.assertEqual(self.__send_command(self.stats, "status"),
+                         (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
+
+    def test_shutdown(self):
+        def __check_shutdown(tested_stats):
+            self.assertTrue(tested_stats.running)
+            self.assertEqual(self.__send_command(tested_stats, "shutdown"),
+                             (0, None))
+            self.assertFalse(tested_stats.running)
+            # override get_interval() so it won't go poll statistics
+            tested_stats.get_interval = lambda : 0
+
+        self.stats = MyStats()
+        self.stats._check_command = lambda: __check_shutdown(self.stats)
+        self.stats.start()
+        self.assertTrue(self.stats.mccs.stopped)
+
+    def test_handlers(self):
+        """Test command_handler"""
+
+        __stats = MyStats()
+
+        # 'show' command.  We're going to check the expected methods are
+        # called in the expected order, and check the resulting response.
+        # Details of each method are tested separately.
+        call_log = []
+        def __steal_method(fn_name, *arg):
+            call_log.append((fn_name, arg))
+            if fn_name == 'update_stat':
+                return False        # "no error"
+            if fn_name == 'showschema':
+                return isc.config.create_answer(0, 'no error')
+
+        # Fake some methods and attributes for inspection
+        __stats.do_polling = lambda: __steal_method('polling')
+        __stats.update_statistics_data = \
+            lambda x, y, z: __steal_method('update_stat', x, y, z)
+        __stats.update_modules = lambda: __steal_method('update_module')
+        __stats.mccs.lname = 'test lname'
+        __stats.statistics_data = {'Init': {'boot_time': self.const_datetime}}
+
+        # skip initial polling
+        stats.get_timestamp = lambda: 0
+        __stats._lasttime_poll = 0
+
+        stats.get_datetime = lambda: 42 # make the result predictable
+
+        # now send the command
+        self.assertEqual(
+            self.__send_command(
+                __stats, 'show',
+                params={ 'owner' : 'Init', 'name'  : 'boot_time' }),
+            (0, {'Init': {'boot_time': self.const_datetime}}))
+        # Check if expected methods are called
+        self.assertEqual([('update_stat',
+                           ('Stats', 'test lname',
+                            {'timestamp': 0,
+                             'report_time': 42})),
+                          ('update_module', ())], call_log)
+
+        # Then update faked timestamp so the initial polling will happen, and
+        # confirm that.
+        call_log = []
+        stats.get_timestamp = lambda: 10
+        self.assertEqual(
+            self.__send_command(
+                __stats, 'show',
+                params={ 'owner' : 'Init', 'name'  : 'boot_time' }),
+            (0, {'Init': {'boot_time': self.const_datetime}}))
+        self.assertEqual([('polling', ()),
+                          ('update_stat',
+                           ('Stats', 'test lname',
+                            {'timestamp': 10,
+                             'report_time': 42})),
+                          ('update_module', ())], call_log)
+
+        # 'status' command.  We can confirm the behavior without any fake
+        self.assertEqual(
+            self.__send_command(__stats, 'status'),
+            (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
+
+        # 'showschema' command.  update_modules() will be called, which
+        # (implicitly) confirms the correct method is called; further details
+        # are tested separately.
+        call_log = []
+        (rcode, value) = self.__send_command(__stats, 'showschema')
+        self.assertEqual([('update_module', ())], call_log)
+
+        # Unknown command.  Error should be returned
+        self.assertEqual(
+            self.__send_command(__stats, '__UNKNOWN__'),
+            (1, "Unknown command: '__UNKNOWN__'"))
+
+    def test_update_modules(self):
+        """Confirm the behavior of Stats.update_modules().
+
+        It checks whether the expected command is sent to ConfigManager,
+        and whether the answer from ConfigManager is handled as expected.
+
+        """
+
+        def __check_rpc_call(command, group):
+            self.assertEqual('ConfigManager', group)
+            self.assertEqual(command,
+                             isc.config.ccsession.COMMAND_GET_STATISTICS_SPEC)
+            answer_value = {'Init': [{
+                        "item_name": "boot_time",
+                        "item_type": "string",
+                        "item_optional": False,
+                        # Use a different default so we can check it below
+                        "item_default": "2013-01-01T00:00:01Z",
+                        "item_title": "Boot time",
+                        "item_description": "dummy desc",
+                        "item_format": "date-time"
+                        }]}
+            return answer_value
+
+        self.stats = MyStats()
+        self.stats.cc_session.rpc_call = __check_rpc_call
+
+        self.stats.update_modules()
+
+        # Stats is always incorporated.  For others, only the ones returned
+        # by group_recvmsg() above is available.
+        self.assertTrue('Stats' in self.stats.modules)
+        self.assertTrue('Init' in self.stats.modules)
+        self.assertFalse('Dummy' in self.stats.modules)
+
+        my_statistics_data = stats.get_spec_defaults(
+            self.stats.modules['Stats'].get_statistics_spec())
+        self.assertTrue('report_time' in my_statistics_data)
+        self.assertTrue('boot_time' in my_statistics_data)
+        self.assertTrue('last_update_time' in my_statistics_data)
+        self.assertTrue('timestamp' in my_statistics_data)
+        self.assertTrue('lname' in my_statistics_data)
+        self.assertEqual(my_statistics_data['report_time'],
+                         self.const_default_datetime)
+        self.assertEqual(my_statistics_data['boot_time'],
+                         self.const_default_datetime)
+        self.assertEqual(my_statistics_data['last_update_time'],
+                         self.const_default_datetime)
+        self.assertEqual(my_statistics_data['timestamp'], 0.0)
+        self.assertEqual(my_statistics_data['lname'], "")
+        my_statistics_data = stats.get_spec_defaults(
+            self.stats.modules['Init'].get_statistics_spec())
+        self.assertTrue('boot_time' in my_statistics_data)
+        self.assertEqual(my_statistics_data['boot_time'],
+                         "2013-01-01T00:00:01Z")
+
+        # Error case
+        def __raise_on_rpc_call(x, y):
+            raise isc.config.RPCError(99, 'error')
+        orig_parse_answer = stats.isc.config.ccsession.parse_answer
+        self.stats.cc_session.rpc_call = __raise_on_rpc_call
+        self.assertRaises(stats.StatsError, self.stats.update_modules)
+
+    def test_get_statistics_data(self):
+        """Confirm the behavior of Stats.get_statistics_data().
+
+        It should first call update_modules(), and then retrieve the requested
+        data from statistics_data.  We confirm this by fake update_modules()
+        where we set the expected data in statistics_data.
+
+        """
+        self.stats = MyStats()
+        def __faked_update_modules():
+            self.stats.statistics_data = { \
+                'Stats': {
+                    'report_time': self.const_default_datetime,
+                    'boot_time': None,
+                    'last_update_time': None,
+                    'timestamp': 0.0,
+                    'lname': 'dummy name'
+                    },
+                'Init': { 'boot_time': None }
+                }
+
+        self.stats.update_modules = __faked_update_modules
+
+        my_statistics_data = self.stats.get_statistics_data()
+        self.assertTrue('Stats' in my_statistics_data)
+        self.assertTrue('Init' in my_statistics_data)
+        self.assertTrue('boot_time' in my_statistics_data['Init'])
+
+        my_statistics_data = self.stats.get_statistics_data(owner='Stats')
+        self.assertTrue('Stats' in my_statistics_data)
+        self.assertTrue('report_time' in my_statistics_data['Stats'])
+        self.assertTrue('boot_time' in my_statistics_data['Stats'])
+        self.assertTrue('last_update_time' in my_statistics_data['Stats'])
+        self.assertTrue('timestamp' in my_statistics_data['Stats'])
+        self.assertTrue('lname' in my_statistics_data['Stats'])
+        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+                          owner='Foo')
+
+        my_statistics_data = self.stats.get_statistics_data(
+            owner='Stats', name='report_time')
+        self.assertEqual(my_statistics_data['Stats']['report_time'],
+                         self.const_default_datetime)
+
+        my_statistics_data = self.stats.get_statistics_data(
+            owner='Stats', name='boot_time')
+        self.assertTrue('boot_time' in my_statistics_data['Stats'])
+
+        my_statistics_data = self.stats.get_statistics_data(
+            owner='Stats', name='last_update_time')
+        self.assertTrue('last_update_time' in my_statistics_data['Stats'])
+
+        my_statistics_data = self.stats.get_statistics_data(
+            owner='Stats', name='timestamp')
+        self.assertEqual(my_statistics_data['Stats']['timestamp'], 0.0)
+
+        my_statistics_data = self.stats.get_statistics_data(
+            owner='Stats', name='lname')
+        self.assertTrue(len(my_statistics_data['Stats']['lname']) >0)
+        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+                          owner='Stats', name='Bar')
+        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+                          owner='Foo', name='Bar')
+        self.assertRaises(stats.StatsError, self.stats.get_statistics_data,
+                          name='Bar')
+
+    def test_update_statistics_data(self):
+        """test for list-type statistics"""
+        self.stats = MyStats()
+        _test_exp1 = {
+              'zonename': 'test1.example',
+              'queries.tcp': 5,
+              'queries.udp': 4
+            }
+        _test_exp2 = {
+              'zonename': 'test2.example',
+              'queries.tcp': 3,
+              'queries.udp': 2
+            }
+        _test_exp3 = {}
+        _test_exp4 = {
+              'queries.udp': 4
+            }
+        _test_exp5_1 = {
+              'queries.perzone': [
+                { },
+                {
+                  'queries.udp': 9876
+                }
+              ]
+            }
+        _test_exp5_2 = {
+              'queries.perzone[1]/queries.udp':
+                  isc.cc.data.find(_test_exp5_1,
+                                   'queries.perzone[1]/queries.udp')
+            }
+        # Success cases
+        self.assertEqual(self.stats.statistics_data['Stats']['lname'],
+                         self.stats.cc_session.lname)
+        self.stats.update_statistics_data(
+            'Stats', self.stats.cc_session.lname,
+            {'lname': 'foo at bar'})
+        self.assertEqual(self.stats.statistics_data['Stats']['lname'],
+                         'foo at bar')
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'queries.perzone': [_test_exp1]}))
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['queries.perzone'],\
+                             [_test_exp1])
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'queries.perzone': [_test_exp2]}))
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['queries.perzone'],\
+                             [_test_exp2])
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'queries.perzone': [_test_exp1,_test_exp2]}))
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['queries.perzone'],
+                         [_test_exp1,_test_exp2])
+        # differential update
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'queries.perzone': [_test_exp3,_test_exp4]}))
+        _new_data = stats.merge_oldnew(_test_exp2,_test_exp4)
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['queries.perzone'], \
+                             [_test_exp1,_new_data])
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', _test_exp5_2))
+        _new_data = stats.merge_oldnew(_new_data,
+                                       _test_exp5_1['queries.perzone'][1])
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['queries.perzone'], \
+                             [_test_exp1,_new_data])
+        # Error cases
+        self.assertEqual(self.stats.update_statistics_data('Stats', None,
+                                                           {'lname': 0.0}),
+                         ['0.0 should be a string'])
+        self.assertEqual(self.stats.update_statistics_data('Dummy', None,
+                                                           {'foo': 'bar'}),
+                         ['unknown module name: Dummy'])
+        self.assertEqual(self.stats.update_statistics_data(
+                'Auth', 'foo1', {'queries.perzone': [None]}), ['None should be a map'])
+
+    def test_update_statistics_data_pt2(self):
+        """test for named_set-type statistics"""
+        self.stats = MyStats()
+        _test_exp1 = \
+            { 'test10.example': { 'queries.tcp': 5, 'queries.udp': 4 } }
+        _test_exp2 = \
+            { 'test20.example': { 'queries.tcp': 3, 'queries.udp': 2 } }
+        _test_exp3 = {}
+        _test_exp4 = { 'test20.example': { 'queries.udp': 4 } }
+        _test_exp5_1 = { 'test10.example': { 'queries.udp': 5432 } }
+        _test_exp5_2 ={
+              'nds_queries.perzone/test10.example/queries.udp':
+                  isc.cc.data.find(_test_exp5_1, 'test10.example/queries.udp')
+            }
+        _test_exp6 = { 'foo/bar':  'brabra' }
+        _test_exp7 = { 'foo[100]': 'bar' }
+        # Success cases
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'nds_queries.perzone': _test_exp1}))
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['nds_queries.perzone'],\
+                             _test_exp1)
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'nds_queries.perzone': _test_exp2}))
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['nds_queries.perzone'],\
+                         dict(_test_exp1,**_test_exp2))
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'nds_queries.perzone':
+                                 dict(_test_exp1, **_test_exp2)}))
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['nds_queries.perzone'],
+                         dict(_test_exp1, **_test_exp2))
+        # differential update
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', {'nds_queries.perzone':
+                                 dict(_test_exp3, **_test_exp4)}))
+        _new_val = dict(_test_exp1,
+                        **stats.merge_oldnew(_test_exp2,_test_exp4))
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['nds_queries.perzone'],\
+                             _new_val)
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo1', _test_exp5_2))
+        _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['nds_queries.perzone'],\
+                             _new_val)
+        self.assertIsNone(self.stats.update_statistics_data(
+            'Auth', 'foo2', _test_exp5_2))
+        _new_val = stats.merge_oldnew(_new_val, _test_exp5_1)
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo2']['nds_queries.perzone'],\
+                             _test_exp5_1)
+        # Error cases
+        self.assertEqual(self.stats.update_statistics_data(
+                'Auth', 'foo1', {'nds_queries.perzone': None}),
+                         ['None should be a map'])
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['nds_queries.perzone'],\
+                             _new_val)
+        self.assertEqual(self.stats.update_statistics_data(
+                'Auth', 'foo1', _test_exp6), ['unknown item foo'])
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']\
+                             ['foo1']['nds_queries.perzone'],\
+                             _new_val)
+        self.assertEqual(self.stats.update_statistics_data(
+                'Init', 'bar1', _test_exp7), ["KeyError: 'foo'"])
+        self.assertEqual(self.stats.update_statistics_data(
+                'Foo', 'foo1', _test_exp6), ['unknown module name: Foo'])
+
+    def test_update_statistics_data_withmid(self):
+        self.stats = MyStats()
+
+        # This test relies on existing statistics data at the Stats object.
+        # This version of test prepares the data using the do_polling() method;
+        # that's a bad practice because a unittest for a method
+        # (update_statistics_data) would heavily depend on details of another
+        # method (do_polling).  However, there's currently no direct test
+        # for do_polling (which is also bad), so we still keep that approach,
+        # partly for testing do_polling indirectly.  #2781 should provide
+        # direct test for do_polling, with which this test scenario should
+        # also be changed to be more stand-alone.
+
+        # We use the knowledge of what kind of messages are sent via
+        # do_polling, and return the following faked answer directly.
+        create_answer = isc.config.ccsession.create_answer # shortcut
+        self.stats._answers = [
+            # Answer for "show_processes"
+            (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
+                               [1035, 'b10-auth-2', 'Auth']]),  None),
+            # Answers for "getstats".  2 for Auth instances and 1 for Init.
+            # we return some bogus values for Init, but the rest of the test
+            # doesn't need it, so it's okay.
+            (create_answer(0, self.stats._auth_sdata), {'from': 'auth1'}),
+            (create_answer(0, self.stats._auth_sdata), {'from': 'auth2'}),
+            (create_answer(0, self.stats._auth_sdata), {'from': 'auth3'})
+            ]
+        # do_polling calls update_modules internally; in our scenario there's
+        # no change in modules, so we make it no-op.
+        self.stats.update_modules = lambda: None
+        # Now call do_polling.
+        self.stats.do_polling()
+
+        # samples of query number
+        bar1_tcp = 1001
+        bar2_tcp = 2001
+        bar3_tcp = 1002
+        bar3_udp = 1003
+        # two auth instances invoked, so we double the pre-set stat values
+        sum_qtcp = self.stats._queries_tcp * 2
+        sum_qudp = self.stats._queries_udp * 2
+        self.stats.update_statistics_data('Auth', "bar1 at foo",
+                                          {'queries.tcp': bar1_tcp})
+        self.assertTrue('Auth' in self.stats.statistics_data)
+        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+                         bar1_tcp + sum_qtcp)
+        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+        self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
+        self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid
+                        ['Auth']['bar1 at foo'])
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
+                         {'queries.tcp': bar1_tcp})
+        # check consolidation of statistics data even if there is
+        # non-existent mid of Auth
+        self.stats.update_statistics_data('Auth', "bar2 at foo",
+                                          {'queries.tcp': bar2_tcp})
+        self.assertTrue('Auth' in self.stats.statistics_data)
+        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+                         bar1_tcp + bar2_tcp + sum_qtcp)
+        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+        self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
+        self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo'],
+                         {'queries.tcp': bar1_tcp})
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar2 at foo'],
+                         {'queries.tcp': bar2_tcp})
+        # kill running Auth but the statistics data doesn't change
+        self.stats.update_statistics_data()
+        self.assertTrue('Auth' in self.stats.statistics_data)
+        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+        self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+                         bar1_tcp + bar2_tcp + sum_qtcp)
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
+                         sum_qudp)
+        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+        # restore statistics data of killed auth
+        self.stats.update_statistics_data('Auth',
+                                          "bar1 at foo",
+                                          {'queries.tcp': bar1_tcp})
+        # set another mid of Auth
+        self.stats.update_statistics_data('Auth',
+                                          "bar3 at foo",
+                                          {'queries.tcp':bar3_tcp,
+                                           'queries.udp':bar3_udp})
+        self.assertTrue('Auth' in self.stats.statistics_data)
+        self.assertTrue('queries.tcp' in self.stats.statistics_data['Auth'])
+        self.assertTrue('queries.udp' in self.stats.statistics_data['Auth'])
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.tcp'],
+                         bar1_tcp + bar2_tcp + bar3_tcp + sum_qtcp)
+        self.assertEqual(self.stats.statistics_data['Auth']['queries.udp'],
+                         bar3_udp + sum_qudp)
+        self.assertTrue('Auth' in self.stats.statistics_data_bymid)
+        self.assertTrue('bar1 at foo' in self.stats.statistics_data_bymid['Auth'])
+        self.assertTrue('bar3 at foo' in self.stats.statistics_data_bymid['Auth'])
+        self.assertTrue('queries.tcp' in self.stats.statistics_data_bymid['Auth']['bar1 at foo'])
+        self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
+        self.assertTrue('queries.udp' in self.stats.statistics_data_bymid['Auth']['bar3 at foo'])
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar1 at foo']['queries.tcp'], bar1_tcp)
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.tcp'], bar3_tcp)
+        self.assertEqual(self.stats.statistics_data_bymid['Auth']['bar3 at foo']['queries.udp'], bar3_udp)
+
+    def test_config(self):
+        orig_get_timestamp = stats.get_timestamp
+        stats.get_timestamp = lambda : self.const_timestamp
+        stat = MyStats()
+
+        # test updating poll-interval
+        self.assertEqual(stat.config['poll-interval'], 60)
+        self.assertEqual(stat.get_interval(), 60)
+        self.assertEqual(stat.next_polltime, self.const_timestamp + 60)
+        self.assertEqual(stat.config_handler({'poll-interval': 120}),
+                         isc.config.create_answer(0))
+        self.assertEqual(stat.config['poll-interval'], 120)
+        self.assertEqual(stat.get_interval(), 120)
+        self.assertEqual(stat.next_polltime, self.const_timestamp + 120)
+        stats.get_timestamp = orig_get_timestamp
+        self.assertEqual(stat.config_handler({'poll-interval': "foo"}),
+                         isc.config.create_answer(1, 'foo should be an integer'))
+        self.assertEqual(stat.config_handler({'poll-interval': -1}),
+                         isc.config.create_answer(1, 'Negative integer ignored'))
+        # unknown item
+        self.assertEqual(
+            stat.config_handler({'_UNKNOWN_KEY_': None}),
+            isc.config.ccsession.create_answer(
+                1, "unknown item _UNKNOWN_KEY_"))
+        # test no change if zero interval time
+        self.assertEqual(stat.config_handler({'poll-interval': 0}),
+                         isc.config.create_answer(0))
+        self.assertEqual(stat.config['poll-interval'], 0)
+
+        # see the comment for test_update_statistics_data_withmid.  We abuse
+        # do_polling here, too.  With #2781 we should make it more direct.
+        create_answer = isc.config.ccsession.create_answer # shortcut
+        stat._answers = [\
+            # Answer for "show_processes"
+            (create_answer(0, []),  None),
+            # Answers for "getstats" for Init (the other one for Auth, but
+            # that doesn't matter for this test)
+            (create_answer(0, stat._init_sdata), {'from': 'init'}),
+            (create_answer(0, stat._init_sdata), {'from': 'init'})
+            ]
+        stat.update_modules = lambda: None
+
+        self.assertEqual(
+            self.__send_command(
+                stat, 'show',
+                params={ 'owner' : 'Init', 'name'  : 'boot_time' }),
+            (0, {'Init': {'boot_time': self.const_datetime}}))
+
+    def test_commands(self):
+        self.stats = MyStats()
+
+        # status
+        self.assertEqual(self.stats.command_status(),
+                isc.config.create_answer(
+                0, "Stats is up. (PID " + str(os.getpid()) + ")"))
+
+        # shutdown
+        self.stats.running = True
+        self.assertEqual(self.stats.command_shutdown(),
+                         isc.config.create_answer(0))
+        self.assertFalse(self.stats.running)
+
+    def test_command_show_error(self):
+        self.stats = MyStats()
+        self.assertEqual(self.stats.command_show(owner='Foo', name=None),
+                         isc.config.create_answer(
+                1,
+                "specified arguments are incorrect: owner: Foo, name: None"))
+        self.assertEqual(self.stats.command_show(owner='Foo', name='_bar_'),
+                         isc.config.create_answer(
+                1,
+                "specified arguments are incorrect: owner: Foo, name: _bar_"))
+        self.assertEqual(self.stats.command_show(owner='Foo', name='bar'),
+                         isc.config.create_answer(
+                1,
+                "specified arguments are incorrect: owner: Foo, name: bar"))
+
+    def test_command_show_auth(self):
+        self.stats = MyStats()
+        self.stats.update_modules = lambda: None
+
+        # Test data borrowed from test_update_statistics_data_withmid
+        create_answer = isc.config.ccsession.create_answer # shortcut
+        self.stats._answers = [
+            (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
+                               [1035, 'b10-auth-2', 'Auth']]),  None),
+            (create_answer(0, self.stats._auth_sdata), {'from': 'auth1'}),
+            (create_answer(0, self.stats._auth_sdata), {'from': 'auth2'}),
+            (create_answer(0, self.stats._auth_sdata), {'from': 'auth3'})
+            ]
+
+        num_instances = 2
+        sum_qtcp = 0
+        sum_qudp = 0
+        sum_qtcp_perzone1 = 0
+        sum_qudp_perzone1 = 0
+        sum_qtcp_perzone2 = 4 * num_instances
+        sum_qudp_perzone2 = 3 * num_instances
+        sum_qtcp_nds_perzone10 = 0
+        sum_qudp_nds_perzone10 = 0
+        sum_qtcp_nds_perzone20 = 4 * num_instances
+        sum_qudp_nds_perzone20 = 3 * num_instances
+
+        self.maxDiff = None
+        for a in (0, num_instances):
+            sum_qtcp += self.stats._queries_tcp
+            sum_qudp += self.stats._queries_udp
+            sum_qtcp_perzone1 += self.stats._queries_per_zone[0]['queries.tcp']
+            sum_qudp_perzone1 += self.stats._queries_per_zone[0]['queries.udp']
+            sum_qtcp_nds_perzone10 += \
+                self.stats._nds_queries_per_zone['test10.example']['queries.tcp']
+            sum_qudp_nds_perzone10 += \
+                self.stats._nds_queries_per_zone['test10.example']['queries.udp']
+
+        self.assertEqual(self.stats.command_show(owner='Auth'),
+                         isc.config.create_answer(
+                0, {'Auth':{ 'queries.udp': sum_qudp,
+                     'queries.tcp': sum_qtcp,
+                     'queries.perzone': [{ 'zonename': 'test1.example',
+                                           'queries.udp': sum_qudp_perzone1,
+                                           'queries.tcp': sum_qtcp_perzone1 },
+                                         { 'zonename': 'test2.example',
+                                           'queries.udp': sum_qudp_perzone2,
+                                           'queries.tcp': sum_qtcp_perzone2 }
+                                         ],
+                     'nds_queries.perzone': { 'test10.example' : {
+                                              'queries.udp': sum_qudp_nds_perzone10,
+                                              'queries.tcp': sum_qtcp_nds_perzone10 },
+                                              'test20.example' : {
+                                              'queries.udp': sum_qudp_nds_perzone20,
+                                              'queries.tcp': sum_qtcp_nds_perzone20 }
+                             }}}))
+        self.assertEqual(self.stats.command_show(owner='Auth', name='queries.udp'),
+                         isc.config.create_answer(
+                0, {'Auth': {'queries.udp': sum_qudp}}))
+        self.assertEqual(self.stats.command_show(owner='Auth', name='queries.perzone'),
+                         isc.config.create_answer(
+                0, {'Auth': {'queries.perzone': [
+                            { 'zonename': 'test1.example',
+                              'queries.udp': sum_qudp_perzone1,
+                              'queries.tcp': sum_qtcp_perzone1 },
+                            { 'zonename': 'test2.example',
+                              'queries.udp': sum_qudp_perzone2,
+                              'queries.tcp': sum_qtcp_perzone2 }]}}))
+        self.assertEqual(self.stats.command_show(owner='Auth', name='nds_queries.perzone'),
+                         isc.config.create_answer(
+                0, {'Auth': {'nds_queries.perzone': {
+                            'test10.example': {
+                                'queries.udp': sum_qudp_nds_perzone10,
+                                'queries.tcp': sum_qtcp_nds_perzone10 },
+                            'test20.example': {
+                                'queries.udp': sum_qudp_nds_perzone20,
+                                'queries.tcp': sum_qtcp_nds_perzone20 }}}}))
+
+    def test_command_show_stats(self):
+        self.stats = MyStats()
+        orig_get_datetime = stats.get_datetime
+        orig_get_timestamp = stats.get_timestamp
+        stats.get_datetime = lambda x=None: self.const_datetime
+        stats.get_timestamp = lambda : self.const_timestamp
+        self.assertEqual(self.stats.command_show(owner='Stats',
+                                                 name='report_time'),
+                         isc.config.create_answer(
+                0, {'Stats': {'report_time':self.const_datetime}}))
+        self.assertEqual(self.stats.command_show(owner='Stats',
+                                                 name='timestamp'),
+                         isc.config.create_answer(
+                0, {'Stats': {'timestamp':self.const_timestamp}}))
+        stats.get_datetime = orig_get_datetime
+        stats.get_timestamp = orig_get_timestamp
+        self.stats.do_polling = lambda : None
+        self.stats.modules[self.stats.module_name] = \
+            isc.config.module_spec.ModuleSpec(
+            { "module_name": self.stats.module_name, "statistics": [] } )
+        self.assertRaises(
+            stats.StatsError, self.stats.command_show,
+            owner=self.stats.module_name, name='bar')
+
+    def test_command_showchema(self):
+        self.stats = MyStats()
+        (rcode, value) = isc.config.ccsession.parse_answer(
+            self.stats.command_showschema())
+        self.assertEqual(rcode, 0)
+        self.assertEqual(len(value), 3)
+        self.assertTrue('Stats' in value)
+        self.assertTrue('Init' in value)
+        self.assertTrue('Auth' in value)
+        self.assertFalse('__Dummy__' in value)
+        schema = value['Stats']
+        self.assertEqual(len(schema), 5)
+        for item in schema:
+            self.assertTrue(len(item) == 6 or len(item) == 7)
+            self.assertTrue('item_name' in item)
+            self.assertTrue('item_type' in item)
+            self.assertTrue('item_optional' in item)
+            self.assertTrue('item_default' in item)
+            self.assertTrue('item_title' in item)
+            self.assertTrue('item_description' in item)
+            if len(item) == 7:
+                self.assertTrue('item_format' in item)
+
+        schema = value['Init']
+        self.assertEqual(len(schema), 1)
+        for item in schema:
+            self.assertTrue(len(item) == 7)
+            self.assertTrue('item_name' in item)
+            self.assertTrue('item_type' in item)
+            self.assertTrue('item_optional' in item)
+            self.assertTrue('item_default' in item)
+            self.assertTrue('item_title' in item)
+            self.assertTrue('item_description' in item)
+            self.assertTrue('item_format' in item)
+
+        schema = value['Auth']
+        self.assertEqual(len(schema), 4)
+        for item in schema:
+            if item['item_type'] == 'list' or item['item_type'] == 'named_set':
+                self.assertEqual(len(item), 7)
+            else:
+                self.assertEqual(len(item), 6)
+            self.assertTrue('item_name' in item)
+            self.assertTrue('item_type' in item)
+            self.assertTrue('item_optional' in item)
+            self.assertTrue('item_default' in item)
+            self.assertTrue('item_title' in item)
+            self.assertTrue('item_description' in item)
+
+        (rcode, value) = isc.config.ccsession.parse_answer(
+            self.stats.command_showschema(owner='Stats'))
+        self.assertEqual(rcode, 0)
+        self.assertTrue('Stats' in value)
+        self.assertFalse('Init' in value)
+        self.assertFalse('Auth' in value)
+        for item in value['Stats']:
+            self.assertTrue(len(item) == 6 or len(item) == 7)
+            self.assertTrue('item_name' in item)
+            self.assertTrue('item_type' in item)
+            self.assertTrue('item_optional' in item)
+            self.assertTrue('item_default' in item)
+            self.assertTrue('item_title' in item)
+            self.assertTrue('item_description' in item)
+            if len(item) == 7:
+                self.assertTrue('item_format' in item)
+
+        (rcode, value) = isc.config.ccsession.parse_answer(
+            self.stats.command_showschema(owner='Stats', name='report_time'))
+        self.assertEqual(rcode, 0)
+        self.assertTrue('Stats' in value)
+        self.assertFalse('Init' in value)
+        self.assertFalse('Auth' in value)
+        self.assertEqual(len(value['Stats'][0]), 7)
+        self.assertTrue('item_name' in value['Stats'][0])
+        self.assertTrue('item_type' in value['Stats'][0])
+        self.assertTrue('item_optional' in value['Stats'][0])
+        self.assertTrue('item_default' in value['Stats'][0])
+        self.assertTrue('item_title' in value['Stats'][0])
+        self.assertTrue('item_description' in value['Stats'][0])
+        self.assertTrue('item_format' in value['Stats'][0])
+        self.assertEqual(value['Stats'][0]['item_name'], 'report_time')
+        self.assertEqual(value['Stats'][0]['item_format'], 'date-time')
+
+        self.assertEqual(self.stats.command_showschema(owner='Foo'),
+                         isc.config.create_answer(
+                1, "specified arguments are incorrect: owner: Foo, name: None"))
+        self.assertEqual(self.stats.command_showschema(owner='Foo', name='bar'),
+                         isc.config.create_answer(
+                1, "specified arguments are incorrect: owner: Foo, name: bar"))
+        self.assertEqual(self.stats.command_showschema(owner='Auth'),
+                         isc.config.create_answer(
+                0, {'Auth': [{
+                        "item_default": 0,
+                        "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
+                        "item_name": "queries.tcp",
+                        "item_optional": False,
+                        "item_title": "Queries TCP",
+                        "item_type": "integer"
+                        },
+                    {
+                        "item_default": 0,
+                        "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially",
+                        "item_name": "queries.udp",
+                        "item_optional": False,
+                        "item_title": "Queries UDP",
+                        "item_type": "integer"
+                        },
+                    {
+                        "item_name": "queries.perzone",
+                        "item_type": "list",
+                        "item_optional": False,
+                        "item_default": [
+                            {
+                                "zonename" : "test1.example",
+                                "queries.udp" : 1,
+                                "queries.tcp" : 2
+                                },
+                            {
+                                "zonename" : "test2.example",
+                                "queries.udp" : 3,
+                                "queries.tcp" : 4
+                                }
+                        ],
+                        "item_title": "Queries per zone",
+                        "item_description": "Queries per zone",
+                        "list_item_spec": {
+                            "item_name": "zones",
+                            "item_type": "map",
+                            "item_optional": False,
+                            "item_default": {},
+                            "map_item_spec": [
+                                {
+                                    "item_name": "zonename",
+                                    "item_type": "string",
+                                    "item_optional": False,
+                                    "item_default": "",
+                                    "item_title": "Zonename",
+                                    "item_description": "Zonename"
+                                    },
+                                {
+                                    "item_name": "queries.udp",
+                                    "item_type": "integer",
+                                    "item_optional": False,
+                                    "item_default": 0,
+                                    "item_title": "Queries UDP per zone",
+                                    "item_description": "A number of UDP query counts per zone"
+                                    },
+                                {
+                                    "item_name": "queries.tcp",
+                                    "item_type": "integer",
+                                    "item_optional": False,
+                                    "item_default": 0,
+                                    "item_title": "Queries TCP per zone",
+                                    "item_description": "A number of TCP query counts per zone"
+                                    }
+                                ]
+                            }
+                        },
+                    {
+                        "item_name": "nds_queries.perzone",
+                        "item_type": "named_set",
+                        "item_optional": False,
+                        "item_default": {
+                            "test10.example" : {
+                                "queries.udp" : 1,
+                                "queries.tcp" : 2
+                            },
+                            "test20.example" : {
+                                "queries.udp" : 3,
+                                "queries.tcp" : 4
+                            }
+                        },
+                        "item_title": "Queries per zone",
+                        "item_description": "Queries per zone",
+                        "named_set_item_spec": {
+                            "item_name": "zonename",
+                            "item_type": "map",
+                            "item_optional": False,
+                            "item_default": {},
+                            "item_title": "Zonename",
+                            "item_description": "Zonename",
+                            "map_item_spec": [
+                                {
+                                    "item_name": "queries.udp",
+                                    "item_type": "integer",
+                                    "item_optional": False,
+                                    "item_default": 0,
+                                    "item_title": "Queries UDP per zone",
+                                    "item_description": "A number of UDP query counts per zone"
+                                    },
+                                {
+                                    "item_name": "queries.tcp",
+                                    "item_type": "integer",
+                                    "item_optional": False,
+                                    "item_default": 0,
+                                    "item_title": "Queries TCP per zone",
+                                    "item_description": "A number of TCP query counts per zone"
+                                    }
+                                ]
+                            }
+                        }]}))
+        self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.tcp'),
+                         isc.config.create_answer(
+                0, {'Auth': [{
+                    "item_default": 0,
+                    "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially",
+                    "item_name": "queries.tcp",
+                    "item_optional": False,
+                    "item_title": "Queries TCP",
+                    "item_type": "integer"
+                    }]}))
+        self.assertEqual(self.stats.command_showschema(owner='Auth', name='queries.perzone'),
+                         isc.config.create_answer(
+                0, {'Auth':[{
+                    "item_name": "queries.perzone",
+                    "item_type": "list",
+                    "item_optional": False,
+                    "item_default": [
+                        {
+                            "zonename" : "test1.example",
+                            "queries.udp" : 1,
+                            "queries.tcp" : 2
+                            },
+                        {
+                            "zonename" : "test2.example",
+                            "queries.udp" : 3,
+                            "queries.tcp" : 4
+                            }
+                    ],
+                    "item_title": "Queries per zone",
+                    "item_description": "Queries per zone",
+                    "list_item_spec": {
+                        "item_name": "zones",
+                        "item_type": "map",
+                        "item_optional": False,
+                        "item_default": {},
+                        "map_item_spec": [
+                            {
+                                "item_name": "zonename",
+                                "item_type": "string",
+                                "item_optional": False,
+                                "item_default": "",
+                                "item_title": "Zonename",
+                                "item_description": "Zonename"
+                                },
+                            {
+                                "item_name": "queries.udp",
+                                "item_type": "integer",
+                                "item_optional": False,
+                                "item_default": 0,
+                                "item_title": "Queries UDP per zone",
+                                "item_description": "A number of UDP query counts per zone"
+                                },
+                            {
+                                "item_name": "queries.tcp",
+                                "item_type": "integer",
+                                "item_optional": False,
+                                "item_default": 0,
+                                "item_title": "Queries TCP per zone",
+                                "item_description": "A number of TCP query counts per zone"
+                                }
+                            ]
+                         }
+                     }]}))
+        self.assertEqual(self.stats.command_showschema(owner='Auth', name='nds_queries.perzone'),
+                         isc.config.create_answer(
+                0, {'Auth':[{
+                    "item_name": "nds_queries.perzone",
+                    "item_type": "named_set",
+                    "item_optional": False,
+                    "item_default": {
+                        "test10.example" : {
+                            "queries.udp" : 1,
+                            "queries.tcp" : 2
+                        },
+                        "test20.example" : {
+                            "queries.udp" : 3,
+                            "queries.tcp" : 4
+                        }
+                    },
+                    "item_title": "Queries per zone",
+                    "item_description": "Queries per zone",
+                    "named_set_item_spec": {
+                        "item_name": "zonename",
+                        "item_type": "map",
+                        "item_optional": False,
+                        "item_default": {},
+                        "item_title": "Zonename",
+                        "item_description": "Zonename",
+                        "map_item_spec": [
+                            {
+                                "item_name": "queries.udp",
+                                "item_type": "integer",
+                                "item_optional": False,
+                                "item_default": 0,
+                                "item_title": "Queries UDP per zone",
+                                "item_description": "A number of UDP query counts per zone"
+                                },
+                            {
+                                "item_name": "queries.tcp",
+                                "item_type": "integer",
+                                "item_optional": False,
+                                "item_default": 0,
+                                "item_title": "Queries TCP per zone",
+                                "item_description": "A number of TCP query counts per zone"
+                                }
+                            ]
+                        }
+                    }]}))
+
+        self.assertEqual(self.stats.command_showschema(owner='Stats', name='bar'),
+                         isc.config.create_answer(
+                1, "specified arguments are incorrect: owner: Stats, name: bar"))
+        self.assertEqual(self.stats.command_showschema(name='bar'),
+                         isc.config.create_answer(
+                1, "module name is not specified"))
+
+    def test_polling_init(self):
+        """check statistics data of 'Init'."""
+
+        stat = MyStats()
+        stat.update_modules = lambda: None
+        create_answer = isc.config.ccsession.create_answer # shortcut
+
+        stat._answers = [
+            # Answer for "show_processes"
+            (create_answer(0, []),  None),
+            # Answers for "getstats" for Init (type of boot_time is invalid)
+            (create_answer(0, {'boot_time': self.const_datetime}),
+             {'from': 'init'}),
+            ]
+
+        stat.do_polling()
+        self.assertEqual(
+            stat.statistics_data_bymid['Init']['init'],
+            {'boot_time': self.const_datetime})
+
+    def test_polling_consolidate(self):
+        """check statistics data of multiple instances of same module."""
+        stat = MyStats()
+        stat.update_modules = lambda: None
+        create_answer = isc.config.ccsession.create_answer # shortcut
+
+        # Test data borrowed from test_update_statistics_data_withmid
+        stat._answers = [
+            (create_answer(0, [[1034, 'b10-auth-1', 'Auth'],
+                               [1035, 'b10-auth-2', 'Auth']]),  None),
+            (create_answer(0, stat._auth_sdata), {'from': 'auth1'}),
+            (create_answer(0, stat._auth_sdata), {'from': 'auth2'}),
+            (create_answer(0, stat._auth_sdata), {'from': 'auth3'})
+            ]
+
+        stat.do_polling()
+
+        # check statistics data of each 'Auth' instances.  expected data
+        # for 'nds_queries.perzone' is special as it needs data merge.
+        self.assertEqual(2, len(stat.statistics_data_bymid['Auth'].values()))
+        for s in stat.statistics_data_bymid['Auth'].values():
+            self.assertEqual(
+                s, {'queries.perzone': stat._auth_sdata['queries.perzone'],
+                    'nds_queries.perzone': stat._nds_queries_per_zone,
+                    'queries.tcp': stat._auth_sdata['queries.tcp'],
+                    'queries.udp': stat._auth_sdata['queries.udp']})
+
+        # check consolidation of statistics data of the auth instances.
+        # it's union of the reported data and the spec default.
+        n = len(stat.statistics_data_bymid['Auth'].values())
+        self.maxDiff = None
+        self.assertEqual(
+            stat.statistics_data['Auth'],
+            {'queries.perzone': [
+                    {'zonename': 'test1.example',
+                     'queries.tcp': 5 * n,
+                     'queries.udp': 4 * n},
+                    {'zonename': 'test2.example',
+                     'queries.tcp': 4 * n,
+                     'queries.udp': 3 * n},
+                    ],
+             'nds_queries.perzone': {
+                    'test10.example': {
+                        'queries.tcp': 5 * n,
+                        'queries.udp': 4 * n
+                        },
+                    'test20.example': {
+                        'queries.tcp': 4 * n,
+                        'queries.udp': 3 * n
+                        },
+                    },
+             'queries.tcp': 3 * n,
+             'queries.udp': 2 * n})
+
+    def test_polling_stats(self):
+        """Check statistics data of 'Stats'
+
+        This is actually irrelevant to do_polling(), but provided to
+        compatibility of older tests.
+
+        """
+        stat = MyStats()
+        self.assertEqual(len(stat.statistics_data['Stats']), 5)
+        self.assertTrue('boot_time' in stat.statistics_data['Stats'])
+        self.assertTrue('last_update_time' in stat.statistics_data['Stats'])
+        self.assertTrue('report_time' in stat.statistics_data['Stats'])
+        self.assertTrue('timestamp' in stat.statistics_data['Stats'])
+        self.assertEqual(stat.statistics_data['Stats']['lname'],
+                         stat.mccs._session.lname)
+
+    def test_polling2(self):
+        """Test do_polling() doesn't incorporate broken statistics data.
+
+        Actually, this is not a test for do_polling() itself.  It's bad, but
+        fixing that is a subject of different ticket.
+
+        """
+        stat = MyStats()
+        # check default statistics data of 'Init'
+        self.assertEqual(
+             stat.statistics_data['Init'],
+             {'boot_time': self.const_default_datetime})
+
+        # set invalid statistics
+        create_answer = isc.config.ccsession.create_answer # shortcut
+        stat._answers = [
+            # Answer for "show_processes"
+            (create_answer(0, []),  None),
+            # Answers for "getstats" for Init (type of boot_time is invalid)
+            (create_answer(0, {'boot_time': 1}), {'from': 'init'}),
+            ]
+        stat.update_modules = lambda: None
+
+        # do_polling() should ignore the invalid answer;
+        # default data shouldn't be replaced.
+        stat.do_polling()
+        self.assertEqual(
+             stat.statistics_data['Init'],
+             {'boot_time': self.const_default_datetime})
+
+class Z_TestOSEnv(unittest.TestCase):
+    # Running this test would break logging setting.  To prevent it from
+    # affecting other tests we use the same workaround as
+    # Z_TestStatsHttpdError.
+    def test_osenv(self):
+        """
+        test for the environ variable "B10_FROM_SOURCE"
+        "B10_FROM_SOURCE" is set in Makefile
+        """
+        # test case having B10_FROM_SOURCE
+        self.assertTrue("B10_FROM_SOURCE" in os.environ)
+        self.assertEqual(stats.SPECFILE_LOCATION, \
+                             os.environ["B10_FROM_SOURCE"] + os.sep + \
+                             "src" + os.sep + "bin" + os.sep + "stats" + \
+                             os.sep + "stats.spec")
+        # test case not having B10_FROM_SOURCE
+        path = os.environ["B10_FROM_SOURCE"]
+        os.environ.pop("B10_FROM_SOURCE")
+        self.assertFalse("B10_FROM_SOURCE" in os.environ)
+        # import stats again
+        imp.reload(stats)
+        # revert the changes
+        os.environ["B10_FROM_SOURCE"] = path
+        imp.reload(stats)
+
+if __name__ == "__main__":
+    isc.log.resetUnitTestRootLogger()
+    unittest.main()
diff --git a/src/bin/stats/tests/test_utils.py b/src/bin/stats/tests/test_utils.py
index 45e9a17..8886ad2 100644
--- a/src/bin/stats/tests/test_utils.py
+++ b/src/bin/stats/tests/test_utils.py
@@ -20,13 +20,11 @@ Utilities and mock modules for unittests of statistics modules
 import os
 import io
 import time
-import sys
 import threading
-import tempfile
 import json
 import signal
+import socket
 
-import msgq
 import isc.config.cfgmgr
 import stats
 import stats_httpd
@@ -51,19 +49,6 @@ class SignalHandler():
         """invokes unittest.TestCase.fail as a signal handler"""
         self.fail_handler("A deadlock might be detected")
 
-def send_command(command_name, module_name, params=None):
-    cc_session = isc.cc.Session()
-    command = isc.config.ccsession.create_command(command_name, params)
-    seq = cc_session.group_sendmsg(command, module_name)
-    try:
-        (answer, env) = cc_session.group_recvmsg(False, seq)
-        if answer:
-            return isc.config.ccsession.parse_answer(answer)
-    except isc.cc.SessionTimeout:
-        pass
-    finally:
-        cc_session.close()
-
 class ThreadingServerManager:
     def __init__(self, server, *args, **kwargs):
         self.server = server(*args, **kwargs)
@@ -91,45 +76,7 @@ class ThreadingServerManager:
         else:
             self.server._thread.join(0) # timeout is 0
 
-class MockMsgq:
-    def __init__(self):
-        self._started = threading.Event()
-        self.msgq = msgq.MsgQ(verbose=False)
-        result = self.msgq.setup()
-        if result:
-            sys.exit("Error on Msgq startup: %s" % result)
-
-    def run(self):
-        self._started.set()
-        try:
-            self.msgq.run()
-        finally:
-            # Make sure all the sockets, etc, are removed once it stops.
-            self.msgq.shutdown()
-
-    def shutdown(self):
-        # Ask it to terminate nicely
-        self.msgq.stop()
-
-class MockCfgmgr:
-    def __init__(self):
-        self._started = threading.Event()
-        self.cfgmgr = isc.config.cfgmgr.ConfigManager(
-            os.environ['CONFIG_TESTDATA_PATH'], "b10-config.db")
-        self.cfgmgr.read_config()
-
-    def run(self):
-        self._started.set()
-        try:
-            self.cfgmgr.run()
-        except Exception:
-            pass
-
-    def shutdown(self):
-        self.cfgmgr.running = False
-
-class MockInit:
-    spec_str = """\
+INIT_SPEC_STR = """\
 {
   "module_spec": {
     "module_name": "Init",
@@ -221,56 +168,12 @@ class MockInit:
   }
 }
 """
-    _BASETIME = CONST_BASETIME
 
-    def __init__(self):
-        self._started = threading.Event()
-        self.running = False
-        self.spec_file = io.StringIO(self.spec_str)
-        # create ModuleCCSession object
-        self.mccs = isc.config.ModuleCCSession(
-            self.spec_file,
-            self.config_handler,
-            self.command_handler)
-        self.spec_file.close()
-        self.cc_session = self.mccs._session
-        self.got_command_name = ''
-        self.pid_list = [[ 9999, "b10-auth", "Auth" ],
-                         [ 9998, "b10-auth-2", "Auth" ]]
-        self.statistics_data = {
-            'boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', self._BASETIME)
-            }
-
-    def run(self):
-        self.mccs.start()
-        self.running = True
-        self._started.set()
-        try:
-            while self.running:
-                self.mccs.check_command(False)
-        except Exception:
-            pass
-
-    def shutdown(self):
-        self.running = False
-
-    def config_handler(self, new_config):
-        return isc.config.create_answer(0)
-
-    def command_handler(self, command, *args, **kwargs):
-        self._started.set()
-        self.got_command_name = command
-        sdata = self.statistics_data
-        if command == 'getstats':
-            return isc.config.create_answer(0, sdata)
-        elif command == 'show_processes':
-            # Return dummy pids
-            return isc.config.create_answer(
-                0, self.pid_list)
-        return isc.config.create_answer(1, "Unknown Command")
-
-class MockAuth:
-    spec_str = """\
+# Note: this is derived of the spec for the DNS authoritative server, but
+# for the purpose of this test, it's completely irrelevant to DNS.
+# Some statisittics specs do not make sense for practical sense but used
+# just cover various types of statistics data (list, map/dict, etc).
+AUTH_SPEC_STR = """\
 {
   "module_spec": {
     "module_name": "Auth",
@@ -392,68 +295,6 @@ class MockAuth:
   }
 }
 """
-    def __init__(self):
-        self._started = threading.Event()
-        self.running = False
-        self.spec_file = io.StringIO(self.spec_str)
-        # create ModuleCCSession object
-        self.mccs = isc.config.ModuleCCSession(
-            self.spec_file,
-            self.config_handler,
-            self.command_handler)
-        self.spec_file.close()
-        self.cc_session = self.mccs._session
-        self.got_command_name = ''
-        self.queries_tcp = 3
-        self.queries_udp = 2
-        self.queries_per_zone = [{
-                'zonename': 'test1.example',
-                'queries.tcp': 5,
-                'queries.udp': 4
-                }]
-        self.nds_queries_per_zone = {
-            'test10.example': {
-                'queries.tcp': 5,
-                'queries.udp': 4
-                }
-            }
-
-    def run(self):
-        self.mccs.start()
-        self.running = True
-        self._started.set()
-        try:
-            while self.running:
-                self.mccs.check_command(False)
-        except Exception:
-            pass
-
-    def shutdown(self):
-        self.running = False
-
-    def config_handler(self, new_config):
-        return isc.config.create_answer(0)
-
-    def command_handler(self, command, *args, **kwargs):
-        self.got_command_name = command
-        sdata = { 'queries.tcp': self.queries_tcp,
-                  'queries.udp': self.queries_udp,
-                  'queries.perzone' : self.queries_per_zone,
-                  'nds_queries.perzone' : {
-                    'test10.example': {
-                    'queries.tcp': \
-                      isc.cc.data.find(
-                        self.nds_queries_per_zone,
-                        'test10.example/queries.tcp')
-                    }
-                  },
-                  'nds_queries.perzone/test10.example/queries.udp' :
-                      isc.cc.data.find(self.nds_queries_per_zone,
-                                       'test10.example/queries.udp')
-                }
-        if command == 'getstats':
-            return isc.config.create_answer(0, sdata)
-        return isc.config.create_answer(1, "Unknown Command")
 
 class MyModuleCCSession(isc.config.ConfigData):
     """Mocked ModuleCCSession class.
@@ -468,6 +309,7 @@ class MyModuleCCSession(isc.config.ConfigData):
         isc.config.ConfigData.__init__(self, module_spec)
         self._session = self
         self.stopped = False
+        self.closed = False
         self.lname = 'mock_mod_ccs'
 
     def start(self):
@@ -476,10 +318,13 @@ class MyModuleCCSession(isc.config.ConfigData):
     def send_stopping(self):
         self.stopped = True     # just record it's called to inspect it later
 
-class SimpleStats(stats.Stats):
+    def close(self):
+        self.closed = True
+
+class MyStats(stats.Stats):
     """A faked Stats class for unit tests.
 
-    This class inherits most of the real Stats class, but replace the
+    This class inherits most of the real Stats class, but replaces the
     ModuleCCSession with a fake one so we can avoid network I/O in tests,
     and can also inspect or tweak messages via the session more easily.
     This class also maintains some faked module information and statistics
@@ -500,9 +345,9 @@ class SimpleStats(stats.Stats):
         # the default answer from faked recvmsg if _answers is empty
         self.__default_answer = isc.config.ccsession.create_answer(
             0, {'Init':
-                    json.loads(MockInit.spec_str)['module_spec']['statistics'],
+                    json.loads(INIT_SPEC_STR)['module_spec']['statistics'],
                 'Auth':
-                    json.loads(MockAuth.spec_str)['module_spec']['statistics']
+                    json.loads(AUTH_SPEC_STR)['module_spec']['statistics']
                 })
         # setup faked auth statistics
         self.__init_auth_stat()
@@ -530,24 +375,24 @@ class SimpleStats(stats.Stats):
     def __init_auth_stat(self):
         self._queries_tcp = 3
         self._queries_udp = 2
-        self.__queries_per_zone = [{
+        self._queries_per_zone = [{
                 'zonename': 'test1.example', 'queries.tcp': 5, 'queries.udp': 4
                 }]
-        self.__nds_queries_per_zone = \
+        self._nds_queries_per_zone = \
             { 'test10.example': { 'queries.tcp': 5, 'queries.udp': 4 } }
         self._auth_sdata = \
             { 'queries.tcp': self._queries_tcp,
               'queries.udp': self._queries_udp,
-              'queries.perzone' : self.__queries_per_zone,
+              'queries.perzone' : self._queries_per_zone,
               'nds_queries.perzone' : {
                 'test10.example': {
                     'queries.tcp': isc.cc.data.find(
-                        self.__nds_queries_per_zone,
+                        self._nds_queries_per_zone,
                         'test10.example/queries.tcp')
                     }
                 },
               'nds_queries.perzone/test10.example/queries.udp' :
-                  isc.cc.data.find(self.__nds_queries_per_zone,
+                  isc.cc.data.find(self._nds_queries_per_zone,
                                    'test10.example/queries.udp')
               }
 
@@ -589,32 +434,62 @@ class SimpleStats(stats.Stats):
         answer, _ = self.__group_recvmsg(None, None)
         return isc.config.ccsession.parse_answer(answer)[1]
 
-class MyStats(stats.Stats):
-
-    stats._BASETIME = CONST_BASETIME
-    stats.get_timestamp = lambda: time.mktime(CONST_BASETIME)
-    stats.get_datetime = lambda x=None: time.strftime("%Y-%m-%dT%H:%M:%SZ", CONST_BASETIME)
-
-    def __init__(self):
-        self._started = threading.Event()
-        stats.Stats.__init__(self)
+class MyStatsHttpd(stats_httpd.StatsHttpd):
+    """A faked StatsHttpd class for unit tests.
 
-    def run(self):
-        self._started.set()
-        try:
-            self.start()
-        except Exception:
-            pass
+    This class inherits most of the real StatsHttpd class, but replaces the
+    ModuleCCSession with a fake one so we can avoid network I/O in tests,
+    and can also inspect or tweak messages via the session more easily.
 
-    def shutdown(self):
-        self.command_shutdown()
+    """
 
-class MyStatsHttpd(stats_httpd.StatsHttpd):
     ORIG_SPECFILE_LOCATION = stats_httpd.SPECFILE_LOCATION
     def __init__(self, *server_address):
         self._started = threading.Event()
+        self.__dummy_sock = None # see below
+
+        # Prepare commonly used statistics schema and data requested in
+        # stats-httpd tests.  For the purpose of these tests, the content of
+        # statistics data is not so important (they don't test whther the
+        # counter values are correct, etc), so hardcoding the common case
+        # should suffice.  Note also that some of the statistics values and
+        # specs don't make sense in practice (see also comments on
+        # AUTH_SPEC_STR).
+        with open(stats.SPECFILE_LOCATION) as f:
+            stat_spec_str = f.read()
+        self.__default_spec_answer = {
+            'Init': json.loads(INIT_SPEC_STR)['module_spec']['statistics'],
+            'Auth': json.loads(AUTH_SPEC_STR)['module_spec']['statistics'],
+            'Stats': json.loads(stat_spec_str)['module_spec']['statistics']
+            }
+        self.__default_data_answer = {
+            'Init': {'boot_time':
+                         time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME)},
+            'Stats': {'last_update_time':
+                          time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+                      'report_time':
+                          time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+                      'lname': 'test-lname',
+                      'boot_time':
+                          time.strftime('%Y-%m-%dT%H:%M:%SZ', CONST_BASETIME),
+                      'timestamp': time.mktime(CONST_BASETIME)},
+            'Auth': {'queries.udp': 4, 'queries.tcp': 6,
+                     'queries.perzone': [
+                    {'queries.udp': 8, 'queries.tcp': 10,
+                     'zonename': 'test1.example'},
+                    {'queries.udp': 6, 'queries.tcp': 8,
+                     'zonename': 'test2.example'}],
+                     'nds_queries.perzone': {
+                    'test10.example': {'queries.udp': 8, 'queries.tcp': 10},
+                    'test20.example': {'queries.udp': 6, 'queries.tcp': 8}}}}
+
+        # if set, use them as faked response to rpc_call (see below).
+        # it's a list of answer data of rpc_call.
+        self._rpc_answers = []
+
         if server_address:
-            stats_httpd.SPECFILE_LOCATION = self.create_specfile(*server_address)
+            stats_httpd.SPECFILE_LOCATION = \
+                self.__create_specfile(*server_address)
             try:
                 stats_httpd.StatsHttpd.__init__(self)
             finally:
@@ -624,7 +499,51 @@ class MyStatsHttpd(stats_httpd.StatsHttpd):
         else:
             stats_httpd.StatsHttpd.__init__(self)
 
-    def create_specfile(self, *server_address):
+        # replace some (faked) ModuleCCSession methods so we can inspect/fake.
+        # in order to satisfy select.select() we need some real socket.  We
+        # use an unusable AF_UNIX socket; we won't actually use it for
+        # communication.
+        self.cc_session.rpc_call = self.__rpc_call
+        self.__dummy_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        self.mccs.get_socket = lambda: self.__dummy_sock
+
+    def open_mccs(self):
+        self.mccs = MyModuleCCSession(stats_httpd.SPECFILE_LOCATION,
+                                      self.config_handler,
+                                      self.command_handler)
+        self.cc_session = self.mccs._session
+        self.mccs.start = self.load_config # force reload
+
+    def close_mccs(self):
+        super().close_mccs()
+        if self.__dummy_sock is not None:
+            self.__dummy_sock.close()
+            self.__dummy_sock = None
+
+    def __rpc_call(self, command, group, params={}):
+        """Faked ModuleCCSession.rpc_call for tests.
+
+        The stats httpd module only issues two commands: 'showschema' and
+        'show'.  In most cases we can simply use the prepared default
+        answer.  If customization is needed, the test case can add a
+        faked answer by appending it to _rpc_answers.  If the added object
+        is of Exception type this method raises it instead of return it,
+        emulating the situation where rpc_call() results in an exception.
+
+        """
+        if len(self._rpc_answers) == 0:
+            if command == 'showschema':
+                return self.__default_spec_answer
+            elif command == 'show':
+                return self.__default_data_answer
+            assert False, "unexpected command for faked rpc_call: " + command
+
+        answer = self._rpc_answers.pop(0)
+        if issubclass(type(answer), Exception):
+            raise answer
+        return answer
+
+    def __create_specfile(self, *server_address):
         spec_io = open(self.ORIG_SPECFILE_LOCATION)
         try:
             spec = json.load(spec_io)
@@ -633,7 +552,8 @@ class MyStatsHttpd(stats_httpd.StatsHttpd):
             for i in range(len(config)):
                 if config[i]['item_name'] == 'listen_on':
                     config[i]['item_default'] = \
-                        [ dict(address=a[0], port=a[1]) for a in server_address ]
+                        [ dict(address=a[0], port=a[1])
+                          for a in server_address ]
                     break
             return io.StringIO(json.dumps(spec))
         finally:
@@ -641,53 +561,4 @@ class MyStatsHttpd(stats_httpd.StatsHttpd):
 
     def run(self):
         self._started.set()
-        try:
-            self.start()
-        except Exception:
-            pass
-
-    def shutdown(self):
-        self.command_handler('shutdown', None)
-
-class BaseModules:
-    def __init__(self):
-        # MockMsgq
-        self.msgq = ThreadingServerManager(MockMsgq)
-        self.msgq.run()
-        # Check whether msgq is ready. A SessionTimeout is raised here if not.
-        isc.cc.session.Session().close()
-        # MockCfgmgr
-        self.cfgmgr = ThreadingServerManager(MockCfgmgr)
-        self.cfgmgr.run()
-        # MockInit
-        self.b10_init = ThreadingServerManager(MockInit)
-        self.b10_init.run()
-        # MockAuth
-        self.auth = ThreadingServerManager(MockAuth)
-        self.auth.run()
-        self.auth2 = ThreadingServerManager(MockAuth)
-        self.auth2.run()
-
-
-    def shutdown(self):
-        # MockMsgq. We need to wait (blocking) for it, otherwise it'll wipe out
-        # a socket for another test during its shutdown.
-        self.msgq.shutdown(True)
-
-        # We also wait for the others, but these are just so we don't create
-        # too many threads in parallel.
-
-        # MockAuth
-        self.auth2.shutdown(True)
-        self.auth.shutdown(True)
-        # MockInit
-        self.b10_init.shutdown(True)
-        # MockCfgmgr
-        self.cfgmgr.shutdown(True)
-        # remove the unused socket file
-        socket_file = self.msgq.server.msgq.socket_file
-        try:
-            if os.path.exists(socket_file):
-                os.remove(socket_file)
-        except OSError:
-            pass
+        self.start()
diff --git a/src/bin/xfrin/b10-xfrin.xml b/src/bin/xfrin/b10-xfrin.xml
index 588048a..0da7ce0 100644
--- a/src/bin/xfrin/b10-xfrin.xml
+++ b/src/bin/xfrin/b10-xfrin.xml
@@ -213,6 +213,132 @@ operation
 -->
 
   </refsect1>
+  <refsect1>
+    <title>STATISTICS DATA</title>
+
+    <para>
+      The statistics data collected by the <command>b10-xfrin</command>
+      daemon for <quote>Xfrin</quote> include:
+    </para>
+
+    <variablelist>
+
+      <varlistentry>
+        <term>zones</term>
+        <listitem><simpara>
+          A directory name of per-zone statistics
+          </simpara>
+          <variablelist>
+
+            <varlistentry>
+              <term><replaceable>zonename</replaceable></term>
+              <listitem><simpara>
+                An actual zone name or special zone name
+                <quote>_SERVER_</quote> representing the entire server.
+                Zone classes (e.g. IN, CH, and HS) are mixed and counted so
+                far. But these will be distinguished in future release.
+                </simpara>
+                <variablelist>
+
+                  <varlistentry>
+                    <term>soaoutv4</term>
+                    <listitem><simpara>
+                      Number of IPv4 SOA queries sent from Xfrin
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>soaoutv6</term>
+                    <listitem><simpara>
+                      Number of IPv6 SOA queries sent from Xfrin
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>axfrreqv4</term>
+                    <listitem><simpara>
+                      Number of IPv4 AXFR requests sent from Xfrin
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>axfrreqv6</term>
+                    <listitem><simpara>
+                      Number of IPv6 AXFR requests sent from Xfrin
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>ixfrreqv4</term>
+                    <listitem><simpara>
+                      Number of IPv4 IXFR requests sent from Xfrin
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>ixfrreqv6</term>
+                    <listitem><simpara>
+                      Number of IPv6 IXFR requests sent from Xfrin
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>xfrsuccess</term>
+                    <listitem><simpara>
+                      Number of zone transfer requests succeeded.
+                      These include the case where the zone turns
+                      out to be the latest as a result of an
+                      initial SOA query (and there is actually no
+                      AXFR or IXFR transaction).
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>xfrfail</term>
+                    <listitem><simpara>
+                      Number of zone transfer requests failed
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>last_axfr_duration</term>
+                    <listitem><simpara>
+                      Duration in seconds of the last successful AXFR.  0.0
+                      means no successful AXFR done or means a successful AXFR
+                      done in less than a microsecond.  If an AXFR is aborted
+                      due to some failure, this duration won't be updated.
+                    </simpara></listitem>
+                  </varlistentry>
+
+                  <varlistentry>
+                    <term>last_ixfr_duration</term>
+                    <listitem><simpara>
+                      Duration in seconds of the last successful IXFR.  0.0
+                      means no successful IXFR done or means a successful IXFR
+                      done in less than a microsecond.  If an IXFR is aborted
+                      due to some failure, this duration won't be updated.
+                    </simpara></listitem>
+                  </varlistentry>
+
+                </variablelist>
+              </listitem>
+            </varlistentry><!-- end of zonename -->
+
+          </variablelist>
+        </listitem>
+      </varlistentry><!-- end of zones -->
+
+    </variablelist>
+
+    <para>
+      In per-zone counters the special zone name <quote>_SERVER_</quote>
+      exists.
+      It doesn't mean a specific zone. It represents the entire server
+      and the counter value of this special zone is the total of the
+      same counter for all zones.
+    </para>
+
+  </refsect1>
 
 <!--
   <refsect1>
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index 5759bb1..2305cdb 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2009-2011  Internet Systems Consortium.
+# Copyright (C) 2009-2013  Internet Systems Consortium.
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -19,6 +19,7 @@ import shutil
 import socket
 import sys
 import io
+from datetime import datetime
 from isc.testutils.tsigctx_mock import MockTSIGContext
 from isc.testutils.ccsession_mock import MockModuleCCSession
 from isc.testutils.rrset_utils import *
@@ -717,7 +718,7 @@ class TestXfrinConnection(unittest.TestCase):
         self.sock_map = {}
         self.conn = MockXfrinConnection(self.sock_map, TEST_ZONE_NAME,
                                         TEST_RRCLASS, None, threading.Event(),
-                                        TEST_MASTER_IPV4_ADDRINFO)
+                                        self._master_addrinfo)
         self.conn.init_socket()
         self.soa_response_params = {
             'questions': [example_soa_question],
@@ -749,6 +750,10 @@ class TestXfrinConnection(unittest.TestCase):
             os.remove(TEST_DB_FILE)
         xfrin.check_zone = self.__orig_check_zone
 
+    @property
+    def _master_addrinfo(self):
+        return TEST_MASTER_IPV4_ADDRINFO
+
     def __check_zone(self, name, rrclass, rrsets, callbacks):
         '''
         A mock function used instead of dns.check_zone.
@@ -1065,6 +1070,20 @@ class TestAXFR(TestXfrinConnection):
         self.assertRaises(XfrinProtocolError,
                           self.conn._handle_xfrin_responses)
 
+    def test_ipver_str(self):
+        addrs = (((socket.AF_INET, socket.SOCK_STREAM), 'v4'),
+                 ((socket.AF_INET6, socket.SOCK_STREAM), 'v6'),
+                 ((socket.AF_UNIX, socket.SOCK_STREAM), None))
+        for (info, ver) in addrs:
+            c = MockXfrinConnection({}, TEST_ZONE_NAME, RRClass.CH, None,
+                                    threading.Event(), info)
+            c.init_socket()
+            if ver is not None:
+                self.assertEqual(ver, c._get_ipver_str())
+            else:
+                self.assertRaises(ValueError, c._get_ipver_str)
+            c.close()
+
     def test_soacheck(self):
         # we need to defer the creation until we know the QID, which is
         # determined in _check_soa_serial(), so we use response_generator.
@@ -2104,6 +2123,187 @@ class TestXFRSessionWithSQLite3(TestXfrinConnection):
         self.assertFalse(self.record_exist(Name('dns01.example.com'),
                                            RRType.A))
 
+class TestStatisticsXfrinConn(TestXfrinConnection):
+    '''Test class based on TestXfrinConnection and including paramters
+    and methods related to statistics tests'''
+    def setUp(self):
+        super().setUp()
+        # clear all statistics counters before each test
+        self.conn._counters.clear_all()
+        # fake datetime
+        self.__orig_datetime = isc.statistics.counters.datetime
+        self.__orig_start_timer = isc.statistics.counters._start_timer
+        time1 = datetime(2000, 1, 1, 0, 0, 0, 0)
+        time2 = datetime(2000, 1, 1, 0, 0, 0, 1)
+        class FakeDateTime:
+            @classmethod
+            def now(cls): return time2
+        isc.statistics.counters.datetime = FakeDateTime
+        isc.statistics.counters._start_timer = lambda : time1
+        delta = time2 - time1
+        self._const_sec = round(delta.days * 86400 + delta.seconds +
+                                delta.microseconds * 1E-6, 6)
+        # List of statistics counter names and expected initial values
+        self.__name_to_counter = (('axfrreqv4', 0),
+                                 ('axfrreqv6', 0),
+                                 ('ixfrreqv4', 0),
+                                 ('ixfrreqv6', 0),
+                                 ('last_axfr_duration', 0.0),
+                                 ('last_ixfr_duration', 0.0),
+                                 ('soaoutv4', 0),
+                                 ('soaoutv6', 0),
+                                 ('xfrfail', 0),
+                                 ('xfrsuccess', 0))
+        self.__zones = 'zones'
+
+    def tearDown(self):
+        super().tearDown()
+        isc.statistics.counters.datetime = self.__orig_datetime
+        isc.statistics.counters._start_timer = self.__orig_start_timer
+
+    @property
+    def _ipver(self):
+        return 'v4'
+
+    def _check_init_statistics(self):
+        '''checks exception being raised if not incremented statistics
+        counter gotten'''
+        for (name, exp) in self.__name_to_counter:
+            self.assertRaises(isc.cc.data.DataNotFoundError,
+                              self.conn._counters.get, self.__zones,
+                              TEST_ZONE_NAME_STR, name)
+
+    def _check_updated_statistics(self, overwrite):
+        '''checks getting expect values after updating the pairs of
+        statistics counter name and value on to the "overwrite"
+        dictionary'''
+        name2count = dict(self.__name_to_counter)
+        name2count.update(overwrite)
+        for (name, exp) in name2count.items():
+            act = self.conn._counters.get(self.__zones,
+                                          TEST_ZONE_NAME_STR,
+                                          name)
+            msg = '%s is expected %s but actually %s' % (name, exp, act)
+            self.assertEqual(exp, act, msg=msg)
+
+class TestStatisticsXfrinAXFRv4(TestStatisticsXfrinConn):
+    '''Xfrin AXFR tests for IPv4 to check statistics counters'''
+    def test_soaout(self):
+        '''tests that an soaoutv4 or soaoutv6 counter is incremented
+        when an soa query succeeds'''
+        self.conn.response_generator = self._create_soa_response_data
+        self._check_init_statistics()
+        self.assertEqual(self.conn._check_soa_serial(), XFRIN_OK)
+        self._check_updated_statistics({'soaout' + self._ipver: 1})
+
+    def test_axfrreq_xfrsuccess_last_axfr_duration(self):
+        '''tests that axfrreqv4 or axfrreqv6 and xfrsuccess counters
+        and last_axfr_duration timer are incremented when xfr succeeds'''
+        self.conn.response_generator = self._create_normal_response_data
+        self._check_init_statistics()
+        self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
+        self._check_updated_statistics({'axfrreq' + self._ipver: 1,
+                                        'xfrsuccess': 1,
+                                        'last_axfr_duration': self._const_sec})
+
+    def test_axfrreq_xfrsuccess_last_axfr_duration2(self):
+        '''tests that axfrreqv4 or axfrreqv6 and xfrsuccess counters
+        and last_axfr_duration timer are incremented when raising
+        XfrinZoneUptodate. The exception is treated as success.'''
+        def exception_raiser():
+            raise XfrinZoneUptodate()
+        self.conn._handle_xfrin_responses = exception_raiser
+        self._check_init_statistics()
+        self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
+        self._check_updated_statistics({'axfrreq' + self._ipver: 1,
+                                        'xfrsuccess': 1,
+                                        'last_axfr_duration':
+                                            self._const_sec})
+
+    def test_axfrreq_xfrfail(self):
+        '''tests that axfrreqv4 or axfrreqv6 and xfrfail counters are
+        incremented even if some failure exceptions are expected to be
+        raised inside do_xfrin(): XfrinZoneError, XfrinProtocolError,
+        XfrinException, and Exception'''
+        self._check_init_statistics()
+        count = 0
+        for ex in [XfrinZoneError, XfrinProtocolError, XfrinException,
+                   Exception]:
+            def exception_raiser():
+                raise ex()
+            self.conn._handle_xfrin_responses = exception_raiser
+            self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+            count += 1
+            self._check_updated_statistics({'axfrreq' + self._ipver: count,
+                                            'xfrfail': count})
+
+class TestStatisticsXfrinIXFRv4(TestStatisticsXfrinConn):
+    '''Xfrin IXFR tests for IPv4 to check statistics counters'''
+    def test_ixfrreq_xfrsuccess_last_ixfr_duration(self):
+        '''tests that ixfrreqv4 or ixfrreqv6 and xfrsuccess counters
+        and last_ixfr_duration timer are incremented when xfr succeeds'''
+        def create_ixfr_response():
+            self.conn.reply_data = self.conn.create_response_data(
+                questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
+                                    RRType.IXFR)],
+                answers=[soa_rrset, begin_soa_rrset, soa_rrset, soa_rrset])
+        self.conn.response_generator = create_ixfr_response
+        self._check_init_statistics()
+        self.assertEqual(XFRIN_OK, self.conn.do_xfrin(False, RRType.IXFR))
+        self._check_updated_statistics({'ixfrreq' + self._ipver: 1,
+                                        'xfrsuccess': 1,
+                                        'last_ixfr_duration':
+                                            self._const_sec})
+
+    def test_ixfrreq_xfrsuccess_last_ixfr_duration2(self):
+        '''tests that ixfrreqv4 or ixfrreqv6 and xfrsuccess counters
+        and last_ixfr_duration timer are incremented when raising
+        XfrinZoneUptodate. The exception is treated as success.'''
+        def exception_raiser():
+            raise XfrinZoneUptodate()
+        self.conn._handle_xfrin_responses = exception_raiser
+        self._check_init_statistics()
+        self.assertEqual(self.conn.do_xfrin(False, RRType.IXFR), XFRIN_OK)
+        self._check_updated_statistics({'ixfrreq' + self._ipver: 1,
+                                        'xfrsuccess': 1,
+                                        'last_ixfr_duration':
+                                            self._const_sec})
+
+    def test_ixfrreq_xfrfail(self):
+        '''tests that ixfrreqv4 or ixfrreqv6 and xfrfail counters are
+        incremented even if some failure exceptions are expected to be
+        raised inside do_xfrin(): XfrinZoneError, XfrinProtocolError,
+        XfrinException, and Exception'''
+        self._check_init_statistics()
+        count = 0
+        for ex in [XfrinZoneError, XfrinProtocolError, XfrinException,
+                   Exception]:
+            def exception_raiser():
+                raise ex()
+            self.conn._handle_xfrin_responses = exception_raiser
+            self.assertEqual(self.conn.do_xfrin(False, RRType.IXFR), XFRIN_FAIL)
+            count += 1
+            self._check_updated_statistics({'ixfrreq' + self._ipver: count,
+                                            'xfrfail': count})
+
+class TestStatisticsXfrinAXFRv6(TestStatisticsXfrinAXFRv4):
+    '''Same tests as TestStatisticsXfrinAXFRv4 for IPv6'''
+    @property
+    def _master_addrinfo(self):
+        return TEST_MASTER_IPV6_ADDRINFO
+    @property
+    def _ipver(self):
+        return 'v6'
+
+class TestStatisticsIXFRv6(TestStatisticsXfrinIXFRv4):
+    '''Same tests as TestStatisticsXfrinIXFRv4 for IPv6'''
+    @property
+    def _master_addrinfo(self):
+        return TEST_MASTER_IPV6_ADDRINFO
+    @property
+    def _ipver(self):
+        return 'v6'
+
 class TestXfrinRecorder(unittest.TestCase):
     def setUp(self):
         self.recorder = XfrinRecorder()
@@ -2512,6 +2712,14 @@ class TestXfrin(unittest.TestCase):
         self.assertEqual(self.xfr.config_handler({'transfers_in': 3})['result'][0], 0)
         self.assertEqual(self.xfr._max_transfers_in, 3)
 
+    def test_command_handler_getstats(self):
+        module_spec = isc.config.module_spec_from_file(
+            xfrin.SPECFILE_LOCATION)
+        ans = isc.config.parse_answer(
+            self.xfr.command_handler("getstats", None))
+        self.assertEqual(0, ans[0])
+        self.assertTrue(module_spec.validate_statistics(False, ans[1]))
+
     def _check_zones_config(self, config_given):
         if 'transfers_in' in config_given:
             self.assertEqual(config_given['transfers_in'],
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index 52d230b..2066404 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -1,6 +1,6 @@
 #!@PYTHON@
 
-# Copyright (C) 2009-2011  Internet Systems Consortium.
+# Copyright (C) 2009-2013  Internet Systems Consortium.
 #
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -28,6 +28,7 @@ import time
 from functools import reduce
 from optparse import OptionParser, OptionValueError
 from isc.config.ccsession import *
+from isc.statistics import Counters
 from isc.notify import notify_out
 import isc.util.process
 from isc.datasrc import DataSourceClient, ZoneFinder
@@ -612,6 +613,7 @@ class XfrinConnection(asyncore.dispatcher):
         # keep a record of this specific transfer to log on success
         # (time, rr/s, etc)
         self._transfer_stats = XfrinTransferStats()
+        self._counters = Counters(SPECFILE_LOCATION)
 
     def init_socket(self):
         '''Initialize the underlyig socket.
@@ -891,6 +893,19 @@ class XfrinConnection(asyncore.dispatcher):
         # All okay, return it
         return soa
 
+    def _get_ipver_str(self):
+        """Returns a 'v4' or 'v6' string representing a IP version
+        depending on the socket family. This is for an internal use
+        only (except for tests). This is supported only for IP sockets.
+        It raises a ValueError exception on other address families.
+
+        """
+        if self.socket.family == socket.AF_INET:
+            return 'v4'
+        elif self.socket.family == socket.AF_INET6:
+            return 'v6'
+        raise ValueError("Invalid address family. "
+                         "This is supported only for IP sockets")
 
     def _check_soa_serial(self):
         '''Send SOA query and compare the local and remote serials.
@@ -902,6 +917,9 @@ class XfrinConnection(asyncore.dispatcher):
         '''
 
         self._send_query(RRType.SOA)
+        # count soaoutv4 or soaoutv6 requests
+        self._counters.inc('zones', self._zone_name.to_text(),
+                           'soaout' + self._get_ipver_str())
         data_len = self._get_request_response(2)
         msg_len = socket.htons(struct.unpack('H', data_len)[0])
         soa_response = self._get_request_response(msg_len)
@@ -931,9 +949,7 @@ class XfrinConnection(asyncore.dispatcher):
         try:
             ret = XFRIN_OK
             self._request_type = request_type
-            # Right now RRType.[IA]XFR().to_text() is 'TYPExxx', so we need
-            # to hardcode here.
-            req_str = 'IXFR' if request_type == RRType.IXFR else 'AXFR'
+            req_str = request_type.to_text()
             if check_soa:
                 self._check_soa_serial()
                 self.close()
@@ -941,7 +957,16 @@ class XfrinConnection(asyncore.dispatcher):
                 if not self.connect_to_master():
                     raise XfrinException('Unable to reconnect to master')
 
+            # start statistics timer
+            # Note: If the timer for the zone is already started but
+            # not yet stopped due to some error, the last start time
+            # is overwritten at this point.
+            self._counters.start_timer('zones', self._zone_name.to_text(),
+                                       'last_' + req_str.lower() + '_duration')
             logger.info(XFRIN_XFR_TRANSFER_STARTED, req_str, self.zone_str())
+            # An AXFR or IXFR is being requested.
+            self._counters.inc('zones', self._zone_name.to_text(),
+                               req_str.lower() + 'req' + self._get_ipver_str())
             self._send_query(self._request_type)
             self.__state = XfrinInitialSOA()
             self._handle_xfrin_responses()
@@ -968,7 +993,6 @@ class XfrinConnection(asyncore.dispatcher):
                             "%.3f" % self._transfer_stats.get_running_time(),
                             "%.f" % self._transfer_stats.get_bytes_per_second()
                            )
-
         except XfrinZoneUptodate:
             # Eventually we'll probably have to treat this case as a trigger
             # of trying another primary server, etc, but for now we treat it
@@ -1004,11 +1028,21 @@ class XfrinConnection(asyncore.dispatcher):
                          self.zone_str(), str(e))
             ret = XFRIN_FAIL
         finally:
+            # A xfrsuccess or xfrfail counter is incremented depending on
+            # the result.
+            result = {XFRIN_OK: 'xfrsuccess', XFRIN_FAIL: 'xfrfail'}[ret]
+            self._counters.inc('zones', self._zone_name.to_text(), result)
+            # The started statistics timer is finally stopped only in
+            # a successful case.
+            if ret == XFRIN_OK:
+                self._counters.stop_timer('zones',
+                                          self._zone_name.to_text(),
+                                          'last_' + req_str.lower() +
+                                          '_duration')
             # Make sure any remaining transaction in the diff is closed
             # (if not yet - possible in case of xfr-level exception) as soon
             # as possible
             self._diff = None
-
         return ret
 
     def _check_response_header(self, msg):
@@ -1339,6 +1373,7 @@ class Xfrin:
         self._cc_setup()
         self.recorder = XfrinRecorder()
         self._shutdown_event = threading.Event()
+        self._counters = Counters(SPECFILE_LOCATION)
 
     def _cc_setup(self):
         '''This method is used only as part of initialization, but is
@@ -1484,6 +1519,7 @@ class Xfrin:
             th.join()
 
     def command_handler(self, command, args):
+        logger.debug(DBG_XFRIN_TRACE, XFRIN_RECEIVED_COMMAND, command)
         answer = create_answer(0)
         try:
             if command == 'shutdown':
@@ -1552,6 +1588,14 @@ class Xfrin:
                                        (False if command == 'retransfer' else True))
                 answer = create_answer(ret[0], ret[1])
 
+            # return statistics data to the stats daemon
+            elif command == "getstats":
+                # The log level is here set to debug in order to avoid
+                # that a log becomes too verbose. Because the
+                # b10-stats daemon is periodically asking to the
+                # b10-xfrin daemon.
+                answer = create_answer(0, self._counters.get_statistics())
+
             else:
                 answer = create_answer(1, 'unknown command: ' + command)
         except XfrinException as err:
diff --git a/src/bin/xfrin/xfrin.spec b/src/bin/xfrin/xfrin.spec
index 7ab1085..dc993f7 100644
--- a/src/bin/xfrin/xfrin.spec
+++ b/src/bin/xfrin/xfrin.spec
@@ -94,6 +94,119 @@
           }
         ]
       }
+    ],
+    "statistics": [
+      {
+        "item_name": "zones",
+        "item_type": "named_set",
+        "item_optional": false,
+        "item_default": {
+          "_SERVER_" : {
+	    "soaoutv4": 0,
+	    "soaoutv6": 0,
+	    "axfrreqv4": 0,
+	    "axfrreqv6": 0,
+	    "ixfrreqv4": 0,
+	    "ixfrreqv6": 0,
+	    "xfrsuccess": 0,
+	    "xfrfail": 0,
+	    "last_ixfr_duration": 0.0,
+	    "last_axfr_duration": 0.0
+          }
+        },
+        "item_title": "Zone names",
+        "item_description": "A directory name of per-zone statistics",
+        "named_set_item_spec": {
+          "item_name": "zonename",
+          "item_type": "map",
+          "item_optional": false,
+          "item_default": {},
+          "item_title": "Zone name",
+          "item_description": "An actual zone name or special zone name _SERVER_ representing the entire server. Zone classes (e.g. IN, CH, and HS) are mixed and counted so far. But these will be distinguished in future release.",
+          "map_item_spec": [
+            {
+              "item_name": "soaoutv4",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "SOAOutv4",
+              "item_description": "Number of IPv4 SOA queries sent from Xfrin"
+            },
+            {
+              "item_name": "soaoutv6",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "SOAOutv6",
+              "item_description": "Number of IPv6 SOA queries sent from Xfrin"
+            },
+            {
+              "item_name": "axfrreqv4",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "AXFRReqv4",
+              "item_description": "Number of IPv4 AXFR requests sent from Xfrin"
+            },
+            {
+              "item_name": "axfrreqv6",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "AXFRReqv6",
+              "item_description": "Number of IPv6 AXFR requests sent from Xfrin"
+            },
+            {
+              "item_name": "ixfrreqv4",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "IXFRReqv4",
+              "item_description": "Number of IPv4 IXFR requests sent from Xfrin"
+            },
+            {
+              "item_name": "ixfrreqv6",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "IXFRReqv6",
+              "item_description": "Number of IPv6 IXFR requests sent from Xfrin"
+            },
+            {
+              "item_name": "xfrsuccess",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "XfrSuccess",
+              "item_description": "Number of zone transfer requests succeeded. These include the case where the zone turns out to be the latest as a result of an initial SOA query (and there is actually no AXFR or IXFR transaction)."
+            },
+            {
+              "item_name": "xfrfail",
+              "item_type": "integer",
+              "item_optional": false,
+              "item_default": 0,
+              "item_title": "XfrFail",
+              "item_description": "Number of zone transfer requests failed"
+            },
+            {
+              "item_name": "last_axfr_duration",
+              "item_type": "real",
+              "item_optional": false,
+              "item_default": 0.0,
+              "item_title": "Last AXFR duration",
+              "item_description": "Duration in seconds of the last successful AXFR.  0.0 means no successful AXFR done or means a successful AXFR done in less than a microsecond.  If an AXFR is aborted due to some failure, this duration won't be updated."
+            },
+            {
+              "item_name": "last_ixfr_duration",
+              "item_type": "real",
+              "item_optional": false,
+              "item_default": 0.0,
+              "item_title": "Last IXFR duration",
+              "item_description": "Duration in seconds of the last successful IXFR.  0.0 means no successful IXFR done or means a successful IXFR done in less than a microsecond.  If an IXFR is aborted due to some failure, this duration won't be updated."
+            }
+          ]
+        }
+      }
     ]
   }
 }
diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes
index 1d90b75..eeddee9 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -135,6 +135,9 @@ from does not match the master address in the Xfrin configuration. The notify
 is ignored. This may indicate that the configuration for the master is wrong,
 that a wrong machine is sending notifies, or that fake notifies are being sent.
 
+% XFRIN_RECEIVED_COMMAND received command: %1
+The xfrin daemon received a command on the command channel.
+
 % XFRIN_RETRANSFER_UNKNOWN_ZONE got notification to retransfer unknown zone %1
 There was an internal command to retransfer the given zone, but the
 zone is not known to the system. This may indicate that the configuration
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..cabc8bb 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,52 @@ 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.
+    // SunStudio doesn't like a boost::function object to be passed, so
+    // we use the wrapper class as a workaround.
+    class CallbackWrapper {
+    public:
+        CallbackWrapper(boost::function<void(const asio::error_code&, size_t)>
+                        callback) :
+            callback_(callback)
+        {}
+        void operator()(const asio::error_code& error, size_t len) {
+            callback_(error, len);
+        }
+    private:
+        boost::function<void(const asio::error_code&, size_t)> callback_;
+    };
+    const CallbackWrapper recv_callback_;
 
     // Auxiliary functions
 
@@ -144,3 +188,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..df08316 100644
--- a/src/lib/asiolink/io_service.cc
+++ b/src/lib/asiolink/io_service.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011,2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -24,6 +24,23 @@
 namespace isc {
 namespace asiolink {
 
+namespace {
+// A trivial wrapper for boost::function.  SunStudio doesn't seem to be capable
+// of handling a boost::function object if directly passed to
+// io_service::post().
+class CallbackWrapper {
+public:
+    CallbackWrapper(const boost::function<void()>& callback) :
+        callback_(callback)
+    {}
+    void operator()() {
+        callback_();
+    }
+private:
+    boost::function<void()> callback_;
+};
+}
+
 class IOServiceImpl {
 private:
     IOServiceImpl(const IOService& source);
@@ -63,6 +80,10 @@ public:
     /// It will eventually be removed once the wrapper interface is
     /// generalized.
     asio::io_service& get_io_service() { return io_service_; };
+    void post(const boost::function<void ()>& callback) {
+        const CallbackWrapper wrapper(callback);
+        io_service_.post(wrapper);
+    }
 private:
     asio::io_service io_service_;
     asio::io_service::work work_;
@@ -96,5 +117,10 @@ IOService::get_io_service() {
     return (io_impl_->get_io_service());
 }
 
+void
+IOService::post(const boost::function<void ()>& callback) {
+    return (io_impl_->post(callback));
+}
+
 } // namespace asiolink
 } // namespace isc
diff --git a/src/lib/asiolink/io_service.h b/src/lib/asiolink/io_service.h
index e0198dd..e086997 100644
--- a/src/lib/asiolink/io_service.h
+++ b/src/lib/asiolink/io_service.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011,2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -15,6 +15,8 @@
 #ifndef ASIOLINK_IO_SERVICE_H
 #define ASIOLINK_IO_SERVICE_H 1
 
+#include <boost/function.hpp>
+
 namespace asio {
     class io_service;
 }
@@ -70,6 +72,17 @@ public:
     /// generalized.
     asio::io_service& get_io_service();
 
+    /// \brief Post a callback to the end of the queue.
+    ///
+    /// Requests the callback be called sometime later. It is not guaranteed
+    /// by the underlying asio, but it can reasonably be expected the callback
+    /// is put to the end of the callback queue. It is not called from within
+    /// this function.
+    ///
+    /// It may be used to implement "background" work, for example (doing stuff
+    /// by small bits that are called from time to time).
+    void post(const boost::function<void ()>& callback);
+
 private:
     IOServiceImpl* io_impl_;
 };
diff --git a/src/lib/asiolink/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/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_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 49e0779..a5a7488 100644
--- a/src/lib/datasrc/client_list.h
+++ b/src/lib/datasrc/client_list.h
@@ -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 aa8c939..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>
@@ -1707,15 +1707,16 @@ DatabaseUpdater::deleteRRset(const AbstractRRset& rrset) {
             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,
-              nsec3_type ? cvtr.getNSEC3Name() : cvtr.getRevName() };
         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 88a4140..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,14 +116,13 @@ 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 from is more convenient
+    /// 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
@@ -132,16 +131,29 @@ public:
     /// 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_RNAME = 3, ///< As DEL_NAME, but with the labels of domain name
-                       ///< in reverse order (eg. org.example.). With NSEC3,
-                       ///< it is the same as DEL_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.
     ///
     /// This is used as the "operation" parameter value of addRecordDiff().
@@ -588,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.
@@ -602,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 (&params)[DEL_PARAM_COUNT]) = 0;
+        const std::string (&params)[DEL_NSEC3_PARAM_COUNT]) = 0;
 
     /// \brief Start a general transaction.
     ///
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index f2a7eca..2c345c2 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -354,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/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/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 1653168..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>
@@ -1311,20 +1311,14 @@ SQLite3Accessor::deleteRecordInZone(const string (&params)[DEL_PARAM_COUNT]) {
 
 void
 SQLite3Accessor::deleteNSEC3RecordInZone(
-    const string (&params)[DEL_PARAM_COUNT])
+    const string (&params)[DEL_NSEC3_PARAM_COUNT])
 {
     if (!dbparameters_->updating_zone) {
         isc_throw(DataSourceError, "deleting NSEC3-related record in SQLite3 "
                   "data source without transaction");
     }
-    const size_t SQLITE3_DEL_PARAM_COUNT = DEL_PARAM_COUNT - 1;
-    const string sqlite3_params[SQLITE3_DEL_PARAM_COUNT] = {
-        params[DEL_NAME],
-        params[DEL_TYPE],
-        params[DEL_RDATA]
-    };
-    doUpdate<const string (&)[SQLITE3_DEL_PARAM_COUNT]>(
-        *dbparameters_, DEL_NSEC3_RECORD, sqlite3_params,
+    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 4d05ef6..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 (&params)[DEL_PARAM_COUNT]);
 
     virtual void deleteNSEC3RecordInZone(
-        const std::string (&params)[DEL_PARAM_COUNT]);
+        const std::string (&params)[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
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 (&params)[DEL_PARAM_COUNT])
-    {
+    template<size_t param_count>
+    void deleteRecord(Domains& domains, const string (&params)[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 (&params)[DEL_PARAM_COUNT])
+        const string (&params)[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 f8c1d78..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>
 
@@ -823,8 +823,7 @@ const char* const nsec3_sig_data[DatabaseAccessor::ADD_NSEC3_COLUMN_COUNT] = {
 const char* const nsec3_deleted_data[] = {
     // Delete parameters for nsec3_data
     apex_hash, nsec3_data[DatabaseAccessor::ADD_NSEC3_TYPE],
-    nsec3_data[DatabaseAccessor::ADD_NSEC3_RDATA],
-    apex_hash
+    nsec3_data[DatabaseAccessor::ADD_NSEC3_RDATA]
 };
 
 class SQLite3Update : public SQLite3AccessorTest {
@@ -854,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
@@ -1193,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.
@@ -1251,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);
 }
 
diff --git a/src/lib/datasrc/tests/zone_finder_context_unittest.cc b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
index 1ce1cee..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);
 }
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/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 a5e6b51..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 identifies 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.
     ///
@@ -678,6 +728,16 @@ private:
     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 bfb267e..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
@@ -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;
 
@@ -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/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/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index 4c9bdbc..ede7abf 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -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
 // tolerance to 0.01s.
 const uint32_t TIMEOUT_TOLERANCE = 10000;
 
+/// Mock object implementing PktFilter class.  It is used by
+/// IfaceMgrTest::setPacketFilter to verify that IfaceMgr::setPacketFilter
+/// sets this object as a handler for opening sockets. This dummy
+/// class simply records that openSocket function was called by
+/// the IfaceMgr as expected.
+///
+/// @todo This class currently doesn't verify that send/receive functions
+/// were called. In order to test it, there is a need to supply dummy
+/// function performing select() on certain sockets. The system select()
+/// call will fail when dummy socket descriptor is provided and thus
+/// TestPktFilter::receive will never be called. The appropriate extension
+/// to IfaceMgr is planned along with implementation of other "Packet
+/// Filters" such as these supporting Linux Packet Filtering and
+/// Berkley Packet Filtering.
+class TestPktFilter : public PktFilter {
+public:
+
+    /// Constructor
+    TestPktFilter()
+        : open_socket_called_(false) {
+    }
+
+    /// 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
@@ -163,7 +211,7 @@ TEST_F(IfaceMgrTest, basic) {
 TEST_F(IfaceMgrTest, ifaceClass) {
     // Basic tests for Iface inner class
 
-    IfaceMgr::Iface iface("eth5", 7);
+    Iface iface("eth5", 7);
     EXPECT_STREQ("eth5/7", iface.getFullName().c_str());
 }
 
@@ -175,10 +223,10 @@ TEST_F(IfaceMgrTest, getIface) {
     scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
 
     // Interface name, ifindex
-    IfaceMgr::Iface iface1("lo1", 100);
-    IfaceMgr::Iface iface2("eth9", 101);
-    IfaceMgr::Iface iface3("en3", 102);
-    IfaceMgr::Iface iface4("e1000g4", 103);
+    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;
@@ -199,7 +247,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());
@@ -345,7 +393,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;
@@ -353,7 +401,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();
@@ -370,7 +418,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.
@@ -748,6 +796,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) {
 
@@ -775,15 +855,15 @@ TEST_F(IfaceMgrTest, socket4) {
 
 // Test the Iface structure itself
 TEST_F(IfaceMgrTest, iface) {
-    scoped_ptr<IfaceMgr::Iface> iface;
-    EXPECT_NO_THROW(iface.reset(new IfaceMgr::Iface("eth0",1)));
+    boost::scoped_ptr<Iface> iface;
+    EXPECT_NO_THROW(iface.reset(new Iface("eth0",1)));
 
     EXPECT_EQ("eth0", iface->getName());
     EXPECT_EQ(1, iface->getIndex());
     EXPECT_EQ("eth0/1", iface->getFullName());
 
     // Let's make a copy of this address collection.
-    IfaceMgr::AddressCollection addrs = iface->getAddresses();
+    Iface::AddressCollection addrs = iface->getAddresses();
 
     EXPECT_EQ(0, addrs.size());
 
@@ -811,13 +891,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());
@@ -826,7 +906,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
     );
 
@@ -834,11 +914,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()));
 }
@@ -846,14 +926,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_);
@@ -861,7 +941,7 @@ TEST_F(IfaceMgrTest, socketInfo) {
 
     // Now let's test if IfaceMgr handles socket info properly
     scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
-    IfaceMgr::Iface* loopback = ifacemgr->getIface(LOOPBACK);
+    Iface* loopback = ifacemgr->getIface(LOOPBACK);
     ASSERT_TRUE(loopback);
     loopback->addSocket(sock1);
     loopback->addSocket(sock2);
@@ -936,7 +1016,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
@@ -1026,7 +1106,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
 
@@ -1038,8 +1118,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);
             }
         }
@@ -1150,8 +1230,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() << " ";
         }
@@ -1193,13 +1273,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/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/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/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/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/python/isc/datasrc/client_python.cc b/src/lib/python/isc/datasrc/client_python.cc
index df19492..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>
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/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/statistics/counters.py b/src/lib/python/isc/statistics/counters.py
index 8138ab6..279c14b 100644
--- a/src/lib/python/isc/statistics/counters.py
+++ b/src/lib/python/isc/statistics/counters.py
@@ -217,8 +217,8 @@ class Counters():
         zones/example.com./ixfrreqv6
         zones/example.com./xfrsuccess
         zones/example.com./xfrfail
-        zones/example.com./time_to_ixfr
-        zones/example.com./time_to_axfr
+        zones/example.com./last_ixfr_duration
+        zones/example.com./last_axfr_duration
         ixfr_running
         axfr_running
         socket/unixdomain/open
@@ -327,7 +327,9 @@ class Counters():
     def start_timer(self, *args):
         """Starts a timer which is identified by args and keeps it
         running until stop_timer() is called. It acquires a lock to
-        support multi-threaded use."""
+        support multi-threaded use. If the specified timer is already
+        started but not yet stopped, the last start time is
+        overwritten."""
         identifier = _concat(*args)
         with self._rlock:
             if self._disabled: return
diff --git a/src/lib/python/isc/statistics/tests/counters_test.py b/src/lib/python/isc/statistics/tests/counters_test.py
index 395a959..5567dda 100644
--- a/src/lib/python/isc/statistics/tests/counters_test.py
+++ b/src/lib/python/isc/statistics/tests/counters_test.py
@@ -197,7 +197,7 @@ class BaseTestCounters():
         # for per-zone counters
         for name in self.counters._zones_item_list:
             args = (self._perzone_prefix, TEST_ZONE_NAME_STR, name)
-            if name.find('time_to_') == 0:
+            if name.find('last_') == 0 and name.endswith('_duration'):
                 self.counters.start_timer(*args)
                 self.counters.stop_timer(*args)
                 self.assertGreaterEqual(self.counters.get(*args), 0.0)
@@ -278,7 +278,7 @@ class BaseTestCounters():
         # setting all counters to zero
         for name in self.counters._zones_item_list:
             args = (self._perzone_prefix, TEST_ZONE_NAME_STR, name)
-            if name.find('time_to_') == 0:
+            if name.find('last_') == 0 and name.endswith('_duration'):
                 zero = 0.0
             else:
                 zero = 0
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
index c97a09a..6c06f69 100644
--- a/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
@@ -19,8 +19,8 @@
 	    "ixfrreqv6": 0,
 	    "xfrsuccess": 0,
 	    "xfrfail": 0,
-	    "time_to_ixfr": 0.0,
-	    "time_to_axfr": 0.0
+	    "last_ixfr_duration": 0.0,
+	    "last_axfr_duration": 0.0
           }
         },
         "item_title": "Zone names",
@@ -98,20 +98,20 @@
               "item_description": "Number of zone transfer requests failed"
             },
             {
-              "item_name": "time_to_ixfr",
+              "item_name": "last_ixfr_duration",
               "item_type": "real",
               "item_optional": false,
               "item_default": 0.0,
-              "item_title": "Time to IXFR",
-              "item_description": "Elapsed time in seconds to do the last IXFR"
+              "item_title": "Last IXFR duration",
+              "item_description": "Duration of the last IXFR. 0.0 means no successful IXFR done."
             },
             {
-              "item_name": "time_to_axfr",
+              "item_name": "last_axfr_duration",
               "item_type": "real",
               "item_optional": false,
               "item_default": 0.0,
-              "item_title": "Time to AXFR",
-              "item_description": "Elapsed time in seconds to do the last AXFR"
+              "item_title": "Last AXFR duration",
+              "item_description": "Duration of the last AXFR. 0.0 means no successful AXFR done."
             }
           ]
         }
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/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/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/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
index 1b2953d..d7ea9a5 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_master.conf.orig
@@ -33,9 +33,6 @@
             "port": 47806
         } ]
     },
-    "Stats": {
-        "poll-interval": 1
-    },
     "Init": {
         "components": {
             "b10-auth": { "kind": "needed", "special": "auth" },
diff --git a/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig
new file mode 100644
index 0000000..755c91b
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_master_v4.conf.orig
@@ -0,0 +1,45 @@
+{
+    "version": 3,
+    "Logging": {
+        "loggers": [ {
+            "debuglevel": 99,
+            "severity": "DEBUG",
+            "name": "*"
+        } ]
+    },
+    "Auth": {
+        "database_file": "data/example.org.sqlite3",
+        "listen_on": [ {
+            "address": "127.0.0.1",
+            "port": 47809
+        } ]
+    },
+    "data_sources": {
+        "classes": {
+            "IN": [{
+                "type": "sqlite3",
+                "params": {
+                    "database_file": "data/example.org.sqlite3"
+                }
+            }]
+        }
+    },
+    "Xfrout": {
+        "zone_config": [ {
+            "origin": "example.org"
+        } ],
+        "also_notify": [ {
+            "address": "127.0.0.1",
+            "port": 47806
+        } ]
+    },
+    "Init": {
+        "components": {
+            "b10-auth": { "kind": "needed", "special": "auth" },
+            "b10-xfrout": { "address": "Xfrout", "kind": "dispensable" },
+            "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+            "b10-stats": { "address": "Stats", "kind": "dispensable" },
+            "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+        }
+    }
+}
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
index a5c22b1..3040b6c 100644
--- a/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_notify.conf.orig
@@ -42,6 +42,7 @@
             "b10-auth": { "kind": "needed", "special": "auth" },
             "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
             "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+            "b10-stats": { "address": "Stats", "kind": "dispensable" },
             "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
         }
     }
diff --git a/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf b/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf
new file mode 100644
index 0000000..67ebfd3
--- /dev/null
+++ b/tests/lettuce/configurations/xfrin/retransfer_slave_notify_v4.conf
@@ -0,0 +1,49 @@
+{
+    "version": 3,
+    "Logging": {
+        "loggers": [ {
+            "debuglevel": 99,
+            "severity": "DEBUG",
+            "name": "*"
+        } ]
+    },
+    "Auth": {
+        "database_file": "data/xfrin-notify.sqlite3",
+        "listen_on": [ {
+            "address": "127.0.0.1",
+            "port": 47806
+        } ]
+    },
+    "data_sources": {
+        "classes": {
+            "IN": [{
+                "type": "sqlite3",
+                "params": {
+                    "database_file": "data/xfrin-notify.sqlite3"
+                }
+            }]
+        }
+    },
+    "Xfrin": {
+        "zones": [ {
+            "name": "example.org",
+            "master_addr": "127.0.0.1",
+            "master_port": 47809
+        } ]
+    },
+    "Zonemgr": {
+        "secondary_zones": [ {
+            "name": "example.org",
+            "class": "IN"
+        } ]
+    },
+    "Init": {
+        "components": {
+            "b10-auth": { "kind": "needed", "special": "auth" },
+            "b10-xfrin": { "address": "Xfrin", "kind": "dispensable" },
+            "b10-zonemgr": { "address": "Zonemgr", "kind": "dispensable" },
+            "b10-stats": { "address": "Stats", "kind": "dispensable" },
+            "b10-cmdctl": { "special": "cmdctl", "kind": "needed" }
+        }
+    }
+}
diff --git a/tests/lettuce/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/terrain/bind10_control.py b/tests/lettuce/features/terrain/bind10_control.py
index 5aab431..e288aa9 100644
--- a/tests/lettuce/features/terrain/bind10_control.py
+++ b/tests/lettuce/features/terrain/bind10_control.py
@@ -120,13 +120,15 @@ def have_bind10_running(step, config_file, cmdctl_port, process_name):
     step.given(start_step)
 
 # function to send lines to bindctl, and store the result
-def run_bindctl(commands, cmdctl_port=None):
+def run_bindctl(commands, cmdctl_port=None, ignore_failure=False):
     """Run bindctl.
        Parameters:
        commands: a sequence of strings which will be sent.
        cmdctl_port: a port number on which cmdctl is listening, is converted
                     to string if necessary. If not provided, or None, defaults
                     to 47805
+       ignore_failure(bool): if set to True, don't examin the result code
+                    of bindctl and assert it succeeds.
 
        bindctl's stdout and stderr streams are stored (as one multiline string
        in world.last_bindctl_stdout/stderr.
@@ -140,6 +142,8 @@ def run_bindctl(commands, cmdctl_port=None):
     for line in commands:
         bindctl.stdin.write(line + "\n")
     (stdout, stderr) = bindctl.communicate()
+    if ignore_failure:
+        return
     result = bindctl.returncode
     world.last_bindctl_stdout = stdout
     world.last_bindctl_stderr = stderr
@@ -306,19 +310,25 @@ def config_remove_command(step, name, value, cmdctl_port):
                 "quit"]
     run_bindctl(commands, cmdctl_port)
 
- at step('send bind10(?: with cmdctl port (\d+))? the command (.+)')
-def send_command(step, cmdctl_port, command):
+ at step('send bind10(?: with cmdctl port (\d+))?( ignoring failure)? the command (.+)')
+def send_command(step, cmdctl_port, ignore_failure, command):
     """
     Run bindctl, send the given command, and exit bindctl.
     Parameters:
     command ('the command <command>'): The command to send.
     cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
                 the command to. Defaults to 47805.
-    Fails if cmdctl does not exit with status code 0.
+    ignore_failure ('ignoring failure', optional): set to None if bindctl
+    is expected to succeed (normal case, which is the default); if it is
+    not None, it means bindctl is expected to fail (and it's acceptable).
+
+    Fails if bindctl does not exit with status code 0 and ignore_failure
+    is not None.
+
     """
     commands = [command,
                 "quit"]
-    run_bindctl(commands, cmdctl_port)
+    run_bindctl(commands, cmdctl_port, ignore_failure is not None)
 
 @step('bind10 module (\S+) should( not)? be running')
 def module_is_running(step, name, not_str):
@@ -379,67 +389,6 @@ def query_statistics(step, statistics, name, cmdctl_port):
         % (port_str, name,\
                ' name=%s' % statistics if statistics else ''))
 
-def find_value(dictionary, key):
-    """A helper method. Recursively find a value corresponding to the
-    key of the dictionary and returns it. Returns None if the
-    dictionary is not dict type."""
-    if type(dictionary) is not dict:
-        return
-    if key in dictionary:
-        return dictionary[key]
-    else:
-        for v in dictionary.values():
-            return find_value(v, key)
-
- at step('the statistics counter (\S+)(?: in the category (\S+))?'+ \
-          '(?: for the zone (\S+))? should be' + \
-          '(?:( greater than| less than| between))? (\-?\d+)(?: and (\-?\d+))?')
-def check_statistics(step, counter, category, zone, gtltbt, number, upper):
-    """
-    check the output of bindctl for statistics of specified counter
-    and zone.
-    Parameters:
-    counter ('counter <counter>'): The counter name of statistics.
-    category ('category <category>', optional): The category of counter.
-    zone ('zone <zone>', optional): The zone name.
-    gtltbt (' greater than'|' less than'|' between', optional): greater than
-          <number> or less than <number> or between <number> and <upper>.
-    number ('<number>): The expect counter number. <number> is assumed
-          to be an unsigned integer.
-    upper ('<upper>, optional): The expect upper counter number when
-          using 'between'.
-    """
-    output = parse_bindctl_output_as_data_structure()
-    found = None
-    category_str = ""
-    zone_str = ""
-    depth = []
-    if category:
-        depth.insert(0, category)
-        category_str = " for category %s" % category
-    if zone:
-        depth.insert(0, zone)
-        zone_str = " for zone %s" % zone
-    for level in depth:
-        output = find_value(output, level)
-    found = find_value(output, counter)
-    assert found is not None, \
-        'Not found statistics counter %s%s%s' % \
-            (counter, category_str, zone_str)
-    msg = "Got %s, expected%s %s as counter %s%s" % \
-        (found, gtltbt, number, counter, zone_str)
-    if gtltbt and 'between' in gtltbt and upper:
-        msg = "Got %s, expected%s %s and %s as counter %s%s" % \
-            (found, gtltbt, number, upper, counter, zone_str)
-        assert int(number) <= int(found) \
-            and int(found) <= int(upper), msg
-    elif gtltbt and 'greater' in gtltbt:
-        assert int(found) > int(number), msg
-    elif gtltbt and 'less' in gtltbt:
-        assert int(found) < int(number), msg
-    else:
-        assert int(found) == int(number), msg
-
 @step('statistics counters are 0 in category (\S+)( except for the' + \
           ' following items)?')
 def check_statistics_items(step, category, has_except_for):
@@ -451,9 +400,14 @@ def check_statistics_items(step, category, has_except_for):
         with the multiline part.
 
     Expected values of items are taken from the multiline part of the step in
-    the scenario. The multiline part has two columns: item_name and item_value.
-    item_name is a relative name to category. item_value is an expected value
-    for item_name.
+    the scenario. The multiline part has at most four columns: item_name,
+    item_value, min_value, and max_value. item_name is a relative name
+    to category. item_value is an expected value for
+    item_name. min_value and max_value are expected to be used when
+    item_value cannot be specified to be item_value. min_value is the
+    minimum value in the expected range, and max_value is the maximum
+    value in the expected range. Values would be examined if they are
+    in columns corresponding to these.
     """
 
     def flatten(dictionary, prefix=''):
@@ -470,15 +424,55 @@ def check_statistics_items(step, category, has_except_for):
         # fetch step tables in the scnario as hashes
         for item in step.hashes:
             name = category+'.'+item['item_name']
-            value = item['item_value']
             assert stats.has_key(name), \
                 'Statistics item %s was not found' % (name)
             found = stats[name]
-            assert int(found) == int(value), \
-                'Statistics item %s has unexpected value %s (expect %s)' % \
+            if 'item_value' in item and item['item_value']:
+                value = item['item_value']
+                assert int(found) == int(value), \
+                    'Statistics item %s has unexpected value %s (expect %s)' % \
+                    (name, found, value)
+            if 'min_value' in item and item['min_value']:
+                value = item['min_value']
+                assert float(value) <= float(found), \
+                    'Statistics item %s has unexpected value %s (expect %s or greater than)' % \
+                    (name, found, value)
+            if 'max_value' in item and item['max_value']:
+                value = item['max_value']
+                assert float(found) <= float(value), \
+                    'Statistics item %s has unexpected value %s (expect %s or less than)' % \
                     (name, found, value)
             del(stats[name])
     for name, found in stats.items():
         assert int(found) == 0, \
             'Statistics item %s has unexpected value %s (expect %s)' % \
                 (name, found, 0)
+
+ at step('check initial statistics(?:( not)? containing (\S+))? for (\S+)'
+      '( with cmdctl port \d+)?( except for the following items)?')
+def check_init_statistics(step, notv, string, name, cmdctl_port, has_except_for):
+    """Checks the initial statistics for the module. Also checks a
+    string is contained or not contained in them. Statistics counters
+      other than zero can follow below.
+    Parameters:
+    notv ('not'): reverse the check (fail if string is found)
+    string ('containing <string>') string to look for
+    name ('module <name>'): The name of the module (case sensitive!)
+    cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
+                the command to.
+    has_except_for ('except for the following items'): checks values of items
+        with the multiline part.
+    """
+    query_str = 'query statistics of bind10 module ' + name
+    if cmdctl_port:
+        query_str = query_str + cmdctl_port
+    notcontain_str = 'last bindctl output should%s contain "%s"'
+    check_str = 'statistics counters are 0 in category .' + name
+    if has_except_for:
+        check_str = check_str + has_except_for + "\n" \
+            + step.represent_hashes()
+    step.given(query_str)
+    step.given(notcontain_str % (' not', 'error'))
+    if string is not None:
+        step.given(notcontain_str % (notv, string))
+    step.given(check_str)
diff --git a/tests/lettuce/features/terrain/terrain.py b/tests/lettuce/features/terrain/terrain.py
index 2f01723..b861442 100644
--- a/tests/lettuce/features/terrain/terrain.py
+++ b/tests/lettuce/features/terrain/terrain.py
@@ -68,6 +68,8 @@ copylist = [
      "configurations/ddns/noddns.config"],
     ["configurations/xfrin/retransfer_master.conf.orig",
      "configurations/xfrin/retransfer_master.conf"],
+    ["configurations/xfrin/retransfer_master_v4.conf.orig",
+     "configurations/xfrin/retransfer_master_v4.conf"],
     ["configurations/xfrin/retransfer_master_nons.conf.orig",
      "configurations/xfrin/retransfer_master_nons.conf"],
     ["configurations/xfrin/retransfer_slave.conf.orig",
diff --git a/tests/lettuce/features/xfrin_notify_handling.feature b/tests/lettuce/features/xfrin_notify_handling.feature
index a17ce42..375a8a9 100644
--- a/tests/lettuce/features/xfrin_notify_handling.feature
+++ b/tests/lettuce/features/xfrin_notify_handling.feature
@@ -1,5 +1,10 @@
 Feature: Xfrin incoming notify handling
-    Tests for Xfrin incoming notify handling.
+    Tests for Xfrin incoming notify handling. They also test
+    statistics counters incremented, which are related to notifying
+    and transferring by Xfrout and receiveing by Xfrin. Some cases are
+    considered: Transferring is done via IPv4 or IPv6 transport. A
+    transfer request from Xfrin is rejected by Xfrout. The master
+    server or slave server is unreachable.
 
     Scenario: Handle incoming notify
     Given I have bind10 running with configuration xfrin/retransfer_master.conf with cmdctl port 47804 as master
@@ -20,33 +25,125 @@ Feature: Xfrin incoming notify handling
     A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
 
     #
-    # Test for statistics
+    # Test1 for Xfrout statistics
+    #
+    check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+      | item_name                | item_max | item_min |
+      | socket.unixdomain.open   |        1 |        0 |
+    # Note: .Xfrout.socket.unixdomain.open can be either expected to
+    # be 0 or 1 here.  The reason is: if b10-xfrout has started up and is
+    # ready for a request from b10-stats, then b10-stats does request
+    # to b10-xfrout and the value results in 1. Otherwise if
+    # b10-xfrout is starting and isn't yet ready, then b10-stats
+    # doesn't request to b10-xfrout and the value still remains to be the
+    # default value(0).
+
+    #
+    # Test2 for Xfrin statistics
+    #
+    check initial statistics not containing example.org for Xfrin
+
+    When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+    Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
+    Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
+    # From this point we can't reliably 'wait for new' because the ordering
+    # of logs from different processes is unpredictable.  But these
+    # should be okay in this case.
+    Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+    Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+    Then wait for bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
+    Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
+    Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
+
+    A query for www.example.org to [::1]:47806 should have rcode NOERROR
+    # Make sure handling statistics command handling checked below is
+    # after this query
+    And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
+
+    #
+    # Test3 for Xfrout statistics
     #
-    # check for initial statistics
+    # check statistics change
     #
+
+    # wait until the last stats requesting is finished
     When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+    # note that this does not 100% guarantee the stats updated Xfrout
+    # statistics.  But there doesn't seem to be a better log message that
+    # suggests this event.
+    wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
     last bindctl output should not contain "error"
-    last bindctl output should not contain "example.org."
-    Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
-    Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 0
-    Then the statistics counter xfrrej for the zone _SERVER_ should be 0
-    Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
-
-    When I query statistics ixfr_running of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter ixfr_running should be 0
 
-    When I query statistics axfr_running of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter axfr_running should be 0
+    When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+    The statistics counters are 0 in category .Xfrout.zones except for the following items
+      | item_name                | item_value |
+      | _SERVER_.notifyoutv6     |          1 |
+      | _SERVER_.xfrreqdone      |          1 |
+      | example.org..notifyoutv6 |          1 |
+      | example.org..xfrreqdone  |          1 |
 
     When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter open should be between 0 and 1
-    Then the statistics counter openfail should be 0
-    Then the statistics counter close should be 0
-    Then the statistics counter bindfail should be 0
-    Then the statistics counter acceptfail should be 0
-    Then the statistics counter accept should be 0
-    Then the statistics counter senderr should be 0
-    Then the statistics counter recverr should be 0
+    The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+      | item_name | item_value |
+      | open      |          1 |
+      | accept    |          1 |
+
+    #
+    # Test4 for Xfrin statistics
+    #
+    # check statistics change
+    #
+
+    # wait until the last stats requesting is finished
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
+    last bindctl output should not contain "error"
+
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    The statistics counters are 0 in category .Xfrin.zones except for the following items
+      | item_name                       | item_value | min_value |
+      | _SERVER_.soaoutv6               |          1 |           |
+      | _SERVER_.axfrreqv6              |          1 |           |
+      | _SERVER_.xfrsuccess             |          1 |           |
+      | _SERVER_.last_axfr_duration     |            |       0.0 |
+      | example.org..soaoutv6           |          1 |           |
+      | example.org..axfrreqv6          |          1 |           |
+      | example.org..xfrsuccess         |          1 |           |
+      | example.org..last_axfr_duration |            |       0.0 |
+
+    #
+    # Test for handling incoming notify only in IPv4
+    #
+    Scenario: Handle incoming notify (IPv4)
+    Given I have bind10 running with configuration xfrin/retransfer_master_v4.conf with cmdctl port 47804 as master
+    And wait for master stderr message BIND10_STARTED_CC
+    And wait for master stderr message CMDCTL_STARTED
+    And wait for master stderr message AUTH_SERVER_STARTED
+    And wait for master stderr message XFROUT_STARTED
+    And wait for master stderr message ZONEMGR_STARTED
+    And wait for master stderr message STATS_STARTING
+
+    And I have bind10 running with configuration xfrin/retransfer_slave_notify_v4.conf
+    And wait for bind10 stderr message BIND10_STARTED_CC
+    And wait for bind10 stderr message CMDCTL_STARTED
+    And wait for bind10 stderr message AUTH_SERVER_STARTED
+    And wait for bind10 stderr message XFRIN_STARTED
+    And wait for bind10 stderr message ZONEMGR_STARTED
+
+    A query for www.example.org to 127.0.0.1:47806 should have rcode NXDOMAIN
+
+    #
+    # Test1 for Xfrout statistics
+    #
+    check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+      | item_name                | item_max | item_min |
+      | socket.unixdomain.open   |        1 |        0 |
+    # Note: See above about .Xfrout.socket.unixdomain.open.
+
+    #
+    # Test2 for Xfrin statistics
+    #
+    check initial statistics not containing example.org for Xfrin
 
     When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
     Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
@@ -60,43 +157,61 @@ Feature: Xfrin incoming notify handling
     Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_SUCCESS
     Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
 
-    A query for www.example.org to [::1]:47806 should have rcode NOERROR
+    A query for www.example.org to 127.0.0.1:47806 should have rcode NOERROR
     # Make sure handling statistics command handling checked below is
     # after this query
     And wait for bind10 stderr message AUTH_SEND_NORMAL_RESPONSE
 
     #
-    # Test for statistics
+    # Test3 for Xfrout statistics
     #
-    # check for statistics change
+    # check statistics change
     #
 
     # wait until the last stats requesting is finished
+    When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
     # note that this does not 100% guarantee the stats updated Xfrout
     # statistics.  But there doesn't seem to be a better log message that
     # suggests this event.
     wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
+    last bindctl output should not contain "error"
 
     When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
-    last bindctl output should not contain "error"
-    Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
-    Then the statistics counter notifyoutv4 for the zone example.org. should be 0
-    Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 5
-    Then the statistics counter notifyoutv6 for the zone example.org. should be 5
-    Then the statistics counter xfrrej for the zone _SERVER_ should be 0
-    Then the statistics counter xfrrej for the zone example.org. should be 0
-    Then the statistics counter xfrreqdone for the zone _SERVER_ should be 1
-    Then the statistics counter xfrreqdone for the zone example.org. should be 1
+    The statistics counters are 0 in category .Xfrout.zones except for the following items
+      | item_name                | item_value |
+      | _SERVER_.notifyoutv4     |          1 |
+      | _SERVER_.xfrreqdone      |          1 |
+      | example.org..notifyoutv4 |          1 |
+      | example.org..xfrreqdone  |          1 |
 
     When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter open should be 1
-    Then the statistics counter openfail should be 0
-    Then the statistics counter close should be 0
-    Then the statistics counter bindfail should be 0
-    Then the statistics counter acceptfail should be 0
-    Then the statistics counter accept should be 1
-    Then the statistics counter senderr should be 0
-    Then the statistics counter recverr should be 0
+    The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+      | item_name | item_value |
+      | open      |          1 |
+      | accept    |          1 |
+
+    #
+    # Test4 for Xfrin statistics
+    #
+    # check statistics change
+    #
+
+    # wait until the last stats requesting is finished
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
+    last bindctl output should not contain "error"
+
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    The statistics counters are 0 in category .Xfrin.zones except for the following items
+      | item_name                       | item_value | min_value |
+      | _SERVER_.soaoutv4               |          1 |           |
+      | _SERVER_.axfrreqv4              |          1 |           |
+      | _SERVER_.xfrsuccess             |          1 |           |
+      | _SERVER_.last_axfr_duration     |            |       0.0 |
+      | example.org..soaoutv4           |          1 |           |
+      | example.org..axfrreqv4          |          1 |           |
+      | example.org..xfrsuccess         |          1 |           |
+      | example.org..last_axfr_duration |            |       0.0 |
 
     #
     # Test for Xfr request rejected
@@ -120,33 +235,123 @@ Feature: Xfrin incoming notify handling
     A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
 
     #
-    # Test1 for statistics
+    # Test1 for Xfrout statistics
     #
-    # check for initial statistics
+    check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+      | item_name                | item_max | item_min |
+      | socket.unixdomain.open   |        1 |        0 |
+    # Note: See above about .Xfrout.socket.unixdomain.open.
+
     #
+    # Test2 for Xfrin statistics
+    #
+    check initial statistics not containing example.org for Xfrin
+
+    #
+    # set transfer_acl rejection
+    # Local xfr requests from Xfrin module would be rejected here.
+    #
+    When I send bind10 the following commands with cmdctl port 47804
+    """
+    config set Xfrout/zone_config[0]/transfer_acl [{"action":  "REJECT", "from": "::1"}]
+    config commit
+    """
+    last bindctl output should not contain Error
+
+    When I send bind10 with cmdctl port 47804 the command Xfrout notify example.org IN
+    Then wait for new master stderr message XFROUT_NOTIFY_COMMAND
+    Then wait for new bind10 stderr message AUTH_RECEIVED_NOTIFY
+    # can't use 'wait for new' below.
+    Then wait for bind10 stderr message ZONEMGR_RECEIVE_NOTIFY
+    Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_STARTED
+    Then wait for bind10 stderr message XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION not XFRIN_TRANSFER_SUCCESS
+    Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
+    Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
+
+    A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+
+    #
+    # Test3 for Xfrout statistics
+    #
+    # check statistics change
+    #
+
+    # wait until the last stats requesting is finished
     When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+    wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
     last bindctl output should not contain "error"
-    last bindctl output should not contain "example.org."
-    Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
-    Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 0
-    Then the statistics counter xfrrej for the zone _SERVER_ should be 0
-    Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
-
-    When I query statistics ixfr_running of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter ixfr_running should be 0
 
-    When I query statistics axfr_running of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter axfr_running should be 0
+    When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
+    The statistics counters are 0 in category .Xfrout.zones except for the following items
+      | item_name                | item_value | min_value | max_value |
+      | _SERVER_.notifyoutv6     |          1 |           |           |
+      | _SERVER_.xfrrej          |            |         1 |         3 |
+      | example.org..notifyoutv6 |          1 |           |           |
+      | example.org..xfrrej      |            |         1 |         3 |
+    # Note: The above rejection counters might sometimes be increased
+    # up to 3. See this for details
+    # http://git.bind10.isc.org/~tester/builder/BIND10-lettuce/20120918210000-MacOS/logs/lettuce.out
 
     When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter open should be between 0 and 1
-    Then the statistics counter openfail should be 0
-    Then the statistics counter close should be 0
-    Then the statistics counter bindfail should be 0
-    Then the statistics counter acceptfail should be 0
-    Then the statistics counter accept should be 0
-    Then the statistics counter senderr should be 0
-    Then the statistics counter recverr should be 0
+    The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+      | item_name | item_value |
+      | open      |          1 |
+      | accept    |          1 |
+
+    #
+    # Test4 for Xfrin statistics
+    #
+    # check statistics change
+    #
+
+    # wait until the last stats requesting is finished
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
+    last bindctl output should not contain "error"
+
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    The statistics counters are 0 in category .Xfrin.zones except for the following items
+      | item_name              | item_value |
+      | _SERVER_.soaoutv6      |          1 |
+      | _SERVER_.axfrreqv6     |          1 |
+      | _SERVER_.xfrfail       |          1 |
+      | example.org..soaoutv6  |          1 |
+      | example.org..axfrreqv6 |          1 |
+      | example.org..xfrfail   |          1 |
+
+    #
+    # Test for Xfr request rejected in IPv4
+    #
+    Scenario: Handle incoming notify (XFR request rejected in IPv4)
+    Given I have bind10 running with configuration xfrin/retransfer_master_v4.conf with cmdctl port 47804 as master
+    And wait for master stderr message BIND10_STARTED_CC
+    And wait for master stderr message CMDCTL_STARTED
+    And wait for master stderr message AUTH_SERVER_STARTED
+    And wait for master stderr message XFROUT_STARTED
+    And wait for master stderr message ZONEMGR_STARTED
+    And wait for master stderr message STATS_STARTING
+
+    And I have bind10 running with configuration xfrin/retransfer_slave_notify_v4.conf
+    And wait for bind10 stderr message BIND10_STARTED_CC
+    And wait for bind10 stderr message CMDCTL_STARTED
+    And wait for bind10 stderr message AUTH_SERVER_STARTED
+    And wait for bind10 stderr message XFRIN_STARTED
+    And wait for bind10 stderr message ZONEMGR_STARTED
+
+    A query for www.example.org to 127.0.0.1:47806 should have rcode NXDOMAIN
+
+    #
+    # Test1 for Xfrout statistics
+    #
+    check initial statistics not containing example.org for Xfrout with cmdctl port 47804 except for the following items
+      | item_name                | item_max | item_min |
+      | socket.unixdomain.open   |        1 |        0 |
+    # Note: See above about .Xfrout.socket.unixdomain.open.
+
+    #
+    # Test2 for Xfrin statistics
+    #
+    check initial statistics not containing example.org for Xfrin
 
     #
     # set transfer_acl rejection
@@ -154,7 +359,7 @@ Feature: Xfrin incoming notify handling
     #
     When I send bind10 the following commands with cmdctl port 47804
     """
-    config set Xfrout/zone_config[0]/transfer_acl [{"action":  "REJECT", "from": "::1"}]
+    config set Xfrout/zone_config[0]/transfer_acl [{"action":  "REJECT", "from": "127.0.0.1"}]
     config commit
     """
     last bindctl output should not contain Error
@@ -169,39 +374,56 @@ Feature: Xfrin incoming notify handling
     Then wait for bind10 stderr message ZONEMGR_RECEIVE_XFRIN_FAILED not ZONEMGR_RECEIVE_XFRIN_SUCCESS
     Then wait for master stderr message NOTIFY_OUT_REPLY_RECEIVED
 
-    A query for www.example.org to [::1]:47806 should have rcode NXDOMAIN
+    A query for www.example.org to 127.0.0.1:47806 should have rcode NXDOMAIN
 
     #
-    # Test2 for statistics
+    # Test3 for Xfrout statistics
     #
-    # check for statistics change
+    # check statistics change
     #
 
+    When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
     # wait until stats request at least after NOTIFY_OUT_REPLY_RECEIVED
     wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
+    last bindctl output should not contain "error"
 
     When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
-    last bindctl output should not contain "error"
-    Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
-    Then the statistics counter notifyoutv4 for the zone example.org. should be 0
-    Then the statistics counter notifyoutv6 for the zone _SERVER_ should be 5
-    Then the statistics counter notifyoutv6 for the zone example.org. should be 5
-    # The counts of rejection would be between 1 and 2. They are not
-    # fixed. It would depend on timing or the platform.
-    Then the statistics counter xfrrej for the zone _SERVER_ should be greater than 0
-    Then the statistics counter xfrrej for the zone example.org. should be greater than 0
-    Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
-    Then the statistics counter xfrreqdone for the zone example.org. should be 0
+    The statistics counters are 0 in category .Xfrout.zones except for the following items
+      | item_name                | item_value | min_value | max_value |
+      | _SERVER_.notifyoutv4     |          1 |           |           |
+      | _SERVER_.xfrrej          |            |         1 |         3 |
+      | example.org..notifyoutv4 |          1 |           |           |
+      | example.org..xfrrej      |            |         1 |         3 |
+    # Note: The above rejection counters might sometimes be increased
+    # up to 3. See this for details
+    # http://git.bind10.isc.org/~tester/builder/BIND10-lettuce/20120918210000-MacOS/logs/lettuce.out
 
     When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter open should be 1
-    Then the statistics counter openfail should be 0
-    Then the statistics counter close should be 0
-    Then the statistics counter bindfail should be 0
-    Then the statistics counter acceptfail should be 0
-    Then the statistics counter accept should be 1
-    Then the statistics counter senderr should be 0
-    Then the statistics counter recverr should be 0
+    The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+      | item_name | item_value |
+      | open      |          1 |
+      | accept    |          1 |
+
+    #
+    # Test4 for Xfrin statistics
+    #
+    # check statistics change
+    #
+
+    # wait until the last stats requesting is finished
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    wait for new bind10 stderr message XFRIN_RECEIVED_COMMAND
+    last bindctl output should not contain "error"
+
+    When I query statistics zones of bind10 module Xfrin with cmdctl
+    The statistics counters are 0 in category .Xfrin.zones except for the following items
+      | item_name              | item_value |
+      | _SERVER_.soaoutv4      |          1 |
+      | _SERVER_.axfrreqv4     |          1 |
+      | _SERVER_.xfrfail       |          1 |
+      | example.org..soaoutv4  |          1 |
+      | example.org..axfrreqv4 |          1 |
+      | example.org..xfrfail   |          1 |
 
     #
     # Test for unreachable slave
@@ -226,29 +448,21 @@ Feature: Xfrin incoming notify handling
     # check statistics change
     #
 
+    When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
     # wait until stats request at least after NOTIFY_OUT_TIMEOUT
     wait for new master stderr message XFROUT_RECEIVED_GETSTATS_COMMAND
+    last bindctl output should not contain "error"
 
     When I query statistics zones of bind10 module Xfrout with cmdctl port 47804
-    last bindctl output should not contain "error"
-    Then the statistics counter notifyoutv4 for the zone _SERVER_ should be 0
-    Then the statistics counter notifyoutv4 for the zone example.org. should be 0
-    Then the statistics counter notifyoutv6 for the zone _SERVER_ should be greater than 0
-    Then the statistics counter notifyoutv6 for the zone example.org. should be greater than 0
-    Then the statistics counter xfrrej for the zone _SERVER_ should be 0
-    Then the statistics counter xfrrej for the zone example.org. should be 0
-    Then the statistics counter xfrreqdone for the zone _SERVER_ should be 0
-    Then the statistics counter xfrreqdone for the zone example.org. should be 0
+    The statistics counters are 0 in category .Xfrout.zones except for the following items
+      | item_name                | min_value | max_value |
+      | _SERVER_.notifyoutv6     |         1 |	       5 |
+      | example.org..notifyoutv6 |         1 |	       5 |
 
     When I query statistics socket of bind10 module Xfrout with cmdctl port 47804
-    Then the statistics counter open should be 1
-    Then the statistics counter openfail should be 0
-    Then the statistics counter close should be 0
-    Then the statistics counter bindfail should be 0
-    Then the statistics counter acceptfail should be 0
-    Then the statistics counter accept should be 0
-    Then the statistics counter senderr should be 0
-    Then the statistics counter recverr should be 0
+    The statistics counters are 0 in category .Xfrout.socket.unixdomain except for the following items
+      | item_name | item_value |
+      | open      |          1 |
 
     #
     # Test for NOTIFY that would result in NOTAUTH
diff --git a/tests/tools/perfdhcp/stats_mgr.h b/tests/tools/perfdhcp/stats_mgr.h
index 2ad5aa9..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())
@@ -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.
diff --git a/tests/tools/perfdhcp/test_control.cc b/tests/tools/perfdhcp/test_control.cc
index 425d978..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) {
@@ -661,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 "
@@ -1799,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");
     }
@@ -1825,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");
     }
diff --git a/tests/tools/perfdhcp/test_control.h b/tests/tools/perfdhcp/test_control.h
index f1b1300..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



More information about the bind10-changes mailing list