BIND 10 trac3036, updated. 646a4738545ebe5a44c6922a2950296a0f3f7c55 [3036] Merge branch 'master' into trac3036

BIND 10 source code commits bind10-changes at lists.isc.org
Tue Aug 13 17:04:32 UTC 2013


The branch, trac3036 has been updated
       via  646a4738545ebe5a44c6922a2950296a0f3f7c55 (commit)
       via  598e458c7af7d5bb81131112396e4c5845060ecd (commit)
       via  1745301647282b8db81d5618543aabe66b255dd8 (commit)
       via  35a7432feeb263c42eae96d6388729476f20b6f2 (commit)
       via  cf7cb62d777a1d74db1abca0753e5fb2cfe0c310 (commit)
       via  535abe75debe26e48284244d0e856b9cad4eeec6 (commit)
       via  7660c95d4f700d1324eca7cff26bbfa16444c4e8 (commit)
       via  2092528ae67cf42002c2aef17140d95cbcab8a54 (commit)
       via  f59e953a20f34118e276c215ed5d75f0e0611c7f (commit)
       via  290fb63c1c7640c8fdc8801b93c36f8ca0432b96 (commit)
       via  fef265b2e3bfae238c91bfd820bed760efc748aa (commit)
       via  10d9ff6b5b241f41939bee5bc5304d24b3147e15 (commit)
       via  73372e3a10aef030bb33f2834357fe2f4a3bb481 (commit)
       via  34081cbfbc9f0be52330015f8b58dfbbc7997ef2 (commit)
       via  540dd0449121094a56f294c500c2ed811f6016b6 (commit)
       via  1b46569fab6d606359ada85f6736951c969e8eb0 (commit)
       via  d72675617d6b60e3eb6160305738771f015849ba (commit)
       via  0fc5b23ed78c9b9ed16437a5b53e45881d413e77 (commit)
       via  9c88046f2b021dded1981966b930029e765b5915 (commit)
       via  feeb79f56b2fd672d8ba20607ed26164d362d0e1 (commit)
       via  ff0b9b45869b1d9a4b99e785fbce421e184c2e93 (commit)
       via  6faea427392fa8809269171dc7d98d42177af846 (commit)
       via  4f2cfbfa1b3edf01bab9c4d8b38a651ab857f52d (commit)
       via  40b956102de5751498e0acc27dbf99a7c554d627 (commit)
       via  f4d4e14ab8c0756bd19db78365b4eb1593a91867 (commit)
       via  ebed5cb932b16588af7a8fafbd6fc31321154f3e (commit)
       via  307eb1cc6cd90c831d95af0511424b5b468e4d37 (commit)
       via  72bcad54040078acf2e30d2f31aa177616a570df (commit)
       via  a970f6c5255e000c053a2dc47926cea7cec2761c (commit)
       via  508b40fdd135122a0e006524ac882794d28cfc9f (commit)
       via  ce9289d1ef31525a60dccbe0daa72e8f8db1be63 (commit)
       via  0efb9524bf1782a3e5875982a844a86e6b40460f (commit)
       via  067550e38d9bba5831375e9cbbd93674f1353ee2 (commit)
       via  26a805c7e49a9ec85ee825f179cda41a2358f4c6 (commit)
       via  c79031b28b835ccaf0d683b80ae877a5c94c12d4 (commit)
       via  029dc6aa03d7d72d60cb424f1121d916064cd6a4 (commit)
       via  6674553cc7932dee920e06a4936ba28916552e25 (commit)
       via  869ac09027a0fc146ac995e7b1dcc875e6184608 (commit)
       via  b68e8f32a7128d99e69a9ed42f9d8d21d6a68706 (commit)
       via  09f557d871faef090ed444ebeee7f13e142184a0 (commit)
       via  2b589c6418e5882bd14bc9471d292d6fc21247d2 (commit)
       via  9c3f96ecec2fe57a90680b80fbee8079c94c87f3 (commit)
       via  7c0bad1643af13dedf9356e9fb3a51264b7481de (commit)
       via  07f62405839389c802c751ddac3ca0dd43ceb754 (commit)
       via  59a4d0243d77ae0a12a8832e383892485157447a (commit)
       via  22cd67d216fb454b2aae9086a7a0afdec3c79c32 (commit)
       via  36c92588ee9973fe6400547dd38a18fe62161c2e (commit)
       via  9388cae1ccc1d3e5c6934c47c857a61ab17cf52e (commit)
       via  27d057a79de7e769cef2a87e59e08807491bf44e (commit)
       via  b8fa96e3498580f2c01b6baa1d38e649edee418b (commit)
       via  ccdb49aa255c988dd3f03be21b9511794af288f5 (commit)
       via  f36f677e841adddbaa762480d0cf8b961c098df1 (commit)
       via  c386d06ac5239ae26446c3bb75143ac54820698b (commit)
       via  56f41442f0a647c959e23d8b96ef949607ce98d4 (commit)
       via  f6fd9cc583267c0ddf779ee0d0fce7ffa697c554 (commit)
       via  e323b235b5cd03fda009b976fa7765f032e4ed35 (commit)
       via  063f70ad570dfc5d8697341bf8643ebe3eb80d7c (commit)
       via  363e37a380d7bbfca480e2008b3c135029647d69 (commit)
       via  ee0e068ef274d482659b11f444d67fa5d0b06de7 (commit)
       via  d5c2bffa4970fbf53178e4a1975a0e476d3bda32 (commit)
       via  26b0ae80ecf50d3868ca059a5965ecb275bb135b (commit)
       via  e3e92f0134210e040c537753fb32e36679f4b1b0 (commit)
       via  2e4d7c7f734e5d10881698e05a26193199558fc5 (commit)
       via  ceb084b6e3c728d23154c5ea4ae8d10bb3b91477 (commit)
       via  35c53d9ab3422ba82ebd2c4f9afb73f1a5dc58dc (commit)
       via  6606b6d02a352c15fa60f842bef9ccfecab93c41 (commit)
       via  2b38d4d369e3519b835cda94f1f167b4b96d5a0f (commit)
       via  940eae0ca5d303a9420747a5c84358f91515075c (commit)
       via  8ddaa8da24109d602cb7aced60e57f8422d0c529 (commit)
       via  4bca9c538b8deddfc49694d35bebd44c0bbdfc81 (commit)
       via  b920504363ea51053de5d287dbc68bd271a39f90 (commit)
       via  c9f3fca8e79716778c0b296a8a0b78db6571f4f2 (commit)
       via  8acbf043daf590a9f2ad003e715cd4ffb0b3f979 (commit)
       via  f964dbc5497ea2aef884251c708007e97185df2e (commit)
       via  c989ea1255cfb2086655c86234270b10c55efdff (commit)
       via  51e0b7424c7b3992e1bec9d524c7554ab33f45ab (commit)
       via  775f572c3c6cedcd8f1985c5cf969cb18dbe2e9c (commit)
       via  434edbb7276472a4ac31ccb63257fda00698baab (commit)
       via  9b71351b47386012f178803b56b71f90a972e9b2 (commit)
       via  c46e16711aa802046edb5557dfe89afb04be229e (commit)
       via  3056c7dd61a56b7053d04819b0b778e0297ffa25 (commit)
       via  f395f315a51a23ac058456ec15580231f145bd75 (commit)
       via  63d34705073649a2de35b2f85cf66c1bf64aaddf (commit)
       via  ed713618c11b1e63b4a6e10fb487a4bf3339c2ac (commit)
       via  9694e88402b35b3df56880c656270c1108e0df13 (commit)
       via  3b2674982d558f22f9a2db3b43c922e29ba7520c (commit)
       via  2459cd14aeff5cfccac711b6e6de4a4cf42817bb (commit)
       via  2d47b144b1a760c4338f534848b9b801fd709964 (commit)
       via  a33cd74d9c8c8b2d2789e98db9ad2c94ecafdafd (commit)
       via  b62e62a49012ae039ecace4c4d68ad196e151cc1 (commit)
       via  b16f3317c3fe5fab8334f8d87ab7aa905dddb0d0 (commit)
       via  a00b45ec9cdb590b973e5c00135f087ebdcb9aa6 (commit)
       via  3d39bccaf3f0565152ef73ec3e2cd03e77572c56 (commit)
       via  df4d44fb1d1b127847a61a84f5cb529a7a4cd324 (commit)
       via  cffabd52cf16cb75e896abb29ee194e6e90223b9 (commit)
       via  3eb5c11e8b91bd09f8d7111cbeb8df74deb2ea20 (commit)
       via  df8447e4fa4445aaa26fcdd9c3a09e3cdd15fc1d (commit)
       via  bb117dc8ada64fe650b090a693af1df45f1c805f (commit)
       via  99e8782450496aba1595fbee4058d0f1e5a4477d (commit)
       via  478d2eda7aca5a6e7f4a0e017192854f100641b5 (commit)
       via  8f0b820ed66c3dea6e9045bd8568af26cfa8d36e (commit)
       via  7a6cf1357a4c85a794388475ddfcde9ff92cb6ea (commit)
       via  8015b408d16d9d5e69c678a1201a9ec0649697a5 (commit)
       via  29228dab743c022c07585dca775ee732a42b5571 (commit)
       via  786a742bc321666586ee923cf15e2d2624ddee42 (commit)
       via  a3e596573657218631e0efba4432a4c07a05c593 (commit)
       via  649a0f8f2fbfa14b4b9ef9d233fc3290aaf7386e (commit)
       via  cbdbafbef1e3eaebcf057e15afdef9e6b387e058 (commit)
       via  f0b494fa3a9ead7321864d5f28c81092226fabf0 (commit)
       via  fc5e5746a148b1194f5a4d623bb96d1a8d9f085c (commit)
       via  ee4bbde1015148df78d7d5dbed07852866ba99b0 (commit)
       via  b54530b4539cec4476986442e72c047dddba7b48 (commit)
       via  d165e1c0d34e34a24b4519106af41af74d92b197 (commit)
       via  9643f2fc5610a68278c7a1ea7cc167991467122e (commit)
       via  bc799623e178c6d3e979df3630b8dc65c92fbb62 (commit)
       via  105e9affbca307c53328b34302020d27435153f8 (commit)
       via  c9874eac89f0e49c56c3911446e2e2d0f6d48657 (commit)
       via  fb7ab395e160cdaefa9ecffb4a70343d24cb80b6 (commit)
       via  7d4a5388296c225d11014d6e123cf0ee2cc8eb9f (commit)
       via  762f2fe1566c467eab8d48444f3d0c0c388d9e9b (commit)
       via  35f65cf6f3644e5bc97d058ceffe87b2253305be (commit)
       via  f8d312a623932ae279f24e6ec9587696ab2418a1 (commit)
       via  c5c64939c80687faa5692fef51c8435209736d55 (commit)
       via  988bb28c2391905a35e6f0c9113f78feb8f8bc97 (commit)
       via  979f2981bc3518e349e036f92f5058aac2b1faf8 (commit)
       via  39a853b0f561e12604fcb31a2b3746d5d577071a (commit)
       via  c845a96005b28acde2d62a93ee8c046162da4cb4 (commit)
       via  456a43775ebd7982ba98f57286702c420f983cf5 (commit)
       via  5c56a768045e78576cc71346a5cf4de9f2a35a13 (commit)
       via  e21cb1dba24a85e7afea480562d2b7277050e0dc (commit)
       via  0f845ed94f462dee85b67f056656b2a197878b04 (commit)
       via  4f96d3ab2e65356e9cb778ba140a9b8793f8e879 (commit)
       via  77e3dfce7c96bc924de4f1ca7060287c147e53e5 (commit)
       via  724e39ef6eaba76229a8208d2bf33449f16f3c01 (commit)
       via  941c1d8bfc0468b585039c92eba3165e20f6d08d (commit)
       via  40471d97b2dbe22189147f2f46f5956c587fa34f (commit)
       via  fe2331e7e0cadd2e70dfecdb9cf5a67f6484a5bd (commit)
       via  135b47d64fba42eda8cd194f257ed77efb441797 (commit)
       via  7915732b2210e5540a74041eb759b13af0f6c405 (commit)
       via  6f5ce2f575e16f71a88ac35ec93c07ecd254d231 (commit)
       via  8525babb4efb54926675d2d1a1398638cbc6676d (commit)
       via  4b0906418973479e742cf831d5acdb291384014e (commit)
       via  0a3773e5df10cc118dfd9dab3ce35bc612ca8ae7 (commit)
       via  07c566bffc7e726945d1c3fc58988c0186d5e0c0 (commit)
       via  616213225fee89adc57c8ea19a2195f8273dc5fa (commit)
       via  6f304d9ee4e85db2e3aa708ee31135c4e42ef347 (commit)
       via  d8183b83d92016696106a6665fde369fa7d49779 (commit)
       via  9cccfbbc4e13274cc1c5750704f03fbc577189ef (commit)
       via  61363a11804b7838e848b8f07cba13dce80e078f (commit)
       via  f1c0d7070d9a3968ff900663bffcba20e53851b0 (commit)
       via  feebb25811b38ebfee97c2a11a1470f7aaaf6696 (commit)
       via  b05bfd741c5b4ec90ada47017af8a299f8a8b4d6 (commit)
       via  589f61f8a630643bdee4d8bb2be68a79fba64112 (commit)
       via  be65cfba939a6a7abd3c93931ce35c33d3e8247b (commit)
       via  770c05222d92752594605e1fd16e9527e1d079cb (commit)
       via  f4dcd7d3757902c90bfa3e7b7db1600c624ea510 (commit)
       via  a1df84adccebfc6e3f6625aef58f1c9f31cd3eea (commit)
       via  946804f3beabb2367474b15bcb173c1fc53265d6 (commit)
       via  4ebc8cf417703e5350d68ccee409a8f1f9daef4e (commit)
       via  831bd66ffc6def8d0db2c0ab0761bfe0ef0259f4 (commit)
       via  d71ab2e5570d20ec6259b764472687c81009be24 (commit)
       via  f5e72732b725a2e20eabe2846a85ac8abb499f76 (commit)
       via  17a2fab7ec8ccc6886e887f9d9bff9e51fe080a9 (commit)
       via  f48a3bff3fbbd15584d788a264d5966154394f04 (commit)
       via  4084c5f2a9a24009d645def53697b139bf989124 (commit)
       via  89d6be56eefa1ed79d9c0cfb8112243e1435beb6 (commit)
       via  088a0124e731718b6e8be8092a47a087436ec1dc (commit)
       via  6b65ea8a618d32efcdc0697cf0acc534b6f13404 (commit)
       via  0d4abba272e2e77132b374a657aa96f65505f26c (commit)
       via  ca5c18086bcc1fb712a33c0f4f96c27cb76348cd (commit)
       via  92ebabdbcf6168666b03d7f7fbb31f899be39322 (commit)
       via  d7c133c2e26b0174c96ef48781352feec5f9e03b (commit)
       via  949a25220c81c7bd4faed53e0434446c18171ec9 (commit)
       via  473c1e672ab88928d1f9bda61a1c2690393c018b (commit)
       via  da1318c5b12541c6a101408e49108b41c2d4aff1 (commit)
       via  a765638850588cc8980c622351124f1319c46365 (commit)
       via  bf471e6c6228edca829deb48f810944ea650a19b (commit)
       via  0657510f32552e1d2510ec34e807d41c1a0f964e (commit)
       via  5b097c33037c8891a50d77f71ee0518e9c7c955a (commit)
       via  18dd133a37d37df043d36008286bb769838617be (commit)
       via  f683ad20d4a1936679ebfbc3f7eabedee1ff5182 (commit)
       via  1f09795960479dcbe6e53ae9ead9f969c0705107 (commit)
       via  cd9d3e1a07cba7cabf1118ffec7f7dacebc0ecfe (commit)
       via  9c54e830d9f3b18232de8304edc490f08ceb4f0e (commit)
       via  27a4c2199989c2802b0a5002000c32e6c4e00220 (commit)
       via  e61c29836e74f2fa7b0767b44d274ee36e8dd6ea (commit)
       via  7c6999a010297cde490304a97570aa48e88b2099 (commit)
       via  049d11fa1b6b08e8ad866686e57726e5906ced1c (commit)
       via  ecd9691c1791c973b9afabe17b95c108596be0f2 (commit)
       via  2cbcb533f792251d19339c8b38ac7b87db94b28a (commit)
       via  02141f2524808a5ccf1ac9b620fc702e37607e31 (commit)
       via  07074c5295156356a0608fbaf8d706f5eaaaaef8 (commit)
       via  a076d799d926d23e67f495df40812cead7425aa4 (commit)
       via  01bb436c45f4d92a3575ff4ad2f5c1cb348ce399 (commit)
       via  a8a8909b511278b2abe1679a3c801bd1fd1ece74 (commit)
       via  15471a94d7cd89204b1a7de89398255662590458 (commit)
       via  ca9233574b5bea413b38916a0e3593ff8c581b1c (commit)
       via  5b10e071de6d724d42e2f6eba4cc78bb7fbacd23 (commit)
       via  1626b91e6d458b0dfe8fb71434b52273edaeb795 (commit)
       via  0f4c53e60316f5b7006d96627d6236645285fe8c (commit)
       via  272122f27fdf10a479c94e6270326f6ab028939f (commit)
       via  d3c480a5ef3803787564cb8f1bbd92df4b7185d9 (commit)
       via  3c1f096e54e42bb79fec8a28abc6528b3c5fc857 (commit)
       via  8e13f44e871bde3f44ffc080a1996149d61fc13d (commit)
       via  56393640a8c2d3b11b549f1c1133991452c7a088 (commit)
       via  a6c4e80483303e6fb4deabb586a357987b79b194 (commit)
       via  0f8f7995fa2fd6f340d405305783a97b34307ca9 (commit)
       via  87ba16e0b1db1a831e394650c7713b028a7d42c8 (commit)
       via  d6de376f97313ba40fef989e4a437d184fdf70cc (commit)
       via  14018a4e7e240d9770dfd565284abdf2f82f9b30 (commit)
       via  2221a1de37ddc5c35e52ad0b8b99d7dec430b00c (commit)
       via  0d8bee5c0cc3cf65a0a14890d7ff76de328e4316 (commit)
       via  2aa3e8e3ad750691dc97badcac032ef1ca92737f (commit)
       via  661dae35e38a4b4a7d3f22b785509ceb528003e1 (commit)
       via  7276a2eddfc2fefb354a4848aa4376b9dbeb675e (commit)
       via  3194a2019cc116c9c668cf4adbf879e06b927879 (commit)
       via  7cd60567cc43fe83a74a7799373e5e6410d435f3 (commit)
       via  d6c9e292ff83ad12b82b8aa73e59ae05d23ee65a (commit)
       via  617aa41e35a33420f00dae8bb9f39918208dc1a9 (commit)
       via  cb814de47219dbc310f277838106d5b4d7e17668 (commit)
       via  228807659e177e95f4a3dbedc6978dcb8a02a2c3 (commit)
       via  ef523ea6235d16e0676d0263c964570134a59750 (commit)
       via  eb01e3dce1476254ec6720798c861e709d32c9be (commit)
       via  87c3781cf964f63b044153a39b05518f72d8b80e (commit)
       via  02e0859b64a98789b422bc57c457825bd991749f (commit)
       via  d26eac8dfc0780bd6211ff90f9d922bbe2bf7edb (commit)
       via  36815d6a2501933007ae6e8f823be08a64064058 (commit)
       via  37753c0dc215875538463720a5bdd68afdb4fc4e (commit)
       via  cca0075f103ef62c5b8413bb19a90c03d7bd4e04 (commit)
       via  75df517663d7743adfa1295e87f8a77ca97a059d (commit)
       via  39fa196b6b7eda249caa0501feb6d049cdfd9123 (commit)
       via  98c85cb61b5bcbf948e1ea0923adcfcdca753cbb (commit)
       via  abad8addef410474f0bb6bf6e15831902017e579 (commit)
       via  78fdf6daaf77f5e341e921bb1cb3ac7084060c75 (commit)
       via  4337b26b8553e2815c61e979a3c6442b38687f8c (commit)
       via  9f8d746079cad652e014d482327b6a73d5ee2f12 (commit)
       via  0a5d26dfc2ee6a4a84d596cbcb39e894e7432f3c (commit)
       via  377e88195e5f86ad60bf02bf02dcbd202daa9fa0 (commit)
       via  d9b56777aaa3a4e70c9b80ecde1b69aab30cb0ee (commit)
       via  7027266480db82481529f3680c506a23289ea460 (commit)
       via  9517e24b3bc274e4a8f080cb15358db8017c3f44 (commit)
       via  42407bf17bcaad492cfb9264dd713ad0e0c4f84c (commit)
       via  2ca4520e166d0d07232d0fb9cc31587f77176960 (commit)
       via  42de7b42fd0fd6653489efe5412b34365d3a4a77 (commit)
       via  339d60a7b7a5b83a2b9c5064af5d621e3047582f (commit)
       via  6f1a5fd22d80b90a5854d4e9c03f1aed13f32bac (commit)
       via  0ee5175c54020abcdcbafe078cc2e2dbb53e8dcb (commit)
       via  bcaf721d44f6805b6b73ab92c317597066e7f752 (commit)
       via  7110eb5282cb4f028867d7da7ffdccb5fc503653 (commit)
       via  62bbaa215675fbf0e1d7ca34219d5771a04eae4c (commit)
       via  f3d988ec344e2ef369534c86952b35d7ed0a1471 (commit)
       via  ffdc326d413b2eb9d95dc73f3948788ca7ece728 (commit)
       via  2de17f71bab2fe5b02ca8563477441dc2805661c (commit)
       via  72b06693a049e925d240971d92dd8427d3fa8f73 (commit)
       via  8c65dc3a4cc369eab2235b0ebc6d56dae4589c5a (commit)
       via  fd70aa81aeebbb2768d89185d1859520cd535b58 (commit)
       via  0d98fbf5f919f2ca5a5134bd6d3527bed6606d10 (commit)
       via  682cac0da7b318a81746d93424a638faa867ee7c (commit)
       via  cc508f6c28f0fa48720d1d78dc89bc4c287e207d (commit)
       via  58ad4fd28118d01c5207fcb00422f3a88e50cfab (commit)
       via  644a03f0da719b01456ea0115cc0bd661ff9e636 (commit)
       via  89a447a134798d2d59ae7a5565a9f07271ed6e9e (commit)
       via  292f28a54f954f44b4771f359c9a3cc4753da22a (commit)
       via  1bbc1906476bee5320eee4acb954af7b47c2b2d6 (commit)
       via  af62ea00182e18f2bfa448870ca37490eb830f09 (commit)
       via  a84352bebaacb3f414e5a01b1b25a3c7d3e50bbf (commit)
       via  d4fba54ddee752b447164bbed089735f2999f28b (commit)
       via  fd38783ce713c1ee718805cd5cc7bd33b07dbc4c (commit)
       via  d474c8d06214a2ef3dfc991dbd7b73dbfa6572b8 (commit)
       via  e7855781ad5d287d91c883143bbe2552bcc29f8d (commit)
       via  59c4a138d18a6fb2a0adc0daff488406b6a6ad09 (commit)
       via  af0db15b1727fb986a5a08819624b231f97999e8 (commit)
       via  3401846345cc6654eec63649e6bf1c71261b8b4f (commit)
       via  e68217dd349bb078aae6029b95e182ed165e7bcf (commit)
       via  c6e820b3078f39bfc19c40afb1c0294c15c16707 (commit)
       via  19c93b0845947d0bb5ff60c602d5fd3f64c531f2 (commit)
       via  bdeeb21d2f97586d4bf754b4a1156412810ef441 (commit)
       via  8df3463968ed8d79144303a9ec82accc7f7302db (commit)
       via  e29189407016909a302b2c7d4e9fb77f2ffb50e2 (commit)
       via  894180b8bfb30d7dfd7619cccffd0a2b4c3d4a2f (commit)
       via  a06a52e6609159bb97b49169192b19d9657aceff (commit)
       via  4507063fc988329516dccad11ff28936aac8a08e (commit)
       via  c6b3ce6d16f92b80db77487900fb57a7a67b3203 (commit)
       via  9785efa0afab6667a61d322f9a3a3dee048dd0dc (commit)
       via  df2ee17ce15c5259ec2632ebf536f9f117419c88 (commit)
       via  2ba18911d1daf85d52fea1e380f9425b7e8c3101 (commit)
       via  334cb51d057d58fc24049e8605a136096be8dc3f (commit)
       via  d2846e4ecf8679ec53b2cd16e543b7b2488f2692 (commit)
       via  ef242ca52d93c62bfc2ef42260ebf5069d31cde5 (commit)
       via  5e4b2f06b8dfe4cf1eb7f26836942d10e3c719e4 (commit)
       via  cdb34f7debacbbbc4adec956f682c07e0a77d706 (commit)
       via  5482fb745b8fc0f3a3f80b25416752682c345211 (commit)
       via  328ab84c1ddd6cf3f7e45a1b6fb6e1552bb26f68 (commit)
       via  eff9543714f70df752ab02db33b7a7b579b6a225 (commit)
       via  77c1644d0d1dc8f3c1589138c335323b6a4e5c35 (commit)
      from  2babc7e39394de13cf72b0009e47c3c994f346c8 (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 646a4738545ebe5a44c6922a2950296a0f3f7c55
Merge: 2babc7e 598e458
Author: Marcin Siodelski <marcin at isc.org>
Date:   Tue Aug 13 18:59:53 2013 +0200

    [3036] Merge branch 'master' into trac3036
    
    Conflicts:
    	src/bin/dhcp6/dhcp6_messages.mes
    	src/bin/dhcp6/dhcp6_srv.cc
    	src/bin/dhcp6/dhcp6_srv.h
    	src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
    	src/lib/dhcp_ddns/tests/ncr_unittests.cc
    	src/lib/dhcpsrv/alloc_engine.cc
    	src/lib/dhcpsrv/alloc_engine.h
    	src/lib/dhcpsrv/tests/alloc_engine_unittest.cc

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

Summary of changes:
 ChangeLog                                          |   97 +-
 configure.ac                                       |   21 +-
 doc/Doxyfile                                       |   43 +-
 doc/design/datasrc/data-source-classes.txt         |    6 +-
 doc/devel/mainpage.dox                             |    6 +
 doc/guide/.gitignore                               |    1 +
 doc/guide/Makefile.am                              |    4 +-
 doc/guide/bind10-guide.xml                         |  532 ++++---
 m4macros/ax_boost_for_bind10.m4                    |   18 +
 src/bin/auth/auth_messages.mes                     |   24 +
 src/bin/auth/auth_srv.cc                           |   82 +-
 src/bin/auth/auth_srv.h                            |   15 +-
 src/bin/auth/b10-auth.xml.pre                      |   33 +-
 src/bin/auth/datasrc_clients_mgr.h                 |   94 +-
 src/bin/auth/main.cc                               |    9 +-
 src/bin/auth/tests/auth_srv_unittest.cc            |   50 +-
 .../auth/tests/datasrc_clients_builder_unittest.cc |  117 +-
 src/bin/auth/tests/datasrc_clients_mgr_unittest.cc |   27 +
 src/bin/cmdctl/Makefile.am                         |   13 +-
 src/bin/cmdctl/cmdctl.py.in                        |    9 +-
 src/bin/cmdctl/tests/b10-certgen_test.py           |    2 +
 src/bin/cmdctl/tests/cmdctl_test.py                |   33 +-
 src/bin/d2/Makefile.am                             |    9 +-
 src/bin/d2/d2_cfg_mgr.cc                           |  118 +-
 src/bin/d2/d2_cfg_mgr.h                            |   80 +-
 src/bin/d2/d2_config.cc                            |   73 +-
 src/bin/d2/d2_config.h                             |   14 +-
 src/bin/d2/d2_messages.mes                         |   52 +-
 src/bin/d2/d2_queue_mgr.cc                         |  217 +++
 src/bin/d2/d2_queue_mgr.h                          |  335 +++++
 src/bin/d2/d2_update_mgr.cc                        |  230 +++
 src/bin/d2/d2_update_mgr.h                         |  294 ++++
 src/bin/d2/tests/Makefile.am                       |    8 +-
 src/bin/d2/tests/d2_cfg_mgr_unittests.cc           |   53 +-
 src/bin/d2/tests/d2_queue_mgr_unittests.cc         |  430 ++++++
 src/bin/d2/tests/d2_update_mgr_unittests.cc        |  443 ++++++
 src/bin/dhcp4/Makefile.am                          |    1 +
 src/bin/dhcp4/config_parser.cc                     |   94 +-
 src/bin/dhcp4/config_parser.h                      |    4 +-
 src/bin/dhcp4/ctrl_dhcp4_srv.cc                    |   34 +-
 src/bin/dhcp4/ctrl_dhcp4_srv.h                     |    3 +-
 src/bin/dhcp4/dhcp4.dox                            |    4 +
 src/bin/dhcp4/dhcp4.spec                           |    6 +-
 src/bin/dhcp4/dhcp4_hooks.dox                      |  138 ++
 src/bin/dhcp4/dhcp4_log.h                          |    3 +
 src/bin/dhcp4/dhcp4_messages.mes                   |   41 +-
 src/bin/dhcp4/dhcp4_srv.cc                         |  250 +++-
 src/bin/dhcp4/dhcp4_srv.h                          |   69 +-
 src/bin/dhcp4/tests/Makefile.am                    |    1 +
 src/bin/dhcp4/tests/config_parser_unittest.cc      |   99 +-
 src/bin/dhcp4/tests/dhcp4_srv_unittest.cc          |  824 ++++++++++-
 src/bin/dhcp4/tests/marker_file.h                  |   69 +
 src/bin/dhcp4/tests/test_libraries.h               |   51 +
 src/bin/dhcp6/Makefile.am                          |    7 +-
 src/bin/dhcp6/config_parser.cc                     |   92 +-
 src/bin/dhcp6/config_parser.h                      |    6 +-
 src/bin/dhcp6/ctrl_dhcp6_srv.cc                    |   31 +-
 src/bin/dhcp6/dhcp6.dox                            |    8 +-
 src/bin/dhcp6/dhcp6.spec                           |    6 +-
 src/bin/dhcp6/dhcp6_hooks.dox                      |  239 ++++
 src/bin/dhcp6/dhcp6_log.h                          |    3 +
 src/bin/dhcp6/dhcp6_messages.mes                   |   78 +-
 src/bin/dhcp6/dhcp6_srv.cc                         |  549 ++++++--
 src/bin/dhcp6/dhcp6_srv.h                          |  103 +-
 src/bin/dhcp6/tests/Makefile.am                    |    9 +-
 src/bin/dhcp6/tests/config_parser_unittest.cc      |  124 +-
 src/bin/dhcp6/tests/dhcp6_srv_unittest.cc          |  336 +----
 src/bin/dhcp6/tests/dhcp6_test_utils.h             |  407 ++++++
 src/bin/dhcp6/tests/hooks_unittest.cc              | 1457 ++++++++++++++++++++
 src/bin/dhcp6/tests/marker_file.h                  |   69 +
 src/bin/dhcp6/tests/test_libraries.h               |   51 +
 src/bin/memmgr/memmgr.py.in                        |    2 +-
 src/bin/memmgr/tests/memmgr_test.py                |   19 +-
 src/bin/resolver/main.cc                           |    3 +-
 src/bin/resolver/resolver.cc                       |   18 -
 src/bin/resolver/resolver.h                        |    4 -
 src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py   |   13 +
 src/bin/xfrin/xfrin.py.in                          |   33 +-
 src/bin/xfrin/xfrin_messages.mes                   |   10 +-
 src/bin/xfrout/xfrout.py.in                        |   12 +-
 src/bin/xfrout/xfrout.spec.pre.in                  |    2 +-
 src/lib/Makefile.am                                |    2 +-
 src/lib/asiodns/README                             |    5 +-
 src/lib/asiodns/dns_service.cc                     |   11 +-
 src/lib/asiodns/dns_service.h                      |    6 +-
 src/lib/asiodns/tcp_server.cc                      |   15 +-
 src/lib/asiodns/tcp_server.h                       |    3 -
 src/lib/asiodns/tests/dns_server_unittest.cc       |   39 +-
 src/lib/asiodns/tests/dns_service_unittest.cc      |    2 +-
 src/lib/asiodns/udp_server.cc                      |   26 +-
 src/lib/asiodns/udp_server.h                       |    2 -
 src/lib/asiolink/Makefile.am                       |    2 +-
 src/lib/asiolink/tcp_socket.h                      |    1 -
 src/lib/asiolink/udp_socket.h                      |    1 -
 src/lib/config/ccsession.cc                        |    2 +
 src/lib/config/ccsession.h                         |   46 +
 src/lib/config/tests/ccsession_unittests.cc        |   37 +
 src/lib/datasrc/client_list.cc                     |    5 +-
 src/lib/datasrc/client_list.h                      |    7 +-
 src/lib/datasrc/memory/domaintree.h                |  238 +++-
 src/lib/datasrc/static.zone.pre                    |   12 +-
 src/lib/datasrc/tests/client_list_unittest.cc      |   10 +-
 .../datasrc/tests/memory/domaintree_unittest.cc    |  114 +-
 src/lib/dhcp/iface_mgr.cc                          |    9 +-
 src/lib/dhcp/iface_mgr.h                           |    8 +
 src/lib/dhcp/option6_client_fqdn.h                 |    4 +-
 src/lib/dhcp/pkt4.cc                               |   91 +-
 src/lib/dhcp/pkt4.h                                |    4 +-
 src/lib/dhcp/pkt6.cc                               |   20 +-
 src/lib/dhcp/pkt6.h                                |   45 +-
 src/lib/dhcp/tests/iface_mgr_unittest.cc           |    6 +-
 src/lib/dhcp/tests/pkt6_unittest.cc                |    8 +-
 src/lib/dhcp_ddns/.gitignore                       |    2 +
 src/lib/dhcp_ddns/Makefile.am                      |   54 +
 .../d2_log.cc => lib/dhcp_ddns/dhcp_ddns_log.cc}   |   12 +-
 .../d2/d2_log.h => lib/dhcp_ddns/dhcp_ddns_log.h}  |   17 +-
 src/lib/dhcp_ddns/dhcp_ddns_messages.mes           |   63 +
 src/lib/dhcp_ddns/libdhcp_ddns.dox                 |   47 +
 src/lib/dhcp_ddns/ncr_io.cc                        |  283 ++++
 src/lib/dhcp_ddns/ncr_io.h                         |  638 +++++++++
 src/{bin/d2 => lib/dhcp_ddns}/ncr_msg.cc           |   28 +-
 src/{bin/d2 => lib/dhcp_ddns}/ncr_msg.h            |   90 +-
 src/lib/dhcp_ddns/ncr_udp.cc                       |  299 ++++
 src/lib/dhcp_ddns/ncr_udp.h                        |  562 ++++++++
 src/lib/dhcp_ddns/tests/.gitignore                 |    1 +
 src/lib/dhcp_ddns/tests/Makefile.am                |   61 +
 src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc       |  498 +++++++
 .../d2 => lib/dhcp_ddns}/tests/ncr_unittests.cc    |   19 +-
 src/lib/{dhcp => dhcp_ddns}/tests/run_unittests.cc |    0
 src/lib/dhcpsrv/Makefile.am                        |    2 +
 src/lib/dhcpsrv/alloc_engine.cc                    |  205 ++-
 src/lib/dhcpsrv/alloc_engine.h                     |   30 +-
 src/lib/dhcpsrv/cfgmgr.cc                          |   55 +-
 src/lib/dhcpsrv/cfgmgr.h                           |   92 ++
 src/lib/dhcpsrv/dhcp_parsers.cc                    |  206 ++-
 src/lib/dhcpsrv/dhcp_parsers.h                     |  154 ++-
 src/lib/dhcpsrv/dhcpsrv_log.h                      |    3 +
 src/lib/dhcpsrv/dhcpsrv_messages.mes               |   26 +
 src/lib/dhcpsrv/tests/Makefile.am                  |    1 +
 src/lib/dhcpsrv/tests/alloc_engine_unittest.cc     |  504 ++++++-
 src/lib/dhcpsrv/tests/cfgmgr_unittest.cc           |   45 +
 src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc     |  128 +-
 src/lib/dhcpsrv/tests/lease_mgr_unittest.cc        |    4 +
 src/lib/dhcpsrv/tests/test_libraries.h             |   51 +
 src/lib/hooks/.gitignore                           |    2 +
 src/lib/hooks/callout_manager.cc                   |   33 +-
 src/lib/hooks/callout_manager.h                    |   31 +-
 src/lib/hooks/hook_user.dox                        | 1031 ++++++++++++++
 src/lib/hooks/hooks_manager.cc                     |   31 +-
 src/lib/hooks/hooks_manager.h                      |   40 +
 src/lib/hooks/hooks_messages.mes                   |  101 +-
 src/lib/hooks/images/DataScopeArgument.dia         |  Bin 0 -> 1887 bytes
 src/lib/hooks/images/DataScopeArgument.png         |  Bin 0 -> 11672 bytes
 src/lib/hooks/images/DataScopeContext.dia          |  Bin 0 -> 2161 bytes
 src/lib/hooks/images/DataScopeContext.png          |  Bin 0 -> 14180 bytes
 src/lib/hooks/library_manager.cc                   |  115 +-
 src/lib/hooks/library_manager.h                    |   59 +-
 src/lib/hooks/library_manager_collection.cc        |   41 +-
 src/lib/hooks/library_manager_collection.h         |   45 +-
 src/lib/hooks/server_hooks.cc                      |   27 +-
 src/lib/hooks/server_hooks.h                       |   16 +-
 src/lib/hooks/tests/.gitignore                     |    4 +
 src/lib/hooks/tests/callout_manager_unittest.cc    |    4 -
 src/lib/hooks/tests/handles_unittest.cc            |   18 +
 src/lib/hooks/tests/hooks_manager_unittest.cc      |  124 +-
 .../tests/library_manager_collection_unittest.cc   |  143 +-
 src/lib/hooks/tests/library_manager_unittest.cc    |   14 +
 src/lib/log/Makefile.am                            |    1 -
 src/lib/log/dummylog.cc                            |   37 -
 src/lib/log/dummylog.h                             |   61 -
 src/lib/python/isc/log_messages/Makefile.am        |    2 +
 .../python/isc/log_messages/libmemmgr_messages.py  |    1 +
 src/lib/python/isc/memmgr/Makefile.am              |   17 +-
 src/lib/python/isc/memmgr/builder.py               |  112 +-
 src/lib/python/isc/memmgr/datasrc_info.py          |  201 ++-
 src/lib/python/isc/memmgr/libmemmgr_messages.mes   |   35 +
 .../python/isc/{server_common => memmgr}/logger.py |    6 +-
 src/lib/python/isc/memmgr/tests/Makefile.am        |    4 +-
 src/lib/python/isc/memmgr/tests/builder_tests.py   |  131 +-
 .../python/isc/memmgr/tests/datasrc_info_tests.py  |  285 +++-
 .../python/isc/memmgr/tests/testdata/Makefile.am   |    2 +
 .../tests/testdata/example.com.zone}               |    0
 src/lib/python/isc/notify/notify_out.py            |    2 +-
 src/lib/python/isc/statistics/Makefile.am          |    2 +-
 src/lib/python/isc/statistics/counters.py          |  192 +--
 src/lib/python/isc/statistics/dns.py               |  225 +++
 src/lib/python/isc/statistics/tests/Makefile.am    |    2 +-
 .../python/isc/statistics/tests/counters_test.py   |  225 +--
 .../tests/{counters_test.py => dns_test.py}        |  208 +--
 src/lib/resolve/tests/recursive_query_unittest.cc  |   10 +-
 .../resolve/tests/recursive_query_unittest_2.cc    |    2 +-
 .../resolve/tests/recursive_query_unittest_3.cc    |    2 +-
 .../util/tests/memory_segment_mapped_unittest.cc   |    9 +
 tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc   |   10 +-
 .../tools/perfdhcp/tests/test_control_unittest.cc  |   15 +-
 195 files changed, 15106 insertions(+), 2507 deletions(-)
 create mode 100644 src/bin/d2/d2_queue_mgr.cc
 create mode 100644 src/bin/d2/d2_queue_mgr.h
 create mode 100644 src/bin/d2/d2_update_mgr.cc
 create mode 100644 src/bin/d2/d2_update_mgr.h
 create mode 100644 src/bin/d2/tests/d2_queue_mgr_unittests.cc
 create mode 100644 src/bin/d2/tests/d2_update_mgr_unittests.cc
 create mode 100644 src/bin/dhcp4/dhcp4_hooks.dox
 create mode 100644 src/bin/dhcp4/tests/marker_file.h
 create mode 100644 src/bin/dhcp4/tests/test_libraries.h
 create mode 100644 src/bin/dhcp6/dhcp6_hooks.dox
 create mode 100644 src/bin/dhcp6/tests/dhcp6_test_utils.h
 create mode 100644 src/bin/dhcp6/tests/hooks_unittest.cc
 create mode 100644 src/bin/dhcp6/tests/marker_file.h
 create mode 100644 src/bin/dhcp6/tests/test_libraries.h
 create mode 100644 src/lib/dhcp_ddns/.gitignore
 create mode 100644 src/lib/dhcp_ddns/Makefile.am
 copy src/{bin/d2/d2_log.cc => lib/dhcp_ddns/dhcp_ddns_log.cc} (75%)
 copy src/{bin/d2/d2_log.h => lib/dhcp_ddns/dhcp_ddns_log.h} (77%)
 create mode 100644 src/lib/dhcp_ddns/dhcp_ddns_messages.mes
 create mode 100644 src/lib/dhcp_ddns/libdhcp_ddns.dox
 create mode 100644 src/lib/dhcp_ddns/ncr_io.cc
 create mode 100644 src/lib/dhcp_ddns/ncr_io.h
 rename src/{bin/d2 => lib/dhcp_ddns}/ncr_msg.cc (95%)
 rename src/{bin/d2 => lib/dhcp_ddns}/ncr_msg.h (86%)
 create mode 100644 src/lib/dhcp_ddns/ncr_udp.cc
 create mode 100644 src/lib/dhcp_ddns/ncr_udp.h
 create mode 100644 src/lib/dhcp_ddns/tests/.gitignore
 create mode 100644 src/lib/dhcp_ddns/tests/Makefile.am
 create mode 100644 src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
 rename src/{bin/d2 => lib/dhcp_ddns}/tests/ncr_unittests.cc (97%)
 copy src/lib/{dhcp => dhcp_ddns}/tests/run_unittests.cc (100%)
 create mode 100644 src/lib/dhcpsrv/tests/test_libraries.h
 create mode 100644 src/lib/hooks/.gitignore
 create mode 100644 src/lib/hooks/hook_user.dox
 create mode 100644 src/lib/hooks/images/DataScopeArgument.dia
 create mode 100644 src/lib/hooks/images/DataScopeArgument.png
 create mode 100644 src/lib/hooks/images/DataScopeContext.dia
 create mode 100644 src/lib/hooks/images/DataScopeContext.png
 create mode 100644 src/lib/hooks/tests/.gitignore
 delete mode 100644 src/lib/log/dummylog.cc
 delete mode 100644 src/lib/log/dummylog.h
 create mode 100644 src/lib/python/isc/log_messages/libmemmgr_messages.py
 create mode 100644 src/lib/python/isc/memmgr/libmemmgr_messages.mes
 copy src/lib/python/isc/{server_common => memmgr}/logger.py (81%)
 create mode 100644 src/lib/python/isc/memmgr/tests/testdata/Makefile.am
 copy src/lib/python/isc/{datasrc/tests/testdata/example.com => memmgr/tests/testdata/example.com.zone} (100%)
 create mode 100644 src/lib/python/isc/statistics/dns.py
 copy src/lib/python/isc/statistics/tests/{counters_test.py => dns_test.py} (55%)

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 1354073..53e650c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,96 @@
+656.	[func]		tomek
+	Additional hooks (buffer6_receive, lease6_renew,
+	lease6_release, buffer6_send) added to the DHCPv6 server.
+	(Trac #2984, git 540dd0449121094a56f294c500c2ed811f6016b6)
+
+655.	[func]		tmark
+	Added D2UpdateMgr class to b10-dhcp-ddns. This class is the b10-dhcp-ddns
+	task master, instantiating and supervising transactions that carry out the
+	DNS updates needed to fulfill the requests (NameChangeRequests) received
+	from b10-dhcp-ddns clients (e.g. DHCP servers).
+	(Trac #3059 git d72675617d6b60e3eb6160305738771f015849ba)
+
+654.	[bug]		stephen
+	Always clear "skip" flag before calling any callouts on a hook.
+	(Trac# 3050, git ff0b9b45869b1d9a4b99e785fbce421e184c2e93)
+
+653.	[func]		tmark
+	Added initial implementation of D2QueueMgr to
+	b10-dhcp-ddns.  This class manages the receipt and
+	queueing of requests received by b10-dhcp-ddns from
+	its clients (e.g. DHCP servers)
+	(Trac# 3052, git a970f6c5255e000c053a2dc47926cea7cec2761c)
+
+652.	[doc]		stephen
+	Added the "Hook Developer's Guide" to the BIND 10 developer
+	documentation.
+	(Trac# 2982, git 26a805c7e49a9ec85ee825f179cda41a2358f4c6)
+
+651.	[bug]		muks
+	A race condition when creating cmdctl certificates caused corruption
+	of these certificates in rare cases. This has now been fixed.
+	(Trac# 2962, git 09f557d871faef090ed444ebeee7f13e142184a0)
+
+650.	[func]		muks
+	The DomainTree rebalancing code has been updated to be more
+	understandable. This ChangeLog entry is made just to make a note
+	of this change. The change should not cause any observable
+	difference whatsoever.
+	(Trac# 2811, git 7c0bad1643af13dedf9356e9fb3a51264b7481de)
+
+649.	[func]		muks
+	The default b10-xfrout also_notify port has been changed from
+	0 to 53.
+	(Trac# 2925, git 8acbf043daf590a9f2ad003e715cd4ffb0b3f979)
+
+648.	[func]		tmark
+	Moved classes pertaining to sending and receiving
+	NameChangeRequests from src/bin/d2 into their own library,
+	libdhcp_ddns, in src/lib/dhcp_ddns.  This allows the
+	classes to be shared between DHDCP-DDNS and its clients,
+	such as the DHCP servers.
+	(Trac# 3065, git 3d39bccaf3f0565152ef73ec3e2cd03e77572c56)
+
+647.	[func]		tmark
+	Added initial implementation of classes for sending
+	and receiving NameChangeRequests between DHCP-DDNS
+	and its clients such as DHCP. This includes both
+	abstract classes and a derivation which traffics
+	requests across UDP sockets.
+	(Trac #3008, git b54530b4539cec4476986442e72c047dddba7b48)
+
+646.	[func]		stephen
+	Extended the hooks framework to add a "validate libraries" function.
+	This will be used to check libraries specified during BIND 10
+	configuration.
+	(Trac #3054, git 0f845ed94f462dee85b67f056656b2a197878b04)
+
+645.	[func]		tomek
+	Added initial set of hooks (pkt4_receive, subnet4_select,
+	lease4_select, pkt4_send) to the DHCPv6 server.
+	(Trac #2994, git be65cfba939a6a7abd3c93931ce35c33d3e8247b)
+
+644.	[func]		marcin
+	b10-dhcp4, b10-dhcp6: Implemented selection of the interfaces
+	that server listens on, using Configuration Manager. It is
+	possible to specify interface names explicitly or use asterisk
+	to specify that server should listen on all available interfaces.
+	Sockets are reopened according to the new configuration as
+	soon as it is committed.
+	(Trac #1555, git f48a3bff3fbbd15584d788a264d5966154394f04)
+
+643.	[bug]		muks
+	When running some unittests as root that depended on insufficient
+	file permissions, the tests used to fail because the root user
+	could still access such files. Such tests are now skipped when
+	they are run as the root user.
+	(Trac #3056, git 92ebabdbcf6168666b03d7f7fbb31f899be39322)
+
+642.	[func]		tomek
+	Added initial set of hooks (pkt6_receive, subnet6_select,
+	lease6_select, pkt6_send) to the DHCPv6 server.
+	(Trac #2995, git d6de376f97313ba40fef989e4a437d184fdf70cc)
+
 641.	[func]		stephen
 	Added the hooks framework. This allows shared libraries of
 	user-written functions to be loaded at run-time and the
@@ -23,13 +116,13 @@
 	structure of per-zone statistics.
 	(Trac #2884, git c0153581c3533ef045a92e68e0464aab00947cbb)
 
-637.	[func]		[tmark]
+637.	[func]		tmark
 	Added initial implementation of NameChangeRequest,
 	which embodies DNS update requests sent to DHCP-DDNS
 	by its clients.
 	(trac3007 git f33bdd59c6a8c8ea883f11578b463277d01c2b70)
 
-636.	[func]		[tmark]
+636.	[func]		tmark
 	Added the initial implementation of configuration parsing for
 	DCHP-DDNS.
 	(Trac #2957, git c04fb71fa44c2a458aac57ae54eeb1711c017a49)
diff --git a/configure.ac b/configure.ac
index 72a825e..41b0900 100644
--- a/configure.ac
+++ b/configure.ac
@@ -129,7 +129,7 @@ AC_SUBST(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
 
 # gcc specific settings:
 if test "X$GXX" = "Xyes"; then
-B10_CXXFLAGS="$B10_CXXFLAGS -Wall -Wextra -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare"
+B10_CXXFLAGS="$B10_CXXFLAGS -Wall -Wextra -Wnon-virtual-dtor -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare"
 case "$host" in
 *-solaris*)
 	MULTITHREADING_FLAG=-pthreads
@@ -912,6 +912,17 @@ if test "x$use_shared_memory" = "xyes"; then
 fi
 AC_SUBST(BOOST_MAPPED_FILE_CXXFLAG)
 
+if test "$BOOST_OFFSET_PTR_OLD" = "yes" -a "$use_shared_memory" = "yes" ; then
+    AC_MSG_ERROR([You're trying to compile against boost older than 1.48 with
+shared memory. Older versions of boost have a bug which causes segfaults in
+offset_ptr implementation when compiled by GCC with optimisations enabled.
+See ticket no. 3025 for details.
+
+Either update boost to newer version or use --without-shared-memory.
+Note that most users likely don't need shared memory support.
+])
+fi
+
 # Add some default CPP flags needed for Boost, identified by the AX macro.
 CPPFLAGS="$CPPFLAGS $CPPFLAGS_BOOST_THREADCONF"
 
@@ -1132,6 +1143,11 @@ if test "x$enable_generate_docs" != xno ; then
     fi
     AC_MSG_RESULT(yes)
   fi
+
+  AC_PATH_PROG([ELINKS], [elinks])
+  if test -z "$ELINKS"; then
+    AC_MSG_ERROR("elinks not found; it is required for --enable-generate-docs")
+  fi
 fi
 
 
@@ -1295,6 +1311,7 @@ AC_CONFIG_FILES([Makefile
                  src/lib/python/isc/ddns/tests/Makefile
                  src/lib/python/isc/memmgr/Makefile
                  src/lib/python/isc/memmgr/tests/Makefile
+                 src/lib/python/isc/memmgr/tests/testdata/Makefile
                  src/lib/python/isc/xfrin/Makefile
                  src/lib/python/isc/xfrin/tests/Makefile
                  src/lib/python/isc/server_common/Makefile
@@ -1316,6 +1333,8 @@ AC_CONFIG_FILES([Makefile
                  src/lib/dns/benchmarks/Makefile
                  src/lib/dhcp/Makefile
                  src/lib/dhcp/tests/Makefile
+                 src/lib/dhcp_ddns/Makefile
+                 src/lib/dhcp_ddns/tests/Makefile
                  src/lib/dhcpsrv/Makefile
                  src/lib/dhcpsrv/tests/Makefile
                  src/lib/exceptions/Makefile
diff --git a/doc/Doxyfile b/doc/Doxyfile
index 57c6ce1..9967df2 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -661,36 +661,37 @@ WARN_LOGFILE           =
 # directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 
-INPUT                  = ../src/lib/exceptions \
+INPUT                  = ../src/bin/auth \
+                         ../src/bin/d2 \
+                         ../src/bin/dhcp4 \
+                         ../src/bin/dhcp6 \
+                         ../src/bin/resolver \
+                         ../src/bin/sockcreator \
+                         ../src/lib/acl \
+                         ../src/lib/asiolink \
+                         ../src/lib/bench \
+                         ../src/lib/cache \
                          ../src/lib/cc \
                          ../src/lib/config \
                          ../src/lib/cryptolink \
-                         ../src/lib/dns \
                          ../src/lib/datasrc \
                          ../src/lib/datasrc/memory \
-                         ../src/bin/auth \
-                         ../src/bin/resolver \
-                         ../src/lib/bench \
+                         ../src/lib/dhcp \
+                         ../src/lib/dhcp_ddns \
+                         ../src/lib/dhcpsrv \
+                         ../src/lib/dns \
+                         ../src/lib/exceptions \
+                         ../src/lib/hooks \
                          ../src/lib/log \
                          ../src/lib/log/compiler \
-                         ../src/lib/asiolink/ \
                          ../src/lib/nsas \
-                         ../src/lib/testutils \
-                         ../src/lib/cache \
-                         ../src/lib/server_common/ \
-                         ../src/bin/sockcreator/ \
-                         ../src/lib/hooks/ \
-                         ../src/lib/util/ \
-                         ../src/lib/util/io/ \
-                         ../src/lib/util/threads/ \
                          ../src/lib/resolve \
-                         ../src/lib/acl \
+                         ../src/lib/server_common \
                          ../src/lib/statistics \
-                         ../src/bin/dhcp6 \
-                         ../src/lib/dhcp \
-                         ../src/lib/dhcpsrv \
-                         ../src/bin/dhcp4 \
-                         ../src/bin/d2 \
+                         ../src/lib/testutils \
+                         ../src/lib/util \
+                         ../src/lib/util/io \
+                         ../src/lib/util/threads \
                          ../tests/tools/perfdhcp \
                          devel
 
@@ -777,7 +778,7 @@ EXAMPLE_RECURSIVE      = NO
 # directories that contain image that are included in the documentation (see
 # the \image command).
 
-IMAGE_PATH             = ../doc/images
+IMAGE_PATH             = ../doc/images ../src/lib/hooks/images
 
 # The INPUT_FILTER tag can be used to specify a program that doxygen should
 # invoke to filter for each input file. Doxygen will invoke the filter program
diff --git a/doc/design/datasrc/data-source-classes.txt b/doc/design/datasrc/data-source-classes.txt
index ac7e5a9..0f2dcbb 100644
--- a/doc/design/datasrc/data-source-classes.txt
+++ b/doc/design/datasrc/data-source-classes.txt
@@ -236,7 +236,8 @@ image::auth-mapped.png[Sequence diagram for auth server using mapped memory segm
    argument and the segment mode of `READ_ONLY`.
    Note that the auth module handles the command argument as mostly
    opaque data; it's not expected to deal with details of segment
-   type-specific behavior.
+   type-specific behavior. If the reset fails, auth aborts (as there's
+   no clear way to handle the failure).
 
 6. `ConfigurableClientList::resetMemorySegment()` subsequently calls
    `reset()` method on the corresponding `ZoneTableSegment` with the
@@ -254,7 +255,8 @@ image::auth-mapped.png[Sequence diagram for auth server using mapped memory segm
    underlying memory segment is swapped with a new one.  The old
    memory segment object is destroyed.  Note that
    this "destroy" just means unmapping the memory region; the data
-   stored in the file are intact.
+   stored in the file are intact. Again, if mapping fails, auth
+   aborts.
 
 8. If the auth module happens to receive a reload command from other
    module, it could call
diff --git a/doc/devel/mainpage.dox b/doc/devel/mainpage.dox
index 5c8efa2..685d9e6 100644
--- a/doc/devel/mainpage.dox
+++ b/doc/devel/mainpage.dox
@@ -36,6 +36,9 @@
  * Regardless of your field of expertise, you are encouraged to visit
  * <a href="http://bind10.isc.org/">BIND10 webpage (http://bind10.isc.org)</a>
  * @section hooksFramework Hooks Framework
+ * - @subpage hooksdgDevelopersGuide
+ * - @subpage dhcpv4Hooks
+ * - @subpage dhcpv6Hooks
  * - @subpage hooksComponentDeveloperGuide
  *
  * @section dnsMaintenanceGuide DNS Maintenance Guide
@@ -48,10 +51,12 @@
  *   - @subpage dhcpv4Session
  *   - @subpage dhcpv4ConfigParser
  *   - @subpage dhcpv4ConfigInherit
+ *   - @subpage dhcpv4Other
  * - @subpage dhcp6
  *   - @subpage dhcpv6Session
  *   - @subpage dhcpv6ConfigParser
  *   - @subpage dhcpv6ConfigInherit
+ *   - @subpage dhcpv6Other
  * - @subpage libdhcp
  *   - @subpage libdhcpIntro
  *   - @subpage libdhcpRelay
@@ -62,6 +67,7 @@
  *   - @subpage allocengine
  * - @subpage dhcpDatabaseBackends
  * - @subpage perfdhcpInternals
+ * - @subpage libdhcp_ddns
  *
  * @section miscellaneousTopics Miscellaneous topics
  * - @subpage LoggingApi
diff --git a/doc/guide/.gitignore b/doc/guide/.gitignore
index 168d4ed..fc2510e 100644
--- a/doc/guide/.gitignore
+++ b/doc/guide/.gitignore
@@ -1,3 +1,4 @@
 /bind10-guide.html
 /bind10-guide.txt
 /bind10-messages.html
+/bind10-messages.xml
diff --git a/doc/guide/Makefile.am b/doc/guide/Makefile.am
index 1d63c04..8f3aaaf 100644
--- a/doc/guide/Makefile.am
+++ b/doc/guide/Makefile.am
@@ -21,10 +21,8 @@ bind10-guide.html: bind10-guide.xml
 		http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl \
 		$(srcdir)/bind10-guide.xml
 
-HTML2TXT = elinks -dump -no-numbering -no-references
-
 bind10-guide.txt: bind10-guide.html
-	$(HTML2TXT) bind10-guide.html > $@
+	@ELINKS@ -dump -no-numbering -no-references bind10-guide.html > $@
 
 bind10-messages.html: bind10-messages.xml
 	@XSLTPROC@ --novalid --xinclude --nonet \
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 88f2536..00f7864 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -427,7 +427,7 @@ var/
         <listitem>
           <para>Go into the source and run configure:
             <screen>$ <userinput>cd bind10-<replaceable>VERSION</replaceable></userinput>
-  $ <userinput>./configure</userinput></screen>
+$ <userinput>./configure</userinput></screen>
           </para>
         </listitem>
 
@@ -438,21 +438,29 @@ var/
         </listitem>
 
         <listitem>
-          <para>Install it as root (to default /usr/local):
+          <para>Install it as root (by default to prefix
+          <filename>/usr/local/</filename>):
             <screen>$ <userinput>make install</userinput></screen>
           </para>
         </listitem>
 
         <listitem>
+          <para>Change directory to the install prefix (by default
+          <filename>/usr/local/</filename>):
+            <screen>$ <userinput>cd /usr/local/</userinput></screen>
+          </para>
+        </listitem>
+
+        <listitem>
           <para>Create a user for yourself:
-            <screen>$ <userinput>cd /usr/local/etc/bind10/</userinput></screen>
-            <screen>$ <userinput>/usr/local/sbin/b10-cmdctl-usermgr</userinput></screen>
+            <screen>$ <userinput>sbin/b10-cmdctl-usermgr add root</userinput></screen>
+	  and enter a newly chosen password when prompted.
           </para>
         </listitem>
 
         <listitem>
           <para>Start the server (as root):
-            <screen>$ <userinput>/usr/local/sbin/bind10</userinput></screen>
+            <screen>$ <userinput>sbin/bind10</userinput></screen>
           </para>
         </listitem>
 
@@ -461,7 +469,7 @@ var/
 	    configuration.  In another console, enable the authoritative
 	    DNS service (by using the <command>bindctl</command> utility
 	    to configure the <command>b10-auth</command> component to
-	    run): <screen>$ <userinput>bindctl</userinput></screen>
+	    run): <screen>$ <userinput>bin/bindctl</userinput></screen>
 	    (Login with the username and password you used above to create a user.)
             <screen>
 > <userinput>config add Init/components b10-auth</userinput>
@@ -481,7 +489,7 @@ var/
 
         <listitem>
           <para>Load desired zone file(s), for example:
-            <screen>$ <userinput>b10-loadzone <replaceable>-c '{"database_file": "/usr/local/var/bind10/zone.sqlite3"}'</replaceable> <replaceable>your.zone.example.org</replaceable> <replaceable>your.zone.file</replaceable></userinput></screen>
+            <screen>$ <userinput>bin/b10-loadzone <replaceable>-c '{"database_file": "/usr/local/var/bind10/zone.sqlite3"}'</replaceable> <replaceable>your.zone.example.org</replaceable> <replaceable>your.zone.file</replaceable></userinput></screen>
           </para>
 	  (If you use the sqlite3 data source with the default DB
 	  file, you can omit the -c option).
@@ -2601,21 +2609,26 @@ can use various data source backends.
 > <userinput>config set data_sources/classes/IN[1]/params { "example.org": "/path/to/example.org", "example.com": "/path/to/example.com" }</userinput>
 > <userinput>config commit</userinput></screen>
 
+          Unfortunately, due to current technical limitations, the
+          params must be set as one JSON blob. To reload a zone, use the
+          same <command>Auth loadzone</command> command as above.
+	</para>
+
+	<para>
           Initially, a map value has to be set, but this value may be an
-          empty map. After that, key/value pairs can be added with 'config
-          add' and keys can be removed with 'config remove'. The initial
-          value may be an empty map, but it has to be set before zones are
-          added or removed.
+          empty map. After that, key/value pairs can be added with
+          <command>config add</command> and keys can be removed with
+          <command>config remove</command>. The initial value may be an
+          empty map, but it has to be set before zones are added or
+          removed.
 
           <screen>
 > <userinput>config set data_sources/classes/IN[1]/params {}</userinput>
 > <userinput>config add data_sources/classes/IN[1]/params another.example.org /path/to/another.example.org</userinput>
 > <userinput>config add data_sources/classes/IN[1]/params another.example.com /path/to/another.example.com</userinput>
 > <userinput>config remove data_sources/classes/IN[1]/params another.example.org</userinput>
-          </screen>
+> <userinput>config commit</userinput></screen>
 
-          <command>bindctl</command>. To reload a zone, you the same command
-          as above.
         </para>
       </section>
 
@@ -2634,6 +2647,50 @@ can use various data source backends.
       </para>
       </note>
 
+      <section id='datasrc-static'>
+        <title>Adding a static data source</title>
+
+	<para>
+	  BIND 10 includes a zone file named
+	  <filename>static.zone</filename> in the CH (Chaos) class for
+	  providing information about the server via the AUTHORS.BIND
+	  and VERSION.BIND TXT records. By default, this BIND zone is
+	  configured and its records are served.
+	</para>
+
+        <para>
+          If you have removed this zone from the configuration (e.g., by
+          using the commands in the previous section to disable the
+          "built-in data source"), here is how you can add it back to
+          serve the zones in the <filename>static.zone</filename> file.
+        </para>
+
+	<para>First, add the CH class if it doesn't exist:
+
+          <screen>> <userinput>config add data_sources/classes CH</userinput>
+> <userinput>config commit</userinput></screen>
+
+        Then, add a data source of type <emphasis>MasterFiles</emphasis>
+        in the CH class to serve the zones in
+        <filename>static.zone</filename>:
+
+          <screen>> <userinput>config add data_sources/classes/CH</userinput>
+> <userinput>config set data_sources/classes/CH[0]/type MasterFiles</userinput>
+> <userinput>config set data_sources/classes/CH[0]/cache-enable true</userinput>
+> <userinput>config set data_sources/classes/CH[0]/params {"BIND": "/usr/local/bind10/share/bind10/static.zone"}</userinput>
+> <userinput>config commit</userinput></screen>
+
+        Then, lookup the static data from
+        <filename>static.zone</filename> to test it (assuming your
+        authoritative server is running on <command>localhost</command>):
+
+          <screen>> <userinput>dig @localhost -c CH -t TXT version.bind</userinput>
+> <userinput>dig @localhost -c CH -t TXT authors.bind</userinput></screen>
+
+	</para>
+
+      </section>
+
     </section>
 
     <section>
@@ -3599,7 +3656,7 @@ $</screen>
         will be available. It will look similar to this:
 <screen>
 > <userinput>config show Dhcp4</userinput>
-Dhcp4/interface/	list	(default)
+Dhcp4/interfaces/	list	(default)
 Dhcp4/renew-timer	1000	integer	(default)
 Dhcp4/rebind-timer	2000	integer	(default)
 Dhcp4/valid-lifetime	4000	integer	(default)
@@ -3686,6 +3743,60 @@ Dhcp4/subnet4	[]	list	(default)
       </note>
       </section>
 
+      <section id="dhcp4-interface-selection">
+      <title>Interface selection</title>
+      <para>
+        When DHCPv4 server starts up, by default it will listen to the DHCP
+        traffic and respond to it on all interfaces detected during startup.
+        However, in many cases it is desired to configure the server to listen and
+        respond on selected interfaces only. The sample commands in this section
+        show how to make interface selection using bindctl.
+      </para>
+      <para>
+        The default configuration can be presented with the following command:
+        <screen>
+> <userinput>config show Dhcp4/interfaces</userinput>
+<userinput>Dhcp4/interfaces[0] "*" string</userinput></screen>
+        An asterisk sign plays a role of the wildcard and means "listen on all interfaces".
+      </para>
+      <para>
+        In order to override the default configuration, the existing entry can be replaced
+        with the actual interface name:
+        <screen>
+> <userinput>config set Dhcp4/interfaces[0] eth1</userinput>
+> <userinput>config commit</userinput></screen>
+        Other interface names can be added on one-by-one basis:
+        <screen>
+> <userinput>config add Dhcp4/interfaces eth2</userinput>
+> <userinput>config commit</userinput></screen>
+        Configuration will now contain two interfaces which can be presented as follows:
+        <screen>
+> <userinput>config show Dhcp4/interfaces</userinput>
+<userinput>Dhcp4/interfaces[0]	"eth1"	string</userinput>
+<userinput>Dhcp4/interfaces[1]	"eth2"	string</userinput></screen>
+        When configuration gets committed, the server will start to listen on
+        eth1 and eth2 interfaces only.
+      </para>
+      <para>
+        It is possible to use wildcard interface name (asterisk) concurrently with explicit
+        interface names:
+        <screen>
+> <userinput>config add Dhcp4/interfaces *</userinput>
+> <userinput>config commit</userinput></screen>
+        This will result in the following configuration:
+        <screen>
+> <userinput>config show Dhcp4/interfaces</userinput>
+<userinput>Dhcp4/interfaces[0]	"eth1"	string</userinput>
+<userinput>Dhcp4/interfaces[1]	"eth2"	string</userinput>
+<userinput>Dhcp4/interfaces[2]	"*"	string</userinput></screen>
+        The presence of the wildcard name implies that server will listen on all interfaces.
+        In order to fall back to the previous configuration when server listens on eth1 and eth2:
+        <screen>
+> <userinput>config remove Dhcp4/interfaces[2]</userinput>
+> <userinput>config commit</userinput></screen>
+      </para>
+      </section>
+
       <section id="dhcp4-address-config">
       <title>Configuration of Address Pools</title>
       <para>
@@ -3830,7 +3941,10 @@ Dhcp4/subnet4	[]	list	(default)
       </note>
 
       <para>
-        Below is a list of currently supported standard DHCPv4 options. The "Name" and "Code"
+        The currently supported standard DHCPv4 options are
+        listed in <xref linkend="dhcp4-std-options-list"/>
+        and <xref linkend="dhcp4-std-options-list-part2"/>.
+        The "Name" and "Code"
         are the values that should be used as a name in the option-data
         structures. "Type" designates the format of the data: the meanings of
         the various types is given in <xref linkend="dhcp-types"/>.
@@ -3844,115 +3958,155 @@ Dhcp4/subnet4	[]	list	(default)
       <!-- @todo: describe record types -->
 
       <para>
-        <table border="1" cellpadding="5%" id="dhcp4-std-options-list">
-          <caption>List of standard DHCPv4 options</caption>
+        <table frame="all" id="dhcp4-std-options-list">
+          <title>List of standard DHCPv4 options</title>
+          <tgroup cols='4'>
+          <colspec colname='name'/>
+          <colspec colname='code'/>
+          <colspec colname='type'/>
+          <colspec colname='array'/>
           <thead>
-            <tr><th>Name</th><th>Code</th><th>Type</th><th>Array?</th></tr>
+            <row>
+              <entry>Name</entry>
+              <entry>Code</entry>
+              <entry>Type</entry>
+              <entry>Array?</entry>
+            </row>
           </thead>
           <tbody>
-<tr><td>subnet-mask</td><td>1</td><td>ipv4-address</td><td>false</td></tr>
-<tr><td>time-offset</td><td>2</td><td>uint32</td><td>false</td></tr>
-<tr><td>routers</td><td>3</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>time-servers</td><td>4</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>name-servers</td><td>5</td><td>ipv4-address</td><td>false</td></tr>
-<tr><td>domain-name-servers</td><td>6</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>log-servers</td><td>7</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>cookie-servers</td><td>8</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>lpr-servers</td><td>9</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>impress-servers</td><td>10</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>resource-location-servers</td><td>11</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>host-name</td><td>12</td><td>string</td><td>false</td></tr>
-<tr><td>boot-size</td><td>13</td><td>uint16</td><td>false</td></tr>
-<tr><td>merit-dump</td><td>14</td><td>string</td><td>false</td></tr>
-<tr><td>domain-name</td><td>15</td><td>fqdn</td><td>false</td></tr>
-<tr><td>swap-server</td><td>16</td><td>ipv4-address</td><td>false</td></tr>
-<tr><td>root-path</td><td>17</td><td>string</td><td>false</td></tr>
-<tr><td>extensions-path</td><td>18</td><td>string</td><td>false</td></tr>
-<tr><td>ip-forwarding</td><td>19</td><td>boolean</td><td>false</td></tr>
-<tr><td>non-local-source-routing</td><td>20</td><td>boolean</td><td>false</td></tr>
-<tr><td>policy-filter</td><td>21</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>max-dgram-reassembly</td><td>22</td><td>uint16</td><td>false</td></tr>
-<tr><td>default-ip-ttl</td><td>23</td><td>uint8</td><td>false</td></tr>
-<tr><td>path-mtu-aging-timeout</td><td>24</td><td>uint32</td><td>false</td></tr>
-<tr><td>path-mtu-plateau-table</td><td>25</td><td>uint16</td><td>true</td></tr>
-<tr><td>interface-mtu</td><td>26</td><td>uint16</td><td>false</td></tr>
-<tr><td>all-subnets-local</td><td>27</td><td>boolean</td><td>false</td></tr>
-<tr><td>broadcast-address</td><td>28</td><td>ipv4-address</td><td>false</td></tr>
-<tr><td>perform-mask-discovery</td><td>29</td><td>boolean</td><td>false</td></tr>
-<tr><td>mask-supplier</td><td>30</td><td>boolean</td><td>false</td></tr>
-<tr><td>router-discovery</td><td>31</td><td>boolean</td><td>false</td></tr>
-<tr><td>router-solicitation-address</td><td>32</td><td>ipv4-address</td><td>false</td></tr>
-<tr><td>static-routes</td><td>33</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>trailer-encapsulation</td><td>34</td><td>boolean</td><td>false</td></tr>
-<tr><td>arp-cache-timeout</td><td>35</td><td>uint32</td><td>false</td></tr>
-<tr><td>ieee802-3-encapsulation</td><td>36</td><td>boolean</td><td>false</td></tr>
-<tr><td>default-tcp-ttl</td><td>37</td><td>uint8</td><td>false</td></tr>
-<tr><td>tcp-keepalive-internal</td><td>38</td><td>uint32</td><td>false</td></tr>
-<tr><td>tcp-keepalive-garbage</td><td>39</td><td>boolean</td><td>false</td></tr>
-<tr><td>nis-domain</td><td>40</td><td>string</td><td>false</td></tr>
-<tr><td>nis-servers</td><td>41</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>ntp-servers</td><td>42</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>vendor-encapsulated-options</td><td>43</td><td>empty</td><td>false</td></tr>
-<tr><td>netbios-name-servers</td><td>44</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>netbios-dd-server</td><td>45</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>netbios-node-type</td><td>46</td><td>uint8</td><td>false</td></tr>
-<tr><td>netbios-scope</td><td>47</td><td>string</td><td>false</td></tr>
-<tr><td>font-servers</td><td>48</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>x-display-manager</td><td>49</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>dhcp-requested-address</td><td>50</td><td>ipv4-address</td><td>false</td></tr>
+<row><entry>subnet-mask</entry><entry>1</entry><entry>ipv4-address</entry><entry>false</entry></row>
+<row><entry>time-offset</entry><entry>2</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>routers</entry><entry>3</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>time-servers</entry><entry>4</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>name-servers</entry><entry>5</entry><entry>ipv4-address</entry><entry>false</entry></row>
+<row><entry>domain-name-servers</entry><entry>6</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>log-servers</entry><entry>7</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>cookie-servers</entry><entry>8</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>lpr-servers</entry><entry>9</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>impress-servers</entry><entry>10</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>resource-location-servers</entry><entry>11</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>host-name</entry><entry>12</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>boot-size</entry><entry>13</entry><entry>uint16</entry><entry>false</entry></row>
+<row><entry>merit-dump</entry><entry>14</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>domain-name</entry><entry>15</entry><entry>fqdn</entry><entry>false</entry></row>
+<row><entry>swap-server</entry><entry>16</entry><entry>ipv4-address</entry><entry>false</entry></row>
+<row><entry>root-path</entry><entry>17</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>extensions-path</entry><entry>18</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>ip-forwarding</entry><entry>19</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>non-local-source-routing</entry><entry>20</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>policy-filter</entry><entry>21</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>max-dgram-reassembly</entry><entry>22</entry><entry>uint16</entry><entry>false</entry></row>
+<row><entry>default-ip-ttl</entry><entry>23</entry><entry>uint8</entry><entry>false</entry></row>
+<row><entry>path-mtu-aging-timeout</entry><entry>24</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>path-mtu-plateau-table</entry><entry>25</entry><entry>uint16</entry><entry>true</entry></row>
+<row><entry>interface-mtu</entry><entry>26</entry><entry>uint16</entry><entry>false</entry></row>
+<row><entry>all-subnets-local</entry><entry>27</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>broadcast-address</entry><entry>28</entry><entry>ipv4-address</entry><entry>false</entry></row>
+<row><entry>perform-mask-discovery</entry><entry>29</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>mask-supplier</entry><entry>30</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>router-discovery</entry><entry>31</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>router-solicitation-address</entry><entry>32</entry><entry>ipv4-address</entry><entry>false</entry></row>
+<row><entry>static-routes</entry><entry>33</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>trailer-encapsulation</entry><entry>34</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>arp-cache-timeout</entry><entry>35</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>ieee802-3-encapsulation</entry><entry>36</entry><entry>boolean</entry><entry>false</entry></row>
+<row><entry>default-tcp-ttl</entry><entry>37</entry><entry>uint8</entry><entry>false</entry></row>
+<row><entry>tcp-keepalive-internal</entry><entry>38</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>tcp-keepalive-garbage</entry><entry>39</entry><entry>boolean</entry><entry>false</entry></row>
+
+          </tbody>
+          </tgroup>
+        </table>
+      </para>
+
+      <para>
+        <table frame="all" id="dhcp4-std-options-list-part2">
+          <title>List of standard DHCPv4 options (continued)</title>
+          <tgroup cols='4'>
+          <colspec colname='name'/>
+          <colspec colname='code'/>
+          <colspec colname='type'/>
+          <colspec colname='array'/>
+          <thead>
+            <row>
+              <entry>Name</entry>
+              <entry>Code</entry>
+              <entry>Type</entry>
+              <entry>Array?</entry>
+            </row>
+          </thead>
+          <tbody>
+
+<row><entry>nis-domain</entry><entry>40</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>nis-servers</entry><entry>41</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>ntp-servers</entry><entry>42</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>vendor-encapsulated-options</entry><entry>43</entry><entry>empty</entry><entry>false</entry></row>
+<row><entry>netbios-name-servers</entry><entry>44</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>netbios-dd-server</entry><entry>45</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>netbios-node-type</entry><entry>46</entry><entry>uint8</entry><entry>false</entry></row>
+<row><entry>netbios-scope</entry><entry>47</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>font-servers</entry><entry>48</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>x-display-manager</entry><entry>49</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>dhcp-requested-address</entry><entry>50</entry><entry>ipv4-address</entry><entry>false</entry></row>
 <!-- Lease time should not be configured by a user.
-<tr><td>dhcp-lease-time</td><td>51</td><td>uint32</td><td>false</td></tr>
+<row><entry>dhcp-lease-time</entry><entry>51</entry><entry>uint32</entry><entry>false</entry></row>
 -->
-<tr><td>dhcp-option-overload</td><td>52</td><td>uint8</td><td>false</td></tr>
+<row><entry>dhcp-option-overload</entry><entry>52</entry><entry>uint8</entry><entry>false</entry></row>
 <!-- Message Type, Server Identifier and Parameter Request List should not be configured by a user.
-<tr><td>dhcp-message-type</td><td>53</td><td>uint8</td><td>false</td></tr>
-<tr><td>dhcp-server-identifier</td><td>54</td><td>ipv4-address</td><td>false</td></tr>
-<tr><td>dhcp-parameter-request-list</td><td>55</td><td>uint8</td><td>true</td></tr>
+<row><entry>dhcp-message-type</entry><entry>53</entry><entry>uint8</entry><entry>false</entry></row>
+<row><entry>dhcp-server-identifier</entry><entry>54</entry><entry>ipv4-address</entry><entry>false</entry></row>
+<row><entry>dhcp-parameter-request-list</entry><entry>55</entry><entry>uint8</entry><entry>true</entry></row>
 -->
-<tr><td>dhcp-message</td><td>56</td><td>string</td><td>false</td></tr>
-<tr><td>dhcp-max-message-size</td><td>57</td><td>uint16</td><td>false</td></tr>
+<row><entry>dhcp-message</entry><entry>56</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>dhcp-max-message-size</entry><entry>57</entry><entry>uint16</entry><entry>false</entry></row>
 <!-- Renewal and rebinding time should not be configured by a user.
-<tr><td>dhcp-renewal-time</td><td>58</td><td>uint32</td><td>false</td></tr>
-<tr><td>dhcp-rebinding-time</td><td>59</td><td>uint32</td><td>false</td></tr>
+<row><entry>dhcp-renewal-time</entry><entry>58</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>dhcp-rebinding-time</entry><entry>59</entry><entry>uint32</entry><entry>false</entry></row>
 -->
-<tr><td>vendor-class-identifier</td><td>60</td><td>binary</td><td>false</td></tr>
+<row><entry>vendor-class-identifier</entry><entry>60</entry><entry>binary</entry><entry>false</entry></row>
 <!-- Client identifier should not be configured by a user.
-<tr><td>dhcp-client-identifier</td><td>61</td><td>binary</td><td>false</td></tr>
+<row><entry>dhcp-client-identifier</entry><entry>61</entry><entry>binary</entry><entry>false</entry></row>
 -->
-<tr><td>nwip-domain-name</td><td>62</td><td>string</td><td>false</td></tr>
-<tr><td>nwip-suboptions</td><td>63</td><td>binary</td><td>false</td></tr>
-<tr><td>user-class</td><td>77</td><td>binary</td><td>false</td></tr>
-<tr><td>fqdn</td><td>81</td><td>record</td><td>false</td></tr>
-<tr><td>dhcp-agent-options</td><td>82</td><td>empty</td><td>false</td></tr>
-<tr><td>authenticate</td><td>90</td><td>binary</td><td>false</td></tr>
-<tr><td>client-last-transaction-time</td><td>91</td><td>uint32</td><td>false</td></tr>
-<tr><td>associated-ip</td><td>92</td><td>ipv4-address</td><td>true</td></tr>
-<tr><td>subnet-selection</td><td>118</td><td>ipv4-address</td><td>false</td></tr>
-<tr><td>domain-search</td><td>119</td><td>binary</td><td>false</td></tr>
-<tr><td>vivco-suboptions</td><td>124</td><td>binary</td><td>false</td></tr>
-<tr><td>vivso-suboptions</td><td>125</td><td>binary</td><td>false</td></tr>
+<row><entry>nwip-domain-name</entry><entry>62</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>nwip-suboptions</entry><entry>63</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>user-class</entry><entry>77</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>fqdn</entry><entry>81</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>dhcp-agent-options</entry><entry>82</entry><entry>empty</entry><entry>false</entry></row>
+<row><entry>authenticate</entry><entry>90</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>client-last-transaction-time</entry><entry>91</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>associated-ip</entry><entry>92</entry><entry>ipv4-address</entry><entry>true</entry></row>
+<row><entry>subnet-selection</entry><entry>118</entry><entry>ipv4-address</entry><entry>false</entry></row>
+<row><entry>domain-search</entry><entry>119</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>vivco-suboptions</entry><entry>124</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>vivso-suboptions</entry><entry>125</entry><entry>binary</entry><entry>false</entry></row>
           </tbody>
+          </tgroup>
         </table>
+
       </para>
       <para>
-        <table border="1" cellpadding="5%" id="dhcp-types">
-          <caption>List of standard DHCP option types</caption>
+        <table frame="all" id="dhcp-types">
+          <title>List of standard DHCP option types</title>
+          <tgroup cols='2'>
+          <colspec colname='name'/>
+          <colspec colname='meaning'/>
           <thead>
-            <tr><th>Name</th><th>Meaning</th></tr>
+            <row><entry>Name</entry><entry>Meaning</entry></row>
           </thead>
           <tbody>
-            <tr><td>binary</td><td>An arbitrary string of bytes, specified as a set of hexadecimal digits.</td></tr>
-            <tr><td>boolean</td><td>Boolean value with allowed values true or false</td></tr>
-            <tr><td>empty</td><td>No value, data is carried in suboptions</td></tr>
-            <tr><td>fqdn</td><td>Fully qualified domain name (e.g. www.example.com)</td></tr>
-            <tr><td>ipv4-address</td><td>IPv4 address in the usual dotted-decimal notation (e.g. 192.0.2.1)</td></tr>
-            <tr><td>ipv6-address</td><td>IPv6 address in the usual colon notation (e.g. 2001:db8::1)</td></tr>
-            <tr><td>record</td><td>Structured data that may comprise any types (except "record" and "empty")</td></tr>
-            <tr><td>string</td><td>Any text</td></tr>
-            <tr><td>uint8</td><td>8 bit unsigned integer with allowed values 0 to 255</td></tr>
-            <tr><td>uint16</td><td>16 bit unsinged integer with allowed values 0 to 65535</td></tr>
-            <tr><td>uint32</td><td>32 bit unsigned integer with allowed values 0 to 4294967295</td></tr>
+            <row><entry>binary</entry><entry>An arbitrary string of bytes, specified as a set of hexadecimal digits.</entry></row>
+            <row><entry>boolean</entry><entry>Boolean value with allowed values true or false</entry></row>
+            <row><entry>empty</entry><entry>No value, data is carried in suboptions</entry></row>
+            <row><entry>fqdn</entry><entry>Fully qualified domain name (e.g. www.example.com)</entry></row>
+            <row><entry>ipv4-address</entry><entry>IPv4 address in the usual dotted-decimal notation (e.g. 192.0.2.1)</entry></row>
+            <row><entry>ipv6-address</entry><entry>IPv6 address in the usual colon notation (e.g. 2001:db8::1)</entry></row>
+            <row><entry>record</entry><entry>Structured data that may comprise any types (except "record" and "empty")</entry></row>
+            <row><entry>string</entry><entry>Any text</entry></row>
+            <row><entry>uint8</entry><entry>8 bit unsigned integer with allowed values 0 to 255</entry></row>
+            <row><entry>uint16</entry><entry>16 bit unsinged integer with allowed values 0 to 65535</entry></row>
+            <row><entry>uint32</entry><entry>32 bit unsigned integer with allowed values 0 to 4294967295</entry></row>
           </tbody>
+          </tgroup>
        </table>
       </para>
     </section>
@@ -4366,7 +4520,7 @@ Dhcp4/renew-timer	1000	integer	(default)
         will be available. It will look similar to this:
 <screen>
 > <userinput>config show Dhcp6</userinput>
-Dhcp6/interface/	list	(default)
+Dhcp6/interfaces/	list	(default)
 Dhcp6/renew-timer	1000	integer	(default)
 Dhcp6/rebind-timer	2000	integer	(default)
 Dhcp6/preferred-lifetime	3000	integer	(default)
@@ -4459,6 +4613,59 @@ Dhcp6/subnet6/	list
       </note>
       </section>
 
+      <section id="dhcp6-interface-selection">
+      <title>Interface selection</title>
+      <para>
+        When DHCPv6 server starts up, by default it will listen to the DHCP
+        traffic and respond to it on all interfaces detected during startup.
+        However, in many cases it is desired to configure the server to listen and
+        respond on selected interfaces only. The sample commands in this section
+        show how to make interface selection using bindctl.
+      </para>
+      <para>
+        The default configuration can be presented with the following command:
+        <screen>
+> <userinput>config show Dhcp6/interfaces</userinput>
+<userinput>Dhcp6/interfaces[0] "*" string</userinput></screen>
+        An asterisk sign plays a role of the wildcard and means "listen on all interfaces".
+      </para>
+      <para>
+        In order to override the default configuration, the existing entry can be replaced
+        with the actual interface name:
+        <screen>
+> <userinput>config set Dhcp6/interfaces[0] eth1</userinput>
+> <userinput>config commit</userinput></screen>
+        Other interface names can be added on one-by-one basis:
+        <screen>
+> <userinput>config add Dhcp6/interfaces eth2</userinput>
+> <userinput>config commit</userinput></screen>
+        Configuration will now contain two interfaces which can be presented as follows:
+        <screen>
+> <userinput>config show Dhcp6/interfaces</userinput>
+<userinput>Dhcp6/interfaces[0]	"eth1"	string</userinput>
+<userinput>Dhcp6/interfaces[1]	"eth2"	string</userinput></screen>
+        When configuration gets committed, the server will start to listen on
+        eth1 and eth2 interfaces only.
+      </para>
+      <para>
+        It is possible to use wildcard interface name (asterisk) concurrently with explicit
+        interface names:
+        <screen>
+> <userinput>config add Dhcp6/interfaces *</userinput>
+> <userinput>config commit</userinput></screen>
+        This will result in the following configuration:
+        <screen>
+> <userinput>config show Dhcp6/interfaces</userinput>
+<userinput>Dhcp6/interfaces[0]	"eth1"	string</userinput>
+<userinput>Dhcp6/interfaces[1]	"eth2"	string</userinput>
+<userinput>Dhcp6/interfaces[2]	"*"	string</userinput></screen>
+        The presence of the wildcard name implies that server will listen on all interfaces.
+        In order to fall back to the previous configuration when server listens on eth1 and eth2:
+        <screen>
+> <userinput>config remove Dhcp6/interfaces[2]</userinput>
+> <userinput>config commit</userinput></screen>
+      </para>
+    </section>
 
     <section>
       <title>Subnet and Address Pool</title>
@@ -4545,7 +4752,7 @@ Dhcp6/subnet6/	list
       contains information on all global options that the server is
       supposed to configure in all subnets. The second line specifies
       option name. For a complete list of currently supported names,
-      see <xref linkend="dhcp6-std-options-list"/> below.
+      see <xref linkend="dhcp6-std-options-list"/>.
       The third line specifies option code, which must match one of the
       values from that
       list. Line 4 specifies option space, which must always
@@ -4615,7 +4822,9 @@ Dhcp6/subnet6/	list
 
 
     <para>
-      Below is a list of currently supported standard DHCPv6 options. The "Name" and "Code"
+      The currently supported standard DHCPv6 options are
+      listed in <xref linkend="dhcp6-std-options-list"/>.
+      The "Name" and "Code"
       are the values that should be used as a name in the option-data
       structures. "Type" designates the format of the data: the meanings of
       the various types is given in <xref linkend="dhcp-types"/>.
@@ -4630,63 +4839,68 @@ Dhcp6/subnet6/	list
 <!-- @todo: describe record types -->
 
     <para>
-      <table border="1" cellpadding="5%" id="dhcp6-std-options-list">
-        <caption>List of standard DHCPv6 options</caption>
+      <table frame="all" id="dhcp6-std-options-list">
+        <title>List of standard DHCPv6 options</title>
+        <tgroup cols='4'>
+        <colspec colname='name'/>
+        <colspec colname='code'/>
+        <colspec colname='type'/>
+        <colspec colname='array'/>
         <thead>
-          <tr><th>Name</th><th>Code</th><th>Type</th><th>Array?</th></tr>
-          <tr></tr>
+          <row><entry>Name</entry><entry>Code</entry><entry>Type</entry><entry>Array?</entry></row>
         </thead>
         <tbody>
 <!-- Our engine uses those options on its own, admin must not configure them on his own
-<tr><td>clientid</td><td>1</td><td>binary</td><td>false</td></tr>
-<tr><td>serverid</td><td>2</td><td>binary</td><td>false</td></tr>
-<tr><td>ia-na</td><td>3</td><td>record</td><td>false</td></tr>
-<tr><td>ia-ta</td><td>4</td><td>uint32</td><td>false</td></tr>
-<tr><td>iaaddr</td><td>5</td><td>record</td><td>false</td></tr>
-<tr><td>oro</td><td>6</td><td>uint16</td><td>true</td></tr> -->
-<tr><td>preference</td><td>7</td><td>uint8</td><td>false</td></tr>
+<row><entry>clientid</entry><entry>1</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>serverid</entry><entry>2</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>ia-na</entry><entry>3</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>ia-ta</entry><entry>4</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>iaaddr</entry><entry>5</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>oro</entry><entry>6</entry><entry>uint16</entry><entry>true</entry></row> -->
+<row><entry>preference</entry><entry>7</entry><entry>uint8</entry><entry>false</entry></row>
 
 <!-- Our engine uses those options on its own, admin must not configure them on his own
-<tr><td>elapsed-time</td><td>8</td><td>uint16</td><td>false</td></tr>
-<tr><td>relay-msg</td><td>9</td><td>binary</td><td>false</td></tr>
-<tr><td>auth</td><td>11</td><td>binary</td><td>false</td></tr>
-<tr><td>unicast</td><td>12</td><td>ipv6-address</td><td>false</td></tr>
-<tr><td>status-code</td><td>13</td><td>record</td><td>false</td></tr>
-<tr><td>rapid-commit</td><td>14</td><td>empty</td><td>false</td></tr>
-<tr><td>user-class</td><td>15</td><td>binary</td><td>false</td></tr>
-<tr><td>vendor-class</td><td>16</td><td>record</td><td>false</td></tr>
-<tr><td>vendor-opts</td><td>17</td><td>uint32</td><td>false</td></tr>
-<tr><td>interface-id</td><td>18</td><td>binary</td><td>false</td></tr>
-<tr><td>reconf-msg</td><td>19</td><td>uint8</td><td>false</td></tr>
-<tr><td>reconf-accept</td><td>20</td><td>empty</td><td>false</td></tr> -->
-<tr><td>sip-server-dns</td><td>21</td><td>fqdn</td><td>true</td></tr>
-<tr><td>sip-server-addr</td><td>22</td><td>ipv6-address</td><td>true</td></tr>
-<tr><td>dns-servers</td><td>23</td><td>ipv6-address</td><td>true</td></tr>
-<tr><td>domain-search</td><td>24</td><td>fqdn</td><td>true</td></tr>
-<!-- <tr><td>ia-pd</td><td>25</td><td>record</td><td>false</td></tr> -->
-<!-- <tr><td>iaprefix</td><td>26</td><td>record</td><td>false</td></tr> -->
-<tr><td>nis-servers</td><td>27</td><td>ipv6-address</td><td>true</td></tr>
-<tr><td>nisp-servers</td><td>28</td><td>ipv6-address</td><td>true</td></tr>
-<tr><td>nis-domain-name</td><td>29</td><td>fqdn</td><td>true</td></tr>
-<tr><td>nisp-domain-name</td><td>30</td><td>fqdn</td><td>true</td></tr>
-<tr><td>sntp-servers</td><td>31</td><td>ipv6-address</td><td>true</td></tr>
-<tr><td>information-refresh-time</td><td>32</td><td>uint32</td><td>false</td></tr>
-<tr><td>bcmcs-server-dns</td><td>33</td><td>fqdn</td><td>true</td></tr>
-<tr><td>bcmcs-server-addr</td><td>34</td><td>ipv6-address</td><td>true</td></tr>
-<tr><td>geoconf-civic</td><td>36</td><td>record</td><td>false</td></tr>
-<tr><td>remote-id</td><td>37</td><td>record</td><td>false</td></tr>
-<tr><td>subscriber-id</td><td>38</td><td>binary</td><td>false</td></tr>
-<tr><td>client-fqdn</td><td>39</td><td>record</td><td>false</td></tr>
-<tr><td>pana-agent</td><td>40</td><td>ipv6-address</td><td>true</td></tr>
-<tr><td>new-posix-timezone</td><td>41</td><td>string</td><td>false</td></tr>
-<tr><td>new-tzdb-timezone</td><td>42</td><td>string</td><td>false</td></tr>
-<tr><td>ero</td><td>43</td><td>uint16</td><td>true</td></tr>
-<tr><td>lq-query</td><td>44</td><td>record</td><td>false</td></tr>
-<tr><td>client-data</td><td>45</td><td>empty</td><td>false</td></tr>
-<tr><td>clt-time</td><td>46</td><td>uint32</td><td>false</td></tr>
-<tr><td>lq-relay-data</td><td>47</td><td>record</td><td>false</td></tr>
-<tr><td>lq-client-link</td><td>48</td><td>ipv6-address</td><td>true</td></tr>
+<row><entry>elapsed-time</entry><entry>8</entry><entry>uint16</entry><entry>false</entry></row>
+<row><entry>relay-msg</entry><entry>9</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>auth</entry><entry>11</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>unicast</entry><entry>12</entry><entry>ipv6-address</entry><entry>false</entry></row>
+<row><entry>status-code</entry><entry>13</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>rapid-commit</entry><entry>14</entry><entry>empty</entry><entry>false</entry></row>
+<row><entry>user-class</entry><entry>15</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>vendor-class</entry><entry>16</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>vendor-opts</entry><entry>17</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>interface-id</entry><entry>18</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>reconf-msg</entry><entry>19</entry><entry>uint8</entry><entry>false</entry></row>
+<row><entry>reconf-accept</entry><entry>20</entry><entry>empty</entry><entry>false</entry></row> -->
+<row><entry>sip-server-dns</entry><entry>21</entry><entry>fqdn</entry><entry>true</entry></row>
+<row><entry>sip-server-addr</entry><entry>22</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>dns-servers</entry><entry>23</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>domain-search</entry><entry>24</entry><entry>fqdn</entry><entry>true</entry></row>
+<!-- <row><entry>ia-pd</entry><entry>25</entry><entry>record</entry><entry>false</entry></row> -->
+<!-- <row><entry>iaprefix</entry><entry>26</entry><entry>record</entry><entry>false</entry></row> -->
+<row><entry>nis-servers</entry><entry>27</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>nisp-servers</entry><entry>28</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>nis-domain-name</entry><entry>29</entry><entry>fqdn</entry><entry>true</entry></row>
+<row><entry>nisp-domain-name</entry><entry>30</entry><entry>fqdn</entry><entry>true</entry></row>
+<row><entry>sntp-servers</entry><entry>31</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>information-refresh-time</entry><entry>32</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>bcmcs-server-dns</entry><entry>33</entry><entry>fqdn</entry><entry>true</entry></row>
+<row><entry>bcmcs-server-addr</entry><entry>34</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>geoconf-civic</entry><entry>36</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>remote-id</entry><entry>37</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>subscriber-id</entry><entry>38</entry><entry>binary</entry><entry>false</entry></row>
+<row><entry>client-fqdn</entry><entry>39</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>pana-agent</entry><entry>40</entry><entry>ipv6-address</entry><entry>true</entry></row>
+<row><entry>new-posix-timezone</entry><entry>41</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>new-tzdb-timezone</entry><entry>42</entry><entry>string</entry><entry>false</entry></row>
+<row><entry>ero</entry><entry>43</entry><entry>uint16</entry><entry>true</entry></row>
+<row><entry>lq-query</entry><entry>44</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>client-data</entry><entry>45</entry><entry>empty</entry><entry>false</entry></row>
+<row><entry>clt-time</entry><entry>46</entry><entry>uint32</entry><entry>false</entry></row>
+<row><entry>lq-relay-data</entry><entry>47</entry><entry>record</entry><entry>false</entry></row>
+<row><entry>lq-client-link</entry><entry>48</entry><entry>ipv6-address</entry><entry>true</entry></row>
         </tbody>
+        </tgroup>
       </table>
     </para>
     </section>
diff --git a/m4macros/ax_boost_for_bind10.m4 b/m4macros/ax_boost_for_bind10.m4
index 3a71337..cc6408c 100644
--- a/m4macros/ax_boost_for_bind10.m4
+++ b/m4macros/ax_boost_for_bind10.m4
@@ -31,6 +31,12 @@ dnl                             It is of no use if "WOULDFAIL" is yes.
 dnl   BOOST_STATIC_ASSERT_WOULDFAIL set to "yes" if BOOST_STATIC_ASSERT would
 dnl                                 cause build error; otherwise set to "no"
 
+dnl   BOOST_OFFSET_PTR_OLD set to "yes" if the version of boost is older than
+dnl                        1.48. Older versions of boost have a bug which
+dnl                        causes segfaults in offset_ptr implementation when
+dnl                        compiled by GCC with optimisations enabled.
+dnl                        See ticket no. 3025 for details.
+
 AC_DEFUN([AX_BOOST_FOR_BIND10], [
 AC_LANG_SAVE
 AC_LANG([C++])
@@ -106,9 +112,21 @@ if test "X$GXX" = "Xyes"; then
     BOOST_NUMERIC_CAST_WOULDFAIL=yes])
 
    CXXFLAGS="$CXXFLAGS_SAVED"
+
+   AC_MSG_CHECKING([Boost rbtree is old])
+   AC_TRY_COMPILE([
+   #include <boost/version.hpp>
+   #if BOOST_VERSION < 104800
+   #error Too old
+   #endif
+   ],,[AC_MSG_RESULT(no)
+       BOOST_OFFSET_PTR_OLD=no
+   ],[AC_MSG_RESULT(yes)
+      BOOST_OFFSET_PTR_OLD=yes])
 else
    # This doesn't matter for non-g++
    BOOST_NUMERIC_CAST_WOULDFAIL=no
+   BOOST_OFFSET_PTR_OLD=no
 fi
 
 # Boost interprocess::managed_mapped_file is highly system dependent and
diff --git a/src/bin/auth/auth_messages.mes b/src/bin/auth/auth_messages.mes
index e38468a..e5b655a 100644
--- a/src/bin/auth/auth_messages.mes
+++ b/src/bin/auth/auth_messages.mes
@@ -145,6 +145,30 @@ reconfigure, and has now started this process.
 The thread for maintaining data source clients has finished reconfiguring
 the data source clients, and is now running with the new configuration.
 
+% AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_BAD_CLASS invalid RRclass %1 at segment update
+A memory segment update message was sent to the authoritative
+server. But the class contained there is invalid. This means that the
+system is in an inconsistent state and the authoritative server aborts
+to minimize the problem. This is likely caused by a bug in the code.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_ERROR error updating the memory segment: %1
+The authoritative server tried to update the memory segment, but the update
+failed. The authoritative server aborts to avoid system inconsistency. This is
+likely caused by a bug in the code.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_NO_DATASRC there's no data source named %2 in class %1
+The authoritative server was asked to update the memory segment of the
+given data source, but no data source by that name was found. The
+authoritative server aborts because this indicates that the system is in
+an inconsistent state. This is likely caused by a bug in the code.
+
+% AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_UNKNOWN_CLASS unknown class %1 at segment update
+A memory segment update message was sent to the authoritative
+server. The class name for which the update should happen is valid, but
+no client lists are configured for that class. The system is in an
+inconsistent state and the authoritative server aborts. This may be
+caused by a bug in the code.
+
 % AUTH_DATASRC_CLIENTS_BUILDER_STARTED data source builder thread started
 A separate thread for maintaining data source clients has been started.
 
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index 27779a9..ce5c02b 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -306,6 +306,8 @@ public:
                       MessageAttributes& stats_attrs,
                       const bool done);
 
+    /// Are we currently subscribed to the SegmentReader group?
+    bool readers_group_subscribed_;
 private:
     bool xfrout_connected_;
     AbstractXfroutClient& xfrout_client_;
@@ -322,6 +324,7 @@ AuthSrvImpl::AuthSrvImpl(AbstractXfroutClient& xfrout_client,
     datasrc_clients_mgr_(io_service_),
     ddns_base_forwarder_(ddns_forwarder),
     ddns_forwarder_(NULL),
+    readers_group_subscribed_(false),
     xfrout_connected_(false),
     xfrout_client_(xfrout_client)
 {}
@@ -372,28 +375,11 @@ public:
     {}
 };
 
-// This is a derived class of \c SimpleCallback, to serve
-// as a callback in the asiolink module.  It checks for queued
-// configuration messages, and executes them if found.
-class ConfigChecker : public SimpleCallback {
-public:
-    ConfigChecker(AuthSrv* srv) : server_(srv) {}
-    virtual void operator()(const IOMessage&) const {
-        ModuleCCSession* cfg_session = server_->getConfigSession();
-        if (cfg_session != NULL && cfg_session->hasQueuedMsgs()) {
-            cfg_session->checkCommand();
-        }
-    }
-private:
-    AuthSrv* server_;
-};
-
 AuthSrv::AuthSrv(isc::xfr::AbstractXfroutClient& xfrout_client,
                  isc::util::io::BaseSocketSessionForwarder& ddns_forwarder) :
     dnss_(NULL)
 {
     impl_ = new AuthSrvImpl(xfrout_client, ddns_forwarder);
-    checkin_ = new ConfigChecker(this);
     dns_lookup_ = new MessageLookup(this);
     dns_answer_ = new MessageAnswer(this);
 }
@@ -405,7 +391,6 @@ AuthSrv::stop() {
 
 AuthSrv::~AuthSrv() {
     delete impl_;
-    delete checkin_;
     delete dns_lookup_;
     delete dns_answer_;
 }
@@ -939,3 +924,64 @@ void
 AuthSrv::setTCPRecvTimeout(size_t timeout) {
     dnss_->setTCPRecvTimeout(timeout);
 }
+
+namespace {
+
+bool
+hasMappedSegment(auth::DataSrcClientsMgr& mgr) {
+    auth::DataSrcClientsMgr::Holder holder(mgr);
+    const std::vector<dns::RRClass>& classes(holder.getClasses());
+    BOOST_FOREACH(const dns::RRClass& rrclass, classes) {
+        const boost::shared_ptr<datasrc::ConfigurableClientList>&
+            list(holder.findClientList(rrclass));
+        const std::vector<DataSourceStatus>& states(list->getStatus());
+        BOOST_FOREACH(const datasrc::DataSourceStatus& status, states) {
+            if (status.getSegmentState() != datasrc::SEGMENT_UNUSED &&
+                status.getSegmentType() == "mapped")
+                // We use some segment and it's not a local one, so it
+                // must be remote.
+                return true;
+        }
+    }
+    // No remote segment found in any of the lists
+    return false;
+}
+
+}
+
+void
+AuthSrv::listsReconfigured() {
+    const bool has_remote = hasMappedSegment(impl_->datasrc_clients_mgr_);
+    if (has_remote && !impl_->readers_group_subscribed_) {
+        impl_->config_session_->subscribe("SegmentReader");
+        impl_->config_session_->
+            setUnhandledCallback(boost::bind(&AuthSrv::foreignCommand, this,
+                                             _1, _2, _3));
+        impl_->readers_group_subscribed_ = true;
+    } else if (!has_remote && impl_->readers_group_subscribed_) {
+        impl_->config_session_->unsubscribe("SegmentReader");
+        impl_->config_session_->
+            setUnhandledCallback(isc::config::ModuleCCSession::
+                                 UnhandledCallback());
+        impl_->readers_group_subscribed_ = false;
+    }
+}
+
+void
+AuthSrv::reconfigureDone(ConstElementPtr params) {
+    // ACK the segment
+    impl_->config_session_->
+        groupSendMsg(isc::config::createCommand("segment_info_update_ack",
+                                                params), "MemMgr");
+}
+
+void
+AuthSrv::foreignCommand(const std::string& command, const std::string&,
+                        const ConstElementPtr& params)
+{
+    if (command == "segment_info_update") {
+        impl_->datasrc_clients_mgr_.
+            segmentInfoUpdate(params, boost::bind(&AuthSrv::reconfigureDone,
+                                                  this, params));
+    }
+}
diff --git a/src/bin/auth/auth_srv.h b/src/bin/auth/auth_srv.h
index 8ad72be..b8147e0 100644
--- a/src/bin/auth/auth_srv.h
+++ b/src/bin/auth/auth_srv.h
@@ -190,9 +190,6 @@ public:
     /// \brief Return pointer to the DNS Answer callback function
     isc::asiodns::DNSAnswer* getDNSAnswerProvider() const { return (dns_answer_); }
 
-    /// \brief Return pointer to the Checkin callback function
-    isc::asiolink::SimpleCallback* getCheckinProvider() const { return (checkin_); }
-
     /// \brief Return data source clients manager.
     ///
     /// \throw None
@@ -273,9 +270,19 @@ public:
     /// open forever.
     void setTCPRecvTimeout(size_t timeout);
 
+    /// \brief Notify the authoritative server that the client lists were
+    ///     reconfigured.
+    ///
+    /// This is to be called when the work thread finishes reconfiguration
+    /// of the data sources. It involeves some book keeping and asking the
+    /// memory manager for segments, if some are remotely mapped.
+    void listsReconfigured();
+
 private:
+    void reconfigureDone(isc::data::ConstElementPtr request);
+    void foreignCommand(const std::string& command, const std::string&,
+                        const isc::data::ConstElementPtr& params);
     AuthSrvImpl* impl_;
-    isc::asiolink::SimpleCallback* checkin_;
     isc::asiodns::DNSLookup* dns_lookup_;
     isc::asiodns::DNSAnswer* dns_answer_;
     isc::asiodns::DNSServiceBase* dnss_;
diff --git a/src/bin/auth/b10-auth.xml.pre b/src/bin/auth/b10-auth.xml.pre
index 2bf20c8..b8e2946 100644
--- a/src/bin/auth/b10-auth.xml.pre
+++ b/src/bin/auth/b10-auth.xml.pre
@@ -20,7 +20,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>May 22, 2013</date>
+    <date>July 16, 2013</date>
   </refentryinfo>
 
   <refmeta>
@@ -81,8 +81,8 @@
       <varlistentry>
         <term><option>-v</option></term>
         <listitem><para>
-	  Enable verbose logging mode. This enables logging of
-	  diagnostic messages at the maximum debug level.
+          Enable verbose logging mode. This enables logging of
+          diagnostic messages at the maximum debug level.
         </para></listitem>
       </varlistentry>
 
@@ -250,15 +250,24 @@
       </para>
 
       <para>
-	The <quote>qryrecursion</quote> counter is limited to queries
-	(requests of opcode 0) even though the RD bit is not specific
-	to queries.  In practice, this bit is generally just ignored for
-	other types of requests, while DNS servers behave differently
-	for queries depending on this bit.  It is also known that
-	some authoritative-only servers receive a non negligible
-	number of queries with the RD bit being set, so it would be
-	of particular interest to have a specific counters for such
-	requests.
+        The <quote>qryrecursion</quote> counter is limited to queries
+        (requests of opcode 0) even though the RD bit is not specific
+        to queries.  In practice, this bit is generally just ignored for
+        other types of requests, while DNS servers behave differently
+        for queries depending on this bit.  It is also known that
+        some authoritative-only servers receive a non negligible
+        number of queries with the RD bit being set, so it would be
+        of particular interest to have a specific counters for such
+        requests.
+      </para>
+
+      <para>
+        There are two request counters related to EDNS:
+        <quote>request.edns0</quote> and <quote>request.badednsver</quote>.
+        The latter is a counter of requests with unsupported EDNS version:
+        other than version 0 in the current implementation. Therefore, total
+        number of requests with EDNS is a sum of <quote>request.edns0</quote>
+        and <quote>request.badednsver</quote>.
       </para>
     </note>
 
diff --git a/src/bin/auth/datasrc_clients_mgr.h b/src/bin/auth/datasrc_clients_mgr.h
index a2d3d1f..c6eed31 100644
--- a/src/bin/auth/datasrc_clients_mgr.h
+++ b/src/bin/auth/datasrc_clients_mgr.h
@@ -81,6 +81,7 @@ enum CommandID {
     LOADZONE,     ///< Load a new version of zone into a memory,
                   ///  the argument to the command is a map containing 'class'
                   ///  and 'origin' elements, both should have been validated.
+    SEGMENT_INFO_UPDATE, ///< The memory manager sent an update about segments.
     SHUTDOWN,     ///< Shutdown the builder; no argument
     NUM_COMMANDS
 };
@@ -212,6 +213,24 @@ public:
                 return (it->second);
             }
         }
+        /// \brief Return list of classes that are present.
+        ///
+        /// Get the list of classes for which there's a client list. It is
+        /// returned in form of a vector, copied from the internals. As the
+        /// number of classes in there is expected to be small, it is not
+        /// a performance issue.
+        ///
+        /// \return The list of classes.
+        /// \throw std::bad_alloc for problems allocating the result.
+        std::vector<dns::RRClass> getClasses() const {
+            std::vector<dns::RRClass> result;
+            for (ClientListsMap::const_iterator it =
+                 mgr_.clients_map_->begin(); it != mgr_.clients_map_->end();
+                 ++it) {
+                result.push_back(it->first);
+            }
+            return (result);
+        }
     private:
         DataSrcClientsMgrBase& mgr_;
         typename MutexType::Locker locker_;
@@ -381,6 +400,36 @@ public:
         sendCommand(datasrc_clientmgr_internal::LOADZONE, args, callback);
     }
 
+    void segmentInfoUpdate(const data::ConstElementPtr& args,
+                           const datasrc_clientmgr_internal::FinishedCallback&
+                           callback =
+                           datasrc_clientmgr_internal::FinishedCallback()) {
+        // Some minimal validation
+        if (!args) {
+            isc_throw(CommandError, "segmentInfoUpdate argument empty");
+        }
+        if (args->getType() != isc::data::Element::map) {
+            isc_throw(CommandError, "segmentInfoUpdate argument not a map");
+        }
+        const char* params[] = {
+            "data-source-name",
+            "data-source-class",
+            "segment-params",
+            NULL
+        };
+        for (const char** param = params; *param; ++param) {
+            if (!args->contains(*param)) {
+                isc_throw(CommandError,
+                          "segmentInfoUpdate argument has no '" << param <<
+                          "' value");
+            }
+        }
+
+
+        sendCommand(datasrc_clientmgr_internal::SEGMENT_INFO_UPDATE, args,
+                    callback);
+    }
+
 private:
     // This is expected to be called at the end of the destructor.  It
     // actually does nothing, but provides a customization point for
@@ -407,7 +456,7 @@ private:
 
     int createFds() {
         int fds[2];
-        int result = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
+        int result = socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
         if (result != 0) {
             isc_throw(Unexpected, "Can't create socket pair: " <<
                       strerror(errno));
@@ -577,6 +626,44 @@ private:
         }
     }
 
+    void doSegmentUpdate(const isc::data::ConstElementPtr& arg) {
+        try {
+            const isc::dns::RRClass
+                rrclass(arg->get("data-source-class")->stringValue());
+            const std::string&
+                name(arg->get("data-source-name")->stringValue());
+            const isc::data::ConstElementPtr& segment_params =
+                arg->get("segment-params");
+            typename MutexType::Locker locker(*map_mutex_);
+            const boost::shared_ptr<isc::datasrc::ConfigurableClientList>&
+                list = (**clients_map_)[rrclass];
+            if (!list) {
+                LOG_FATAL(auth_logger,
+                          AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_UNKNOWN_CLASS)
+                    .arg(rrclass);
+                std::terminate();
+            }
+            if (!list->resetMemorySegment(name,
+                    isc::datasrc::memory::ZoneTableSegment::READ_ONLY,
+                    segment_params)) {
+                LOG_FATAL(auth_logger,
+                          AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_NO_DATASRC)
+                    .arg(rrclass).arg(name);
+                std::terminate();
+            }
+        } catch (const isc::dns::InvalidRRClass& irce) {
+            LOG_FATAL(auth_logger,
+                      AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_BAD_CLASS)
+                .arg(arg->get("data-source-class"));
+            std::terminate();
+        } catch (const isc::Exception& e) {
+            LOG_FATAL(auth_logger,
+                      AUTH_DATASRC_CLIENTS_BUILDER_SEGMENT_ERROR)
+                .arg(e.what());
+            std::terminate();
+        }
+    }
+
     void doLoadZone(const isc::data::ConstElementPtr& arg);
     boost::shared_ptr<datasrc::memory::ZoneWriter> getZoneWriter(
         datasrc::ConfigurableClientList& client_list,
@@ -676,7 +763,7 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
     }
 
     const boost::array<const char*, NUM_COMMANDS> command_desc = {
-        {"NOOP", "RECONFIGURE", "LOADZONE", "SHUTDOWN"}
+        {"NOOP", "RECONFIGURE", "LOADZONE", "SEGMENT_INFO_UPDATE", "SHUTDOWN"}
     };
     LOG_DEBUG(auth_logger, DBGLVL_TRACE_BASIC,
               AUTH_DATASRC_CLIENTS_BUILDER_COMMAND).arg(command_desc.at(cid));
@@ -687,6 +774,9 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
     case LOADZONE:
         doLoadZone(command.params);
         break;
+    case SEGMENT_INFO_UPDATE:
+        doSegmentUpdate(command.params);
+        break;
     case SHUTDOWN:
         return (false);
     case NOOP:
diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc
index dc03be2..d6b1119 100644
--- a/src/bin/auth/main.cc
+++ b/src/bin/auth/main.cc
@@ -109,9 +109,11 @@ datasrcConfigHandler(AuthSrv* server, bool* first_time,
         assert(config_session != NULL);
         *first_time = false;
         server->getDataSrcClientsMgr().reconfigure(
-            config_session->getRemoteConfigValue("data_sources", "classes"));
+            config_session->getRemoteConfigValue("data_sources", "classes"),
+            boost::bind(&AuthSrv::listsReconfigured, server));
     } else if (config->contains("classes")) {
-        server->getDataSrcClientsMgr().reconfigure(config->get("classes"));
+        server->getDataSrcClientsMgr().reconfigure(config->get("classes"),
+            boost::bind(&AuthSrv::listsReconfigured, server));
     }
 }
 
@@ -173,12 +175,11 @@ main(int argc, char* argv[]) {
         auth_server = auth_server_.get();
         LOG_INFO(auth_logger, AUTH_SERVER_CREATED);
 
-        SimpleCallback* checkin = auth_server->getCheckinProvider();
         IOService& io_service = auth_server->getIOService();
         DNSLookup* lookup = auth_server->getDNSLookupProvider();
         DNSAnswer* answer = auth_server->getDNSAnswerProvider();
 
-        DNSService dns_service(io_service, checkin, lookup, answer);
+        DNSService dns_service(io_service, lookup, answer);
         auth_server->setDNSService(dns_service);
         LOG_DEBUG(auth_logger, DBG_AUTH_START, AUTH_DNS_SERVICES_CREATED);
 
diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc
index f17d91e..9a58692 100644
--- a/src/bin/auth/tests/auth_srv_unittest.cc
+++ b/src/bin/auth/tests/auth_srv_unittest.cc
@@ -39,6 +39,9 @@
 #include <auth/statistics_items.h>
 #include <auth/datasrc_config.h>
 
+#include <config/tests/fake_session.h>
+#include <config/ccsession.h>
+
 #include <util/unittests/mock_socketsession.h>
 #include <dns/tests/unittest_util.h>
 #include <testutils/dnsmessage_test.h>
@@ -265,14 +268,15 @@ updateDatabase(AuthSrv& server, const char* params) {
 
 void
 updateInMemory(AuthSrv& server, const char* origin, const char* filename,
-               bool with_static = true)
+               bool with_static = true, bool mapped = false)
 {
     string spec_txt = "{"
         "\"IN\": [{"
         "   \"type\": \"MasterFiles\","
         "   \"params\": {"
         "       \"" + string(origin) + "\": \"" + string(filename) + "\""
-        "   },"
+        "   }," +
+        string(mapped ? "\"cache-type\": \"mapped\"," : "") +
         "   \"cache-enable\": true"
         "}]";
     if (with_static) {
@@ -2138,4 +2142,46 @@ TEST_F(AuthSrvTest, loadZoneCommand) {
     sendCommand(server, "loadzone", args, 0);
 }
 
+// Test that the auth server subscribes to the segment readers group when
+// there's a remotely mapped segment.
+#ifdef USE_SHARED_MEMORY
+TEST_F(AuthSrvTest, postReconfigure) {
+#else
+TEST_F(AuthSrvTest, DISABLED_postReconfigure) {
+#endif
+    FakeSession session(ElementPtr(new ListElement),
+                        ElementPtr(new ListElement),
+                        ElementPtr(new ListElement));
+    const string specfile(string(TEST_OWN_DATA_DIR) + "/spec.spec");
+    session.getMessages()->add(isc::config::createAnswer());
+    isc::config::ModuleCCSession mccs(specfile, session, NULL, NULL, false,
+                                      false);
+    server.setConfigSession(&mccs);
+    // First, no lists are there, so no reason to subscribe
+    server.listsReconfigured();
+    EXPECT_FALSE(session.haveSubscription("SegmentReader", "*"));
+    // Enable remote segment
+    updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE, false, true);
+    {
+        DataSrcClientsMgr &mgr(server.getDataSrcClientsMgr());
+        DataSrcClientsMgr::Holder holder(mgr);
+        EXPECT_EQ(SEGMENT_WAITING, holder.findClientList(RRClass::IN())->
+                  getStatus()[0].getSegmentState());
+    }
+    server.listsReconfigured();
+    EXPECT_TRUE(session.haveSubscription("SegmentReader", "*"));
+    // Set the segment to local again
+    updateInMemory(server, "example.", CONFIG_INMEMORY_EXAMPLE);
+    {
+        DataSrcClientsMgr &mgr(server.getDataSrcClientsMgr());
+        DataSrcClientsMgr::Holder holder(mgr);
+        EXPECT_EQ(SEGMENT_INUSE, holder.findClientList(RRClass::IN())->
+                  getStatus()[0].getSegmentState());
+        EXPECT_EQ("local", holder.findClientList(RRClass::IN())->
+                  getStatus()[0].getSegmentType());
+    }
+    server.listsReconfigured();
+    EXPECT_FALSE(session.haveSubscription("SegmentReader", "*"));
+}
+
 }
diff --git a/src/bin/auth/tests/datasrc_clients_builder_unittest.cc b/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
index 16b23dc..6f85adb 100644
--- a/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
+++ b/src/bin/auth/tests/datasrc_clients_builder_unittest.cc
@@ -43,6 +43,7 @@
 #include <string>
 #include <sstream>
 #include <cerrno>
+#include <unistd.h>
 
 using isc::data::ConstElementPtr;
 using namespace isc::dns;
@@ -69,6 +70,11 @@ protected:
 
     }
 
+    void TearDown() {
+        // Some tests create this file. Delete it if it exists.
+        unlink(TEST_DATA_BUILDDIR "/test1.zone.image");
+    }
+
     void configureZones();      // used for loadzone related tests
 
     ClientListMapPtr clients_map; // configured clients
@@ -86,7 +92,7 @@ protected:
 private:
     int generateSockets() {
         int pair[2];
-        int result = socketpair(AF_LOCAL, SOCK_STREAM, 0, pair);
+        int result = socketpair(AF_UNIX, SOCK_STREAM, 0, pair);
         assert(result == 0);
         write_end = pair[0];
         read_end = pair[1];
@@ -528,6 +534,13 @@ TEST_F(DataSrcClientsBuilderTest, loadBrokenZone) {
 }
 
 TEST_F(DataSrcClientsBuilderTest, loadUnreadableZone) {
+    // If the test is run as the root user, it will fail as insufficient
+    // permissions will not stop the root user from using a file.
+    if (getuid() == 0) {
+        std::cerr << "Skipping test as it's run as the root user" << std::endl;
+        return;
+    }
+
     configureZones();
 
     // install the zone file as unreadable
@@ -641,4 +654,106 @@ TEST_F(DataSrcClientsBuilderTest,
                  TestDataSrcClientsBuilder::InternalCommandError);
 }
 
+// Test the SEGMENT_INFO_UPDATE command. This test is little bit
+// indirect. It doesn't seem possible to fake the client list inside
+// easily. So we create a real image to load and load it. Then we check
+// the segment is used.
+TEST_F(DataSrcClientsBuilderTest,
+#ifdef USE_SHARED_MEMORY
+       segmentInfoUpdate
+#else
+       DISABLED_segmentInfoUpdate
+#endif
+      )
+{
+    // First, prepare the file image to be mapped
+    const ConstElementPtr config = Element::fromJSON(
+        "{"
+        "\"IN\": [{"
+        "   \"type\": \"MasterFiles\","
+        "   \"params\": {"
+        "       \"test1.example\": \""
+        TEST_DATA_BUILDDIR "/test1.zone.copied\"},"
+        "   \"cache-enable\": true,"
+        "   \"cache-type\": \"mapped\""
+        "}]}");
+    const ConstElementPtr segment_config = Element::fromJSON(
+        "{"
+        "  \"mapped-file\": \""
+        TEST_DATA_BUILDDIR "/test1.zone.image" "\"}");
+    clients_map = configureDataSource(config);
+    {
+        const boost::shared_ptr<ConfigurableClientList> list =
+            (*clients_map)[RRClass::IN()];
+        list->resetMemorySegment("MasterFiles",
+                                 memory::ZoneTableSegment::CREATE,
+                                 segment_config);
+        const ConfigurableClientList::ZoneWriterPair result =
+            list->getCachedZoneWriter(isc::dns::Name("test1.example"), false,
+                                      "MasterFiles");
+        ASSERT_EQ(ConfigurableClientList::ZONE_SUCCESS, result.first);
+        result.second->load();
+        result.second->install();
+        // not absolutely necessary, but just in case
+        result.second->cleanup();
+    } // Release this list. That will release the file with the image too,
+      // so we can map it read only from somewhere else.
+
+    // Create a new map, with the same configuration, but without the segments
+    // set
+    clients_map = configureDataSource(config);
+    const boost::shared_ptr<ConfigurableClientList> list =
+        (*clients_map)[RRClass::IN()];
+    EXPECT_EQ(SEGMENT_WAITING, list->getStatus()[0].getSegmentState());
+    // Send the command
+    const ElementPtr command_args = Element::fromJSON(
+        "{"
+        "  \"data-source-name\": \"MasterFiles\","
+        "  \"data-source-class\": \"IN\""
+        "}");
+    command_args->set("segment-params", segment_config);
+    builder.handleCommand(Command(SEGMENT_INFO_UPDATE, command_args,
+                                  FinishedCallback()));
+    // The segment is now used.
+    EXPECT_EQ(SEGMENT_INUSE, list->getStatus()[0].getSegmentState());
+
+    // Some invalid inputs (wrong class, different name of data source, etc).
+
+    // Copy the confing and modify
+    const ElementPtr bad_name = Element::fromJSON(command_args->toWire());
+    // Set bad name
+    bad_name->set("data-source-name", Element::create("bad"));
+    EXPECT_DEATH_IF_SUPPORTED({
+        builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_name,
+                                      FinishedCallback()));
+    }, "");
+
+    // Another copy with wrong class
+    const ElementPtr bad_class = Element::fromJSON(command_args->toWire());
+    // Set bad class
+    bad_class->set("data-source-class", Element::create("bad"));
+    // Aborts (we are out of sync somehow).
+    EXPECT_DEATH_IF_SUPPORTED({
+        builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_class,
+                                      FinishedCallback()));
+    }, "");
+
+    // Class CH is valid, but not present.
+    bad_class->set("data-source-class", Element::create("CH"));
+    EXPECT_DEATH_IF_SUPPORTED({
+        builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_class,
+                                      FinishedCallback()));
+    }, "");
+
+    // And break the segment params
+    const ElementPtr bad_params = Element::fromJSON(command_args->toWire());
+    bad_params->set("segment-params",
+                    Element::fromJSON("{\"mapped-file\": \"/bad/file\"}"));
+
+    EXPECT_DEATH_IF_SUPPORTED({
+        builder.handleCommand(Command(SEGMENT_INFO_UPDATE, bad_class,
+                                      FinishedCallback()));
+    }, "");
+}
+
 } // unnamed namespace
diff --git a/src/bin/auth/tests/datasrc_clients_mgr_unittest.cc b/src/bin/auth/tests/datasrc_clients_mgr_unittest.cc
index 1bf8101..565deb5 100644
--- a/src/bin/auth/tests/datasrc_clients_mgr_unittest.cc
+++ b/src/bin/auth/tests/datasrc_clients_mgr_unittest.cc
@@ -156,6 +156,7 @@ TEST(DataSrcClientsMgrTest, holder) {
         TestDataSrcClientsMgr::Holder holder(mgr);
         EXPECT_FALSE(holder.findClientList(RRClass::IN()));
         EXPECT_FALSE(holder.findClientList(RRClass::CH()));
+        EXPECT_TRUE(holder.getClasses().empty());
         // map should be protected here
         EXPECT_EQ(1, FakeDataSrcClientsBuilder::map_mutex->lock_count);
         EXPECT_EQ(0, FakeDataSrcClientsBuilder::map_mutex->unlock_count);
@@ -174,6 +175,7 @@ TEST(DataSrcClientsMgrTest, holder) {
         TestDataSrcClientsMgr::Holder holder(mgr);
         EXPECT_TRUE(holder.findClientList(RRClass::IN()));
         EXPECT_TRUE(holder.findClientList(RRClass::CH()));
+        EXPECT_EQ(2, holder.getClasses().size());
     }
     // We need to clear command queue by hand
     FakeDataSrcClientsBuilder::command_queue->clear();
@@ -188,6 +190,7 @@ TEST(DataSrcClientsMgrTest, holder) {
         TestDataSrcClientsMgr::Holder holder(mgr);
         EXPECT_TRUE(holder.findClientList(RRClass::IN()));
         EXPECT_FALSE(holder.findClientList(RRClass::CH()));
+        EXPECT_EQ(RRClass::IN(), holder.getClasses()[0]);
     }
 
     // Duplicate lock acquisition is prohibited (only test mgr can detect
@@ -245,6 +248,30 @@ TEST(DataSrcClientsMgrTest, reload) {
     EXPECT_EQ(3, FakeDataSrcClientsBuilder::command_queue->size());
 }
 
+TEST(DataSrcClientsMgrTest, segmentUpdate) {
+    TestDataSrcClientsMgr mgr;
+    EXPECT_TRUE(FakeDataSrcClientsBuilder::started);
+    EXPECT_TRUE(FakeDataSrcClientsBuilder::command_queue->empty());
+
+    isc::data::ElementPtr args =
+        isc::data::Element::fromJSON("{\"data-source-class\": \"IN\","
+                                     " \"data-source-name\": \"sqlite3\","
+                                     " \"segment-params\": {}}");
+    mgr.segmentInfoUpdate(args);
+    EXPECT_EQ(1, FakeDataSrcClientsBuilder::command_queue->size());
+    // Some invalid inputs
+    EXPECT_THROW(mgr.segmentInfoUpdate(isc::data::Element::fromJSON(
+        "{\"data-source-class\": \"IN\","
+        " \"data-source-name\": \"sqlite3\"}")), CommandError);
+    EXPECT_THROW(mgr.segmentInfoUpdate(isc::data::Element::fromJSON(
+        "{\"data-source-name\": \"sqlite3\","
+        " \"segment-params\": {}}")), CommandError);
+    EXPECT_THROW(mgr.segmentInfoUpdate(isc::data::Element::fromJSON(
+        "{\"data-source-class\": \"IN\","
+        " \"segment-params\": {}}")), CommandError);
+    EXPECT_EQ(1, FakeDataSrcClientsBuilder::command_queue->size());
+}
+
 void
 callback(bool* called, int *tag_target, int tag_value) {
     *called = true;
diff --git a/src/bin/cmdctl/Makefile.am b/src/bin/cmdctl/Makefile.am
index 72a400a..54be50f 100644
--- a/src/bin/cmdctl/Makefile.am
+++ b/src/bin/cmdctl/Makefile.am
@@ -57,12 +57,19 @@ b10_certgen_CXXFLAGS = $(BOTAN_INCLUDES)
 b10_certgen_LDFLAGS = $(BOTAN_LIBS)
 
 # Generate the initial certificates immediately
-cmdctl-certfile.pem: b10-certgen
-	./b10-certgen -q -w
-
 cmdctl-keyfile.pem: b10-certgen
 	./b10-certgen -q -w
 
+# This is a hack, as b10-certgen creates both cmdctl-keyfile.pem and
+# cmdctl-certfile.pem, and in a parallel make, making these targets
+# simultaneously may result in corrupted files. With GNU make, there is
+# a non-portable way of working around this with pattern rules, but we
+# adopt this hack instead. The downside is that cmdctl-certfile.pem will
+# not be re-generated if cmdctl-keyfile.pem exists and is older. See
+# Trac ticket #2962.
+cmdctl-certfile.pem: cmdctl-keyfile.pem
+	touch $(builddir)/cmdctl-keyfile.pem
+
 if INSTALL_CONFIGURATIONS
 
 # Below we intentionally use ${INSTALL} -m 640 instead of $(INSTALL_DATA)
diff --git a/src/bin/cmdctl/cmdctl.py.in b/src/bin/cmdctl/cmdctl.py.in
index 7a9e8b8..0b402fe 100755
--- a/src/bin/cmdctl/cmdctl.py.in
+++ b/src/bin/cmdctl/cmdctl.py.in
@@ -601,12 +601,13 @@ class SecureHTTPServer(socketserver_mixin.NoPollMixIn,
             # error)
             return ssl_sock
         except ssl.SSLError as err:
+            self.close_request(sock)
             logger.error(CMDCTL_SSL_SETUP_FAILURE_USER_DENIED, err)
-        except (CmdctlException, IOError) as cce:
+            raise
+        except (CmdctlException, IOError, socket.error) as cce:
+            self.close_request(sock)
             logger.error(CMDCTL_SSL_SETUP_FAILURE_READING_CERT, cce)
-        self.close_request(sock)
-        # raise socket error to finish the request
-        raise socket.error
+            raise
 
     def get_request(self):
         '''Get client request socket and wrap it in SSL context. '''
diff --git a/src/bin/cmdctl/tests/b10-certgen_test.py b/src/bin/cmdctl/tests/b10-certgen_test.py
index 56630bc..8054aa1 100644
--- a/src/bin/cmdctl/tests/b10-certgen_test.py
+++ b/src/bin/cmdctl/tests/b10-certgen_test.py
@@ -200,6 +200,8 @@ class TestCertGenTool(unittest.TestCase):
         # No such file
         self.run_check(105, None, None, [self.TOOL, '-c', 'foo'])
 
+    @unittest.skipIf(os.getuid() == 0,
+                     'test cannot be run as root user')
     def test_permissions(self):
         """
         Test some combinations of correct and bad permissions.
diff --git a/src/bin/cmdctl/tests/cmdctl_test.py b/src/bin/cmdctl/tests/cmdctl_test.py
index 4a6b0e3..dfd66ad 100644
--- a/src/bin/cmdctl/tests/cmdctl_test.py
+++ b/src/bin/cmdctl/tests/cmdctl_test.py
@@ -15,7 +15,7 @@
 
 
 import unittest
-import socket
+import ssl, socket
 import tempfile
 import time
 import stat
@@ -680,11 +680,15 @@ class TestSecureHTTPServer(unittest.TestCase):
         # Just some file that we know exists
         file_name = BUILD_FILE_PATH + 'cmdctl-keyfile.pem'
         check_file(file_name)
-        with UnreadableFile(file_name):
-            self.assertRaises(CmdctlException, check_file, file_name)
         self.assertRaises(CmdctlException, check_file, '/local/not-exist')
         self.assertRaises(CmdctlException, check_file, '/')
 
+    @unittest.skipIf(os.getuid() == 0,
+                     'test cannot be run as root user')
+    def test_check_file_for_unreadable(self):
+        file_name = BUILD_FILE_PATH + 'cmdctl-keyfile.pem'
+        with UnreadableFile(file_name):
+            self.assertRaises(CmdctlException, check_file, file_name)
 
     def test_check_key_and_cert(self):
         keyfile = BUILD_FILE_PATH + 'cmdctl-keyfile.pem'
@@ -702,6 +706,15 @@ class TestSecureHTTPServer(unittest.TestCase):
         self.assertRaises(CmdctlException, self.server._check_key_and_cert,
                          '/', certfile)
 
+        # All OK (also happens to check the context code above works)
+        self.server._check_key_and_cert(keyfile, certfile)
+
+    @unittest.skipIf(os.getuid() == 0,
+                     'test cannot be run as root user')
+    def test_check_key_and_cert_for_unreadable(self):
+        keyfile = BUILD_FILE_PATH + 'cmdctl-keyfile.pem'
+        certfile = BUILD_FILE_PATH + 'cmdctl-certfile.pem'
+
         # no read permission
         with UnreadableFile(certfile):
             self.assertRaises(CmdctlException,
@@ -713,21 +726,17 @@ class TestSecureHTTPServer(unittest.TestCase):
                               self.server._check_key_and_cert,
                               keyfile, certfile)
 
-        # All OK (also happens to check the context code above works)
-        self.server._check_key_and_cert(keyfile, certfile)
-
     def test_wrap_sock_in_ssl_context(self):
         sock = socket.socket()
 
-        # Bad files should result in a socket.error raised by our own
-        # code in the basic file checks
-        self.assertRaises(socket.error,
+        # Bad files should result in a CmdctlException in the basic file
+        # checks
+        self.assertRaises(CmdctlException,
                           self.server._wrap_socket_in_ssl_context,
                           sock,
                           'no_such_file', 'no_such_file')
 
-        # Using a non-certificate file would cause an SSLError, which
-        # is caught by our code which then raises a basic socket.error
+        # Using a non-certificate file would cause an SSLError
         self.assertRaises(socket.error,
                           self.server._wrap_socket_in_ssl_context,
                           sock,
@@ -747,7 +756,7 @@ class TestSecureHTTPServer(unittest.TestCase):
         orig_check_func = self.server._check_key_and_cert
         try:
             self.server._check_key_and_cert = lambda x,y: None
-            self.assertRaises(socket.error,
+            self.assertRaises(IOError,
                               self.server._wrap_socket_in_ssl_context,
                               sock,
                               'no_such_file', 'no_such_file')
diff --git a/src/bin/d2/Makefile.am b/src/bin/d2/Makefile.am
index b1c4c5e..f289815 100644
--- a/src/bin/d2/Makefile.am
+++ b/src/bin/d2/Makefile.am
@@ -2,7 +2,7 @@ SUBDIRS = . tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
-AM_CPPFLAGS += $(BOOST_INCLUDES) $(BOTAN_INCLUDES)
+AM_CPPFLAGS += $(BOOST_INCLUDES)
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 if USE_CLANGPP
@@ -54,10 +54,11 @@ b10_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
 b10_dhcp_ddns_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
 b10_dhcp_ddns_SOURCES += d2_config.cc d2_config.h
 b10_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
+b10_dhcp_ddns_SOURCES += d2_queue_mgr.cc d2_queue_mgr.h
 b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
+b10_dhcp_ddns_SOURCES += d2_update_mgr.cc d2_update_mgr.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
 b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
-b10_dhcp_ddns_SOURCES += ncr_msg.cc ncr_msg.h
 
 nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
 EXTRA_DIST += d2_messages.mes
@@ -68,9 +69,11 @@ b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiodns/libb10-asiodns.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
-b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la 
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
 b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+b10_dhcp_ddns_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
 
 b10_dhcp_ddnsdir = $(pkgdatadir)
 b10_dhcp_ddns_DATA = dhcp-ddns.spec
diff --git a/src/bin/d2/d2_cfg_mgr.cc b/src/bin/d2/d2_cfg_mgr.cc
index 07068d6..ef63a0a 100644
--- a/src/bin/d2/d2_cfg_mgr.cc
+++ b/src/bin/d2/d2_cfg_mgr.cc
@@ -14,18 +14,19 @@
 
 #include <d2/d2_log.h>
 #include <d2/d2_cfg_mgr.h>
+#include <util/encode/hex.h>
 
 #include <boost/foreach.hpp>
 
-using namespace std;
-using namespace isc;
-using namespace isc::dhcp;
-using namespace isc::data;
-using namespace isc::asiolink;
-
 namespace isc {
 namespace d2 {
 
+namespace {
+
+typedef std::vector<uint8_t> ByteAddress;
+
+} // end of unnamed namespace
+
 // *********************** D2CfgContext  *************************
 
 D2CfgContext::D2CfgContext()
@@ -45,7 +46,7 @@ D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : DCfgContextBase(rhs) {
         reverse_mgr_->setDomains(rhs.reverse_mgr_->getDomains());
     }
 
-    keys_ = rhs.keys_; 
+    keys_ = rhs.keys_;
 }
 
 D2CfgContext::~D2CfgContext() {
@@ -53,6 +54,10 @@ D2CfgContext::~D2CfgContext() {
 
 // *********************** D2CfgMgr  *************************
 
+const char* D2CfgMgr::IPV4_REV_ZONE_SUFFIX = "in-addr.arpa.";
+
+const char* D2CfgMgr::IPV6_REV_ZONE_SUFFIX = "ip6.arpa.";
+
 D2CfgMgr::D2CfgMgr() : DCfgMgrBase(DCfgContextBasePtr(new D2CfgContext())) {
     // TSIG keys need to parse before the Domains, so we can catch Domains
     // that specify undefined keys. Create the necessary parsing order now.
@@ -82,17 +87,93 @@ D2CfgMgr::matchForward(const std::string& fqdn, DdnsDomainPtr& domain) {
 }
 
 bool
-D2CfgMgr::matchReverse(const std::string& fqdn, DdnsDomainPtr& domain) {
-    if (fqdn.empty()) {
-        // This is a programmatic error and should not happen.
-        isc_throw(D2CfgError, "matchReverse passed a null or empty fqdn");
-    }
+D2CfgMgr::matchReverse(const std::string& ip_address, DdnsDomainPtr& domain) {
+    // Note, reverseIpAddress will throw if the ip_address is invalid.
+    std::string reverse_address = reverseIpAddress(ip_address);
 
     // Fetch the reverse manager from the D2 context.
     DdnsDomainListMgrPtr mgr = getD2CfgContext()->getReverseMgr();
 
-    // Call the manager's match method and return the result.
-    return (mgr->matchDomain(fqdn, domain));
+    return (mgr->matchDomain(reverse_address, domain));
+}
+
+std::string
+D2CfgMgr::reverseIpAddress(const std::string& address) {
+    try {
+        // Convert string address into an IOAddress and invoke the
+        // appropriate reverse method.
+        isc::asiolink::IOAddress ioaddr(address);
+        if (ioaddr.isV4()) {
+            return (reverseV4Address(ioaddr));
+        }
+
+        return (reverseV6Address(ioaddr));
+
+    } catch (const isc::Exception& ex) {
+        isc_throw(D2CfgError, "D2CfgMgr cannot reverse address: "
+                               << address << " : " << ex.what());
+    }
+}
+
+std::string
+D2CfgMgr::reverseV4Address(const isc::asiolink::IOAddress& ioaddr) {
+    if (!ioaddr.isV4()) {
+        isc_throw(D2CfgError, "D2CfgMgr address is not IPv4 address :"
+                              << ioaddr.toText());
+    }
+
+    // Get the address in byte vector form.
+    const ByteAddress bytes = ioaddr.toBytes();
+
+    // Walk backwards through vector outputting each octet and a dot.
+    std::ostringstream stream;
+
+    // We have to set the following variable to get
+    // const_reverse_iterator type of rend(), otherwise Solaris GCC
+    // complains on operator!= by trying to use the non-const variant.
+    const ByteAddress::const_reverse_iterator end = bytes.rend();
+
+    for (ByteAddress::const_reverse_iterator rit = bytes.rbegin();
+         rit != end;
+         ++rit)
+    {
+        stream << static_cast<unsigned int>(*rit) << ".";
+    }
+
+    // Tack on the suffix and we're done.
+    stream << IPV4_REV_ZONE_SUFFIX;
+    return(stream.str());
+}
+
+std::string
+D2CfgMgr::reverseV6Address(const isc::asiolink::IOAddress& ioaddr) {
+    if (!ioaddr.isV6()) {
+        isc_throw(D2CfgError, "D2Cfg address is not IPv6 address: "
+                              << ioaddr.toText());
+    }
+
+    // Turn the address into a string of digits.
+    const ByteAddress bytes = ioaddr.toBytes();
+    const std::string digits = isc::util::encode::encodeHex(bytes);
+
+    // Walk backwards through string outputting each digits and a dot.
+    std::ostringstream stream;
+
+    // We have to set the following variable to get
+    // const_reverse_iterator type of rend(), otherwise Solaris GCC
+    // complains on operator!= by trying to use the non-const variant.
+    const std::string::const_reverse_iterator end = digits.rend();
+
+    for (std::string::const_reverse_iterator rit = digits.rbegin();
+         rit != end;
+         ++rit)
+    {
+        stream << static_cast<char>(*rit) << ".";
+    }
+
+    // Tack on the suffix and we're done.
+    stream << IPV6_REV_ZONE_SUFFIX;
+    return(stream.str());
 }
 
 
@@ -102,12 +183,14 @@ D2CfgMgr::createConfigParser(const std::string& config_id) {
     D2CfgContextPtr context = getD2CfgContext();
 
     // Create parser instance based on element_id.
-    DhcpConfigParser* parser = NULL;
+    isc::dhcp::DhcpConfigParser* parser = NULL;
     if ((config_id == "interface")  ||
         (config_id == "ip_address")) {
-        parser = new StringParser(config_id, context->getStringStorage());
+        parser = new isc::dhcp::StringParser(config_id,
+                                             context->getStringStorage());
     } else if (config_id == "port") {
-        parser = new Uint32Parser(config_id, context->getUint32Storage());
+        parser = new isc::dhcp::Uint32Parser(config_id,
+                                             context->getUint32Storage());
     } else if (config_id ==  "forward_ddns") {
         parser = new DdnsDomainListMgrParser("forward_mgr",
                                              context->getForwardMgr(),
@@ -129,4 +212,3 @@ D2CfgMgr::createConfigParser(const std::string& config_id) {
 
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
-
diff --git a/src/bin/d2/d2_cfg_mgr.h b/src/bin/d2/d2_cfg_mgr.h
index f9089cb..c9b794f 100644
--- a/src/bin/d2/d2_cfg_mgr.h
+++ b/src/bin/d2/d2_cfg_mgr.h
@@ -105,6 +105,14 @@ typedef boost::shared_ptr<DdnsDomainListMgr> DdnsDomainListMgrPtr;
 /// and retrieving the information on demand.
 class D2CfgMgr : public DCfgMgrBase {
 public:
+    /// @brief Reverse zone suffix added to IPv4 addresses for reverse lookups
+    /// @todo This should be configurable.
+    static const char* IPV4_REV_ZONE_SUFFIX;
+
+    /// @brief Reverse zone suffix added to IPv6 addresses for reverse lookups
+    /// @todo This should be configurable.
+    static const char* IPV6_REV_ZONE_SUFFIX;
+
     /// @brief Constructor
     D2CfgMgr();
 
@@ -119,30 +127,84 @@ public:
     }
 
     /// @brief Matches a given FQDN to a forward domain.
-    /// 
+    ///
     /// This calls the matchDomain method of the forward domain manager to
-    /// match the given FQDN to a forward domain.  
+    /// match the given FQDN to a forward domain.
     ///
     /// @param fqdn is the name for which to look.
     /// @param domain receives the matching domain. Note that it will be reset
     /// upon entry and only set if a match is subsequently found.
     ///
     /// @return returns true if a match is found, false otherwise.
-    /// @throw throws D2CfgError if given an invalid fqdn. 
-    bool matchForward(const std::string& fqdn, DdnsDomainPtr &domain);
+    /// @throw throws D2CfgError if given an invalid fqdn.
+    bool matchForward(const std::string& fqdn, DdnsDomainPtr& domain);
 
-    /// @brief Matches a given FQDN to a reverse domain.
+    /// @brief Matches a given IP address to a reverse domain.
     ///
     /// This calls the matchDomain method of the reverse domain manager to
-    /// match the given FQDN to a forward domain.  
+    /// match the given IPv4 or IPv6 address to a reverse domain.
     ///
-    /// @param fqdn is the name for which to look.
+    /// @param ip_address is the name for which to look.
     /// @param domain receives the matching domain. Note that it will be reset
     /// upon entry and only set if a match is subsequently found.
     ///
     /// @return returns true if a match is found, false otherwise.
-    /// @throw throws D2CfgError if given an invalid fqdn. 
-    bool matchReverse(const std::string& fqdn, DdnsDomainPtr &domain);
+    /// @throw throws D2CfgError if given an invalid fqdn.
+    bool matchReverse(const std::string& ip_address, DdnsDomainPtr& domain);
+
+    /// @brief Generate a reverse order string for the given IP address
+    ///
+    /// This method creates a string containing the given IP address
+    /// contents in reverse order.  This format is used for matching
+    /// against reverse DDNS domains in DHCP_DDNS configuration.
+    /// After reversing the syllables of the address, it appends the
+    /// appropriate suffix.
+    ///
+    /// @param address string containing a valid IPv4 or IPv6 address.
+    ///
+    /// @return a std::string containing the reverse order address.
+    ///
+    /// @throw D2CfgError if given an invalid address.
+    static std::string reverseIpAddress(const std::string& address);
+
+    /// @brief Generate a reverse order string for the given IP address
+    ///
+    /// This method creates a string containing the given IP address
+    /// contents in reverse order.  This format is used for matching
+    /// against reverse DDNS domains in DHCP_DDNS configuration.
+    /// After reversing the syllables of the address, it appends the
+    /// appropriate suffix.
+    ///
+    /// Example:
+    ///   input:  192.168.1.15
+    ///  output:  15.1.168.192.in-addr.arpa.
+    ///
+    /// @param ioaddr is the IPv4 IOaddress to convert
+    ///
+    /// @return a std::string containing the reverse order address.
+    ///
+    /// @throw D2CfgError if not given an IPv4  address.
+    static std::string reverseV4Address(const isc::asiolink::IOAddress& ioaddr);
+
+    /// @brief Generate a reverse order string for the given IP address
+    ///
+    /// This method creates a string containing the given IPv6 address
+    /// contents in reverse order.  This format is used for matching
+    /// against reverse DDNS domains in DHCP_DDNS configuration.
+    /// After reversing the syllables of the address, it appends the
+    /// appropriate suffix.
+    ///
+    /// IPv6 example:
+    /// input:  2001:db8:302:99::
+    /// output:
+    ///0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.9.9.0.0.2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.
+    ///
+    /// @param address string containing a valid IPv6 address.
+    ///
+    /// @return a std::string containing the reverse order address.
+    ///
+    /// @throw D2CfgError if not given an IPv6 address.
+    static std::string reverseV6Address(const isc::asiolink::IOAddress& ioaddr);
 
 protected:
     /// @brief Given an element_id returns an instance of the appropriate
diff --git a/src/bin/d2/d2_config.cc b/src/bin/d2/d2_config.cc
index fe5ad0b..db20cc8 100644
--- a/src/bin/d2/d2_config.cc
+++ b/src/bin/d2/d2_config.cc
@@ -91,44 +91,67 @@ DdnsDomainListMgr::setDomains(DdnsDomainMapPtr domains) {
 
 bool
 DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
-    // Clear the return parameter.
-    domain.reset();
-
     // First check the case of one domain to rule them all.
     if ((size() == 1) && (wildcard_domain_)) {
         domain = wildcard_domain_;
         return (true);
     }
 
-    // Start with the longest version of the fqdn and search the list.
-    // Continue looking for shorter versions of fqdn so long as no match is
-    // found.
-    // @todo This can surely be optimized, time permitting.
-    std::string match_name = fqdn;
-    std::size_t start_pos = 0;
-    while (start_pos != std::string::npos) {
-        match_name = match_name.substr(start_pos, std::string::npos);
-        DdnsDomainMap::iterator gotit = domains_->find(match_name);
-        if (gotit != domains_->end()) {
-            domain = gotit->second;
-            return (true);
+    // Iterate over the domain map looking for the domain which matches
+    // the longest portion of the given fqdn.
+
+    size_t req_len = fqdn.size();
+    size_t match_len = 0;
+    DdnsDomainMapPair map_pair;
+    DdnsDomainPtr best_match;
+    BOOST_FOREACH (map_pair, *domains_) {
+        std::string domain_name = map_pair.first;
+        size_t dom_len = domain_name.size();
+
+        // If the domain name is longer than the fqdn, then it cant be match.
+        if (req_len < dom_len) {
+            continue;
         }
 
-        start_pos = match_name.find_first_of(".");
-        if (start_pos != std::string::npos) {
-            ++start_pos;
+        // If the lengths are identical and the names match we're done.
+        if (req_len == dom_len) {
+            if (fqdn == domain_name) {
+                // exact match, done
+                domain = map_pair.second;
+                return (true);
+            }
+        } else {
+            // The fqdn is longer than the domain name.  Adjust the start
+            // point of comparison by the excess in length.  Only do the
+            // comparison if the adjustment lands on a boundary. This
+            // prevents "onetwo.net" from matching "two.net".
+            size_t offset = req_len - dom_len;
+            if ((fqdn[offset - 1] == '.')  &&
+               (fqdn.compare(offset, std::string::npos, domain_name) == 0)) {
+                // Fqdn contains domain name, keep it if its better than
+                // any we have matched so far.
+                if (dom_len > match_len) {
+                    match_len = dom_len;
+                    best_match = map_pair.second;
+                }
+            }
         }
     }
 
-    // There's no match. If they specified a wild card domain use it
-    // otherwise there's no domain for this entry.
-    if (wildcard_domain_) {
-        domain = wildcard_domain_;
-        return (true);
+    if (!best_match) {
+        // There's no match. If they specified a wild card domain use it
+        // otherwise there's no domain for this entry.
+        if (wildcard_domain_) {
+            domain = wildcard_domain_;
+            return (true);
+        }
+
+        LOG_WARN(dctl_logger, DHCP_DDNS_NO_MATCH).arg(fqdn);
+        return (false);
     }
 
-    LOG_WARN(dctl_logger, DHCP_DDNS_NO_MATCH).arg(fqdn);
-    return (false);
+    domain = best_match;
+    return (true);
 }
 
 // *************************** PARSERS ***********************************
diff --git a/src/bin/d2/d2_config.h b/src/bin/d2/d2_config.h
index 7350291..ba0beaa 100644
--- a/src/bin/d2/d2_config.h
+++ b/src/bin/d2/d2_config.h
@@ -321,8 +321,8 @@ typedef boost::shared_ptr<DnsServerInfoStorage> DnsServerInfoStoragePtr;
 /// it.  It's primary use is to map a domain to the DNS server(s) responsible
 /// for it.
 /// @todo Currently the name entry for a domain is just an std::string. It
-/// may be worthwhile to change this to a dns::Name for purposes of better 
-/// validation and matching capabilities. 
+/// may be worthwhile to change this to a dns::Name for purposes of better
+/// validation and matching capabilities.
 class DdnsDomain {
 public:
     /// @brief Constructor
@@ -385,7 +385,11 @@ typedef boost::shared_ptr<DdnsDomainMap> DdnsDomainMapPtr;
 /// services.  These services are used to match a FQDN to a domain.  Currently
 /// it supports a single matching service, which will return the matching
 /// domain or a wild card domain if one is specified.  The wild card domain is
-/// specified as a domain whose name is "*".
+/// specified as a domain whose name is "*".  The wild card domain will match
+/// any entry and is provided for flexibility in FQDNs  If for instance, all
+/// forward requests are handled by the same servers, the configuration could
+/// specify the wild card domain as the only forward domain. All forward DNS
+/// updates would be sent to that one list of servers, regardless of the FQDN.
 /// As matching capabilities evolve this class is expected to expand.
 class DdnsDomainListMgr {
 public:
@@ -410,8 +414,8 @@ public:
     /// it will be returned immediately for any FQDN.
     ///
     /// @param fqdn is the name for which to look.
-    /// @param domain receives the matching domain. Note that it will be reset
-    /// upon entry and only set if a match is subsequently found.
+    /// @param domain receives the matching domain. If no match is found its
+    /// contents will be unchanged.
     ///
     /// @return returns true if a match is found, false otherwise.
     /// @todo This is a very basic match method, which expects valid FQDNs
diff --git a/src/bin/d2/d2_messages.mes b/src/bin/d2/d2_messages.mes
index d2358ce..298a45d 100644
--- a/src/bin/d2/d2_messages.mes
+++ b/src/bin/d2/d2_messages.mes
@@ -70,7 +70,7 @@ application when it is not running.
 
 % DCTL_ORDER_ERROR configuration contains more elements than the parsing order
 An error message which indicates that configuration being parsed includes
-element ids not specified the configuration manager's parse order list. This 
+element ids not specified the configuration manager's parse order list. This
 is a programmatic error.
 
 % DCTL_ORDER_NO_ELEMENT element: %1 is in the parsing order but is missing from the configuration
@@ -78,7 +78,7 @@ An error message output during a configuration update.  The program is
 expecting an item but has not found it in the new configuration.  This may
 mean that the BIND 10 configuration database is corrupt.
 
-% DCTL_PARSER_FAIL configuration parsing failed for configuration element: %1, reason: %2 
+% DCTL_PARSER_FAIL configuration parsing failed for configuration element: %1, reason: %2
 On receipt of message containing details to a change of its configuration,
 the server failed to create a parser to decode the contents of the named
 configuration element, or the creation succeeded but the parsing actions
@@ -112,6 +112,10 @@ service first starts.
 This is an informational message issued when the controller is exiting
 following a shut down (normal or otherwise) of the service.
 
+% DHCP_DDNS_AT_MAX_TRANSACTIONS application has %1 queued requests but has reached maximum number of %2 concurrent transactions
+This is a debug message that indicates that the application has DHCP_DDNS
+requests in the queue but is working as many concurrent requests as allowed.
+
 % DHCP_DDNS_COMMAND command directive received, command: %1 - args: %2
 This is a debug message issued when the Dhcp-Ddns application command method
 has been invoked.
@@ -124,20 +128,52 @@ has been invoked.
 This is a debug message issued when the Dhcp-Ddns application encounters an
 unrecoverable error from within the event loop.
 
-% DHCP_DDNS_NO_MATCH No DNS servers match FQDN: %1
+% DHCP_DDNS_INVALID_RESPONSE received response to DNS Update message is malformed: %1
+This is a debug message issued when the DHCP-DDNS application encountered an
+error while decoding a response to DNS Update message. Typically, this error
+will be encountered when a response message is malformed.
+
+% DHCP_DDNS_NO_ELIGIBLE_JOBS although there are queued requests, there are pending transactions for each Queue count: %1  Transaction count: %2
+This is a debug messge issued when all of the queued requests represent clients
+for which there is a an update already in progress.  This may occur under
+normal operations but should be temporary situation.
+
+% DHCP_DDNS_NO_FWD_MATCH_ERROR the configured list of forward DDNS domains does not contain a match for FQDN %1  The request has been discarded.
+This is an error message that indicates that DHCP_DDNS received a request to
+update a the forward DNS information for the given FQDN but for which there are
+no configured DDNS domains in the DHCP_DDNS configuration.  Either the DHCP_DDNS
+configuration needs to be updated or the source of the FQDN itself should be
+investigated.
+
+% DHCP_DDNS_NO_MATCH No DNS servers match FQDN %1
 This is warning message issued when there are no domains in the configuration
-which match the cited fully qualified domain name (FQDN).  The DNS Update 
+which match the cited fully qualified domain name (FQDN).  The DNS Update
 request for the FQDN cannot be processed.
 
-% DHCP_DDNS_INVALID_RESPONSE received response to DNS Update message is malformed: %1
-This is a debug message issued when the DHCP-DDNS application encountered an error
-while decoding a response to DNS Update message. Typically, this error will be
-encountered when a response message is malformed.
+% DHCP_DDNS_NO_REV_MATCH_ERROR the configured list of reverse DDNS domains does not contain a match for FQDN %1  The request has been discarded.
+This is an error message that indicates that DHCP_DDNS received a request to
+update a the reverse DNS information for the given FQDN but for which there are
+no configured DDNS domains in the DHCP_DDNS configuration.  Either the DHCP_DDNS
+configuration needs to be updated or the source of the FQDN itself should be
+investigated.
 
 % DHCP_DDNS_PROCESS_INIT application init invoked
 This is a debug message issued when the Dhcp-Ddns application enters
 its init method.
 
+% DHCP_DDNS_QUEUE_MGR_QUEUE_FULL application request queue has reached maximum number of entries %1
+This an error message indicating that DHCP-DDNS is receiving DNS update
+requests faster than they can be processed.  This may mean the maximum queue
+needs to be increased, the DHCP-DDNS clients are simply generating too many
+requests too quickly, or perhaps upstream DNS servers are experiencing
+load issues.
+
+% DHCP_DDNS_QUEUE_MGR_RECV_ERROR application's queue manager was notified of a request receive error by its listener.
+This is an error message indicating that the NameChangeRequest listener used by
+DHCP-DDNS to receive requests encountered a IO error.  There should be
+corresponding log messages from the listener layer with more details. This may
+indicate a network connectivity or system resource issue.
+
 % DHCP_DDNS_RUN_ENTER application has entered the event loop
 This is a debug message issued when the Dhcp-Ddns application enters
 its run method.
diff --git a/src/bin/d2/d2_queue_mgr.cc b/src/bin/d2/d2_queue_mgr.cc
new file mode 100644
index 0000000..10ddb0b
--- /dev/null
+++ b/src/bin/d2/d2_queue_mgr.cc
@@ -0,0 +1,217 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_log.h>
+#include <d2/d2_queue_mgr.h>
+#include <dhcp_ddns/ncr_udp.h>
+
+namespace isc {
+namespace d2 {
+
+// Makes constant visible to Google test macros.
+const size_t D2QueueMgr::MAX_QUEUE_DEFAULT;
+
+D2QueueMgr::D2QueueMgr(isc::asiolink::IOService& io_service,
+                       const size_t max_queue_size)
+    : io_service_(io_service), max_queue_size_(max_queue_size),
+      mgr_state_(NOT_INITTED) {
+    // Use setter to do validation.
+    setMaxQueueSize(max_queue_size);
+}
+
+D2QueueMgr::~D2QueueMgr() {
+    // clean up
+    try {
+        stopListening();
+    } catch (...) {
+        // This catch is strictly for safety's sake, in case a future
+        // implementation isn't tidy or careful. 
+    }
+}
+
+void
+D2QueueMgr::operator()(const dhcp_ddns::NameChangeListener::Result result,
+                       dhcp_ddns::NameChangeRequestPtr& ncr) {
+    // Note that error conditions must be handled here without throwing
+    // exceptions. Remember this is the application level "link" in the
+    // callback chain.  Throwing an exception here will "break" the
+    // io_service "run" we are operating under.  With that in mind,
+    // if we hit a problem, we will stop the listener transition to
+    // the appropriate stopped state.  Upper layer(s) must monitor our
+    // state as well as our queue size.
+
+    // If the receive was successful, attempt to queue the request.
+    if (result == dhcp_ddns::NameChangeListener::SUCCESS) {
+        if (getQueueSize() < getMaxQueueSize()) {
+            // There's room on the queue, add to the end
+            enqueue(ncr);
+            return;
+        }
+
+        // Queue is full, stop the listener.
+        stopListening(STOPPED_QUEUE_FULL);
+        LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_QUEUE_FULL)
+                  .arg(max_queue_size_);
+    } else {
+        // Receive failed, stop the listener.
+        stopListening(STOPPED_RECV_ERROR);
+        LOG_ERROR(dctl_logger, DHCP_DDNS_QUEUE_MGR_RECV_ERROR);
+    }
+}
+
+void
+D2QueueMgr::initUDPListener(const isc::asiolink::IOAddress& ip_address,
+                            const uint32_t port,
+                            const dhcp_ddns::NameChangeFormat format,
+                            const bool reuse_address) {
+
+    if (listener_) {
+        isc_throw(D2QueueMgrError,
+                  "D2QueueMgr listener is already initialized");
+    }
+
+    // Instantiate a UDP listener and set state to INITTED.
+    // Note UDP listener constructor does not throw.
+    listener_.reset(new dhcp_ddns::
+                    NameChangeUDPListener(ip_address, port, format, *this,
+                                          reuse_address));
+    mgr_state_ = INITTED;
+}
+
+void
+D2QueueMgr::startListening() {
+    // We can't listen if we haven't initialized the listener yet.
+    if (!listener_) {
+        isc_throw(D2QueueMgrError, "D2QueueMgr "
+                  "listener is not initialized, cannot start listening");
+    }
+
+    // If we are already listening, we do not want to "reopen" the listener
+    // and really we shouldn't be trying.
+    if (mgr_state_ == RUNNING) {
+        isc_throw(D2QueueMgrError, "D2QueueMgr "
+                  "cannot call startListening from the RUNNING state");
+    }
+
+    // Instruct the listener to start listening and set state accordingly.
+    try {
+        listener_->startListening(io_service_);
+        mgr_state_ = RUNNING;
+    } catch (const isc::Exception& ex) {
+        isc_throw(D2QueueMgrError, "D2QueueMgr listener start failed: "
+                  << ex.what());
+    }
+}
+
+void
+D2QueueMgr::stopListening(const State stop_state) {
+    // Note, stopListening is guaranteed not to throw.
+    if (listener_) {
+        listener_->stopListening();
+    }
+
+    // Enforce only valid "stop" states.
+    if (stop_state != STOPPED && stop_state != STOPPED_QUEUE_FULL &&
+        stop_state != STOPPED_RECV_ERROR) {
+        // This is purely a programmatic error and should never happen.
+        isc_throw(D2QueueMgrError, "D2QueueMgr invalid value for stop state: "
+                  << stop_state);
+    }
+
+    mgr_state_ = stop_state;
+}
+
+void
+D2QueueMgr::removeListener() {
+    // Force our managing layer(s) to stop us properly first.
+    if (mgr_state_ == RUNNING) {
+        isc_throw(D2QueueMgrError,
+                  "D2QueueMgr cannot delete listener while state is RUNNING");
+    }
+
+    listener_.reset();
+    mgr_state_ = NOT_INITTED;
+}
+
+const dhcp_ddns::NameChangeRequestPtr&
+D2QueueMgr::peek() const {
+    if (getQueueSize() ==  0) {
+        isc_throw(D2QueueMgrQueueEmpty,
+                  "D2QueueMgr peek attempted on an empty queue");
+    }
+
+    return (ncr_queue_.front());
+}
+
+const dhcp_ddns::NameChangeRequestPtr&
+D2QueueMgr::peekAt(const size_t index) const {
+    if (index >= getQueueSize()) {
+        isc_throw(D2QueueMgrInvalidIndex,
+                  "D2QueueMgr peek beyond end of queue attempted"
+                  << " index: " << index << " queue size: " << getQueueSize());
+    }
+
+    return (ncr_queue_.at(index));
+}
+
+void
+D2QueueMgr::dequeueAt(const size_t index) {
+    if (index >= getQueueSize()) {
+        isc_throw(D2QueueMgrInvalidIndex,
+                  "D2QueueMgr dequeue beyond end of queue attempted"
+                  << " index: " << index << " queue size: " << getQueueSize());
+    }
+
+    RequestQueue::iterator pos = ncr_queue_.begin() + index;
+    ncr_queue_.erase(pos);
+}
+
+
+void
+D2QueueMgr::dequeue() {
+    if (getQueueSize() ==  0) {
+        isc_throw(D2QueueMgrQueueEmpty,
+                  "D2QueueMgr dequeue attempted on an empty queue");
+    }
+
+    ncr_queue_.pop_front();
+}
+
+void
+D2QueueMgr::enqueue(dhcp_ddns::NameChangeRequestPtr& ncr) {
+    ncr_queue_.push_back(ncr);
+}
+
+void
+D2QueueMgr::clearQueue() {
+    ncr_queue_.clear();
+}
+
+void
+D2QueueMgr::setMaxQueueSize(const size_t new_queue_max) {
+    if (new_queue_max < 1) {
+        isc_throw(D2QueueMgrError,
+                  "D2QueueMgr maximum queue size must be greater than zero");
+    }
+
+    if (new_queue_max < getQueueSize()) {
+        isc_throw(D2QueueMgrError, "D2QueueMgr maximum queue size value cannot"
+                  " be less than the current queue size :" << getQueueSize());
+    }
+
+    max_queue_size_ = new_queue_max;
+}
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/bin/d2/d2_queue_mgr.h b/src/bin/d2/d2_queue_mgr.h
new file mode 100644
index 0000000..a6f3738
--- /dev/null
+++ b/src/bin/d2/d2_queue_mgr.h
@@ -0,0 +1,335 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_QUEUE_MGR_H
+#define D2_QUEUE_MGR_H
+
+/// @file d2_queue_mgr.h This file defines the class D2QueueMgr.
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <exceptions/exceptions.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <dhcp_ddns/ncr_io.h>
+
+#include <boost/noncopyable.hpp>
+#include <deque>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Defines a queue of requests.
+/// @todo This may be replaced with an actual class in the future.
+typedef std::deque<dhcp_ddns::NameChangeRequestPtr> RequestQueue;
+
+/// @brief Thrown if the queue manager encounters a general error.
+class D2QueueMgrError : public isc::Exception {
+public:
+    D2QueueMgrError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Thrown if the queue manager's receive handler is passed
+/// a failure result.
+class D2QueueMgrReceiveError : public isc::Exception {
+public:
+    D2QueueMgrReceiveError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Thrown if the request queue is full when an enqueue is attempted.
+class D2QueueMgrQueueFull : public isc::Exception {
+public:
+    D2QueueMgrQueueFull(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Thrown if the request queue empty and a read is attempted.
+class D2QueueMgrQueueEmpty : public isc::Exception {
+public:
+    D2QueueMgrQueueEmpty(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Thrown if a queue index is beyond the end of the queue
+class D2QueueMgrInvalidIndex : public isc::Exception {
+public:
+    D2QueueMgrInvalidIndex(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief D2QueueMgr creates and manages a queue of DNS update requests.
+///
+/// D2QueueMgr is class specifically designed as an integral part of DHCP-DDNS.
+/// Its primary responsibility is to listen for NameChangeRequests from
+/// DHCP-DDNS clients (e.g. DHCP servers) and queue them for processing. In
+/// addition it may provide a number services to locate entries in the queue
+/// such as by FQDN or DHCID.  These services may eventually be used
+/// for processing optimization.  The initial implementation will support
+/// simple FIFO access.
+///
+/// D2QueueMgr uses a NameChangeListener to asynchronously receive requests.
+/// It derives from NameChangeListener::RequestReceiveHandler and supplies an
+/// implementation of the operator()(Result, NameChangeRequestPtr).  It is
+/// through this operator() that D2QueueMgr is passed inbound NCRs. D2QueueMgr
+/// will add each newly received request onto the back of the request queue
+///
+/// D2QueueMgr defines a simple state model constructed around the status of
+/// its NameChangeListener, consisting of the following states:
+///
+///     * NOT_INITTED - D2QueueMgr has been constructed, but its listener has
+///     not been initialized.
+///
+///     * INITTED - The listener has been initialized, but it is not open for
+///     listening.   To move from NOT_INITTED to INITTED, one of the D2QueueMgr
+///     listener initialization methods must be invoked.  Currently there is
+///     only one type of listener, NameChangeUDPListener, hence there is only
+///     one listener initialization method, initUDPListener.  As more listener
+///     types are created, listener initialization methods will need to be
+///     added.
+///
+///     * RUNNING - The listener is open and listening for requests.
+///     Once initialized, in order to begin listening for requests, the
+///     startListener() method must be invoked.  Upon successful completion of
+///     of this call, D2QueueMgr will begin receiving requests as they arrive
+///     without any further steps.   This method may be called from the INITTED
+///     or one of the STOPPED states.
+///
+///     * STOPPED - The listener has been listening but has been stopped
+///     without error. To return to listening, startListener() must be invoked.
+///
+///     * STOPPED_QUEUE_FULL - Request queue is full, the listener has been
+///     stopped.  D2QueueMgr will enter this state when the request queue
+///     reaches the maximum queue size.  Once this limit is reached, the
+///     listener will be closed and no further requests will be received.
+///     To return to listening, startListener() must be invoked.  Note that so
+///     long as the queue is full, any attempt to queue a request will fail.
+///
+///     * STOPPED_RECV_ERROR - The listener has experienced a receive error
+///     and has been stopped.  D2QueueMgr will enter this state when it is
+///     passed a failed status into the request completion handler.  To return
+///     to listening, startListener() must be invoked.
+///
+/// D2QueueMgr does not attempt to recover from stopped conditions, this is left
+/// to upper layers.
+///
+/// It is important to note that the queue contents are preserved between
+/// state transitions.  In other words entries in the queue remain there
+/// until they are removed explicitly via the deque() or implicitly by
+/// via the clearQueue() method.
+///
+class D2QueueMgr : public dhcp_ddns::NameChangeListener::RequestReceiveHandler,
+                   boost::noncopyable {
+public:
+    /// @brief Maximum number of entries allowed in the request queue.
+    /// NOTE that 1024 is an arbitrary choice picked for the initial
+    /// implementation.
+    static const size_t MAX_QUEUE_DEFAULT = 1024;
+
+    /// @brief Defines the list of possible states for D2QueueMgr.
+    enum State {
+      NOT_INITTED,
+      INITTED,
+      RUNNING,
+      STOPPED_QUEUE_FULL,
+      STOPPED_RECV_ERROR,
+      STOPPED,
+    };
+
+    /// @brief Constructor
+    ///
+    /// Creates a D2QueueMgr instance.  Note that the listener is not created
+    /// in the constructor. The initial state will be NOT_INITTED.
+    ///
+    /// @param io_service IOService instance to be passed into the listener for
+    /// IO management.
+    /// @param max_queue_size the maximum number of entries allowed in the
+    /// queue.
+    /// This value must be greater than zero. It defaults to MAX_QUEUE_DEFAULT.
+    ///
+    /// @throw D2QueueMgrError if max_queue_size is zero.
+    D2QueueMgr(isc::asiolink::IOService& io_service,
+               const size_t max_queue_size = MAX_QUEUE_DEFAULT);
+
+    /// @brief Destructor
+    virtual ~D2QueueMgr();
+
+    /// @brief Initializes the listener as a UDP listener.
+    ///
+    /// Instantiates the listener_ member as NameChangeUDPListener passing
+    /// the given parameters.  Upon successful completion, the D2QueueMgr state
+    /// will be INITTED.
+    ///
+    /// @param ip_address is the network address on which to listen
+    /// @param port is the IP port on which to listen
+    /// @param format is the wire format of the inbound requests.
+    /// @param reuse_address enables IP address sharing when true
+    /// It defaults to false.
+    void initUDPListener(const isc::asiolink::IOAddress& ip_address,
+                         const uint32_t port,
+                         const dhcp_ddns::NameChangeFormat format,
+                         const bool reuse_address = false);
+
+    /// @brief Starts actively listening for requests.
+    ///
+    /// Invokes the listener's startListening method passing in our
+    /// IOService instance.
+    ///
+    /// @throw D2QueueMgrError if the listener has not been initialized,
+    /// state is already RUNNING, or the listener fails to actually start.
+    void startListening();
+
+    /// @brief Function operator implementing the NCR receive callback.
+    ///
+    /// This method is invoked by the listener as part of its receive
+    /// completion callback and is how the inbound NameChangeRequests are
+    /// passed up to the D2QueueMgr for queueing.
+    /// If the given result indicates a successful receive completion and
+    /// there is room left in the queue, the given request is queued.
+    ///
+    /// If the queue is at maximum capacity, stopListening() is invoked and
+    /// the state is set to STOPPED_QUEUE_FULL.
+    ///
+    /// If the result indicates a failed receive, stopListening() is invoked
+    /// and the state is set to STOPPED_RECV_ERROR.
+    ///
+    /// This method specifically avoids throwing on an error as any such throw
+    /// would surface at the io_service::run (or run variant) method invocation
+    /// site. The upper layers are expected to monitor D2QueueMgr's state and
+    /// act accordingly.
+    ///
+    /// @param result contains that receive outcome status.
+    /// @param ncr is a pointer to the newly received NameChangeRequest if
+    /// result is NameChangeListener::SUCCESS.  It is indeterminate other
+    /// wise.
+    virtual void operator ()(const dhcp_ddns::NameChangeListener::Result result,
+                             dhcp_ddns::NameChangeRequestPtr& ncr);
+
+    /// @brief Stops listening for requests.
+    ///
+    /// Invokes the listener's stopListening method which should cause it to
+    /// cancel any pending IO and close its IO source.  It the sets the state
+    /// to the given value.
+    ///
+    /// @param stop_state is one of the three stopped state values.
+    ///
+    /// @throw D2QueueMgrError if stop_state is a valid stop state.
+    void stopListening(const State stop_state = STOPPED);
+
+    /// @brief Deletes the current listener
+    ///
+    /// This method will delete the current listener and returns the manager
+    /// to the NOT_INITTED state.  This is provided to support reconfiguring
+    /// a new listener without losing queued requests.
+    ///
+    /// @throw D2QueMgrError if called when the manager state is RUNNING.
+    void removeListener();
+
+    /// @brief Returns the number of entries in the queue.
+    size_t getQueueSize() const {
+        return (ncr_queue_.size());
+    };
+
+    /// @brief Returns the maximum number of entries allowed in the queue.
+    size_t getMaxQueueSize() const {
+        return (max_queue_size_);
+    }
+
+    /// @brief Sets the maximum number of entries allowed in the queue.
+    ///
+    /// @param max_queue_size is the new maximum size of the queue.
+    ///
+    /// @throw D2QueueMgrError if the new value is less than one or if
+    /// the new value is less than the number of entries currently in the
+    /// queue.
+    void setMaxQueueSize(const size_t max_queue_size);
+
+    /// @brief Returns the current state.
+    State getMgrState() const {
+        return (mgr_state_);
+    }
+
+    /// @brief Returns the entry at the front of the queue.
+    ///
+    /// The entry returned is next in line to be processed, assuming a FIFO
+    /// approach to task selection.  Note, the entry is not removed from the
+    /// queue.
+    ///
+    /// @return Pointer reference to the queue entry.
+    ///
+    /// @throw D2QueueMgrQueEmpty if there are no entries in the queue.
+    const dhcp_ddns::NameChangeRequestPtr& peek() const;
+
+    /// @brief Returns the entry at a given position in the queue.
+    ///
+    /// Note that the entry is not removed from the queue.
+    /// @param index the index of the entry in the queue to fetch.
+    /// Valid values are 0 (front of the queue) to (queue size - 1).
+    ///
+    /// @return Pointer reference to the queue entry.
+    ///
+    /// @throw D2QueueMgrInvalidIndex if the given index is beyond the
+    /// end of the queue.
+    const dhcp_ddns::NameChangeRequestPtr& peekAt(const size_t index) const;
+
+    /// @brief Removes the entry at a given position in the queue.
+    ///
+    /// @param index the index of the entry in the queue to remove.
+    /// Valid values are 0 (front of the queue) to (queue size - 1).
+    ///
+    /// @throw D2QueueMgrInvalidIndex if the given index is beyond the
+    /// end of the queue.
+    void dequeueAt(const size_t index);
+
+    /// @brief Removes the entry at the front of the queue.
+    ///
+    /// @throw D2QueueMgrQueEmpty if there are no entries in the queue.
+    void dequeue();
+
+    /// @brief Adds a request to the end of the queue.
+    ///
+    /// @param ncr pointer to the NameChangeRequest to add to the queue.
+    void enqueue(dhcp_ddns::NameChangeRequestPtr& ncr);
+
+    /// @brief Removes all entries from the queue.
+    void clearQueue();
+
+  private:
+    /// @brief IOService that our listener should use for IO management.
+    isc::asiolink::IOService& io_service_;
+
+    /// @brief Dictates the maximum number of entries allowed in the queue.
+    size_t max_queue_size_;
+
+    /// @brief Queue of received NameChangeRequests.
+    RequestQueue ncr_queue_;
+
+    /// @brief Listener instance from which requests are received.
+    boost::shared_ptr<dhcp_ddns::NameChangeListener> listener_;
+
+    /// @brief Current state of the manager.
+    State mgr_state_;
+
+
+};
+
+/// @brief Defines a pointer for manager instances.
+typedef boost::shared_ptr<D2QueueMgr> D2QueueMgrPtr;
+
+} // namespace isc::d2
+} // namespace isc
+
+#endif
diff --git a/src/bin/d2/d2_update_mgr.cc b/src/bin/d2/d2_update_mgr.cc
new file mode 100644
index 0000000..e625c3e
--- /dev/null
+++ b/src/bin/d2/d2_update_mgr.cc
@@ -0,0 +1,230 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_update_mgr.h>
+
+#include <sstream>
+#include <iostream>
+#include <vector>
+
+namespace isc {
+namespace d2 {
+
+const size_t D2UpdateMgr::MAX_TRANSACTIONS_DEFAULT;
+
+D2UpdateMgr::D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
+                         isc::asiolink::IOService& io_service,
+                         const size_t max_transactions)
+    :queue_mgr_(queue_mgr), cfg_mgr_(cfg_mgr), io_service_(io_service) {
+    if (!queue_mgr_) {
+        isc_throw(D2UpdateMgrError, "D2UpdateMgr queue manager cannot be null");
+    }
+
+    if (!cfg_mgr_) {
+        isc_throw(D2UpdateMgrError,
+                  "D2UpdateMgr configuration manager cannot be null");
+    }
+
+    // Use setter to do validation.
+    setMaxTransactions(max_transactions);
+}
+
+D2UpdateMgr::~D2UpdateMgr() {
+    transaction_list_.clear();
+}
+
+void D2UpdateMgr::sweep() {
+    // cleanup finished transactions;
+    checkFinishedTransactions();
+
+    // if the queue isn't empty, find the next suitable job and
+    // start a transaction for it.
+    // @todo - Do we want to queue max transactions? The logic here will only
+    // start one new transaction per invocation.  On the other hand a busy
+    // system will generate many IO events and this method will be called
+    // frequently.  It will likely achieve max transactions quickly on its own.
+    if (getQueueCount() > 0)  {
+        if (getTransactionCount() >= max_transactions_) {
+            LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL_DATA,
+                      DHCP_DDNS_AT_MAX_TRANSACTIONS).arg(getQueueCount())
+                      .arg(getMaxTransactions());
+
+            return;
+        }
+
+        // We are not at maximum transactions, so pick and start the next job.
+        pickNextJob();
+    }
+}
+
+void
+D2UpdateMgr::checkFinishedTransactions() {
+    // Cycle through transaction list and do whatever needs to be done
+    // for finished transactions.
+    // At the moment all we do is remove them from the list. This is likely
+    // to expand as DHCP_DDNS matures.
+    // NOTE: One must use postfix increments of the iterator on the calls
+    // to erase.  This replaces the old iterator which becomes invalid by the
+    // erase with a the next valid iterator.  Prefix incrementing will not
+    // work. 
+    TransactionList::iterator it = transaction_list_.begin();
+    while (it != transaction_list_.end()) {
+        NameChangeTransactionPtr trans = (*it).second;
+        switch (trans->getNcrStatus())  {
+        case dhcp_ddns::ST_COMPLETED:
+            transaction_list_.erase(it++);
+            break;
+        case dhcp_ddns::ST_FAILED:
+            transaction_list_.erase(it++);
+            break;
+        default:
+            ++it;
+            break;
+        }
+    }
+}
+
+void D2UpdateMgr::pickNextJob() {
+    // Start at the front of the queue, looking for the first entry for
+    // which no transaction is in progress.  If we find an eligible entry
+    // remove it from the queue and  make a transaction for it.
+    // Requests and transactions are associated by DHCID.  If a request has
+    // the same DHCID as a transaction, they are presumed to be for the same
+    // "end user".
+    size_t queue_count = getQueueCount();
+    for (size_t index = 0; index < queue_count; ++index) {
+        dhcp_ddns::NameChangeRequestPtr found_ncr = queue_mgr_->peekAt(index);
+        if (!hasTransaction(found_ncr->getDhcid())) {
+            queue_mgr_->dequeueAt(index);
+            makeTransaction(found_ncr);
+            return;
+        }
+    }
+
+    // There were no eligible jobs. All of the current DHCIDs already have
+    // transactions pending.
+    LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL_DATA, DHCP_DDNS_NO_ELIGIBLE_JOBS)
+              .arg(getQueueCount()).arg(getTransactionCount());
+}
+
+void
+D2UpdateMgr::makeTransaction(dhcp_ddns::NameChangeRequestPtr& next_ncr) {
+    // First lets ensure there is not a transaction in progress for this
+    // DHCID. (pickNextJob should ensure this, as it is the only real caller
+    // but for safety's sake we'll check).
+    const TransactionKey& key = next_ncr->getDhcid();
+    if (findTransaction(key) != transactionListEnd()) {
+        // This is programmatic error.  Caller(s) should be checking this.
+        isc_throw(D2UpdateMgrError, "Transaction already in progress for: "
+            << key.toStr());
+    }
+
+    // If forward change is enabled, match to forward servers.
+    DdnsDomainPtr forward_domain;
+    if (next_ncr->isForwardChange()) {
+        bool matched = cfg_mgr_->matchForward(next_ncr->getFqdn(),
+                                             forward_domain);
+        // Could not find a match for forward DNS server. Log it and get out.
+        // This has the net affect of dropping the request on the floor.
+        if (!matched) {
+            LOG_ERROR(dctl_logger, DHCP_DDNS_NO_FWD_MATCH_ERROR)
+                      .arg(next_ncr->getFqdn());
+            return;
+        }
+    }
+
+    // If reverse change is enabled, match to reverse servers.
+    DdnsDomainPtr reverse_domain;
+    if (next_ncr->isReverseChange()) {
+        bool matched = cfg_mgr_->matchReverse(next_ncr->getIpAddress(),
+                                              reverse_domain);
+        // Could not find a match for reverse DNS server. Log it and get out.
+        // This has the net affect of dropping the request on the floor.
+        if (!matched) {
+            LOG_ERROR(dctl_logger, DHCP_DDNS_NO_REV_MATCH_ERROR)
+                      .arg(next_ncr->getIpAddress());
+            return;
+        }
+    }
+
+    // We matched to the required servers, so construct the transaction.
+    NameChangeTransactionPtr trans(new NameChangeTransaction(io_service_,
+                                                             next_ncr,
+                                                             forward_domain,
+                                                             reverse_domain));
+    // Add the new transaction to the list.
+    transaction_list_[key] = trans;
+}
+
+TransactionList::iterator
+D2UpdateMgr::findTransaction(const TransactionKey& key) {
+    return (transaction_list_.find(key));
+}
+
+bool
+D2UpdateMgr::hasTransaction(const TransactionKey& key) {
+   return (findTransaction(key) != transactionListEnd());
+}
+
+void
+D2UpdateMgr::removeTransaction(const TransactionKey& key) {
+    TransactionList::iterator pos = findTransaction(key);
+    if (pos != transactionListEnd()) {
+        transaction_list_.erase(pos);
+    }
+}
+
+TransactionList::iterator
+D2UpdateMgr::transactionListEnd() {
+    return (transaction_list_.end());
+}
+
+void
+D2UpdateMgr::clearTransactionList() {
+    // @todo for now this just wipes them out. We might need something
+    // more elegant, that allows a cancel first.
+    transaction_list_.clear();
+}
+
+void
+D2UpdateMgr::setMaxTransactions(const size_t new_trans_max) {
+    // Obviously we need at room for at least one transaction.
+    if (new_trans_max < 1) {
+        isc_throw(D2UpdateMgrError, "D2UpdateMgr"
+                  " maximum transactions limit must be greater than zero");
+    }
+
+    // Do not allow the list maximum to be set to less then current list size.
+    if (new_trans_max < getTransactionCount()) {
+        isc_throw(D2UpdateMgrError, "D2UpdateMgr maximum transaction limit "
+                  "cannot be less than the current transaction count :"
+                  << getTransactionCount());
+    }
+
+    max_transactions_ = new_trans_max;
+}
+
+size_t
+D2UpdateMgr::getQueueCount() const {
+    return (queue_mgr_->getQueueSize());
+}
+
+size_t
+D2UpdateMgr::getTransactionCount() const {
+    return (transaction_list_.size());
+}
+
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/bin/d2/d2_update_mgr.h b/src/bin/d2/d2_update_mgr.h
new file mode 100644
index 0000000..555b7be
--- /dev/null
+++ b/src/bin/d2/d2_update_mgr.h
@@ -0,0 +1,294 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef D2_UPDATE_MGR_H
+#define D2_UPDATE_MGR_H
+
+/// @file d2_update_mgr.h This file defines the class D2UpdateMgr.
+
+#include <asiolink/io_service.h>
+#include <exceptions/exceptions.h>
+#include <d2/d2_log.h>
+#include <d2/d2_queue_mgr.h>
+#include <d2/d2_cfg_mgr.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <map>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the update manager encounters a general error.
+class D2UpdateMgrError : public isc::Exception {
+public:
+    D2UpdateMgrError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+//@{
+/// @todo  This is a stub implementation of NameChangeTransaction that is here
+/// strictly to facilitate development of D2UpdateMgr. It will move to its own
+/// source file(s) once NameChangeTransaction class development begins.
+
+/// @brief Defines the key for transactions.
+typedef isc::dhcp_ddns::D2Dhcid TransactionKey;
+
+class NameChangeTransaction {
+public:
+    NameChangeTransaction(isc::asiolink::IOService& io_service,
+                          dhcp_ddns::NameChangeRequestPtr& ncr,
+                          DdnsDomainPtr forward_domain,
+                          DdnsDomainPtr reverse_domain)
+    : io_service_(io_service), ncr_(ncr), forward_domain_(forward_domain),
+      reverse_domain_(reverse_domain) {
+    }
+
+    ~NameChangeTransaction(){
+    }
+
+    const dhcp_ddns::NameChangeRequestPtr& getNcr() const {
+        return (ncr_);
+    }
+
+    const TransactionKey& getTransactionKey() const {
+        return (ncr_->getDhcid());
+    }
+
+    dhcp_ddns::NameChangeStatus getNcrStatus() const {
+        return (ncr_->getStatus());
+    }
+
+private:
+    isc::asiolink::IOService& io_service_;
+
+    dhcp_ddns::NameChangeRequestPtr ncr_;
+
+    DdnsDomainPtr forward_domain_;
+
+    DdnsDomainPtr reverse_domain_;
+};
+
+/// @brief Defines a pointer to a NameChangeTransaction.
+typedef boost::shared_ptr<NameChangeTransaction> NameChangeTransactionPtr;
+
+//@}
+
+/// @brief Defines a list of transactions.
+typedef std::map<TransactionKey, NameChangeTransactionPtr> TransactionList;
+
+
+/// @brief D2UpdateMgr creates and manages update transactions.
+///
+/// D2UpdateMgr is the DHCP_DDNS task master, instantiating and then supervising
+/// transactions that execute the DNS updates needed to fulfill the requests
+/// (NameChangeRequests) received from DHCP_DDNS clients (e.g. DHCP servers).
+///
+/// D2UpdateMgr uses the services of D2QueueMgr to monitor the queue of
+/// NameChangeRequests and select and dequeue requests for processing.
+/// When request is dequeued for processing it is removed from the queue and
+/// wrapped in NameChangeTransaction and added to the D2UpdateMgr's list of
+/// transactions.
+///
+/// As part of the process of forming transactions, D2UpdateMgr matches each
+/// request with the appropriate list of DNS servers.  This matching is  based
+/// upon request attributes, primarily the FQDN and update direction (forward
+/// or reverse).  D2UpdateMgr uses the services of D2CfgMgr to match requests
+/// to DNS server lists.
+///
+/// Once created, each transaction is responsible for carrying out the steps
+/// required to fulfill its specific request.  These steps typically consist of
+/// one or more DNS packet exchanges with the appropriate DNS server.  As
+/// transactions complete,  D2UpdateMgr removes them from the transaction list,
+/// replacing them with new transactions.
+///
+/// D2UpdateMgr carries out each of the above steps, from with a method called
+/// sweep().  This method is intended to be called as IO events complete.
+/// The upper layer(s) are responsible for calling sweep in a timely and cyclic
+/// manner.
+///
+class D2UpdateMgr : public boost::noncopyable {
+public:
+    /// @brief Maximum number of concurrent transactions
+    /// NOTE that 32 is an arbitrary choice picked for the initial
+    /// implementation.
+    static const size_t MAX_TRANSACTIONS_DEFAULT = 32;
+
+    // @todo This structure is not yet used. It is here in anticipation of
+    // enabled statistics capture.
+    struct Stats {
+        uint64_t start_time_;
+        uint64_t stop_time_;
+        uint64_t update_count_;
+        uint64_t min_update_time_;
+        uint64_t max_update_time_;
+        uint64_t server_rejects_;
+        uint64_t server_timeouts_;
+    };
+
+    /// @brief Constructor
+    ///
+    /// @param queue_mgr reference to the queue manager receiving requests
+    /// @param cfg_mgr reference to the configuration manager
+    /// @param io_service IO service used by the upper layer(s) to manage
+    /// IO events
+    /// @param max_transactions the maximum number of concurrent transactions
+    ///
+    /// @throw D2UpdateMgrError if either the queue manager or configuration
+    /// managers are NULL, or max transactions is less than one.
+    D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
+                isc::asiolink::IOService& io_service,
+                const size_t max_transactions = MAX_TRANSACTIONS_DEFAULT);
+
+    /// @brief Destructor
+    virtual ~D2UpdateMgr();
+
+    /// @brief Check current transactions; start transactions for new requests.
+    ///
+    /// This method is the primary public interface used by the upper layer. It
+    /// should be called as IO events complete.  During each invocation it does
+    /// the following:
+    ///
+    /// - Removes all completed transactions from the transaction list.
+    ///
+    /// - If the request queue is not empty and the number of transactions
+    /// in the transaction list has not reached maximum allowed, then select
+    /// a request from the queue.
+    ///
+    /// - If a request was selected, start a new transaction for it and
+    /// add the transaction to the list of transactions.
+    void sweep();
+
+protected:
+    /// @brief Performs post-completion cleanup on completed transactions.
+    ///
+    /// Iterates through the list of transactions and removes any that have
+    /// reached completion.  This method may expand in complexity or even
+    /// disappear altogether as the implementation matures.
+    void checkFinishedTransactions();
+
+    /// @brief Starts a transaction for the next eligible request in the queue.
+    ///
+    /// This method will scan the request queue for the next request to
+    /// dequeue.  The current implementation starts at the front of the queue
+    /// and looks for the first request for whose DHCID there is no current
+    /// transaction in progress.
+    ///
+    /// If a request is selected, it is removed from the queue and transaction
+    /// is constructed for it.
+    ///
+    /// It is possible that no such request exists, though this is likely to be
+    /// rather rare unless a system is frequently seeing requests for the same
+    /// clients in quick succession.
+    void pickNextJob();
+
+    /// @brief Create a new transaction for the given request.
+    ///
+    /// This method will attempt to match the request to a list of configured
+    /// DNS servers.  If a list of servers is found, it will instantiate a
+    /// transaction for it and add the transaction to the transaction list.
+    ///
+    /// If no servers are found that match the request, this constitutes a
+    /// configuration error.  The error will be logged and the request will
+    /// be discarded.
+    ///
+    /// @param ncr the NameChangeRequest for which to create a transaction.
+    ///
+    /// @throw D2UpdateMgrError if a transaction for this DHCID already
+    /// exists. Note this would be programmatic error.
+    void makeTransaction(isc::dhcp_ddns::NameChangeRequestPtr& ncr);
+
+public:
+    /// @brief Returns the maximum number of concurrent transactions.
+    size_t getMaxTransactions() const {
+        return (max_transactions_);
+    }
+
+    /// @brief Sets the maximum number of entries allowed in the queue.
+    ///
+    /// @param max_transactions is the new maximum number of transactions
+    ///
+    /// @throw Throws D2QueueMgrError if the new value is less than one or if
+    /// the new value is less than the number of entries currently in the
+    /// queue.
+    void setMaxTransactions(const size_t max_transactions);
+
+    /// @brief Search the transaction list for the given key.
+    ///
+    /// @param key the transaction key value for which to search.
+    ///
+    /// @return Iterator pointing to the entry found.  If no entry is
+    /// it will point to the list end position.
+    TransactionList::iterator findTransaction(const TransactionKey& key);
+
+    /// @brief Returns the transaction list end position.
+    TransactionList::iterator transactionListEnd();
+
+    /// @brief Convenience method that checks transaction list for the given key
+    ///
+    /// @param key the transaction key value for which to search.
+    ///
+    /// @return Returns true if the key is found within the list, false
+    /// otherwise.
+    bool hasTransaction(const TransactionKey& key);
+
+    /// @brief Removes the entry pointed to by key from the transaction list.
+    ///
+    /// Removes the entry referred to by key if it exists.  It has no effect
+    /// if the entry is not found.
+    ///
+    /// @param key of the transaction to remove
+    void removeTransaction(const TransactionKey& key);
+
+    /// @brief Immediately discards all entries in the transaction list.
+    ///
+    /// @todo For now this just wipes them out. We might need something
+    /// more elegant, that allows a cancel first.
+    void clearTransactionList();
+
+    /// @brief Convenience method that returns the number of requests queued.
+    size_t getQueueCount() const;
+
+    /// @brief Returns the current number of transactions.
+    size_t getTransactionCount() const;
+
+private:
+    /// @brief Pointer to the queue manager.
+    D2QueueMgrPtr queue_mgr_;
+
+    /// @brief Pointer to the configuration manager.
+    D2CfgMgrPtr cfg_mgr_;
+
+    /// @brief Primary IOService instance.
+    /// This is the IOService that the upper layer(s) use for IO events, such
+    /// as shutdown and configuration commands.  It is the IOService that is
+    /// passed into transactions to manager their IO events.
+    /// (For future reference, multi-threaded transactions would each use their
+    /// own IOService instance.)
+    isc::asiolink::IOService& io_service_;
+
+    /// @brief Maximum number of concurrent transactions.
+    size_t max_transactions_;
+
+    /// @brief List of transactions.
+    TransactionList transaction_list_;
+};
+
+/// @brief Defines a pointer to a D2UpdateMgr instance.
+typedef boost::shared_ptr<D2UpdateMgr> D2UpdateMgrPtr;
+
+
+} // namespace isc::d2
+} // namespace isc
+#endif
diff --git a/src/bin/d2/ncr_msg.cc b/src/bin/d2/ncr_msg.cc
deleted file mode 100644
index 6d9115f..0000000
--- a/src/bin/d2/ncr_msg.cc
+++ /dev/null
@@ -1,516 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <d2/ncr_msg.h>
-#include <asiolink/io_address.h>
-#include <asiolink/io_error.h>
-
-#include <botan/sha2_32.h>
-
-#include <sstream>
-#include <limits>
-
-namespace isc {
-namespace d2 {
-
-/********************************* D2Dhcid ************************************/
-
-D2Dhcid::D2Dhcid() {
-}
-
-D2Dhcid::D2Dhcid(const std::string& data) {
-    fromStr(data);
-}
-
-D2Dhcid::D2Dhcid(const isc::dhcp::DUID& duid,
-                 const std::vector<uint8_t>& wire_fqdn) {
-    fromDUID(duid, wire_fqdn);
-}
-
-
-void
-D2Dhcid::fromStr(const std::string& data) {
-    bytes_.clear();
-    try {
-        isc::util::encode::decodeHex(data, bytes_);
-    } catch (const isc::Exception& ex) {
-        isc_throw(NcrMessageError, "Invalid data in Dhcid:" << ex.what());
-    }
-}
-
-std::string
-D2Dhcid::toStr() const {
-    return (isc::util::encode::encodeHex(bytes_));
-}
-
-void
-D2Dhcid::fromDUID(const isc::dhcp::DUID& duid,
-                  const std::vector<uint8_t>& wire_fqdn) {
-    // DHCID created from DUID starts with two bytes representing
-    // a type of the identifier. The value of 0x0002 indicates that
-    // DHCID has been created from DUID. The 3rd byte is equal to 1
-    // which indicates that the SHA-256 algorithm is used to create
-    // a DHCID digest. This value is called digest-type.
-    static uint8_t dhcid_header[] = { 0x00, 0x02, 0x01 };
-
-    // We get FQDN in the wire format, so we don't know if it is
-    // valid. It is caller's responsibility to make sure it is in
-    // the valid format. Here we just make sure it is not empty.
-    if (wire_fqdn.empty()) {
-        isc_throw(isc::d2::NcrMessageError,
-                  "empty FQDN used to create DHCID");
-    }
-
-    // Get the wire representation of the DUID.
-    std::vector<uint8_t> data = duid.getDuid();
-    // It should be DUID class responsibility to validate the DUID
-    // but let's be on the safe side here and make sure that empty
-    // DUID is not returned.
-    if (data.empty()) {
-        isc_throw(isc::d2::NcrMessageError,
-                  "empty DUID used to create DHCID");
-    }
-
-    // Append FQDN in the wire format.
-    data.insert(data.end(), wire_fqdn.begin(), wire_fqdn.end());
-
-    // Use the DUID and FQDN to compute the digest (see RFC4701, section 3).
-    Botan::SecureVector<Botan::byte> secure;
-    try {
-        Botan::SHA_256 sha;
-        // We have checked already that the DUID and FQDN aren't empty
-        // so it is safe to assume that the data buffer is not empty.
-        secure = sha.process(static_cast<const Botan::byte*>(&data[0]),
-                             data.size());
-    } catch (const std::exception& ex) {
-        isc_throw(isc::d2::NcrMessageError,
-                  "error while generating DHCID from DUID: "
-                  << ex.what());
-    }
-
-    // The exception unsafe part is finished, so we can finally replace
-    // the contents of bytes_.
-    bytes_.assign(dhcid_header, dhcid_header + sizeof(dhcid_header));
-    bytes_.insert(bytes_.end(), secure.begin(), secure.end());
-}
-
-
-/**************************** NameChangeRequest ******************************/
-
-NameChangeRequest::NameChangeRequest()
-    : change_type_(CHG_ADD), forward_change_(false),
-    reverse_change_(false), fqdn_(""), ip_address_(""),
-    dhcid_(), lease_expires_on_(), lease_length_(0), status_(ST_NEW) {
-}
-
-NameChangeRequest::NameChangeRequest(const NameChangeType change_type,
-            const bool forward_change, const bool reverse_change,
-            const std::string& fqdn, const std::string& ip_address,
-            const D2Dhcid& dhcid,
-            const uint64_t lease_expires_on,
-            const uint32_t lease_length)
-    : change_type_(change_type), forward_change_(forward_change),
-    reverse_change_(reverse_change), fqdn_(fqdn), ip_address_(ip_address),
-    dhcid_(dhcid), lease_expires_on_(lease_expires_on),
-    lease_length_(lease_length), status_(ST_NEW) {
-
-    // Validate the contents. This will throw a NcrMessageError if anything
-    // is invalid.
-    validateContent();
-}
-
-NameChangeRequestPtr
-NameChangeRequest::fromFormat(const NameChangeFormat format,
-                              isc::util::InputBuffer& buffer) {
-    // Based on the format requested, pull the marshalled request from
-    // InputBuffer and pass it into the appropriate format-specific factory.
-    NameChangeRequestPtr ncr;
-    switch (format) {
-    case FMT_JSON: {
-        try {
-            // Get the length of the JSON text.
-            size_t len = buffer.readUint16();
-
-            // Read the text from the buffer into a vector.
-            std::vector<uint8_t> vec;
-            buffer.readVector(vec, len);
-
-            // Turn the vector into a string.
-            std::string string_data(vec.begin(), vec.end());
-
-            // Pass the string of JSON text into JSON factory to create the
-            // NameChangeRequest instance.  Note the factory may throw
-            // NcrMessageError.
-            ncr = NameChangeRequest::fromJSON(string_data);
-        } catch (isc::util::InvalidBufferPosition& ex) {
-            // Read error accessing data in InputBuffer.
-            isc_throw(NcrMessageError, "fromFormat: buffer read error: "
-                      << ex.what());
-        }
-
-        break;
-        }
-    default:
-        // Programmatic error, shouldn't happen.
-        isc_throw(NcrMessageError, "fromFormat - invalid format");
-        break;
-    }
-
-    return (ncr);
-}
-
-void
-NameChangeRequest::toFormat(const NameChangeFormat format,
-                            isc::util::OutputBuffer& buffer) const {
-    // Based on the format requested, invoke the appropriate format handler
-    // which will marshal this request's contents into the OutputBuffer.
-    switch (format) {
-    case FMT_JSON: {
-        // Invoke toJSON to create a JSON text of this request's contents.
-        std::string json = toJSON();
-        uint16_t length = json.size();
-
-        // Write the length of the JSON text to the OutputBuffer first, then
-        // write the JSON text itself.
-        buffer.writeUint16(length);
-        buffer.writeData(json.c_str(), length);
-        break;
-        }
-    default:
-        // Programmatic error, shouldn't happen.
-        isc_throw(NcrMessageError, "toFormat - invalid format");
-        break;
-    }
-}
-
-NameChangeRequestPtr
-NameChangeRequest::fromJSON(const std::string& json) {
-    // This method leverages the existing JSON parsing provided by isc::data
-    // library.  Should this prove to be a performance issue, it may be that
-    // lighter weight solution would be appropriate.
-
-    // Turn the string of JSON text into an Element set.
-    isc::data::ElementPtr elements;
-    try {
-        elements = isc::data::Element::fromJSON(json);
-    } catch (isc::data::JSONError& ex) {
-        isc_throw(NcrMessageError,
-                  "Malformed NameChangeRequest JSON: " << ex.what());
-    }
-
-    // Get a map of the Elements, keyed by element name.
-    ElementMap element_map = elements->mapValue();
-    isc::data::ConstElementPtr element;
-
-    // Use default constructor to create a "blank" NameChangeRequest.
-    NameChangeRequestPtr ncr(new NameChangeRequest());
-
-    // For each member of NameChangeRequest, find its element in the map and
-    // call the appropriate Element-based setter.  These setters may throw
-    // NcrMessageError if the given Element is the wrong type or its data
-    // content is lexically invalid.   If the element is NOT found in the
-    // map, getElement will throw NcrMessageError indicating the missing
-    // member. Currently there are no optional values.
-    element = ncr->getElement("change_type", element_map);
-    ncr->setChangeType(element);
-
-    element = ncr->getElement("forward_change", element_map);
-    ncr->setForwardChange(element);
-
-    element = ncr->getElement("reverse_change", element_map);
-    ncr->setReverseChange(element);
-
-    element = ncr->getElement("fqdn", element_map);
-    ncr->setFqdn(element);
-
-    element = ncr->getElement("ip_address", element_map);
-    ncr->setIpAddress(element);
-
-    element = ncr->getElement("dhcid", element_map);
-    ncr->setDhcid(element);
-
-    element = ncr->getElement("lease_expires_on", element_map);
-    ncr->setLeaseExpiresOn(element);
-
-    element = ncr->getElement("lease_length", element_map);
-    ncr->setLeaseLength(element);
-
-    // All members were in the Element set and were correct lexically. Now
-    // validate the overall content semantically.  This will throw an
-    // NcrMessageError if anything is amiss.
-    ncr->validateContent();
-
-    // Everything is valid, return the new instance.
-    return (ncr);
-}
-
-std::string
-NameChangeRequest::toJSON() const  {
-    // Create a JSON string of this request's contents.  Note that this method
-    // does NOT use the isc::data library as generating the output is straight
-    // forward.
-    std::ostringstream stream;
-
-    stream << "{\"change_type\":" << getChangeType() << ","
-        << "\"forward_change\":"
-        << (isForwardChange() ? "true" : "false") << ","
-        << "\"reverse_change\":"
-        << (isReverseChange() ? "true" : "false") << ","
-        << "\"fqdn\":\"" << getFqdn() << "\","
-        << "\"ip_address\":\"" << getIpAddress() << "\","
-        << "\"dhcid\":\"" << getDhcid().toStr() << "\","
-        << "\"lease_expires_on\":\""  << getLeaseExpiresOnStr() << "\","
-        << "\"lease_length\":" << getLeaseLength() << "}";
-
-    return (stream.str());
-}
-
-
-void
-NameChangeRequest::validateContent() {
-    //@todo This is an initial implementation which provides a minimal amount
-    // of validation.  FQDN, DHCID, and IP Address members are all currently
-    // strings, these may be replaced with richer classes.
-    if (fqdn_ == "") {
-        isc_throw(NcrMessageError, "FQDN cannot be blank");
-    }
-
-    // Validate IP Address.
-    try {
-        isc::asiolink::IOAddress io_addr(ip_address_);
-    } catch (const isc::asiolink::IOError& ex) {
-        isc_throw(NcrMessageError,
-                  "Invalid ip address string for ip_address: " << ip_address_);
-    }
-
-    // Validate the DHCID.
-    if (dhcid_.getBytes().size()  == 0) {
-        isc_throw(NcrMessageError, "DHCID cannot be blank");
-    }
-
-    // Ensure the request specifies at least one direction to update.
-    if (!forward_change_ && !reverse_change_) {
-        isc_throw(NcrMessageError,
-                  "Invalid Request, forward and reverse flags are both false");
-    }
-}
-
-isc::data::ConstElementPtr
-NameChangeRequest::getElement(const std::string& name,
-                              const ElementMap& element_map) const {
-    // Look for "name" in the element map.
-    ElementMap::const_iterator it = element_map.find(name);
-    if (it == element_map.end()) {
-        // Didn't find the element, so throw.
-        isc_throw(NcrMessageError,
-                  "NameChangeRequest value missing for: " << name );
-    }
-
-    // Found the element, return it.
-    return (it->second);
-}
-
-void
-NameChangeRequest::setChangeType(const NameChangeType value) {
-    change_type_ = value;
-}
-
-
-void
-NameChangeRequest::setChangeType(isc::data::ConstElementPtr element) {
-    long raw_value = -1;
-    try {
-        // Get the element's integer value.
-        raw_value = element->intValue();
-    } catch (isc::data::TypeError& ex) {
-        // We expect a integer Element type, don't have one.
-        isc_throw(NcrMessageError,
-                  "Wrong data type for change_type: " << ex.what());
-    }
-
-    if ((raw_value != CHG_ADD) && (raw_value != CHG_REMOVE)) {
-        // Value is not a valid change type.
-        isc_throw(NcrMessageError,
-                  "Invalid data value for change_type: " << raw_value);
-    }
-
-    // Good to go, make the assignment.
-    setChangeType(static_cast<NameChangeType>(raw_value));
-}
-
-void
-NameChangeRequest::setForwardChange(const bool value) {
-    forward_change_ = value;
-}
-
-void
-NameChangeRequest::setForwardChange(isc::data::ConstElementPtr element) {
-    bool value;
-    try {
-        // Get the element's boolean value.
-        value = element->boolValue();
-    } catch (isc::data::TypeError& ex) {
-        // We expect a boolean Element type, don't have one.
-        isc_throw(NcrMessageError,
-                  "Wrong data type for forward_change :" << ex.what());
-    }
-
-    // Good to go, make the assignment.
-    setForwardChange(value);
-}
-
-void
-NameChangeRequest::setReverseChange(const bool value) {
-    reverse_change_ = value;
-}
-
-void
-NameChangeRequest::setReverseChange(isc::data::ConstElementPtr element) {
-    bool value;
-    try {
-        // Get the element's boolean value.
-        value = element->boolValue();
-    } catch (isc::data::TypeError& ex) {
-        // We expect a boolean Element type, don't have one.
-        isc_throw(NcrMessageError,
-                  "Wrong data type for reverse_change :" << ex.what());
-    }
-
-    // Good to go, make the assignment.
-    setReverseChange(value);
-}
-
-
-void
-NameChangeRequest::setFqdn(isc::data::ConstElementPtr element) {
-    setFqdn(element->stringValue());
-}
-
-void
-NameChangeRequest::setFqdn(const std::string& value) {
-    fqdn_ = value;
-}
-
-void
-NameChangeRequest::setIpAddress(const std::string& value) {
-    ip_address_ = value;
-}
-
-
-void
-NameChangeRequest::setIpAddress(isc::data::ConstElementPtr element) {
-    setIpAddress(element->stringValue());
-}
-
-
-void
-NameChangeRequest::setDhcid(const std::string& value) {
-    dhcid_.fromStr(value);
-}
-
-void
-NameChangeRequest::setDhcid(isc::data::ConstElementPtr element) {
-    setDhcid(element->stringValue());
-}
-
-std::string
-NameChangeRequest::getLeaseExpiresOnStr() const {
-    return (isc::util::timeToText64(lease_expires_on_));
-}
-
-void
-NameChangeRequest::setLeaseExpiresOn(const std::string&  value) {
-    try {
-        lease_expires_on_ = isc::util::timeFromText64(value);
-    } catch(...) {
-        // We were given an invalid string, so throw.
-        isc_throw(NcrMessageError,
-            "Invalid date-time string: [" << value << "]");
-    }
-
-}
-
-void NameChangeRequest::setLeaseExpiresOn(isc::data::ConstElementPtr element) {
-    // Pull out the string value and pass it into the string setter.
-    setLeaseExpiresOn(element->stringValue());
-}
-
-void
-NameChangeRequest::setLeaseLength(const uint32_t value) {
-    lease_length_ = value;
-}
-
-void
-NameChangeRequest::setLeaseLength(isc::data::ConstElementPtr element) {
-    long value = -1;
-    try {
-        // Get the element's integer value.
-        value = element->intValue();
-    } catch (isc::data::TypeError& ex) {
-        // We expect a integer Element type, don't have one.
-        isc_throw(NcrMessageError,
-                  "Wrong data type for lease_length: " << ex.what());
-    }
-
-    // Make sure we the range is correct and value is positive.
-    if (value > std::numeric_limits<uint32_t>::max()) {
-        isc_throw(NcrMessageError, "lease_length value " << value <<
-                "is too large for unsigned 32-bit integer.");
-    }
-    if (value < 0) {
-        isc_throw(NcrMessageError, "lease_length value " << value <<
-             "is negative.  It must greater than or equal to zero ");
-    }
-
-    // Good to go, make the assignment.
-    setLeaseLength(static_cast<uint32_t>(value));
-}
-
-void
-NameChangeRequest::setStatus(const NameChangeStatus value) {
-    status_ = value;
-}
-
-std::string
-NameChangeRequest::toText() const {
-    std::ostringstream stream;
-
-    stream << "Type: " << static_cast<int>(change_type_) << " (";
-    switch (change_type_) {
-    case CHG_ADD:
-        stream << "CHG_ADD)\n";
-        break;
-    case CHG_REMOVE:
-        stream << "CHG_REMOVE)\n";
-        break;
-    default:
-        // Shouldn't be possible.
-        stream << "Invalid Value\n";
-    }
-
-    stream << "Forward Change: " << (forward_change_ ? "yes" : "no")
-           << std::endl
-           << "Reverse Change: " << (reverse_change_ ? "yes" : "no")
-           << std::endl
-           << "FQDN: [" << fqdn_ << "]" << std::endl
-           << "IP Address: [" << ip_address_  << "]" << std::endl
-           << "DHCID: [" << dhcid_.toStr() << "]" << std::endl
-           << "Lease Expires On: " << getLeaseExpiresOnStr() << std::endl
-           << "Lease Length: " << lease_length_ << std::endl;
-
-    return (stream.str());
-}
-
-}; // end of isc::dhcp namespace
-}; // end of isc namespace
diff --git a/src/bin/d2/ncr_msg.h b/src/bin/d2/ncr_msg.h
deleted file mode 100644
index f9e3c62..0000000
--- a/src/bin/d2/ncr_msg.h
+++ /dev/null
@@ -1,503 +0,0 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef NCR_MSG_H
-#define NCR_MSG_H
-
-/// @file ncr_msg.h
-/// @brief This file provides the classes needed to embody, compose, and
-/// decompose DNS update requests that are sent by DHCP-DDNS clients to
-/// DHCP-DDNS. These requests are referred to as NameChangeRequests.
-
-#include <cc/data.h>
-#include <dhcp/duid.h>
-#include <exceptions/exceptions.h>
-#include <util/buffer.h>
-#include <util/encode/hex.h>
-#include <util/time_utilities.h>
-
-#include <time.h>
-#include <string>
-
-namespace isc {
-namespace d2 {
-
-/// @brief Exception thrown when NameChangeRequest marshalling error occurs.
-class NcrMessageError : public isc::Exception {
-public:
-    NcrMessageError(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) { };
-};
-
-/// @brief Defines the types of DNS updates that can be requested.
-enum NameChangeType {
-  CHG_ADD,
-  CHG_REMOVE
-};
-
-/// @brief Defines the runtime processing status values for requests.
-enum NameChangeStatus  {
-  ST_NEW,
-  ST_PENDING,
-  ST_COMPLETED,
-  ST_FAILED,
-};
-
-/// @brief Defines the list of data wire formats supported.
-enum NameChangeFormat {
-  FMT_JSON
-};
-
-/// @brief Container class for handling the DHCID value within a
-/// NameChangeRequest. It provides conversion to and from string for JSON
-/// formatting, but stores the data internally as unsigned bytes.
-class D2Dhcid {
-public:
-    /// @brief Default constructor
-    D2Dhcid();
-
-    /// @brief Constructor - Creates a new instance, populated by converting
-    /// a given string of digits into an array of unsigned bytes.
-    ///
-    /// @param data is a string of hexadecimal digits. The format is simply
-    /// a contiguous stream of digits, with no delimiters. For example a string
-    /// containing "14A3" converts to a byte array containing:  0x14, 0xA3.
-    ///
-    /// @throw throws a NcrMessageError if the input data contains non-digits
-    /// or there is an odd number of digits.
-    D2Dhcid(const std::string& data);
-
-    /// @brief Constructor, creates an instance of the @c D2Dhcid from the
-    /// @c isc::dhcp::DUID.
-    ///
-    /// @param duid An object representing DUID.
-    /// @param wire_fqdn A on-wire canonical representation of the FQDN.
-    D2Dhcid(const isc::dhcp::DUID& duid,
-            const std::vector<uint8_t>& wire_fqdn);
-
-    /// @brief Returns the DHCID value as a string of hexadecimal digits.
-    ///
-    /// @return returns a string containing a contiguous stream of digits.
-    std::string toStr() const;
-
-    /// @brief Sets the DHCID value based on the given string.
-    ///
-    /// @param data is a string of hexadecimal digits. The format is simply
-    /// a contiguous stream of digits, with no delimiters. For example a string
-    /// containing "14A3" converts to a byte array containing:  0x14, 0xA3.
-    ///
-    /// @throw throws a NcrMessageError if the input data contains non-digits
-    /// or there is an odd number of digits.
-    void fromStr(const std::string& data);
-
-    /// @brief Sets the DHCID value based on the DUID and FQDN.
-    ///
-    /// @param duid A @c isc::dhcp::DUID object encapsulating DUID.
-    /// @param wire_fqdn A on-wire canonical representation of the FQDN.
-    void fromDUID(const isc::dhcp::DUID& duid,
-                  const std::vector<uint8_t>& wire_fqdn);
-
-    /// @brief Returns a reference to the DHCID byte vector.
-    ///
-    /// @return returns a reference to the vector.
-    const std::vector<uint8_t>& getBytes() {
-        return (bytes_);
-    }
-
-private:
-    /// @brief Storage for the DHCID value in unsigned bytes.
-    std::vector<uint8_t> bytes_;
-};
-
-class NameChangeRequest;
-/// @brief Defines a pointer to a NameChangeRequest.
-typedef boost::shared_ptr<NameChangeRequest> NameChangeRequestPtr;
-
-/// @brief Defines a map of Elements, keyed by their string name.
-typedef std::map<std::string, isc::data::ConstElementPtr> ElementMap;
-
-/// @brief  Represents a DHCP-DDNS client request.
-/// This class is used by DHCP-DDNS clients (e.g. DHCP4, DHCP6) to
-/// request DNS updates.  Each message contains a single DNS change (either an
-/// add/update or a remove) for a single FQDN.  It provides marshalling services
-/// for moving instances to and from the wire.  Currently, the only format
-/// supported is JSON, however the class provides an interface such that other
-/// formats can be readily supported.
-class NameChangeRequest {
-public:
-    /// @brief Default Constructor.
-    NameChangeRequest();
-
-    /// @brief Constructor.  Full constructor, which provides parameters for
-    /// all of the class members, except status.
-    ///
-    /// @param change_type the type of change (Add or Update)
-    /// @param forward_change indicates if this change should be sent to forward
-    /// DNS servers.
-    /// @param reverse_change indicates if this change should be sent to reverse
-    /// DNS servers.
-    /// @param fqdn the domain name whose pointer record(s) should be
-    /// updated.
-    /// @param ip_address the ip address leased to the given FQDN.
-    /// @param dhcid the lease client's unique DHCID.
-    /// @param lease_expires_on a timestamp containing the date/time the lease
-    /// expires.
-    /// @param lease_length the amount of time in seconds for which the
-    /// lease is valid (TTL).
-    NameChangeRequest(const NameChangeType change_type,
-                      const bool forward_change, const bool reverse_change,
-                      const std::string& fqdn, const std::string& ip_address,
-                      const D2Dhcid& dhcid,
-                      const uint64_t lease_expires_on,
-                      const uint32_t lease_length);
-
-    /// @brief Static method for creating a NameChangeRequest from a
-    /// buffer containing a marshalled request in a given format.
-    ///
-    /// When the format is:
-    ///
-    /// JSON: The buffer is expected to contain a two byte unsigned integer
-    /// which specified the length of the JSON text; followed by the JSON
-    /// text itself.  This method attempts to extract "length" characters
-    /// from the buffer. This data is used to create a character string that
-    /// is than treated as JSON which is then parsed into the data needed
-    /// to create a request instance.
-    ///
-    /// (NOTE currently only JSON is supported.)
-    ///
-    /// @param format indicates the data format to use
-    /// @param buffer is the input buffer containing the marshalled request
-    ///
-    /// @return returns a pointer to the new NameChangeRequest
-    ///
-    /// @throw throws NcrMessageError if an error occurs creating new
-    /// request.
-    static NameChangeRequestPtr fromFormat(const NameChangeFormat format,
-                                           isc::util::InputBuffer& buffer);
-
-    /// @brief Instance method for marshalling the contents of the request
-    /// into the given buffer in the given format.
-    ///
-    /// When the format is:
-    ///
-    /// JSON: Upon completion, the buffer will contain a two byte unsigned
-    /// integer which specifies the length of the JSON text; followed by the
-    /// JSON text itself. The JSON text contains the names and values for all
-    /// the request data needed to reassemble the request on the receiving
-    /// end. The JSON text in the buffer is NOT null-terminated.
-    ///
-    /// (NOTE currently only JSON is supported.)
-    ///
-    /// @param format indicates the data format to use
-    /// @param buffer is the output buffer to which the request should be
-    /// marshalled.
-    void toFormat(const NameChangeFormat format,
-                  isc::util::OutputBuffer& buffer) const;
-
-    /// @brief Static method for creating a NameChangeRequest from a
-    /// string containing a JSON rendition of a request.
-    ///
-    /// @param json is a string containing the JSON text
-    ///
-    /// @return returns a pointer to the new NameChangeRequest
-    ///
-    /// @throw throws NcrMessageError if an error occurs creating new request.
-    static NameChangeRequestPtr fromJSON(const std::string& json);
-
-    /// @brief Instance method for marshalling the contents of the request
-    /// into a string of JSON text.
-    ///
-    /// @return returns a string containing the JSON rendition of the request
-    std::string toJSON() const;
-
-    /// @brief Validates the content of a populated request.  This method is
-    /// used by both the full constructor and from-wire marshalling to ensure
-    /// that the request is content valid.  Currently it enforces the
-    /// following rules:
-    ///
-    ///  - FQDN must not be blank.
-    ///  - The IP address must be a valid address.
-    ///  - The DHCID must not be blank.
-    ///  - The lease expiration date must be a valid date/time.
-    ///  - That at least one of the two direction flags, forward change and
-    ///    reverse change is true.
-    ///
-    /// @todo This is an initial implementation which provides a minimal amount
-    /// of validation.  FQDN, DHCID, and IP Address members are all currently
-    /// strings, these may be replaced with richer classes.
-    ///
-    /// @throw throws a NcrMessageError if the request content violates any
-    /// of the validation rules.
-    void validateContent();
-
-    /// @brief Fetches the request change type.
-    ///
-    /// @return returns the change type
-    NameChangeType getChangeType() const {
-        return (change_type_);
-    }
-
-    /// @brief Sets the change type to the given value.
-    ///
-    /// @param value is the NameChangeType value to assign to the request.
-    void setChangeType(const NameChangeType value);
-
-    /// @brief Sets the change type to the value of the given Element.
-    ///
-    /// @param element is an integer Element containing the change type value.
-    ///
-    /// @throw throws a NcrMessageError if the element is not an integer
-    /// Element or contains an invalid value.
-    void setChangeType(isc::data::ConstElementPtr element);
-
-    /// @brief Checks forward change flag.
-    ///
-    /// @return returns a true if the forward change flag is true.
-    bool isForwardChange() const {
-        return (forward_change_);
-    }
-
-    /// @brief Sets the forward change flag to the given value.
-    ///
-    /// @param value contains the new value to assign to the forward change
-    /// flag
-    void setForwardChange(const bool value);
-
-    /// @brief Sets the forward change flag to the value of the given Element.
-    ///
-    /// @param element is a boolean Element containing the forward change flag
-    /// value.
-    ///
-    /// @throw throws a NcrMessageError if the element is not a boolean
-    /// Element
-    void setForwardChange(isc::data::ConstElementPtr element);
-
-    /// @brief Checks reverse change flag.
-    ///
-    /// @return returns a true if the reverse change flag is true.
-    bool isReverseChange() const {
-        return (reverse_change_);
-    }
-
-    /// @brief Sets the reverse change flag to the given value.
-    ///
-    /// @param value contains the new value to assign to the reverse change
-    /// flag
-    void setReverseChange(const bool value);
-
-    /// @brief Sets the reverse change flag to the value of the given Element.
-    ///
-    /// @param element is a boolean Element containing the reverse change flag
-    /// value.
-    ///
-    /// @throw throws a NcrMessageError if the element is not a boolean
-    /// Element
-    void setReverseChange(isc::data::ConstElementPtr element);
-
-    /// @brief Fetches the request FQDN
-    ///
-    /// @return returns a string containing the FQDN
-    const std::string getFqdn() const {
-        return (fqdn_);
-    }
-
-    /// @brief Sets the FQDN to the given value.
-    ///
-    /// @param value contains the new value to assign to the FQDN
-    void setFqdn(const std::string& value);
-
-    /// @brief Sets the FQDN to the value of the given Element.
-    ///
-    /// @param element is a string Element containing the FQDN
-    ///
-    /// @throw throws a NcrMessageError if the element is not a string
-    /// Element
-    void setFqdn(isc::data::ConstElementPtr element);
-
-    /// @brief Fetches the request IP address.
-    ///
-    /// @return returns a string containing the IP address
-    const std::string& getIpAddress() const {
-        return (ip_address_);
-    }
-
-    /// @brief Sets the IP address to the given value.
-    ///
-    /// @param value contains the new value to assign to the IP address
-    void setIpAddress(const std::string& value);
-
-    /// @brief Sets the IP address to the value of the given Element.
-    ///
-    /// @param element is a string Element containing the IP address
-    ///
-    /// @throw throws a NcrMessageError if the element is not a string
-    /// Element
-    void setIpAddress(isc::data::ConstElementPtr element);
-
-    /// @brief Fetches the request DHCID
-    ///
-    /// @return returns a reference to the request's D2Dhcid
-    const D2Dhcid& getDhcid() const {
-        return (dhcid_);
-    }
-
-    /// @brief Sets the DHCID based on the given string value.
-    ///
-    /// @param value is a string of hexadecimal digits. The format is simply
-    /// a contiguous stream of digits, with no delimiters. For example a string
-    /// containing "14A3" converts to a byte array containing:  0x14, 0xA3.
-    ///
-    /// @throw throws a NcrMessageError if the input data contains non-digits
-    /// or there is an odd number of digits.
-    void setDhcid(const std::string& value);
-
-    /// @brief Sets the DHCID based on the value of the given Element.
-    ///
-    /// @param element is a string Element containing the string of hexadecimal
-    /// digits. (See setDhcid(std::string&) above.)
-    ///
-    /// @throw throws a NcrMessageError if the input data contains non-digits
-    /// or there is an odd number of digits.
-    void setDhcid(isc::data::ConstElementPtr element);
-
-    /// @brief Fetches the request lease expiration
-    ///
-    /// @return returns the lease expiration as the number of seconds since
-    /// the (00:00:00 January 1, 1970)
-    uint64_t getLeaseExpiresOn() const {
-        return (lease_expires_on_);
-    }
-
-    /// @brief Fetches the request lease expiration as string.
-    ///
-    /// The format of the string returned is:
-    ///
-    ///    YYYYMMDDHHmmSS
-    ///
-    /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
-    /// NOTE This is always UTC time.
-    ///
-    /// @return returns a ISO date-time string of the lease expiration.
-    std::string getLeaseExpiresOnStr() const;
-
-    /// @brief Sets the lease expiration based on the given string.
-    ///
-    /// @param value is an date-time string from which to set the
-    /// lease expiration. The format of the input is:
-    ///
-    ///    YYYYMMDDHHmmSS
-    ///
-    /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
-    /// NOTE This is always UTC time.
-    ///
-    /// @throw throws a NcrMessageError if the ISO string is invalid.
-    void setLeaseExpiresOn(const std::string& value);
-
-    /// @brief Sets the lease expiration based on the given Element.
-    ///
-    /// @param element is string Element containing a date-time string.
-    ///
-    /// @throw throws a NcrMessageError if the element is not a string
-    /// Element, or if the element value is an invalid date-time string.
-    void setLeaseExpiresOn(isc::data::ConstElementPtr element);
-
-    /// @brief Fetches the request lease length.
-    ///
-    /// @return returns an integer containing the lease length
-    uint32_t getLeaseLength() const {
-        return (lease_length_);
-    }
-
-    /// @brief Sets the lease length to the given value.
-    ///
-    /// @param value contains the new value to assign to the lease length
-    void setLeaseLength(const uint32_t value);
-
-    /// @brief Sets the lease length to the value of the given Element.
-    ///
-    /// @param element is a integer Element containing the lease length
-    ///
-    /// @throw throws a NcrMessageError if the element is not a string
-    /// Element
-    void setLeaseLength(isc::data::ConstElementPtr element);
-
-    /// @brief Fetches the request status.
-    ///
-    /// @return returns the request status as a NameChangeStatus
-    NameChangeStatus getStatus() const {
-        return (status_);
-    }
-
-    /// @brief Sets the request status to the given value.
-    ///
-    /// @param value contains the new value to assign to request status
-    void setStatus(const NameChangeStatus value);
-
-    /// @brief Given a name, finds and returns an element from a map of
-    /// elements.
-    ///
-    /// @param name is the name of the desired element
-    /// @param element_map is the map of elements to search
-    ///
-    /// @return returns a pointer to the element if located
-    /// @throw throws a NcrMessageError if the element cannot be found within
-    /// the map
-    isc::data::ConstElementPtr getElement(const std::string& name,
-                                          const ElementMap& element_map) const;
-
-    /// @brief Returns a text rendition of the contents of the request.
-    /// This method is primarily for logging purposes.
-    ///
-    /// @return returns a string containing the text.
-    std::string toText() const;
-
-private:
-    /// @brief Denotes the type of this change as either an Add or a Remove.
-    NameChangeType change_type_;
-
-    /// @brief Indicates if this change should sent to forward DNS servers.
-    bool forward_change_;
-
-    /// @brief Indicates if this change should sent to reverse DNS servers.
-    bool reverse_change_;
-
-    /// @brief The domain name whose DNS entry(ies) are to be updated.
-    /// @todo Currently, this is a std::string but may be replaced with
-    /// dns::Name which provides additional validation and domain name
-    /// manipulation.
-    std::string fqdn_;
-
-    /// @brief The ip address leased to the FQDN.
-    std::string ip_address_;
-
-    /// @brief The lease client's unique DHCID.
-    /// @todo Currently, this is uses D2Dhcid it but may be replaced with
-    /// dns::DHCID which provides additional validation.
-    D2Dhcid dhcid_;
-
-    /// @brief The date-time the lease expires.
-    uint64_t lease_expires_on_;
-
-    /// @brief The amount of time in seconds for which the lease is valid (TTL).
-    uint32_t lease_length_;
-
-    /// @brief The processing status of the request.  Used internally.
-    NameChangeStatus status_;
-};
-
-
-}; // end of isc::d2 namespace
-}; // end of isc namespace
-
-#endif
diff --git a/src/bin/d2/tests/Makefile.am b/src/bin/d2/tests/Makefile.am
index 4e888a9..6d0b894 100644
--- a/src/bin/d2/tests/Makefile.am
+++ b/src/bin/d2/tests/Makefile.am
@@ -59,10 +59,11 @@ d2_unittests_SOURCES += ../d2_controller.cc ../d2_controller.h
 d2_unittests_SOURCES += ../d_cfg_mgr.cc ../d_cfg_mgr.h
 d2_unittests_SOURCES += ../d2_config.cc ../d2_config.h
 d2_unittests_SOURCES += ../d2_cfg_mgr.cc ../d2_cfg_mgr.h
+d2_unittests_SOURCES += ../d2_queue_mgr.cc ../d2_queue_mgr.h
 d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
+d2_unittests_SOURCES += ../d2_update_mgr.cc ../d2_update_mgr.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
-d2_unittests_SOURCES += ../ncr_msg.cc ../ncr_msg.h
 d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
 d2_unittests_SOURCES += d2_unittests.cc
 d2_unittests_SOURCES += d2_process_unittests.cc
@@ -70,10 +71,11 @@ d2_unittests_SOURCES += d_controller_unittests.cc
 d2_unittests_SOURCES += d2_controller_unittests.cc
 d2_unittests_SOURCES += d_cfg_mgr_unittests.cc
 d2_unittests_SOURCES += d2_cfg_mgr_unittests.cc
+d2_unittests_SOURCES += d2_queue_mgr_unittests.cc
 d2_unittests_SOURCES += d2_update_message_unittests.cc
+d2_unittests_SOURCES += d2_update_mgr_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
-d2_unittests_SOURCES += ncr_unittests.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
@@ -85,9 +87,11 @@ d2_unittests_LDADD += $(top_builddir)/src/lib/asiodns/libb10-asiodns.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
 
 endif
 
diff --git a/src/bin/d2/tests/d2_cfg_mgr_unittests.cc b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
index 9f5a660..decfa69 100644
--- a/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
+++ b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
@@ -53,7 +53,7 @@ public:
 /// @brief Tests that the spec file is valid.
 /// Verifies that the BIND10 DHCP-DDNS configuration specification file
 //  is valid.
-TEST(D2SpecTest, basicSpecTest) {
+TEST(D2SpecTest, basicSpec) {
     ASSERT_NO_THROW(isc::config::
                     moduleSpecFromFile(specfile("dhcp-ddns.spec")));
 }
@@ -252,7 +252,7 @@ public:
 /// 3. Secret cannot be blank.
 /// @TODO TSIG keys are not fully functional. Only basic validation is
 /// currently supported. This test will need to expand as they evolve.
-TEST_F(TSIGKeyInfoTest, invalidEntryTests) {
+TEST_F(TSIGKeyInfoTest, invalidEntry) {
     // Config with a blank name entry.
     std::string config = "{"
                          " \"name\": \"\" , "
@@ -294,7 +294,7 @@ TEST_F(TSIGKeyInfoTest, invalidEntryTests) {
 
 /// @brief Verifies that TSIGKeyInfo parsing creates a proper TSIGKeyInfo
 /// when given a valid combination of entries.
-TEST_F(TSIGKeyInfoTest, validEntryTests) {
+TEST_F(TSIGKeyInfoTest, validEntry) {
     // Valid entries for TSIG key, all items are required.
     std::string config = "{"
                          " \"name\": \"d2_key_one\" , "
@@ -448,7 +448,7 @@ TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
 /// 1. Specifying both a hostname and an ip address is not allowed.
 /// 2. Specifying both blank a hostname and blank ip address is not allowed.
 /// 3. Specifying a negative port number is not allowed.
-TEST_F(DnsServerInfoTest, invalidEntryTests) {
+TEST_F(DnsServerInfoTest, invalidEntry) {
     // Create a config in which both host and ip address are supplied.
     // Verify that it builds without throwing but commit fails.
     std::string config = "{ \"hostname\": \"pegasus.tmark\", "
@@ -480,7 +480,7 @@ TEST_F(DnsServerInfoTest, invalidEntryTests) {
 /// 1. A DnsServerInfo entry is correctly made, when given only a hostname.
 /// 2. A DnsServerInfo entry is correctly made, when given ip address and port.
 /// 3. A DnsServerInfo entry is correctly made, when given only an ip address.
-TEST_F(DnsServerInfoTest, validEntryTests) {
+TEST_F(DnsServerInfoTest, validEntry) {
     // Valid entries for dynamic host
     std::string config = "{ \"hostname\": \"pegasus.tmark\" }";
     ASSERT_TRUE(fromJSON(config));
@@ -831,7 +831,7 @@ TEST_F(DdnsDomainTest, DdnsDomainListParsing) {
 }
 
 /// @brief Tests that a domain list configuration cannot contain duplicates.
-TEST_F(DdnsDomainTest, duplicateDomainTest) {
+TEST_F(DdnsDomainTest, duplicateDomain) {
     // Create a domain list configuration that contains two domains with
     // the same name.
     std::string config =
@@ -885,7 +885,7 @@ TEST(D2CfgMgr, construction) {
 /// This tests passes the configuration into an instance of D2CfgMgr just
 /// as it would be done by d2_process in response to a configuration update
 /// event.
-TEST_F(D2CfgMgrTest, fullConfigTest) {
+TEST_F(D2CfgMgrTest, fullConfig) {
     // Create a configuration with all of application level parameters, plus
     // both the forward and reverse ddns managers.  Both managers have two
     // domains with three servers per domain.
@@ -1018,7 +1018,7 @@ TEST_F(D2CfgMgrTest, fullConfigTest) {
 /// 2. Given a FQDN for sub-domain in the list, returns the proper match.
 /// 3. Given a FQDN that matches no domain name, returns the wild card domain
 /// as a match.
-TEST_F(D2CfgMgrTest, forwardMatchTest) {
+TEST_F(D2CfgMgrTest, forwardMatch) {
     // Create  configuration with one domain, one sub domain, and the wild
     // card.
     std::string config = "{ "
@@ -1190,14 +1190,22 @@ TEST_F(D2CfgMgrTest, matchReverse) {
                         "\"forward_ddns\" : {}, "
                         "\"reverse_ddns\" : {"
                         "\"ddns_domains\": [ "
-                        "{ \"name\": \"100.168.192.in-addr.arpa\" , "
+                        "{ \"name\": \"5.100.168.192.in-addr.arpa.\" , "
                         "  \"dns_servers\" : [ "
                         "  { \"ip_address\": \"127.0.0.1\" } "
                         "  ] }, "
-                        "{ \"name\": \"168.192.in-addr.arpa\" , "
+                        "{ \"name\": \"100.200.192.in-addr.arpa.\" , "
                         "  \"dns_servers\" : [ "
                         "  { \"ip_address\": \"127.0.0.1\" } "
                         "  ] }, "
+                        "{ \"name\": \"170.192.in-addr.arpa.\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"ip_address\": \"127.0.0.1\" } "
+                        "  ] }, "
+                        "{ \"name\": \"2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.\" , "
+                        "  \"dns_servers\" : [ "
+                        "  { \"ip_address\": \"127.0.0.1\" } "
+                        "  ] },"
                         "{ \"name\": \"*\" , "
                         "  \"dns_servers\" : [ "
                         "  { \"ip_address\": \"127.0.0.1\" } "
@@ -1215,23 +1223,32 @@ TEST_F(D2CfgMgrTest, matchReverse) {
     ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
 
     DdnsDomainPtr match;
+
     // Verify an exact match.
-    EXPECT_TRUE(cfg_mgr_->matchReverse("100.168.192.in-addr.arpa", match));
-    EXPECT_EQ("100.168.192.in-addr.arpa", match->getName());
+    EXPECT_TRUE(cfg_mgr_->matchReverse("192.168.100.5", match));
+    EXPECT_EQ("5.100.168.192.in-addr.arpa.", match->getName());
 
     // Verify a sub-domain match.
-    EXPECT_TRUE(cfg_mgr_->matchReverse("27.100.168.192.in-addr.arpa", match));
-    EXPECT_EQ("100.168.192.in-addr.arpa", match->getName());
+    EXPECT_TRUE(cfg_mgr_->matchReverse("192.200.100.27", match));
+    EXPECT_EQ("100.200.192.in-addr.arpa.", match->getName());
 
     // Verify a sub-domain match.
-    EXPECT_TRUE(cfg_mgr_->matchReverse("30.133.168.192.in-addr.arpa", match));
-    EXPECT_EQ("168.192.in-addr.arpa", match->getName());
+    EXPECT_TRUE(cfg_mgr_->matchReverse("192.170.50.30", match));
+    EXPECT_EQ("170.192.in-addr.arpa.", match->getName());
 
     // Verify a wild card match.
-    EXPECT_TRUE(cfg_mgr_->matchReverse("shouldbe.wildcard", match));
+    EXPECT_TRUE(cfg_mgr_->matchReverse("1.1.1.1", match));
     EXPECT_EQ("*", match->getName());
 
-    // Verify that an attempt to match an empty FQDN throws.
+    // Verify a IPv6 match.
+    EXPECT_TRUE(cfg_mgr_->matchReverse("2001:db8:302:99::",match));
+    EXPECT_EQ("2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.", match->getName());
+
+    // Verify a IPv6 wild card match.
+    EXPECT_TRUE(cfg_mgr_->matchReverse("2001:db8:99:302::",match));
+    EXPECT_EQ("*", match->getName());
+
+    // Verify that an attempt to match an invalid IP address throws.
     ASSERT_THROW(cfg_mgr_->matchReverse("", match), D2CfgError);
 }
 
diff --git a/src/bin/d2/tests/d2_queue_mgr_unittests.cc b/src/bin/d2/tests/d2_queue_mgr_unittests.cc
new file mode 100644
index 0000000..bd5643e
--- /dev/null
+++ b/src/bin/d2/tests/d2_queue_mgr_unittests.cc
@@ -0,0 +1,430 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <asiolink/interval_timer.h>
+#include <d2/d2_queue_mgr.h>
+#include <dhcp_ddns/ncr_udp.h>
+#include <util/time_utilities.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <gtest/gtest.h>
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <vector>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+using namespace isc::d2;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest test messages.
+const char *valid_msgs[] =
+{
+    // Valid Add.
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Valid Remove.
+     "{"
+     " \"change_type\" : 1 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+     // Valid Add with IPv6 address
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}"
+};
+
+static const  int VALID_MSG_CNT = sizeof(valid_msgs)/sizeof(char*);
+
+const char* TEST_ADDRESS = "127.0.0.1";
+const uint32_t LISTENER_PORT = 5301;
+const uint32_t SENDER_PORT = LISTENER_PORT+1;
+const long TEST_TIMEOUT = 5 * 1000;
+
+/// @brief Tests that construction with max queue size of zero is not allowed.
+TEST(D2QueueMgrBasicTest, construction1) {
+    isc::asiolink::IOService io_service;
+
+    // Verify that constructing with max queue size of zero is not allowed.
+    EXPECT_THROW(D2QueueMgr(io_service, 0), D2QueueMgrError);
+}
+
+/// @brief Tests default construction works.
+TEST(D2QueueMgrBasicTest, construction2) {
+    isc::asiolink::IOService io_service;
+
+    // Verify that valid constructor works.
+    D2QueueMgrPtr queue_mgr;
+    ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service)));
+    // Verify queue max is defaulted correctly.
+    EXPECT_EQ(D2QueueMgr::MAX_QUEUE_DEFAULT, queue_mgr->getMaxQueueSize());
+}
+
+/// @brief Tests construction with custom queue size works properly
+TEST(D2QueueMgrBasicTest, construction3) {
+    isc::asiolink::IOService io_service;
+
+    // Verify that custom queue size constructor works.
+    D2QueueMgrPtr queue_mgr;
+    ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service, 100)));
+    // Verify queue max is the custom value.
+    EXPECT_EQ(100, queue_mgr->getMaxQueueSize());
+}
+
+/// @brief Tests  QueueMgr's basic queue functions
+/// This test verifies that:
+/// 1. Following construction queue is empty
+/// 2. Attempting to peek at an empty queue is not allowed
+/// 3. Attempting to dequeue an empty queue is not allowed
+/// 4. Peek returns the first entry on the queue without altering queue content
+/// 5. Dequeue removes the first entry on the queue
+TEST(D2QueueMgrBasicTest, basicQueue) {
+    isc::asiolink::IOService io_service;
+
+    // Construct the manager with max queue size set to number of messages
+    // we'll use.
+    D2QueueMgrPtr queue_mgr;
+    ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service, VALID_MSG_CNT)));
+    ASSERT_EQ(VALID_MSG_CNT, queue_mgr->getMaxQueueSize());
+
+    // Verify queue is empty after construction.
+    EXPECT_EQ(0, queue_mgr->getQueueSize());
+
+    // Verify that peek and dequeue both throw when queue is empty.
+    EXPECT_THROW(queue_mgr->peek(), D2QueueMgrQueueEmpty);
+    EXPECT_THROW(queue_mgr->dequeue(), D2QueueMgrQueueEmpty);
+
+    // Vector to keep track of the NCRs we que.
+    std::vector<NameChangeRequestPtr>ref_msgs;
+    NameChangeRequestPtr ncr;
+
+    // Iterate over the list of requests and add each to the queue.
+    for (int i = 0; i < VALID_MSG_CNT; i++) {
+        // Create the ncr and add to our reference list.
+        ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+        ref_msgs.push_back(ncr);
+
+        // Verify that the request can be added to the queue and queue
+        // size increments accordingly.
+        EXPECT_NO_THROW(queue_mgr->enqueue(ncr));
+        EXPECT_EQ(i+1, queue_mgr->getQueueSize());
+    }
+
+    // Loop through and verify that the queue contents match the
+    // reference list.
+    for (int i = 0; i < VALID_MSG_CNT; i++) {
+        // Verify that peek on a non-empty queue returns first entry
+        // without altering queue content.
+        EXPECT_NO_THROW(ncr = queue_mgr->peek());
+
+        // Verify the peeked entry is the one it should be.
+        ASSERT_TRUE(ncr);
+        EXPECT_TRUE (*(ref_msgs[i]) == *ncr);
+
+        // Verify that peek did not alter the queue size.
+        EXPECT_EQ(VALID_MSG_CNT - i, queue_mgr->getQueueSize());
+
+        // Verify the dequeueing from non-empty queue works
+        EXPECT_NO_THROW(queue_mgr->dequeue());
+
+        // Verify queue size decrements following dequeue.
+        EXPECT_EQ(VALID_MSG_CNT - (i + 1), queue_mgr->getQueueSize());
+    }
+
+    // Iterate over the list of requests and add each to the queue.
+    for (int i = 0; i < VALID_MSG_CNT; i++) {
+        // Create the ncr and add to our reference list.
+        ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+        ref_msgs.push_back(ncr);
+        EXPECT_NO_THROW(queue_mgr->enqueue(ncr));
+    }
+
+    // Verify queue count is correct.
+    EXPECT_EQ(VALID_MSG_CNT, queue_mgr->getQueueSize());
+
+    // Verfiy that peekAt returns the correct entry.
+    EXPECT_NO_THROW(ncr = queue_mgr->peekAt(1));
+    EXPECT_TRUE (*(ref_msgs[1]) == *ncr);
+
+    // Verfiy that dequeueAt removes the correct entry.
+    // Removing it, this should shift the queued entries forward by one.
+    EXPECT_NO_THROW(queue_mgr->dequeueAt(1));
+    EXPECT_NO_THROW(ncr = queue_mgr->peekAt(1));
+    EXPECT_TRUE (*(ref_msgs[2]) == *ncr);
+
+    // Verify the peekAt and dequeueAt throw when given indexes beyond the end.
+    EXPECT_THROW(queue_mgr->peekAt(VALID_MSG_CNT + 1), D2QueueMgrInvalidIndex);
+    EXPECT_THROW(queue_mgr->dequeueAt(VALID_MSG_CNT + 1),
+                 D2QueueMgrInvalidIndex);
+}
+
+/// @brief Compares two NameChangeRequests for equality.
+bool checkSendVsReceived(NameChangeRequestPtr sent_ncr,
+                         NameChangeRequestPtr received_ncr) {
+    return ((sent_ncr && received_ncr) &&
+        (*sent_ncr == *received_ncr));
+}
+
+/// @brief Text fixture that allows testing a listener and sender together
+/// It derives from both the receive and send handler classes and contains
+/// and instance of UDP listener and UDP sender.
+class QueueMgrUDPTest : public virtual ::testing::Test,
+                        NameChangeSender::RequestSendHandler {
+public:
+    isc::asiolink::IOService io_service_;
+    NameChangeSenderPtr   sender_;
+    isc::asiolink::IntervalTimer test_timer_;
+    D2QueueMgrPtr queue_mgr_;
+
+    NameChangeSender::Result send_result_;
+    std::vector<NameChangeRequestPtr> sent_ncrs_;
+    std::vector<NameChangeRequestPtr> received_ncrs_;
+
+    QueueMgrUDPTest() : io_service_(), test_timer_(io_service_) {
+        isc::asiolink::IOAddress addr(TEST_ADDRESS);
+        // Create our sender instance. Note that reuse_address is true.
+        sender_.reset(new NameChangeUDPSender(addr, SENDER_PORT,
+                                              addr, LISTENER_PORT,
+                                              FMT_JSON, *this, 100, true));
+
+        // Set the test timeout to break any running tasks if they hang.
+        test_timer_.setup(boost::bind(&QueueMgrUDPTest::testTimeoutHandler,
+                                      this),
+                          TEST_TIMEOUT);
+    }
+
+    void reset_results() {
+        sent_ncrs_.clear();
+        received_ncrs_.clear();
+    }
+
+    /// @brief Implements the send completion handler.
+    virtual void operator ()(const NameChangeSender::Result result,
+                             NameChangeRequestPtr& ncr) {
+        // save the result and the NCR sent.
+        send_result_ = result;
+        sent_ncrs_.push_back(ncr);
+    }
+
+    /// @brief Handler invoked when test timeout is hit.
+    ///
+    /// This callback stops all running (hanging) tasks on IO service.
+    void testTimeoutHandler() {
+        io_service_.stop();
+        FAIL() << "Test timeout hit.";
+    }
+};
+
+/// @brief Tests D2QueueMgr's state model.
+/// This test verifies that:
+/// 1. Upon construction, initial state is NOT_INITTED.
+/// 2. Cannot start listening from while state is NOT_INITTED.
+/// 3. Successful listener initialization transitions from NOT_INITTED
+/// to INITTED.
+/// 4. Attempting to initialize the listener from INITTED state is not
+/// allowed.
+/// 5. Starting listener from INITTED transitions to RUNNING.
+/// 6. Stopping the  listener transitions from RUNNING to STOPPED.
+/// 7. Starting listener from STOPPED transitions to RUNNING.
+TEST_F (QueueMgrUDPTest, stateModel) {
+    // Create the queue manager.
+    ASSERT_NO_THROW(queue_mgr_.reset(new D2QueueMgr(io_service_,
+                                     VALID_MSG_CNT)));
+
+    // Verify that the initial state is NOT_INITTED.
+    EXPECT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr_->getMgrState());
+
+    // Verify that trying to listen before when not initialized fails.
+    EXPECT_THROW(queue_mgr_->startListening(), D2QueueMgrError);
+
+    // Verify that initializing the listener moves us to INITTED state.
+    isc::asiolink::IOAddress addr(TEST_ADDRESS);
+    EXPECT_NO_THROW(queue_mgr_->initUDPListener(addr, LISTENER_PORT,
+                                              FMT_JSON, true));
+    EXPECT_EQ(D2QueueMgr::INITTED, queue_mgr_->getMgrState());
+
+    // Verify that attempting to initialize the listener, from INITTED
+    // is not allowed.
+    EXPECT_THROW(queue_mgr_->initUDPListener(addr, LISTENER_PORT,
+                                              FMT_JSON, true),
+                 D2QueueMgrError);
+
+    // Verify that we can enter the RUNNING from INITTED by starting the
+    // listener.
+    EXPECT_NO_THROW(queue_mgr_->startListening());
+    EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+    // Verify that we can move from RUNNING to STOPPED by stopping the
+    // listener.
+    EXPECT_NO_THROW(queue_mgr_->stopListening());
+    EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr_->getMgrState());
+
+    // Verify that we can re-enter the RUNNING from STOPPED by starting the
+    // listener.
+    EXPECT_NO_THROW(queue_mgr_->startListening());
+    EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+    // Verify that we cannot remove the listener in the RUNNING state
+    EXPECT_THROW(queue_mgr_->removeListener(), D2QueueMgrError);
+
+    // Stop the listener.
+    EXPECT_NO_THROW(queue_mgr_->stopListening());
+    EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr_->getMgrState());
+
+    // Verify that we can remove the listener in the STOPPED state and
+    // end up back in NOT_INITTED.
+    EXPECT_NO_THROW(queue_mgr_->removeListener());
+    EXPECT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr_->getMgrState());
+}
+
+/// @brief Tests D2QueueMgr's ability to manage received requests
+/// This test verifies that:
+/// 1. Requests can be received, queued, and dequeued
+/// 2. Once the queue is full, a subsequent request transitions
+/// manager to STOPPED_QUEUE_FULL state.
+/// 3. Starting listener returns manager to the RUNNING state.
+/// 4. Queue contents are preserved across state transitions.
+/// 5. Clearing the queue via the clearQueue() method works.
+/// 6. Requests can be received and queued normally after the queue
+/// has been emptied.
+/// 7. setQueueMax disallows values of 0 or less than current queue size.
+TEST_F (QueueMgrUDPTest, liveFeed) {
+    NameChangeRequestPtr send_ncr;
+    NameChangeRequestPtr received_ncr;
+
+    // Create the queue manager and start listening..
+    ASSERT_NO_THROW(queue_mgr_.reset(new D2QueueMgr(io_service_,
+                                                    VALID_MSG_CNT)));
+    ASSERT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr_->getMgrState());
+
+    // Verify that setting max queue size to 0 is not allowed.
+    EXPECT_THROW(queue_mgr_->setMaxQueueSize(0), D2QueueMgrError);
+    EXPECT_EQ(VALID_MSG_CNT, queue_mgr_->getMaxQueueSize());
+
+    isc::asiolink::IOAddress addr(TEST_ADDRESS);
+    ASSERT_NO_THROW(queue_mgr_->initUDPListener(addr, LISTENER_PORT,
+                                              FMT_JSON, true));
+    ASSERT_EQ(D2QueueMgr::INITTED, queue_mgr_->getMgrState());
+
+    ASSERT_NO_THROW(queue_mgr_->startListening());
+    ASSERT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+    // Place the sender into sending state.
+    ASSERT_NO_THROW(sender_->startSending(io_service_));
+    ASSERT_TRUE(sender_->amSending());
+
+    // Iterate over the list of requests sending and receiving
+    // each one.  Verify and dequeue as they arrive.
+    for (int i = 0; i < VALID_MSG_CNT; i++) {
+        // Create the ncr and add to our reference list.
+        ASSERT_NO_THROW(send_ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+        ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+
+        // running two should do the send then the receive
+        io_service_.run_one();
+        io_service_.run_one();
+
+        // Verify that the request can be added to the queue and queue
+        // size increments accordingly.
+        EXPECT_EQ(1, queue_mgr_->getQueueSize());
+
+        // Verify that peek shows the NCR we just sent
+        EXPECT_NO_THROW(received_ncr = queue_mgr_->peek());
+        EXPECT_TRUE(checkSendVsReceived(send_ncr, received_ncr));
+
+        // Verify that we and dequeue the request.
+        EXPECT_NO_THROW(queue_mgr_->dequeue());
+        EXPECT_EQ(0, queue_mgr_->getQueueSize());
+    }
+
+    // Iterate over the list of requests, sending and receiving
+    // each one. Allow them to accumulate in the queue.
+    for (int i = 0; i < VALID_MSG_CNT; i++) {
+        // Create the ncr and add to our reference list.
+        ASSERT_NO_THROW(send_ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+        ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+
+        // running two should do the send then the receive
+        EXPECT_NO_THROW(io_service_.run_one());
+        EXPECT_NO_THROW(io_service_.run_one());
+        EXPECT_EQ(i+1, queue_mgr_->getQueueSize());
+    }
+
+    // Verify that the queue is at max capacity.
+    EXPECT_EQ(queue_mgr_->getMaxQueueSize(), queue_mgr_->getQueueSize());
+
+    // Send another. The send should succeed.
+    ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+    EXPECT_NO_THROW(io_service_.run_one());
+
+    // Now execute the receive which should not throw but should move us
+    // to STOPPED_QUEUE_FULL state.
+    EXPECT_NO_THROW(io_service_.run_one());
+    EXPECT_EQ(D2QueueMgr::STOPPED_QUEUE_FULL, queue_mgr_->getMgrState());
+
+    // Verify queue size did not increase beyond max.
+    EXPECT_EQ(VALID_MSG_CNT, queue_mgr_->getQueueSize());
+
+    // Verify that setting max queue size to a value less than current size of
+    // the queue is not allowed.
+    EXPECT_THROW(queue_mgr_->setMaxQueueSize(VALID_MSG_CNT-1), D2QueueMgrError);
+    EXPECT_EQ(VALID_MSG_CNT, queue_mgr_->getQueueSize());
+
+    // Verify that we can re-enter RUNNING from STOPPED_QUEUE_FULL.
+    EXPECT_NO_THROW(queue_mgr_->startListening());
+    EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+    // Verify that the queue contents were preserved.
+    EXPECT_EQ(queue_mgr_->getMaxQueueSize(), queue_mgr_->getQueueSize());
+
+    // Verify that clearQueue works.
+    EXPECT_NO_THROW(queue_mgr_->clearQueue());
+    EXPECT_EQ(0, queue_mgr_->getQueueSize());
+
+
+    // Verify that we can again receive requests.
+    // Send should be fine.
+    ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+    EXPECT_NO_THROW(io_service_.run_one());
+
+    // Receive should succeed.
+    EXPECT_NO_THROW(io_service_.run_one());
+    EXPECT_EQ(1, queue_mgr_->getQueueSize());
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d2_update_mgr_unittests.cc b/src/bin/d2/tests/d2_update_mgr_unittests.cc
new file mode 100644
index 0000000..0abed5d
--- /dev/null
+++ b/src/bin/d2/tests/d2_update_mgr_unittests.cc
@@ -0,0 +1,443 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <asiolink/interval_timer.h>
+#include <d2/d2_update_mgr.h>
+#include <util/time_utilities.h>
+#include <d_test_stubs.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <gtest/gtest.h>
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <vector>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+using namespace isc::d2;
+
+namespace {
+
+/// @brief Wrapper class for D2UpdateMgr to provide acces non-public methods.
+///
+/// This class faciliates testing by making non-public methods accessible so
+/// they can be invoked directly in test routines.
+class D2UpdateMgrWrapper : public D2UpdateMgr {
+public:
+    /// @brief Constructor
+    ///
+    /// Parameters match those needed by D2UpdateMgr.
+    D2UpdateMgrWrapper(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
+                       isc::asiolink::IOService& io_service,
+                       const size_t max_transactions = MAX_TRANSACTIONS_DEFAULT)
+        : D2UpdateMgr(queue_mgr, cfg_mgr, io_service, max_transactions) {
+    }
+
+    /// @brief Destructor
+    virtual ~D2UpdateMgrWrapper() {
+    }
+
+    // Expose the protected methods to be tested.
+    using D2UpdateMgr::checkFinishedTransactions;
+    using D2UpdateMgr::pickNextJob;
+    using D2UpdateMgr::makeTransaction;
+};
+
+/// @brief Defines a pointer to a D2UpdateMgr instance.
+typedef boost::shared_ptr<D2UpdateMgrWrapper> D2UpdateMgrWrapperPtr;
+
+/// @brief Test fixture for testing D2UpdateMgr.
+///
+/// Note this class uses D2UpdateMgrWrapper class to exercise non-public
+/// aspects of D2UpdateMgr. D2UpdateMgr depends on both D2QueueMgr and
+/// D2CfgMgr.  This fixture provides an instance of each, plus a canned,
+/// valid DHCP_DDNS configuration sufficient to test D2UpdateMgr's basic
+/// functions.
+class D2UpdateMgrTest : public ConfigParseTest {
+public:
+    isc::asiolink::IOService io_service_;
+    D2QueueMgrPtr queue_mgr_;
+    D2CfgMgrPtr cfg_mgr_;
+    //D2UpdateMgrPtr update_mgr_;
+    D2UpdateMgrWrapperPtr update_mgr_;
+    std::vector<NameChangeRequestPtr> canned_ncrs_;
+    size_t canned_count_;
+
+    D2UpdateMgrTest() {
+        queue_mgr_.reset(new D2QueueMgr(io_service_));
+        cfg_mgr_.reset(new D2CfgMgr());
+        update_mgr_.reset(new D2UpdateMgrWrapper(queue_mgr_, cfg_mgr_,
+                                                 io_service_));
+        makeCannedNcrs();
+        makeCannedConfig();
+    }
+
+    ~D2UpdateMgrTest() {
+    }
+
+    /// @brief Creates a list of valid NameChangeRequest.
+    ///
+    /// This method builds a list of NameChangeRequests from a single
+    /// JSON string request. Each request is assigned a unique DHCID.
+    void makeCannedNcrs() {
+        const char* msg_str =
+        "{"
+        " \"change_type\" : 0 , "
+        " \"forward_change\" : true , "
+        " \"reverse_change\" : false , "
+        " \"fqdn\" : \"walah.walah.org.\" , "
+        " \"ip_address\" : \"192.168.2.1\" , "
+        " \"dhcid\" : \"0102030405060708\" , "
+        " \"lease_expires_on\" : \"20130121132405\" , "
+        " \"lease_length\" : 1300 "
+        "}";
+
+        const char* dhcids[] = { "111111", "222222", "333333", "444444"};
+        canned_count_ = 4;
+        for (int i = 0; i < canned_count_; i++) {
+            dhcp_ddns::NameChangeRequestPtr ncr = NameChangeRequest::
+                                                  fromJSON(msg_str);
+            ncr->setDhcid(dhcids[i]);
+            canned_ncrs_.push_back(ncr);
+        }
+    }
+
+    /// @brief Seeds configuration manager with a valid DHCP_DDNS configuration.
+    void makeCannedConfig() {
+        std::string canned_config_ =
+                 "{ "
+                  "\"interface\" : \"eth1\" , "
+                  "\"ip_address\" : \"192.168.1.33\" , "
+                  "\"port\" : 88 , "
+                  "\"tsig_keys\": [] ,"
+                  "\"forward_ddns\" : {"
+                  "\"ddns_domains\": [ "
+                  "{ \"name\": \"two.three.org.\" , "
+                  "  \"dns_servers\" : [ "
+                  "  { \"ip_address\": \"127.0.0.1\" } "
+                  "  ] },"
+                  "{ \"name\": \"org.\" , "
+                  "  \"dns_servers\" : [ "
+                  "  { \"ip_address\": \"127.0.0.1\" } "
+                  "  ] }, "
+                  "] }, "
+                  "\"reverse_ddns\" : { "
+                  "\"ddns_domains\": [ "
+                  "{ \"name\": \"1.168.192.in-addr.arpa.\" , "
+                  "  \"dns_servers\" : [ "
+                  "  { \"ip_address\": \"127.0.0.1\" } "
+                  "  ] }, "
+                  "{ \"name\": \"2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.\" , "
+                  "  \"dns_servers\" : [ "
+                  "  { \"ip_address\": \"127.0.0.1\" } "
+                  "  ] } "
+                  "] } }";
+
+        // If this configuration fails to parse most tests will fail.
+        ASSERT_TRUE(fromJSON(canned_config_));
+        answer_ = cfg_mgr_->parseConfig(config_set_);
+        ASSERT_TRUE(checkAnswer(0));
+    }
+
+};
+
+/// @brief Tests the D2UpdateMgr construction.
+/// This test verifies that:
+/// 1. Construction with invalid queue manager is not allowed
+/// 2. Construction with invalid configuration manager is not allowed
+/// 3. Construction with max transactions of zero is not allowed
+/// 4. Default construction works and max transactions is defaulted properly
+/// 5. Construction with custom max transactions works properly
+TEST(D2UpdateMgr, construction) {
+    isc::asiolink::IOService io_service;
+    D2QueueMgrPtr queue_mgr;
+    D2CfgMgrPtr cfg_mgr;
+    D2UpdateMgrPtr update_mgr;
+
+    // Verify that constrctor fails if given an invalid queue manager.
+    ASSERT_NO_THROW(cfg_mgr.reset(new D2CfgMgr()));
+    EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service),
+                 D2UpdateMgrError);
+
+    // Verify that constrctor fails if given an invalid config manager.
+    ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service)));
+    ASSERT_NO_THROW(cfg_mgr.reset());
+    EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service),
+                 D2UpdateMgrError);
+
+    ASSERT_NO_THROW(cfg_mgr.reset(new D2CfgMgr()));
+
+    // Verify that max transactions cannot be zero.
+    EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service, 0),
+                 D2UpdateMgrError);
+
+    // Verify that given valid values, constructor works.
+    ASSERT_NO_THROW(update_mgr.reset(new D2UpdateMgr(queue_mgr, cfg_mgr,
+                                                      io_service)));
+
+    // Verify that max transactions defaults properly.
+    EXPECT_EQ(D2UpdateMgr::MAX_TRANSACTIONS_DEFAULT,
+              update_mgr->getMaxTransactions());
+
+
+    // Verify that constructor permits custom  max transactions.
+    ASSERT_NO_THROW(update_mgr.reset(new D2UpdateMgr(queue_mgr, cfg_mgr,
+                                                     io_service, 100)));
+
+    // Verify that max transactions is correct.
+    EXPECT_EQ(100, update_mgr->getMaxTransactions());
+}
+
+/// @brief Tests the D2UpdateManager's transaction list services
+/// This test verifies that:
+/// 1. A transaction can be added to the list.
+/// 2. Finding a transaction in the list by key works correctly.
+/// 3. Looking for a non-existant transaction works properly.
+/// 4. Attempting to add a transaction for a DHCID already in the list fails.
+/// 5. Removing a transaction by key works properly.
+/// 6. Attempting to remove an non-existant transaction does no harm.
+TEST_F(D2UpdateMgrTest, transactionList) {
+    // Grab a canned request for test purposes.
+    NameChangeRequestPtr& ncr = canned_ncrs_[0];
+    TransactionList::iterator pos;
+
+    // Verify that we can add a transaction.
+    EXPECT_NO_THROW(update_mgr_->makeTransaction(ncr));
+    EXPECT_EQ(1, update_mgr_->getTransactionCount());
+
+    // Verify that we can find a transaction by key.
+    EXPECT_NO_THROW(pos = update_mgr_->findTransaction(ncr->getDhcid()));
+    EXPECT_TRUE(pos != update_mgr_->transactionListEnd());
+
+    // Verify that convenience method has same result.
+    EXPECT_TRUE(update_mgr_->hasTransaction(ncr->getDhcid()));
+
+    // Verify that we will not find a transaction that isn't there.
+    dhcp_ddns::D2Dhcid bogus_id("FFFF");
+    EXPECT_NO_THROW(pos = update_mgr_->findTransaction(bogus_id));
+    EXPECT_TRUE(pos == update_mgr_->transactionListEnd());
+
+    // Verify that convenience method has same result.
+    EXPECT_FALSE(update_mgr_->hasTransaction(bogus_id));
+
+    // Verify that adding a transaction for the same key fails.
+    EXPECT_THROW(update_mgr_->makeTransaction(ncr), D2UpdateMgrError);
+    EXPECT_EQ(1, update_mgr_->getTransactionCount());
+
+    // Verify the we can remove a transaction by key.
+    EXPECT_NO_THROW(update_mgr_->removeTransaction(ncr->getDhcid()));
+    EXPECT_EQ(0, update_mgr_->getTransactionCount());
+
+    // Verify the we can try to remove a non-existant transaction without harm.
+    EXPECT_NO_THROW(update_mgr_->removeTransaction(ncr->getDhcid()));
+}
+
+/// @brief Tests D2UpdateManager's checkFinishedTransactions method.
+/// This test verifies that:
+/// 1. Completed transactions are removed from the transaction list.
+/// 2. Failed transactions are removed from the transaction list.
+/// @todo This test will need to expand if and when checkFinishedTransactions
+/// method expands to do more than remove them from the list.
+TEST_F(D2UpdateMgrTest, checkFinishedTransaction) {
+    // Ensure we have at least 4 canned requests with which to work.
+    ASSERT_TRUE(canned_count_ >= 4);
+
+    // Create a transaction for each canned request.
+    for (int i = 0; i < canned_count_; i++) {
+        EXPECT_NO_THROW(update_mgr_->makeTransaction(canned_ncrs_[i]));
+    }
+    // Verfiy we have that the transaçtion count is correct.
+    EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+
+    // Set two of the transactions to finished states.
+    (canned_ncrs_[1])->setStatus(dhcp_ddns::ST_COMPLETED);
+    (canned_ncrs_[3])->setStatus(dhcp_ddns::ST_FAILED);
+
+    // Verify that invoking checkFinishedTransactions does not throw.
+    EXPECT_NO_THROW(update_mgr_->checkFinishedTransactions());
+
+    // Verify that the list of transactions has decreased by two.
+    EXPECT_EQ(canned_count_ - 2, update_mgr_->getTransactionCount());
+
+    // Vefity that the transaction list is correct.
+    EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[0]->getDhcid()));
+    EXPECT_FALSE(update_mgr_->hasTransaction(canned_ncrs_[1]->getDhcid()));
+    EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[2]->getDhcid()));
+    EXPECT_FALSE(update_mgr_->hasTransaction(canned_ncrs_[3]->getDhcid()));
+}
+
+/// @brief Tests D2UpdateManager's pickNextJob method.
+/// This test verifies that:
+/// 1. pickNextJob will select and make transactions from NCR queue.
+/// 2. Requests are removed from the queue once selected
+/// 3. Requests for DHCIDs with transactions already in progress are not
+/// selected.
+/// 4. Requests with no matching servers are removed from the queue and
+/// discarded.
+TEST_F(D2UpdateMgrTest, pickNextJob) {
+    // Ensure we have at least 4 canned requests with which to work.
+    ASSERT_TRUE(canned_count_ >= 4);
+
+    // Put each transaction on the queue.
+    for (int i = 0; i < canned_count_; i++) {
+        ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i]));
+    }
+
+    // Invoke pickNextJob canned_count_ times which should create a
+    // transaction for each canned ncr.
+    for (int i = 0; i < canned_count_; i++) {
+        EXPECT_NO_THROW(update_mgr_->pickNextJob());
+        EXPECT_EQ(i + 1, update_mgr_->getTransactionCount());
+        EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[i]->getDhcid()));
+    }
+
+    // Verify that the queue has been drained.
+    EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+    // Now verify that a subsequent request for a DCHID  for which a
+    // transaction is in progress, is not dequeued.
+    // First add the "subsequent" request.
+    dhcp_ddns::NameChangeRequestPtr
+        subsequent_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[2])));
+    EXPECT_NO_THROW(queue_mgr_->enqueue(subsequent_ncr));
+    EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+    // Verify that invoking pickNextJob:
+    // 1. does not throw
+    // 2. does not make a new transaction
+    // 3. does not dequeu the entry
+    EXPECT_NO_THROW(update_mgr_->pickNextJob());
+    EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+    EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+    // Clear out the queue and transaction list.
+    queue_mgr_->clearQueue();
+    update_mgr_->clearTransactionList();
+
+    // Make a forward change NCR with an FQDN that has no forward match.
+    dhcp_ddns::NameChangeRequestPtr
+        bogus_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[0])));
+    bogus_ncr->setForwardChange(true);
+    bogus_ncr->setReverseChange(false);
+    bogus_ncr->setFqdn("bogus.forward.domain.com");
+
+    // Put it on the queue up
+    ASSERT_NO_THROW(queue_mgr_->enqueue(bogus_ncr));
+
+    // Verify that invoking pickNextJob:
+    // 1. does not throw
+    // 2. does not make a new transaction
+    // 3. does dequeue the entry
+    EXPECT_NO_THROW(update_mgr_->pickNextJob());
+    EXPECT_EQ(0, update_mgr_->getTransactionCount());
+    EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+    // Make a reverse change NCR with an FQDN that has no reverse match.
+    bogus_ncr.reset(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[0])));
+    bogus_ncr->setForwardChange(false);
+    bogus_ncr->setReverseChange(true);
+    bogus_ncr->setIpAddress("77.77.77.77");
+
+    // Verify that invoking pickNextJob:
+    // 1. does not throw
+    // 2. does not make a new transaction
+    // 3. does dequeue the entry
+    EXPECT_NO_THROW(update_mgr_->pickNextJob());
+    EXPECT_EQ(0, update_mgr_->getTransactionCount());
+    EXPECT_EQ(0, update_mgr_->getQueueCount());
+}
+
+/// @brief Tests D2UpdateManager's sweep method.
+/// Since sweep is primarly a wrapper around chechFinishedTransactions and
+/// pickNextJob, along with checks on maximum transaction limits, it mostly
+/// verifies that these three pieces work togther to move process jobs.
+/// Most of what is tested here is tested above.
+TEST_F(D2UpdateMgrTest, sweep) {
+    // Ensure we have at least 4 canned requests with which to work.
+    ASSERT_TRUE(canned_count_ >= 4);
+
+    // Set max transactions to same as current transaction count.
+    EXPECT_NO_THROW(update_mgr_->setMaxTransactions(canned_count_));
+    EXPECT_EQ(canned_count_, update_mgr_->getMaxTransactions());
+
+    // Put each transaction on the queue.
+    for (int i = 0; i < canned_count_; i++) {
+        EXPECT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i]));
+    }
+
+    // Invoke sweep canned_count_ times which should create a
+    // transaction for each canned ncr.
+    for (int i = 0; i < canned_count_; i++) {
+        EXPECT_NO_THROW(update_mgr_->sweep());
+        EXPECT_EQ(i + 1, update_mgr_->getTransactionCount());
+        EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[i]->getDhcid()));
+    }
+
+    // Verify that the queue has been drained.
+    EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+    // Verify max transactions can't be less than current transaction count.
+    EXPECT_THROW(update_mgr_->setMaxTransactions(1), D2UpdateMgrError);
+
+    // Queue up a request for a DCHID which has a transaction in progress.
+    dhcp_ddns::NameChangeRequestPtr
+        subsequent_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[2])));
+    EXPECT_NO_THROW(queue_mgr_->enqueue(subsequent_ncr));
+    EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+    // Verify that invoking sweep, does not dequeue the job nor make a
+    // transaction for it.
+    EXPECT_NO_THROW(update_mgr_->sweep());
+    EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+    EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+    // Mark the transaction complete.
+    (canned_ncrs_[2])->setStatus(dhcp_ddns::ST_COMPLETED);
+
+    // Verify that invoking sweep, cleans up the completed transaction,
+    // dequeues the queued job and adds its transaction to the list.
+    EXPECT_NO_THROW(update_mgr_->sweep());
+    EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+    EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+    // Queue up a request from a new DHCID.
+    dhcp_ddns::NameChangeRequestPtr
+        another_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[0])));
+    another_ncr->setDhcid("AABBCCDDEEFF");
+    EXPECT_NO_THROW(queue_mgr_->enqueue(another_ncr));
+    EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+    // Verify that sweep does not dequeue the new request as we are at
+    // transaction count.
+    EXPECT_NO_THROW(update_mgr_->sweep());
+    EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+    EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+    // Set max transactions to same as current transaction count.
+    EXPECT_NO_THROW(update_mgr_->setMaxTransactions(canned_count_ + 1));
+
+    // Verify that invoking sweep, dequeues the request and creates
+    // a transaction for it.
+    EXPECT_NO_THROW(update_mgr_->sweep());
+    EXPECT_EQ(canned_count_ + 1, update_mgr_->getTransactionCount());
+    EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+    // Verify that clearing transaction list works.
+    EXPECT_NO_THROW(update_mgr_->clearTransactionList());
+    EXPECT_EQ(0, update_mgr_->getTransactionCount());
+}
+
+}
diff --git a/src/bin/d2/tests/ncr_unittests.cc b/src/bin/d2/tests/ncr_unittests.cc
deleted file mode 100644
index da81a0d..0000000
--- a/src/bin/d2/tests/ncr_unittests.cc
+++ /dev/null
@@ -1,440 +0,0 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <d2/ncr_msg.h>
-#include <dhcp/duid.h>
-#include <util/time_utilities.h>
-
-#include <gtest/gtest.h>
-#include <algorithm>
-
-using namespace std;
-using namespace isc;
-using namespace isc::d2;
-using namespace isc::dhcp;
-
-namespace {
-
-/// @brief Defines a list of valid JSON NameChangeRequest renditions.
-/// They are used as input to test conversion from JSON.
-const char *valid_msgs[] =
-{
-    // Valid Add.
-     "{"
-     " \"change_type\" : 0 , "
-     " \"forward_change\" : true , "
-     " \"reverse_change\" : false , "
-     " \"fqdn\" : \"walah.walah.com\" , "
-     " \"ip_address\" : \"192.168.2.1\" , "
-     " \"dhcid\" : \"010203040A7F8E3D\" , "
-     " \"lease_expires_on\" : \"20130121132405\" , "
-     " \"lease_length\" : 1300 "
-     "}",
-    // Valid Remove.
-     "{"
-     " \"change_type\" : 1 , "
-     " \"forward_change\" : true , "
-     " \"reverse_change\" : false , "
-     " \"fqdn\" : \"walah.walah.com\" , "
-     " \"ip_address\" : \"192.168.2.1\" , "
-     " \"dhcid\" : \"010203040A7F8E3D\" , "
-     " \"lease_expires_on\" : \"20130121132405\" , "
-     " \"lease_length\" : 1300 "
-     "}",
-     // Valid Add with IPv6 address
-     "{"
-     " \"change_type\" : 0 , "
-     " \"forward_change\" : true , "
-     " \"reverse_change\" : false , "
-     " \"fqdn\" : \"walah.walah.com\" , "
-     " \"ip_address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
-     " \"dhcid\" : \"010203040A7F8E3D\" , "
-     " \"lease_expires_on\" : \"20130121132405\" , "
-     " \"lease_length\" : 1300 "
-     "}"
-};
-
-/// @brief Defines a list of invalid JSON NameChangeRequest renditions.
-/// They are used as input to test conversion from JSON.
-const char *invalid_msgs[] =
-{
-    // Invalid change type.
-     "{"
-     " \"change_type\" : 7 , "
-     " \"forward_change\" : true , "
-     " \"reverse_change\" : false , "
-     " \"fqdn\" : \"walah.walah.com\" , "
-     " \"ip_address\" : \"192.168.2.1\" , "
-     " \"dhcid\" : \"010203040A7F8E3D\" , "
-     " \"lease_expires_on\" : \"20130121132405\" , "
-     " \"lease_length\" : 1300 "
-     "}",
-    // Invalid forward change.
-     "{"
-     " \"change_type\" : 0 , "
-     " \"forward_change\" : \"bogus\" , "
-     " \"reverse_change\" : false , "
-     " \"fqdn\" : \"walah.walah.com\" , "
-     " \"ip_address\" : \"192.168.2.1\" , "
-     " \"dhcid\" : \"010203040A7F8E3D\" , "
-     " \"lease_expires_on\" : \"20130121132405\" , "
-     " \"lease_length\" : 1300 "
-     "}",
-    // Invalid reverse change.
-     "{"
-     " \"change_type\" : 0 , "
-     " \"forward_change\" : true , "
-     " \"reverse_change\" : 500 , "
-     " \"fqdn\" : \"walah.walah.com\" , "
-     " \"ip_address\" : \"192.168.2.1\" , "
-     " \"dhcid\" : \"010203040A7F8E3D\" , "
-     " \"lease_expires_on\" : \"20130121132405\" , "
-     " \"lease_length\" : 1300 "
-     "}",
-    // Forward and reverse change both false.
-     "{"
-     " \"change_type\" : 0 , "
-     " \"forward_change\" : false , "
-     " \"reverse_change\" : false , "
-     " \"fqdn\" : \"walah.walah.com\" , "
-     " \"ip_address\" : \"192.168.2.1\" , "
-     " \"dhcid\" : \"010203040A7F8E3D\" , "
-     " \"lease_expires_on\" : \"20130121132405\" , "
-     " \"lease_length\" : 1300 "
-     "}",
-    // Blank FQDN
-     "{"
-     " \"change_type\" : 0 , "
-     " \"forward_change\" : true , "
-     " \"reverse_change\" : false , "
-     " \"fqdn\" : \"\" , "
-     " \"ip_address\" : \"192.168.2.1\" , "
-     " \"dhcid\" : \"010203040A7F8E3D\" , "
-     " \"lease_expires_on\" : \"20130121132405\" , "
-     " \"lease_length\" : 1300 "
-     "}",
-    // Bad IP address
-     "{"
-     " \"change_type\" : 0 , "
-     " \"forward_change\" : true , "
-     " \"reverse_change\" : false , "
-     " \"fqdn\" : \"walah.walah.com\" , "
-     " \"ip_address\" : \"xxxxxx\" , "
-     " \"dhcid\" : \"010203040A7F8E3D\" , "
-     " \"lease_expires_on\" : \"20130121132405\" , "
-     " \"lease_length\" : 1300 "
-     "}",
-    // Blank DHCID
-     "{"
-     " \"change_type\" : 0 , "
-     " \"forward_change\" : true , "
-     " \"reverse_change\" : false , "
-     " \"fqdn\" : \"walah.walah.com\" , "
-     " \"ip_address\" : \"192.168.2.1\" , "
-     " \"dhcid\" : \"\" , "
-     " \"lease_expires_on\" : \"20130121132405\" , "
-     " \"lease_length\" : 1300 "
-     "}",
-    // Odd number of digits in DHCID
-     "{"
-     " \"change_type\" : 0 , "
-     " \"forward_change\" : true , "
-     " \"reverse_change\" : false , "
-     " \"fqdn\" : \"walah.walah.com\" , "
-     " \"ip_address\" : \"192.168.2.1\" , "
-     " \"dhcid\" : \"010203040A7F8E3\" , "
-     " \"lease_expires_on\" : \"20130121132405\" , "
-     " \"lease_length\" : 1300 "
-     "}",
-    // Text in DHCID
-     "{"
-     " \"change_type\" : 0 , "
-     " \"forward_change\" : true , "
-     " \"reverse_change\" : false , "
-     " \"fqdn\" : \"walah.walah.com\" , "
-     " \"ip_address\" : \"192.168.2.1\" , "
-     " \"dhcid\" : \"THIS IS BOGUS!!!\" , "
-     " \"lease_expires_on\" : \"20130121132405\" , "
-     " \"lease_length\" : 1300 "
-     "}",
-    // Invalid lease expiration string
-     "{"
-     " \"change_type\" : 0 , "
-     " \"forward_change\" : true , "
-     " \"reverse_change\" : false , "
-     " \"fqdn\" : \"walah.walah.com\" , "
-     " \"ip_address\" : \"192.168.2.1\" , "
-     " \"dhcid\" : \"010203040A7F8E3D\" , "
-     " \"lease_expires_on\" : \"Wed Jun 26 13:46:46 EDT 2013\" , "
-     " \"lease_length\" : 1300 "
-     "}",
-    // Non-integer for lease length.
-     "{"
-     " \"change_type\" : 0 , "
-     " \"forward_change\" : true , "
-     " \"reverse_change\" : false , "
-     " \"fqdn\" : \"walah.walah.com\" , "
-     " \"ip_address\" : \"192.168.2.1\" , "
-     " \"dhcid\" : \"010203040A7F8E3D\" , "
-     " \"lease_expires_on\" : \"20130121132405\" , "
-     " \"lease_length\" : \"BOGUS\" "
-     "}"
-
-};
-
-/// @brief Tests the NameChangeRequest constructors.
-/// This test verifies that:
-/// 1. Default constructor works.
-/// 2. "Full" constructor, when given valid parameter values, works.
-/// 3. "Full" constructor, given a blank FQDN fails
-/// 4. "Full" constructor, given an invalid IP Address FQDN fails
-/// 5. "Full" constructor, given a blank DHCID fails
-/// 6. "Full" constructor, given false for both forward and reverse fails
-TEST(NameChangeRequestTest, constructionTests) {
-    // Verify the default constructor works.
-    NameChangeRequestPtr ncr;
-    EXPECT_NO_THROW(ncr.reset(new NameChangeRequest()));
-    EXPECT_TRUE(ncr);
-
-    // Verify that full constructor works.
-    uint64_t expiry = isc::util::detail::gettimeWrapper();
-    D2Dhcid dhcid("010203040A7F8E3D");
-
-    EXPECT_NO_THROW(ncr.reset(new NameChangeRequest(
-                    CHG_ADD, true, true, "walah.walah.com",
-                    "192.168.1.101", dhcid, expiry, 1300)));
-    EXPECT_TRUE(ncr);
-    ncr.reset();
-
-    // Verify blank FQDN is detected.
-    EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "",
-                 "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
-
-    // Verify that an invalid IP address is detected.
-    EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "valid.fqdn",
-                 "xxx.168.1.101", dhcid, expiry, 1300), NcrMessageError);
-
-    // Verify that a blank DHCID is detected.
-    D2Dhcid blank_dhcid;
-    EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "walah.walah.com",
-                 "192.168.1.101", blank_dhcid, expiry, 1300), NcrMessageError);
-
-    // Verify that one or both of direction flags must be true.
-    EXPECT_THROW(NameChangeRequest(CHG_ADD, false, false, "valid.fqdn",
-                "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
-
-}
-
-/// @brief Tests the basic workings of D2Dhcid to and from string conversions.
-/// It verifies that:
-/// 1. DHCID input strings must contain an even number of characters
-/// 2. DHCID input strings must contain only hexadecimal character digits
-/// 3. A valid DHCID string converts correctly.
-/// 4. Converting a D2Dhcid to a string works correctly.
-TEST(NameChangeRequestTest, dhcidTest) {
-    D2Dhcid dhcid;
-
-    // Odd number of digits should be rejected.
-    std::string test_str = "010203040A7F8E3";
-    EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
-
-    // Non digit content should be rejected.
-    test_str = "0102BOGUSA7F8E3D";
-    EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
-
-    // Verify that valid input converts into a proper byte array.
-    test_str = "010203040A7F8E3D";
-    ASSERT_NO_THROW(dhcid.fromStr(test_str));
-
-    // Create a test vector of expected byte contents.
-    const uint8_t bytes[] = { 0x1, 0x2, 0x3, 0x4, 0xA, 0x7F, 0x8E, 0x3D };
-    std::vector<uint8_t> expected_bytes(bytes, bytes + sizeof(bytes));
-
-    // Fetch the byte vector from the dhcid and verify if equals the expected
-    // content.
-    const std::vector<uint8_t>& converted_bytes = dhcid.getBytes();
-    EXPECT_EQ(expected_bytes.size(), converted_bytes.size());
-    EXPECT_TRUE (std::equal(expected_bytes.begin(),
-                            expected_bytes.begin()+expected_bytes.size(),
-                            converted_bytes.begin()));
-
-    // Convert the new dhcid back to string and verify it matches the original
-    // DHCID input string.
-    std::string next_str = dhcid.toStr();
-    EXPECT_EQ(test_str, next_str);
-}
-
-/// Tests that DHCID is correctly created from a DUID and FQDN. The final format
-/// of the DHCID is as follows:
-/// <identifier-type> <digest-type-code> <digest>
-/// where:
-/// - identifier-type (2 octets) is 0x0002.
-/// - digest-type-code (1 octet) indicates SHA-256 hashing and is equal 0x1.
-/// - digest = SHA-256(<DUID> <FQDN>)
-/// Note: FQDN is given in the on-wire canonical format.
-TEST(NameChangeRequestTest, dhcidFromDUID) {
-    D2Dhcid dhcid;
-
-    // Create DUID.
-    uint8_t duid_data[] = { 0, 1, 2, 3, 4, 5, 6 };
-    DUID duid(duid_data, sizeof(duid_data));
-
-    // Create FQDN in on-wire format: myhost.example.com. It is encoded
-    // as a set of labels, each preceded by its length. The whole FQDN
-    // is zero-terminated.
-    const uint8_t fqdn_data[] = {
-        6, 109, 121, 104, 111, 115, 116,     // myhost.
-        7, 101, 120, 97, 109, 112, 108, 101, // example.
-        3, 99, 111, 109, 0                   // com.
-    };
-    std::vector<uint8_t> wire_fqdn(fqdn_data, fqdn_data + sizeof(fqdn_data));
-
-    // Create DHCID.
-    ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn));
-
-    // The reference DHCID (represented as string of hexadecimal digits)
-    // has been calculated using one of the online calculators.
-    std::string dhcid_ref = "0002012191B7B21AF97E0E656DF887C5E2D"
-        "EF30E7758A207EDF4CCB2DE8CA37066021C";
-
-    // Make sure that the DHCID is valid.
-    EXPECT_EQ(dhcid_ref, dhcid.toStr());
-}
-
-/// @brief Verifies the fundamentals of converting from and to JSON.
-/// It verifies that:
-/// 1. A NameChangeRequest can be created from a valid JSON string.
-/// 2. A valid JSON string can be created from a NameChangeRequest
-TEST(NameChangeRequestTest, basicJsonTest) {
-    // Define valid JSON rendition of a request.
-    std::string msg_str = "{"
-                            "\"change_type\":1,"
-                            "\"forward_change\":true,"
-                            "\"reverse_change\":false,"
-                            "\"fqdn\":\"walah.walah.com\","
-                            "\"ip_address\":\"192.168.2.1\","
-                            "\"dhcid\":\"010203040A7F8E3D\","
-                            "\"lease_expires_on\":\"20130121132405\","
-                            "\"lease_length\":1300"
-                          "}";
-
-    // Verify that a NameChangeRequests can be instantiated from the
-    // a valid JSON rendition.
-    NameChangeRequestPtr ncr;
-    ASSERT_NO_THROW(ncr  = NameChangeRequest::fromJSON(msg_str));
-    ASSERT_TRUE(ncr);
-
-    // Verify that the JSON string created by the new request equals the
-    // original input string.
-    std::string json_str = ncr->toJSON();
-    EXPECT_EQ(msg_str, json_str);
-}
-
-/// @brief Tests a variety of invalid JSON message strings.
-/// This test iterates over a list of JSON messages, each containing a single
-/// content error. The list of messages is defined by the global array,
-/// invalid_messages. Currently that list contains the following invalid
-/// conditions:
-///  1. Invalid change type
-///  2. Invalid forward change
-///  3. Invalid reverse change
-///  4. Forward and reverse change both false
-///  5. Invalid forward change
-///  6. Blank FQDN
-///  7. Bad IP address
-///  8. Blank DHCID
-///  9. Odd number of digits in DHCID
-/// 10. Text in DHCID
-/// 11. Invalid lease expiration string
-/// 12. Non-integer for lease length.
-/// If more permutations arise they can easily be added to the list.
-TEST(NameChangeRequestTest, invalidMsgChecks) {
-    // Iterate over the list of JSON strings, attempting to create a
-    // NameChangeRequest. The attempt should throw a NcrMessageError.
-    int num_msgs = sizeof(invalid_msgs)/sizeof(char*);
-    for (int i = 0; i < num_msgs; i++) {
-        EXPECT_THROW(NameChangeRequest::fromJSON(invalid_msgs[i]),
-                     NcrMessageError) << "Invalid message not caught idx: "
-                     << i << std::endl << " text:[" << invalid_msgs[i] << "]"
-                     << std::endl;
-    }
-}
-
-/// @brief Tests a variety of valid JSON message strings.
-/// This test iterates over a list of JSON messages, each containing a single
-/// valid request rendition. The list of messages is defined by the global
-/// array, valid_messages. Currently that list contains the following valid
-/// messages:
-///  1. Valid, IPv4 Add
-///  2. Valid, IPv4 Remove
-///  3. Valid, IPv6 Add
-/// If more permutations arise they can easily be added to the list.
-TEST(NameChangeRequestTest, validMsgChecks) {
-    // Iterate over the list of JSON strings, attempting to create a
-    // NameChangeRequest. The attempt should succeed.
-    int num_msgs = sizeof(valid_msgs)/sizeof(char*);
-    for (int i = 0; i < num_msgs; i++) {
-        EXPECT_NO_THROW(NameChangeRequest::fromJSON(valid_msgs[i]))
-                        << "Valid message failed,  message idx: " << i
-                        << std::endl << " text:[" << valid_msgs[i] << "]"
-                        << std::endl;
-    }
-}
-
-/// @brief Tests converting to and from JSON via isc::util buffer classes.
-/// This test verifies that:
-/// 1. A NameChangeRequest can be rendered in JSON written to an OutputBuffer
-/// 2. A InputBuffer containing a valid JSON request rendition can be used
-/// to create a NameChangeRequest.
-TEST(NameChangeRequestTest, toFromBufferTest) {
-    // Define a string containing a valid JSON NameChangeRequest rendition.
-    std::string msg_str = "{"
-                            "\"change_type\":1,"
-                            "\"forward_change\":true,"
-                            "\"reverse_change\":false,"
-                            "\"fqdn\":\"walah.walah.com\","
-                            "\"ip_address\":\"192.168.2.1\","
-                            "\"dhcid\":\"010203040A7F8E3D\","
-                            "\"lease_expires_on\":\"20130121132405\","
-                            "\"lease_length\":1300"
-                          "}";
-
-    // Create a request from JSON directly.
-    NameChangeRequestPtr ncr;
-    ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str));
-
-    // Verify that we output the request as JSON text to a buffer
-    // without error.
-    isc::util::OutputBuffer output_buffer(1024);
-    ASSERT_NO_THROW(ncr->toFormat(FMT_JSON, output_buffer));
-
-    // Make an InputBuffer from the OutputBuffer.
-    isc::util::InputBuffer input_buffer(output_buffer.getData(),
-                                        output_buffer.getLength());
-
-    // Verify that we can create a new request from the InputBuffer.
-    NameChangeRequestPtr ncr2;
-    ASSERT_NO_THROW(ncr2 =
-                    NameChangeRequest::fromFormat(FMT_JSON, input_buffer));
-
-    // Convert the new request to JSON directly.
-    std::string final_str = ncr2->toJSON();
-
-    // Verify that the final string matches the original.
-    ASSERT_EQ(final_str, msg_str);
-}
-
-
-} // end of anonymous namespace
-
diff --git a/src/bin/dhcp4/Makefile.am b/src/bin/dhcp4/Makefile.am
index b3818c7..80b7fc3 100644
--- a/src/bin/dhcp4/Makefile.am
+++ b/src/bin/dhcp4/Makefile.am
@@ -63,6 +63,7 @@ b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+b10_dhcp4_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
 
 b10_dhcp4dir = $(pkgdatadir)
 b10_dhcp4_DATA = dhcp4.spec
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index f30ff21..726a4a6 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -53,10 +53,10 @@ public:
     /// @param dummy first param, option names are always "Dhcp4/option-data[n]"
     /// @param options is the option storage in which to store the parsed option
     /// upon "commit".
-    /// @param global_context is a pointer to the global context which 
+    /// @param global_context is a pointer to the global context which
     /// stores global scope parameters, options, option defintions.
-    Dhcp4OptionDataParser(const std::string&, 
-        OptionStoragePtr options, ParserContextPtr global_context) 
+    Dhcp4OptionDataParser(const std::string&,
+        OptionStoragePtr options, ParserContextPtr global_context)
         :OptionDataParser("", options, global_context) {
     }
 
@@ -64,7 +64,7 @@ public:
     ///
     /// @param param_name name of the parameter to be parsed.
     /// @param options storage where the parameter value is to be stored.
-    /// @param global_context is a pointer to the global context which 
+    /// @param global_context is a pointer to the global context which
     /// stores global scope parameters, options, option defintions.
     /// @return returns a pointer to a new OptionDataParser. Caller is
     /// is responsible for deleting it when it is no longer needed.
@@ -75,16 +75,16 @@ public:
 
 protected:
     /// @brief Finds an option definition within the server's option space
-    /// 
-    /// Given an option space and an option code, find the correpsonding 
+    ///
+    /// Given an option space and an option code, find the correpsonding
     /// option defintion within the server's option defintion storage.
     ///
-    /// @param option_space name of the parameter option space 
-    /// @param option_code numeric value of the parameter to find 
-    /// @return OptionDefintionPtr of the option defintion or an 
+    /// @param option_space name of the parameter option space
+    /// @param option_code numeric value of the parameter to find
+    /// @return OptionDefintionPtr of the option defintion or an
     /// empty OptionDefinitionPtr if not found.
-    /// @throw DhcpConfigError if the option space requested is not valid 
-    /// for this server. 
+    /// @throw DhcpConfigError if the option space requested is not valid
+    /// for this server.
     virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
                 std::string& option_space, uint32_t option_code) {
         OptionDefinitionPtr def;
@@ -100,11 +100,11 @@ protected:
     }
 };
 
-/// @brief Parser for IPv4 pool definitions.  
+/// @brief Parser for IPv4 pool definitions.
 ///
-/// This is the IPv4 derivation of the PoolParser class and handles pool 
-/// definitions, i.e. a list of entries of one of two syntaxes: min-max and 
-/// prefix/len for IPv4 pools. Pool4 objects are created and stored in chosen 
+/// This is the IPv4 derivation of the PoolParser class and handles pool
+/// definitions, i.e. a list of entries of one of two syntaxes: min-max and
+/// prefix/len for IPv4 pools. Pool4 objects are created and stored in chosen
 /// PoolStorage container.
 ///
 /// It is useful for parsing Dhcp4/subnet4[X]/pool parameters.
@@ -126,9 +126,9 @@ protected:
     ///
     /// @param addr is the IPv4 prefix of the pool.
     /// @param len is the prefix length.
-    /// @param ignored dummy parameter to provide symmetry between the 
+    /// @param ignored dummy parameter to provide symmetry between the
     /// PoolParser derivations. The V6 derivation requires a third value.
-    /// @return returns a PoolPtr to the new Pool4 object. 
+    /// @return returns a PoolPtr to the new Pool4 object.
     PoolPtr poolMaker (IOAddress &addr, uint32_t len, int32_t) {
         return (PoolPtr(new Pool4(addr, len)));
     }
@@ -137,9 +137,9 @@ protected:
     ///
     /// @param min is the first IPv4 address in the pool.
     /// @param max is the last IPv4 address in the pool.
-    /// @param ignored dummy parameter to provide symmetry between the 
+    /// @param ignored dummy parameter to provide symmetry between the
     /// PoolParser derivations. The V6 derivation requires a third value.
-    /// @return returns a PoolPtr to the new Pool4 object. 
+    /// @return returns a PoolPtr to the new Pool4 object.
     PoolPtr poolMaker (IOAddress &min, IOAddress &max, int32_t) {
         return (PoolPtr(new Pool4(min, max)));
     }
@@ -147,8 +147,8 @@ protected:
 
 /// @brief This class parses a single IPv4 subnet.
 ///
-/// This is the IPv4 derivation of the SubnetConfigParser class and it parses 
-/// the whole subnet definition. It creates parsersfor received configuration 
+/// This is the IPv4 derivation of the SubnetConfigParser class and it parses
+/// the whole subnet definition. It creates parsersfor received configuration
 /// parameters as needed.
 class Subnet4ConfigParser : public SubnetConfigParser {
 public:
@@ -158,7 +158,7 @@ public:
     /// stores global scope parameters, options, option defintions.
     Subnet4ConfigParser(const std::string&)
         :SubnetConfigParser("", globalContext()) {
-    } 
+    }
 
     /// @brief Adds the created subnet to a server's configuration.
     /// @throw throws Unexpected if dynamic cast fails.
@@ -167,7 +167,7 @@ public:
             Subnet4Ptr sub4ptr = boost::dynamic_pointer_cast<Subnet4>(subnet_);
             if (!sub4ptr) {
                 // If we hit this, it is a programming error.
-                isc_throw(Unexpected, 
+                isc_throw(Unexpected,
                           "Invalid cast in Subnet4ConfigParser::commit");
             }
 
@@ -191,13 +191,13 @@ protected:
             (config_id.compare("renew-timer") == 0)  ||
             (config_id.compare("rebind-timer") == 0))  {
             parser = new Uint32Parser(config_id, uint32_values_);
-        } else if ((config_id.compare("subnet") == 0) || 
+        } else if ((config_id.compare("subnet") == 0) ||
                    (config_id.compare("interface") == 0)) {
             parser = new StringParser(config_id, string_values_);
         } else if (config_id.compare("pool") == 0) {
             parser = new Pool4Parser(config_id, pools_);
         } else if (config_id.compare("option-data") == 0) {
-           parser = new OptionDataListParser(config_id, options_, 
+           parser = new OptionDataListParser(config_id, options_,
                                              global_context_,
                                              Dhcp4OptionDataParser::factory);
         } else {
@@ -210,7 +210,7 @@ protected:
 
 
     /// @brief Determines if the given option space name and code describe
-    /// a standard option for the DCHP4 server. 
+    /// a standard option for the DCHP4 server.
     ///
     /// @param option_space is the name of the option space to consider
     /// @param code is the numeric option code to consider
@@ -230,12 +230,12 @@ protected:
     }
 
     /// @brief Issues a DHCP4 server specific warning regarding duplicate subnet
-    /// options. 
-    /// 
+    /// options.
+    ///
     /// @param code is the numeric option code of the duplicate option
-    /// @param addr is the subnet address 
+    /// @param addr is the subnet address
     /// @todo a means to know the correct logger and perhaps a common
-    /// message would allow this method to be emitted by the base class. 
+    /// message would allow this method to be emitted by the base class.
     virtual void duplicate_option_warning(uint32_t code,
                                          isc::asiolink::IOAddress& addr) {
         LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
@@ -243,10 +243,10 @@ protected:
     }
 
     /// @brief Instantiates the IPv4 Subnet based on a given IPv4 address
-    /// and prefix length.  
-    /// 
+    /// and prefix length.
+    ///
     /// @param addr is IPv4 address of the subnet.
-    /// @param len is the prefix length 
+    /// @param len is the prefix length
     void initSubnet(isc::asiolink::IOAddress addr, uint8_t len) {
         // Get all 'time' parameters using inheritance.
         // If the subnet-specific value is defined then use it, else
@@ -338,32 +338,32 @@ namespace dhcp {
 ///
 /// @param config_id pointer to received global configuration entry
 /// @return parser for specified global DHCPv4 parameter
-/// @throw NotImplemented if trying to create a parser for unknown 
+/// @throw NotImplemented if trying to create a parser for unknown
 /// config element
 DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
     DhcpConfigParser* parser = NULL;
     if ((config_id.compare("valid-lifetime") == 0)  ||
         (config_id.compare("renew-timer") == 0)  ||
         (config_id.compare("rebind-timer") == 0))  {
-        parser = new Uint32Parser(config_id, 
+        parser = new Uint32Parser(config_id,
                                  globalContext()->uint32_values_);
-    } else if (config_id.compare("interface") == 0) {
+    } else if (config_id.compare("interfaces") == 0) {
         parser = new InterfaceListConfigParser(config_id);
     } else if (config_id.compare("subnet4") == 0) {
         parser = new Subnets4ListConfigParser(config_id);
     } else if (config_id.compare("option-data") == 0) {
-        parser = new OptionDataListParser(config_id, 
-                                          globalContext()->options_, 
+        parser = new OptionDataListParser(config_id,
+                                          globalContext()->options_,
                                           globalContext(),
                                           Dhcp4OptionDataParser::factory);
     } else if (config_id.compare("option-def") == 0) {
-        parser  = new OptionDefListParser(config_id, 
+        parser  = new OptionDefListParser(config_id,
                                           globalContext()->option_defs_);
     } else if (config_id.compare("version") == 0) {
-        parser  = new StringParser(config_id, 
+        parser  = new StringParser(config_id,
                                     globalContext()->string_values_);
     } else if (config_id.compare("lease-database") == 0) {
-        parser = new DbAccessParser(config_id); 
+        parser = new DbAccessParser(config_id);
     } else {
         isc_throw(NotImplemented,
                 "Parser error: Global configuration parameter not supported: "
@@ -384,7 +384,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
     /// @todo: Append most essential info here (like "2 new subnets configured")
     string config_details;
 
-    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, 
+    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND,
               DHCP4_CONFIG_START).arg(config_set->str());
 
     // Some of the values specified in the configuration depend on
@@ -397,6 +397,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
     ParserCollection independent_parsers;
     ParserPtr subnet_parser;
     ParserPtr option_parser;
+    ParserPtr iface_parser;
 
     // The subnet parsers implement data inheritance by directly
     // accessing global storage. For this reason the global data
@@ -428,6 +429,11 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
                 subnet_parser = parser;
             } else if (config_pair.first == "option-data") {
                 option_parser = parser;
+            } else if (config_pair.first == "interfaces") {
+                // The interface parser is independent from any other
+                // parser and can be run here before any other parsers.
+                iface_parser = parser;
+                parser->build(config_pair.second);
             } else {
                 // Those parsers should be started before other
                 // parsers so we can call build straight away.
@@ -483,6 +489,10 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
             if (subnet_parser) {
                 subnet_parser->commit();
             }
+
+            if (iface_parser) {
+                iface_parser->commit();
+            }
         }
         catch (const isc::Exception& ex) {
             LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
diff --git a/src/bin/dhcp4/config_parser.h b/src/bin/dhcp4/config_parser.h
index ea84cc6..3af9911 100644
--- a/src/bin/dhcp4/config_parser.h
+++ b/src/bin/dhcp4/config_parser.h
@@ -30,7 +30,7 @@ namespace dhcp {
 
 class Dhcpv4Srv;
 
-/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration 
+/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration
 /// values.
 ///
 /// This function parses configuration information stored in @c config_set
@@ -44,7 +44,7 @@ class Dhcpv4Srv;
 /// (such as malformed configuration or invalid configuration parameter),
 /// this function returns appropriate error code.
 ///
-/// This function is called every time a new configuration is received. The 
+/// This function is called every time a new configuration is received. The
 /// extra parameter is a reference to DHCPv4 server component. It is currently
 /// not used and CfgMgr::instance() is accessed instead.
 ///
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index eb7d559..46a7ca0 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -20,17 +20,17 @@
 #include <config/ccsession.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcp4/spec_config.h>
 #include <dhcp4/config_parser.h>
 #include <exceptions/exceptions.h>
 #include <util/buffer.h>
-#include <cassert>
-#include <iostream>
 
 #include <cassert>
 #include <iostream>
+#include <sstream>
 
 using namespace isc::asiolink;
 using namespace isc::cc;
@@ -101,7 +101,27 @@ ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
     }
 
     // Configure the server.
-    return (configureDhcp4Server(*server_, merged_config));
+    ConstElementPtr answer = configureDhcp4Server(*server_, merged_config);
+
+    // Check that configuration was successful. If not, do not reopen sockets.
+    int rcode = 0;
+    parseAnswer(rcode, answer);
+    if (rcode != 0) {
+        return (answer);
+    }
+
+    // Configuration may change active interfaces. Therefore, we have to reopen
+    // sockets according to new configuration. This operation is not exception
+    // safe and we really don't want to emit exceptions to the callback caller.
+    // Instead, catch an exception and create appropriate answer.
+    try {
+        server_->openActiveSockets(server_->getPort(), server_->useBroadcast());
+    } catch (std::exception& ex) {
+        std::ostringstream err;
+        err << "failed to open sockets after server reconfiguration: " << ex.what();
+        answer = isc::config::createAnswer(1, err.str());
+    }
+    return (answer);
 }
 
 ConstElementPtr
@@ -172,8 +192,13 @@ void ControlledDhcpv4Srv::establishSession() {
 
     try {
         configureDhcp4Server(*this, config_session_->getFullConfig());
+        // Configuration may disable or enable interfaces so we have to
+        // reopen sockets according to new configuration.
+        openActiveSockets(getPort(), useBroadcast());
+
     } catch (const DhcpConfigError& ex) {
         LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
+
     }
 
     /// Integrate the asynchronous I/O model of BIND 10 configuration
@@ -228,6 +253,5 @@ ControlledDhcpv4Srv::execDhcpv4ServerCommand(const std::string& command_id,
     }
 }
 
-
 };
 };
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h
index 8f14f4b..526d987 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.h
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -130,6 +130,7 @@ protected:
     /// when there is a new command or configuration sent over msgq.
     static void sessionReader(void);
 
+
     /// @brief IOService object, used for all ASIO operations.
     isc::asiolink::IOService io_service_;
 
diff --git a/src/bin/dhcp4/dhcp4.dox b/src/bin/dhcp4/dhcp4.dox
index 86ae845..bd490fb 100644
--- a/src/bin/dhcp4/dhcp4.dox
+++ b/src/bin/dhcp4/dhcp4.dox
@@ -79,4 +79,8 @@ See \ref dhcpv6ConfigParser.
 Configuration inheritance in DHCPv4 follows exactly the same logic as its DHCPv6
 counterpart. See \ref dhcpv6ConfigInherit.
 
+ at section dhcpv4Other Other DHCPv4 topics
+
+ For hooks API support in DHCPv4, see @ref dhcpv4Hooks.
+
 */
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index 59d727e..e940cb7 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -3,16 +3,16 @@
     "module_name": "Dhcp4",
     "module_description": "DHCPv4 server daemon",
     "config_data": [
-      { "item_name": "interface",
+      { "item_name": "interfaces",
         "item_type": "list",
         "item_optional": false,
-        "item_default": [ "all" ],
+        "item_default": [ "*" ],
         "list_item_spec":
         {
           "item_name": "interface_name",
           "item_type": "string",
           "item_optional": false,
-          "item_default": "all"
+          "item_default": "*"
         }
       } ,
 
diff --git a/src/bin/dhcp4/dhcp4_hooks.dox b/src/bin/dhcp4/dhcp4_hooks.dox
new file mode 100644
index 0000000..85fcfc4
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4_hooks.dox
@@ -0,0 +1,138 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/**
+ @page dhcpv4Hooks The Hooks API for the DHCPv4 Server
+
+ @section dhcpv4HooksIntroduction Introduction
+ BIND10 features an API (the "Hooks" API) that allows user-written code to
+ be integrated into BIND 10 and called at specific points in its processing.
+ An overview of the API and a tutorial for writing such code can be found in
+ the @ref hooksdgDevelopersGuide.  Information for BIND 10 maintainers can be
+ found in the @ref hooksComponentDeveloperGuide.
+
+ This manual is more specialised and is aimed at developers of hook
+ code for the DHCPv4 server. It describes each hook point, what the callouts
+ attached to the hook are able to do, and the arguments passed to the
+ callouts.  Each entry in this manual has the following information:
+
+ - Name of the hook point.
+ - Arguments for the callout.  As well as the argument name and data type, the
+   information includes the direction, which can be one of:
+   - @b in - the server passes values to the callout but ignored any data
+     returned.
+   - @b out - the callout is expected to set this value.
+   - <b>in/out</b> - the server passes a value to the callout and uses whatever
+     value the callout sends back.  Note that the callout may choose not to
+     do any modification, in which case the server will use whatever value
+     it sent to the callout.
+ - Description of the hook. This explains where in the processing the hook
+   is located, the possible actions a callout attached to this hook could take,
+   and a description of the data passed to the callouts.
+ - Skip flag action: the action taken by the server if a callout chooses to set
+    the "skip" flag.
+
+ at section dhcpv4HooksHookPoints Hooks in the DHCPv4 Server
+
+The following list is ordered by appearance of specific hook points during
+packet processing. Hook points that are not specific to packet processing
+(e.g. lease expiration) will be added to the end of this list.
+
+ @subsection dhcpv4HooksPkt4Receive pkt4_receive
+
+ - @b Arguments:
+   - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed when an incoming DHCPv4
+   packet is received and its content is parsed. The sole argument -
+   query4 - contains a pointer to an isc::dhcp::Pkt4 object that contains
+   all information regarding incoming packet, including its source and
+   destination addresses, interface over which it was received, a list
+   of all options present within and relay information.  All fields of
+   the Pkt4 object can be modified at this time, except data_. (data_
+   contains the incoming packet as raw buffer. By the time this hook is
+   reached, that information has already parsed and is available though
+   other fields in the Pkt4 object.  For this reason, it doesn't make
+   sense to modify it.)
+
+ - <b>Skip flag action</b>: If any callout sets the skip flag, the server will
+   drop the packet and start processing the next one.  The reason for the drop
+   will be logged if logging is set to the appropriate debug level.
+
+ at subsection dhcpv4HooksSubnet4Select subnet4_select
+
+ - @b Arguments:
+   - name: @b query4, type: isc::dhcp::Pkt4Ptr, direction: <b>in/out</b>
+   - name: @b subnet4, type: isc::dhcp::Subnet4Ptr, direction: <b>in/out</b>
+   - name: @b subnet4collection, type: const isc::dhcp::Subnet4Collection *, direction: <b>in</b>
+
+ - @b Description: this callout is executed when a subnet is being
+   selected for the incoming packet. All parameters and addresses
+   will be assigned from that subnet. A callout can select a
+   different subnet if it wishes so, the list of all subnets currently
+   configured being provided as 'subnet4collection'. The list itself must
+   not be modified.
+
+ - <b>Skip flag action</b>: If any callout installed on 'subnet4_select'
+   sets the skip flag, the server will not select any subnet. Packet processing
+   will continue, but will be severely limited (i.e. only global options
+   will be assigned).
+
+ at subsection dhcpv4HooksLeaseSelect lease4_select
+
+ - @b Arguments:
+   - name: @b subnet4, type: isc::dhcp::Subnet4Ptr, direction: <b>in</b>
+   - name: @b fake_allocation, type: bool, direction: <b>in</b>
+   - name: @b lease4, type: isc::dhcp::Lease4Ptr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed after the server engine
+   has selected a lease for client's request but before the lease
+   has been inserted into the database. Any modifications made to the
+   isc::dhcp::Lease4 object will be stored in the lease's record in the
+   database. The callout should make sure that any modifications are
+   sanity checked as the server will use that data as is with no further
+   checking.\n\n The server processes lease requests for DISCOVER and
+   REQUEST in a very similar way. The only major difference is that
+   for DISCOVER the lease is just selected, but not inserted into
+   the database.  It is possible to distinguish between DISCOVER and
+   REQUEST by checking value of the fake_allocation flag: a value of true
+   means that the lease won't be inserted into the database (DISCOVER),
+   a value of false means that it will (REQUEST).
+
+ - <b>Skip flag action</b>: If any callout installed on 'lease4_select'
+   sets the skip flag, the server will not assign any lease. Packet
+   processing will continue, but client will not get an address.
+
+ at subsection dhcpv4HooksPkt4Send pkt4_send
+
+ - @b Arguments:
+   - name: @b response4, type: isc::dhcp::Pkt4Ptr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed when server's response
+   is about to be send back to the client. The sole argument - response4 -
+   contains a pointer to an isc::dhcp::Pkt4 object that contains the
+   packet, with set source and destination addresses, interface over which
+   it will be send, list of all options and relay information.  All fields
+   of the Pkt4 object can be modified at this time, except bufferOut_.
+   (This is scratch space used for constructing the packet after all
+   pkt4_send callouts are complete, so any changes to that field will
+   be overwritten.)
+
+ - <b>Skip flag action</b>: if any callout sets the skip flag, the server
+   will drop this response packet. However, the original request packet
+   from a client was processed, so server's state was most likely changed
+   (e.g. lease was allocated). Setting this flag merely stops the change
+   being communicated to the client.
+
+*/
diff --git a/src/bin/dhcp4/dhcp4_log.h b/src/bin/dhcp4/dhcp4_log.h
index 07d009a..54dbb40 100644
--- a/src/bin/dhcp4/dhcp4_log.h
+++ b/src/bin/dhcp4/dhcp4_log.h
@@ -38,6 +38,9 @@ const int DBG_DHCP4_COMMAND = DBGLVL_COMMAND;
 // Trace basic operations within the code.
 const int DBG_DHCP4_BASIC = DBGLVL_TRACE_BASIC;
 
+// Trace hook related operations
+const int DBG_DHCP4_HOOKS = DBGLVL_TRACE_BASIC;
+
 // Trace detailed operations, including errors raised when processing invalid
 // packets.  (These are not logged at severities of WARN or higher for fear
 // that a set of deliberately invalid packets set to the server could overwhelm
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index 8b3e255..f70dde3 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -14,6 +14,11 @@
 
 $NAMESPACE isc::dhcp
 
+% DHCP4_ACTIVATE_INTERFACE activating interface %1
+This message is printed when DHCPv4 server enabled an interface to be used
+to receive DHCPv4 traffic. IPv4 socket on this interface will be opened once
+Interface Manager starts up procedure of opening sockets.
+
 % DHCP4_CCSESSION_STARTED control channel session started on socket %1
 A debug message issued during startup after the IPv4 DHCP server has
 successfully established a session with the BIND 10 control channel.
@@ -37,7 +42,7 @@ This critical error message indicates that the initial DHCPv4
 configuration has failed. The server will start, but nothing will be
 served until the configuration has been corrected.
 
-% DHCP4_CONFIG_NEW_SUBNET A new subnet has been added to configuration: %1
+% DHCP4_CONFIG_NEW_SUBNET a new subnet has been added to configuration: %1
 This is an informational message reporting that the configuration has
 been extended to include the specified IPv4 subnet.
 
@@ -60,6 +65,30 @@ This informational message is printed every time DHCPv4 server is started
 and gives both the type and name of the database being used to store
 lease and other information.
 
+% DHCP4_DEACTIVATE_INTERFACE deactivate interface %1
+This message is printed when DHCPv4 server disables an interface from being
+used to receive DHCPv4 traffic. Sockets on this interface will not be opened
+by the Interface Manager until interface is enabled.
+
+% DHCP4_HOOK_PACKET_RCVD_SKIP received DHCPv4 packet was dropped, because a callout set the skip flag.
+This debug message is printed when a callout installed on the pkt4_receive
+hook point sets the skip flag. For this particular hook point, the
+setting of the flag instructs the server to drop the packet.
+
+% DHCP4_HOOK_PACKET_SEND_SKIP prepared DHCPv6 response was not sent, because a callout set skip flag.
+This debug message is printed when a callout installed on the pkt4_send
+hook point sets the skip flag. For this particular hook point, the setting
+of the flag instructs the server to drop the packet. This means that
+the client will not get any response, even though the server processed
+client's request and acted on it (e.g. possibly allocated a lease).
+
+% DHCP4_HOOK_SUBNET4_SELECT_SKIP no subnet was selected, because a callout set skip flag.
+This debug message is printed when a callout installed on the
+subnet4_select hook point sets the skip flag. For this particular hook
+point, the setting of the flag instructs the server not to choose a
+subnet, an action that severely limits further processing; the server
+will be only able to offer global options - no addresses will be assigned.
+
 % DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
 This debug message indicates that the server successfully advertised
 a lease. It is up to the client to choose one server out of othe advertised
@@ -82,6 +111,11 @@ specified client after receiving a REQUEST message from it.  There are many
 possible reasons for such a failure. Additional messages will indicate the
 reason.
 
+% DHCP4_NO_SOCKETS_OPEN no interface configured to listen to DHCP traffic
+This warning message is issued when current server configuration specifies
+no interfaces that server should listen on, or specified interfaces are not
+configured to receive the traffic.
+
 % DHCP4_NOT_RUNNING IPv4 DHCP server is not running
 A warning message is issued when an attempt is made to shut down the
 IPv4 DHCP server but it is not running.
@@ -115,11 +149,6 @@ This error is output if the IPv4 DHCP server fails to send an assembled
 DHCP message to a client. The reason for the error is included in the
 message.
 
-% DHCP4_PACK_FAIL failed to assemble response correctly
-This error is output if the server failed to assemble the data to be
-returned to the client into a valid packet.  The cause is most likely
-to be a programming error: please raise a bug report.
-
 % DHCP4_PARSER_COMMIT_EXCEPTION parser failed to commit changes
 On receipt of message containing details to a change of the IPv4 DHCP
 server configuration, a set of parsers were successfully created, but one
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 8aa913c..c760478 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -30,6 +30,8 @@
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/utils.h>
 #include <dhcpsrv/addr_utilities.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/callout_handle.h>
 
 #include <boost/algorithm/string/erase.hpp>
 
@@ -39,9 +41,30 @@
 using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
+using namespace isc::hooks;
 using namespace isc::log;
 using namespace std;
 
+/// Structure that holds registered hook indexes
+struct Dhcp6Hooks {
+    int hook_index_pkt4_receive_;   ///< index for "pkt4_receive" hook point
+    int hook_index_subnet4_select_; ///< index for "subnet4_select" hook point
+    int hook_index_pkt4_send_;      ///< index for "pkt4_send" hook point
+
+    /// Constructor that registers hook points for DHCPv6 engine
+    Dhcp6Hooks() {
+        hook_index_pkt4_receive_   = HooksManager::registerHook("pkt4_receive");
+        hook_index_subnet4_select_ = HooksManager::registerHook("subnet4_select");
+        hook_index_pkt4_send_      = HooksManager::registerHook("pkt4_send");
+    }
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+Dhcp6Hooks Hooks;
+
 namespace isc {
 namespace dhcp {
 
@@ -58,7 +81,11 @@ static const char* SERVER_ID_FILE = "b10-dhcp4-serverid";
 // grants those options and a single, fixed, hardcoded lease.
 
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
-                     const bool direct_response_desired) {
+                     const bool direct_response_desired)
+: serverid_(), shutdown_(true), alloc_engine_(), port_(port), 
+    use_bcast_(use_bcast), hook_index_pkt4_receive_(-1), 
+    hook_index_subnet4_select_(-1), hook_index_pkt4_send_(-1) {
+
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
     try {
         // First call to instance() will create IfaceMgr (it's a singleton)
@@ -73,7 +100,7 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
         if (port) {
             // open sockets only if port is non-zero. Port 0 is used
             // for non-socket related testing.
-            IfaceMgr::instance().openSockets4(port, use_bcast);
+            IfaceMgr::instance().openSockets4(port_, use_bcast_);
         }
 
         string srvid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_ID_FILE);
@@ -103,6 +130,13 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
         // Instantiate allocation engine
         alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
 
+        // Register hook points
+        hook_index_pkt4_receive_   = Hooks.hook_index_pkt4_receive_;
+        hook_index_subnet4_select_ = Hooks.hook_index_subnet4_select_;
+        hook_index_pkt4_send_      = Hooks.hook_index_pkt4_send_;
+
+        /// @todo call loadLibraries() when handling configuration changes
+
     } catch (const std::exception &e) {
         LOG_ERROR(dhcp4_logger, DHCP4_SRV_CONSTRUCT_ERROR).arg(e.what());
         shutdown_ = true;
@@ -122,6 +156,16 @@ Dhcpv4Srv::shutdown() {
     shutdown_ = true;
 }
 
+Pkt4Ptr
+Dhcpv4Srv::receivePacket(int timeout) {
+    return (IfaceMgr::instance().receive4(timeout));
+}
+
+void
+Dhcpv4Srv::sendPacket(const Pkt4Ptr& packet) {
+    IfaceMgr::instance().send(packet);
+}
+
 bool
 Dhcpv4Srv::run() {
     while (!shutdown_) {
@@ -134,7 +178,7 @@ Dhcpv4Srv::run() {
         Pkt4Ptr rsp;
 
         try {
-            query = IfaceMgr::instance().receive4(timeout);
+            query = receivePacket(timeout);
         } catch (const std::exception& e) {
             LOG_ERROR(dhcp4_logger, DHCP4_PACKET_RECEIVE_FAIL).arg(e.what());
         }
@@ -154,8 +198,34 @@ Dhcpv4Srv::run() {
                       .arg(query->getType())
                       .arg(query->getIface());
             LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_QUERY_DATA)
+                      .arg(static_cast<int>(query->getType()))
                       .arg(query->toText());
 
+            // Let's execute all callouts registered for packet_received
+            if (HooksManager::calloutsPresent(hook_index_pkt4_receive_)) {
+                CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+                // Delete previously set arguments
+                callout_handle->deleteAllArguments();
+
+                // Pass incoming packet as argument
+                callout_handle->setArgument("query4", query);
+
+                // Call callouts
+                HooksManager::callCallouts(hook_index_pkt4_receive_,
+                                           *callout_handle);
+
+                // Callouts decided to skip the next processing step. The next
+                // processing step would to process the packet, so skip at this
+                // stage means drop.
+                if (callout_handle->getSkip()) {
+                    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_PACKET_RCVD_SKIP);
+                    continue;
+                }
+
+                callout_handle->getArgument("query4", query);
+            }
+
             try {
                 switch (query->getType()) {
                 case DHCPDISCOVER:
@@ -220,18 +290,42 @@ Dhcpv4Srv::run() {
                 rsp->setIface(query->getIface());
                 rsp->setIndex(query->getIndex());
 
+                // Execute all callouts registered for packet6_send
+                if (HooksManager::calloutsPresent(hook_index_pkt4_send_)) {
+                    CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+                    // Delete all previous arguments
+                    callout_handle->deleteAllArguments();
+
+                    // Clear skip flag if it was set in previous callouts
+                    callout_handle->setSkip(false);
+
+                    // Set our response
+                    callout_handle->setArgument("response4", rsp);
+
+                    // Call all installed callouts
+                    HooksManager::callCallouts(hook_index_pkt4_send_,
+                                               *callout_handle);
+
+                    // Callouts decided to skip the next processing step. The next
+                    // processing step would to send the packet, so skip at this
+                    // stage means "drop response".
+                    if (callout_handle->getSkip()) {
+                        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_PACKET_SEND_SKIP);
+                        continue;
+                    }
+                }
+
                 LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA,
                           DHCP4_RESPONSE_DATA)
                           .arg(rsp->getType()).arg(rsp->toText());
 
-                if (rsp->pack()) {
-                    try {
-                        IfaceMgr::instance().send(rsp);
-                    } catch (const std::exception& e) {
-                        LOG_ERROR(dhcp4_logger, DHCP4_PACKET_SEND_FAIL).arg(e.what());
-                    }
-                } else {
-                    LOG_ERROR(dhcp4_logger, DHCP4_PACK_FAIL);
+                try {
+                    rsp->pack();
+                    sendPacket(rsp);
+                } catch (const std::exception& e) {
+                    LOG_ERROR(dhcp4_logger, DHCP4_PACKET_SEND_FAIL)
+                              .arg(e.what());
                 }
             }
         }
@@ -514,12 +608,15 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     // allocation.
     bool fake_allocation = (question->getType() == DHCPDISCOVER);
 
+    CalloutHandlePtr callout_handle = getCalloutHandle(question);
+
     // Use allocation engine to pick a lease for this client. Allocation engine
     // will try to honour the hint, but it is just a hint - some other address
     // may be used instead. If fake_allocation is set to false, the lease will
     // be inserted into the LeaseMgr as well.
     Lease4Ptr lease = alloc_engine_->allocateAddress4(subnet, client_id, hwaddr,
-                                                      hint, fake_allocation);
+                                                      hint, fake_allocation,
+                                                      callout_handle);
 
     if (lease) {
         // We have a lease! Let's set it in the packet and send it back to
@@ -632,6 +729,9 @@ Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
 
 Pkt4Ptr
 Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
+
+    sanityCheck(discover, FORBIDDEN);
+
     Pkt4Ptr offer = Pkt4Ptr
         (new Pkt4(DHCPOFFER, discover->getTransid()));
 
@@ -782,17 +882,50 @@ Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
 Subnet4Ptr
 Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
 
+    Subnet4Ptr subnet;
     // Is this relayed message?
     IOAddress relay = question->getGiaddr();
-    if (relay.toText() == "0.0.0.0") {
+    static const IOAddress notset("0.0.0.0");
 
+    if (relay != notset) {
         // Yes: Use relay address to select subnet
-        return (CfgMgr::instance().getSubnet4(relay));
+        subnet = CfgMgr::instance().getSubnet4(relay);
     } else {
 
         // No: Use client's address to select subnet
-        return (CfgMgr::instance().getSubnet4(question->getRemoteAddr()));
+        subnet = CfgMgr::instance().getSubnet4(question->getRemoteAddr());
     }
+
+    /// @todo Implement getSubnet4(interface-name)
+
+    // Let's execute all callouts registered for packet_received
+    if (HooksManager::calloutsPresent(hook_index_subnet4_select_)) {
+        CalloutHandlePtr callout_handle = getCalloutHandle(question);
+
+        // We're reusing callout_handle from previous calls
+        callout_handle->deleteAllArguments();
+
+        // Set new arguments
+        callout_handle->setArgument("query4", question);
+        callout_handle->setArgument("subnet4", subnet);
+        callout_handle->setArgument("subnet4collection", CfgMgr::instance().getSubnets4());
+
+        // Call user (and server-side) callouts
+        HooksManager::callCallouts(hook_index_subnet4_select_, *callout_handle);
+
+        // Callouts decided to skip this step. This means that no subnet will be
+        // selected. Packet processing will continue, but it will be severly limited
+        // (i.e. only global options will be assigned)
+        if (callout_handle->getSkip()) {
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_HOOKS, DHCP4_HOOK_SUBNET4_SELECT_SKIP);
+            return (Subnet4Ptr());
+        }
+
+        // Use whatever subnet was specified by the callout
+        callout_handle->getArgument("subnet4", subnet);
+    }
+
+    return (subnet);
 }
 
 void
@@ -818,7 +951,94 @@ Dhcpv4Srv::sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid) {
         // do nothing here
         ;
     }
+
+    // If there is HWAddress set and it is non-empty, then we're good
+    if (pkt->getHWAddr() && !pkt->getHWAddr()->hwaddr_.empty()) {
+        return;
+    }
+
+    // There has to be something to uniquely identify the client:
+    // either non-zero MAC address or client-id option present (or both)
+    OptionPtr client_id = pkt->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+
+    // If there's no client-id (or a useless one is provided, i.e. 0 length)
+    if (!client_id || client_id->len() == client_id->getHeaderLen()) {
+        isc_throw(RFCViolation, "Missing or useless client-id and no HW address "
+                  " provided in message "
+                  << serverReceivedPacketName(pkt->getType()));
+    }
+}
+
+isc::hooks::CalloutHandlePtr Dhcpv4Srv::getCalloutHandle(const Pkt4Ptr& pkt) {
+    // This method returns a CalloutHandle for a given packet. It is guaranteed
+    // to return the same callout_handle (so user library contexts are
+    // preserved). This method works well if the server processes one packet
+    // at a time. Once the server architecture is extended to cover parallel
+    // packets processing (e.g. delayed-ack, some form of buffering etc.), this
+    // method has to be extended (e.g. store callouts in a map and use pkt as
+    // a key). Additional code would be required to release the callout handle
+    // once the server finished processing.
+
+    CalloutHandlePtr callout_handle;
+    static Pkt4Ptr old_pointer;
+
+    if (!callout_handle ||
+        old_pointer != pkt) {
+        // This is the first packet or a different packet than previously
+        // passed to getCalloutHandle()
+
+        // Remember the pointer to this packet
+        old_pointer = pkt;
+
+        callout_handle = HooksManager::createCalloutHandle();
+    }
+
+    return (callout_handle);
+}
+
+void
+Dhcpv4Srv::openActiveSockets(const uint16_t port,
+                             const bool use_bcast) {
+    IfaceMgr::instance().closeSockets();
+
+    // Get the reference to the collection of interfaces. This reference should
+    // be valid as long as the program is run because IfaceMgr is a singleton.
+    // Therefore we can safely iterate over instances of all interfaces and
+    // modify their flags. Here we modify flags which indicate whether socket
+    // should be open for a particular interface or not.
+    const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+    for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
+         iface != ifaces.end(); ++iface) {
+        Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
+        if (iface_ptr == NULL) {
+            isc_throw(isc::Unexpected, "Interface Manager returned NULL"
+                      << " instance of the interface when DHCPv4 server was"
+                      << " trying to reopen sockets after reconfiguration");
+        }
+        if (CfgMgr::instance().isActiveIface(iface->getName())) {
+            iface_ptr->inactive4_ = false;
+            LOG_INFO(dhcp4_logger, DHCP4_ACTIVATE_INTERFACE)
+                .arg(iface->getFullName());
+
+        } else {
+            // For deactivating interface, it should be sufficient to log it
+            // on the debug level because it is more useful to know what
+            // interface is activated which is logged on the info level.
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC,
+                      DHCP4_DEACTIVATE_INTERFACE).arg(iface->getName());
+            iface_ptr->inactive4_ = true;
+
+        }
+    }
+    // Let's reopen active sockets. openSockets4 will check internally whether
+    // sockets are marked active or inactive.
+    // @todo Optimization: we should not reopen all sockets but rather select
+    // those that have been affected by the new configuration.
+    if (!IfaceMgr::instance().openSockets4(port, use_bcast)) {
+        LOG_WARN(dhcp4_logger, DHCP4_NO_SOCKETS_OPEN);
+    }
 }
 
+
 }   // namespace dhcp
 }   // namespace isc
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index f98233c..432327c 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -20,6 +20,7 @@
 #include <dhcp/option.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/alloc_engine.h>
+#include <hooks/callout_handle.h>
 
 #include <boost/noncopyable.hpp>
 
@@ -79,7 +80,7 @@ public:
               const bool direct_response_desired = true);
 
     /// @brief Destructor. Used during DHCPv4 service shutdown.
-    ~Dhcpv4Srv();
+    virtual ~Dhcpv4Srv();
 
     /// @brief Main server processing loop.
     ///
@@ -113,6 +114,46 @@ public:
     ///         be freed by the caller.
     static const char* serverReceivedPacketName(uint8_t type);
 
+    ///
+    /// @name Public accessors returning values required to (re)open sockets.
+    ///
+    /// These accessors must be public because sockets are reopened from the
+    /// static configuration callback handler. This callback handler invokes
+    /// @c ControlledDhcpv4Srv::openActiveSockets which requires parameters
+    /// which has to be retrieved from the @c ControlledDhcpv4Srv object.
+    /// They are retrieved using these public functions
+    //@{
+    ///
+    /// @brief Get UDP port on which server should listen.
+    ///
+    /// Typically, server listens on UDP port number 67. Other ports are used
+    /// for testing purposes only.
+    ///
+    /// @return UDP port on which server should listen.
+    uint16_t getPort() const {
+        return (port_);
+    }
+
+    /// @brief Return bool value indicating that broadcast flags should be set
+    /// on sockets.
+    ///
+    /// @return A bool value indicating that broadcast should be used (if true).
+    bool useBroadcast() const {
+        return (use_bcast_);
+    }
+    //@}
+
+    /// @brief Open sockets which are marked as active in @c CfgMgr.
+    ///
+    /// This function reopens sockets according to the current settings in the
+    /// Configuration Manager. It holds the list of the interfaces which server
+    /// should listen on. This function will open sockets on these interfaces
+    /// only. This function is not exception safe.
+    ///
+    /// @param port UDP port on which server should listen.
+    /// @param use_bcast should broadcast flags be set on the sockets.
+    static void openActiveSockets(const uint16_t port, const bool use_bcast);
+
 protected:
 
     /// @brief verifies if specified packet meets RFC requirements
@@ -296,6 +337,18 @@ protected:
     /// initiate server shutdown procedure.
     volatile bool shutdown_;
 
+    /// @brief dummy wrapper around IfaceMgr::receive4
+    ///
+    /// This method is useful for testing purposes, where its replacement
+    /// simulates reception of a packet. For that purpose it is protected.
+    virtual Pkt4Ptr receivePacket(int timeout);
+
+    /// @brief dummy wrapper around IfaceMgr::send()
+    ///
+    /// This method is useful for testing purposes, where its replacement
+    /// simulates transmission of a packet. For that purpose it is protected.
+    virtual void sendPacket(const Pkt4Ptr& pkt);
+
 private:
 
     /// @brief Constructs netmask option based on subnet4
@@ -310,6 +363,20 @@ private:
     /// during normal operation (e.g. to use different allocators)
     boost::shared_ptr<AllocEngine> alloc_engine_;
 
+    uint16_t port_;  ///< UDP port number on which server listens.
+    bool use_bcast_; ///< Should broadcast be enabled on sockets (if true).
+
+    /// @brief returns callout handle for specified packet
+    ///
+    /// @param pkt packet for which the handle should be returned
+    ///
+    /// @return a callout handle to be used in hooks related to said packet
+    isc::hooks::CalloutHandlePtr getCalloutHandle(const Pkt4Ptr& pkt);
+
+    /// Indexes for registered hook points
+    int hook_index_pkt4_receive_;
+    int hook_index_subnet4_select_;
+    int hook_index_pkt4_send_;
 };
 
 }; // namespace isc::dhcp
diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am
index 3e10c3b..d3c7171 100644
--- a/src/bin/dhcp4/tests/Makefile.am
+++ b/src/bin/dhcp4/tests/Makefile.am
@@ -71,6 +71,7 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
 endif
 
 noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index 32770b3..05c951d 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -51,11 +51,12 @@ public:
         // deal with sockets here, just check if configuration handling
         // is sane.
         srv_.reset(new Dhcpv4Srv(0));
+        CfgMgr::instance().deleteActiveIfaces();
     }
 
     // Checks if global parameter of name have expected_value
     void checkGlobalUint32(string name, uint32_t expected_value) {
-        const Uint32StoragePtr uint32_defaults = 
+        const Uint32StoragePtr uint32_defaults =
                                         globalContext()->uint32_values_;
         try {
             uint32_t actual_value = uint32_defaults->getParam(name);
@@ -138,7 +139,7 @@ public:
     /// describing an option.
     std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
         std::ostringstream stream;
-        stream << "{ \"interface\": [ \"all\" ],"
+        stream << "{ \"interfaces\": [ \"*\" ],"
             "\"rebind-timer\": 2000, "
             "\"renew-timer\": 1000, "
             "\"subnet4\": [ { "
@@ -245,7 +246,7 @@ public:
     void resetConfiguration() {
         ConstElementPtr status;
 
-        string config = "{ \"interface\": [ \"all\" ],"
+        string config = "{ \"interfaces\": [ \"*\" ],"
             "\"rebind-timer\": 2000, "
             "\"renew-timer\": 1000, "
             "\"valid-lifetime\": 4000, "
@@ -322,7 +323,7 @@ TEST_F(Dhcp4ParserTest, emptySubnet) {
     ConstElementPtr status;
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
-                    Element::fromJSON("{ \"interface\": [ \"all\" ],"
+                    Element::fromJSON("{ \"interfaces\": [ \"*\" ],"
                                       "\"rebind-timer\": 2000, "
                                       "\"renew-timer\": 1000, "
                                       "\"subnet4\": [  ], "
@@ -342,7 +343,7 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
 
     ConstElementPtr status;
 
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"subnet4\": [ { "
@@ -372,7 +373,7 @@ TEST_F(Dhcp4ParserTest, subnetLocal) {
 
     ConstElementPtr status;
 
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"subnet4\": [ { "
@@ -403,7 +404,7 @@ TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
 
     ConstElementPtr status;
 
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"subnet4\": [ { "
@@ -427,7 +428,7 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
 
     ConstElementPtr status;
 
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"subnet4\": [ { "
@@ -949,7 +950,7 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
 // configuration does not include options configuration.
 TEST_F(Dhcp4ParserTest, optionDataDefaults) {
     ConstElementPtr x;
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -1022,7 +1023,7 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
     // The definition is not required for the option that
     // belongs to the 'dhcp4' option space as it is the
     // standard option.
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -1100,7 +1101,7 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
     // at the very end (when all other parameters are configured).
 
     // Starting stage 1. Configure sub-options and their definitions.
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -1149,7 +1150,7 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
     // the configuration from the stage 2 is repeated because BIND
     // configuration manager sends whole configuration for the lists
     // where at least one element is being modified or added.
-    config = "{ \"interface\": [ \"all\" ],"
+    config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -1245,7 +1246,7 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
 // option setting.
 TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
     ConstElementPtr x;
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"option-data\": [ {"
@@ -1317,7 +1318,7 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
 // for multiple subnets.
 TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
     ConstElementPtr x;
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"subnet4\": [ { "
@@ -1597,7 +1598,7 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
     // In the first stahe we create definitions of suboptions
     // that we will add to the base option.
     // Let's create some dummy options: foo and foo2.
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -1650,7 +1651,7 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
     // We add our dummy options to this option space and thus
     // they should be included as sub-options in the 'vendor-opts'
     // option.
-    config = "{ \"interface\": [ \"all\" ],"
+    config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -1749,5 +1750,69 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
     EXPECT_FALSE(desc.option->getOption(3));
 }
 
+// This test verifies that it is possible to select subset of interfaces
+// on which server should listen.
+TEST_F(Dhcp4ParserTest, selectedInterfaces) {
+    ConstElementPtr x;
+    string config = "{ \"interfaces\": [ \"eth0\", \"eth1\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"valid-lifetime\": 4000 }";
 
-};
+    ElementPtr json = Element::fromJSON(config);
+
+    ConstElementPtr status;
+
+    // Make sure the config manager is clean and there is no hanging
+    // interface configuration.
+    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
+    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
+    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+
+    // Apply configuration.
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // eth0 and eth1 were explicitly selected. eth2 was not.
+    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
+    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
+    EXPECT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+}
+
+// This test verifies that it is possible to configure the server in such a way
+// that it listens on all interfaces.
+TEST_F(Dhcp4ParserTest, allInterfaces) {
+    ConstElementPtr x;
+    // This configuration specifies two interfaces on which server should listen
+    // but it also includes asterisk. The asterisk switches server into the
+    // mode when it listens on all interfaces regardless of what interface names
+    // were specified in the "interfaces" parameter.
+    string config = "{ \"interfaces\": [ \"eth0\", \"*\", \"eth1\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    ConstElementPtr status;
+
+    // Make sure there is no old configuration.
+    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
+    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
+    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+
+    // Apply configuration.
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // All interfaces should be now active.
+    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
+    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
+    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2"));
+}
+
+
+
+}
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index c39c56a..0251991 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -16,6 +16,7 @@
 #include <sstream>
 
 #include <asiolink/io_address.h>
+#include <config/ccsession.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/option.h>
@@ -26,11 +27,15 @@
 #include <dhcp/pkt_filter_inet.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
+#include <dhcp4/config_parser.h>
+#include <hooks/server_hooks.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/utils.h>
 #include <gtest/gtest.h>
+#include <hooks/server_hooks.h>
+#include <hooks/hooks_manager.h>
 
 #include <boost/scoped_ptr.hpp>
 
@@ -42,7 +47,9 @@
 using namespace std;
 using namespace isc;
 using namespace isc::dhcp;
+using namespace isc::data;
 using namespace isc::asiolink;
+using namespace isc::hooks;
 
 namespace {
 
@@ -79,6 +86,55 @@ public:
         : Dhcpv4Srv(port, "type=memfile", false, false) {
     }
 
+    /// @brief fakes packet reception
+    /// @param timeout ignored
+    ///
+    /// The method receives all packets queued in receive queue, one after
+    /// another. Once the queue is empty, it initiates the shutdown procedure.
+    ///
+    /// See fake_received_ field for description
+    virtual Pkt4Ptr receivePacket(int /*timeout*/) {
+
+        // If there is anything prepared as fake incoming traffic, use it
+        if (!fake_received_.empty()) {
+            Pkt4Ptr pkt = fake_received_.front();
+            fake_received_.pop_front();
+            return (pkt);
+        }
+
+        // If not, just trigger shutdown and return immediately
+        shutdown();
+        return (Pkt4Ptr());
+    }
+
+    /// @brief fake packet sending
+    ///
+    /// Pretend to send a packet, but instead just store it in fake_send_ list
+    /// where test can later inspect server's response.
+    virtual void sendPacket(const Pkt4Ptr& pkt) {
+        fake_sent_.push_back(pkt);
+    }
+
+    /// @brief adds a packet to fake receive queue
+    ///
+    /// See fake_received_ field for description
+    void fakeReceive(const Pkt4Ptr& pkt) {
+        fake_received_.push_back(pkt);
+    }
+
+    virtual ~NakedDhcpv4Srv() {
+    }
+
+    /// @brief packets we pretend to receive
+    ///
+    /// Instead of setting up sockets on interfaces that change between OSes, it
+    /// is much easier to fake packet reception. This is a list of packets that
+    /// we pretend to have received. You can schedule new packets to be received
+    /// using fakeReceive() and NakedDhcpv4Srv::receivePacket() methods.
+    list<Pkt4Ptr> fake_received_;
+
+    list<Pkt4Ptr> fake_sent_;
+
     using Dhcpv4Srv::adjustRemoteAddr;
     using Dhcpv4Srv::processDiscover;
     using Dhcpv4Srv::processRequest;
@@ -97,11 +153,11 @@ static const char* SRVID_FILE = "server-id-test.txt";
 
 /// @brief Dummy Packet Filtering class.
 ///
-/// This class reports capability to respond directly to the
-/// client which doesn't have address configured yet.
+/// This class reports capability to respond directly to the client which
+/// doesn't have address configured yet.
 ///
-/// All packet and socket handling functions do nothing because
-/// they are not used in unit tests.
+/// All packet and socket handling functions do nothing because they are not
+/// used in unit tests.
 class PktFilterTest : public PktFilter {
 public:
 
@@ -137,7 +193,9 @@ public:
     ///
     /// Initializes common objects used in many tests.
     /// Also sets up initial configuration in CfgMgr.
-    Dhcpv4SrvTest() {
+    Dhcpv4SrvTest() :
+        rcode_(-1)
+    {
         subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1000,
                                          2000, 3000));
         pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
@@ -153,6 +211,19 @@ public:
 
         // it's ok if that fails. There should not be such a file anyway
         unlink(SRVID_FILE);
+
+        const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+        // There must be some interface detected
+        if (ifaces.empty()) {
+            // We can't use ASSERT in constructor
+            ADD_FAILURE() << "No interfaces detected.";
+        }
+
+        valid_iface_ = ifaces.begin()->getName();
+    }
+
+    virtual ~Dhcpv4SrvTest() {
     }
 
     /// @brief Add 'Parameter Request List' option to the packet.
@@ -561,6 +632,13 @@ public:
 
     /// @brief A client-id used in most tests
     ClientIdPtr client_id_;
+
+    int rcode_;
+
+    ConstElementPtr comment_;
+
+    // Name of a valid network interface
+    string valid_iface_;
 };
 
 // Sanity check. Verifies that both Dhcpv4Srv and its derived
@@ -964,6 +1042,7 @@ TEST_F(Dhcpv4SrvTest, DiscoverNoClientId) {
     Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
     dis->setRemoteAddr(IOAddress("192.0.2.1"));
     dis->setYiaddr(hint);
+    dis->setHWAddr(generateHWAddr(6));
 
     // Pass it to the server and get an offer
     Pkt4Ptr offer = srv->processDiscover(dis);
@@ -1325,8 +1404,9 @@ TEST_F(Dhcpv4SrvTest, sanityCheck) {
     ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
 
     Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    pkt->setHWAddr(generateHWAddr(6));
 
-    // Client-id is optional for information-request, so
+    // Server-id is optional for information-request, so
     EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::OPTIONAL));
 
     // Empty packet, no server-id
@@ -1340,6 +1420,11 @@ TEST_F(Dhcpv4SrvTest, sanityCheck) {
     // Server-id is forbidden, but present => exception
     EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::FORBIDDEN),
                  RFCViolation);
+
+    // There's no client-id and no HWADDR. Server needs something to
+    // identify the client
+    pkt->setHWAddr(generateHWAddr(0));
+    EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::MANDATORY), RFCViolation);
 }
 
 // This test verifies that incoming (positive) RELEASE can be handled properly.
@@ -1535,4 +1620,731 @@ TEST_F(Dhcpv4SrvTest, ServerID) {
     EXPECT_EQ(srvid_text, text);
 }
 
+/// @todo Implement tests for subnetSelect See tests in dhcp6_srv_unittest.cc:
+/// selectSubnetAddr, selectSubnetIface, selectSubnetRelayLinkaddr,
+/// selectSubnetRelayInterfaceId. Note that the concept of interface-id is not
+/// present in the DHCPv4, so not everything is applicable directly.
+/// See ticket #3057
+
+// Checks if hooks are registered properly.
+TEST_F(Dhcpv4SrvTest, Hooks) {
+    NakedDhcpv4Srv srv(0);
+
+    // check if appropriate hooks are registered
+    int hook_index_pkt4_received = -1;
+    int hook_index_select_subnet = -1;
+    int hook_index_pkt4_send     = -1;
+
+    // check if appropriate indexes are set
+    EXPECT_NO_THROW(hook_index_pkt4_received = ServerHooks::getServerHooks()
+                    .getIndex("pkt4_receive"));
+    EXPECT_NO_THROW(hook_index_select_subnet = ServerHooks::getServerHooks()
+                    .getIndex("subnet4_select"));
+    EXPECT_NO_THROW(hook_index_pkt4_send     = ServerHooks::getServerHooks()
+                    .getIndex("pkt4_send"));
+
+    EXPECT_TRUE(hook_index_pkt4_received > 0);
+    EXPECT_TRUE(hook_index_select_subnet > 0);
+    EXPECT_TRUE(hook_index_pkt4_send > 0);
+}
+
+    // a dummy MAC address
+    const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5};
+
+    // A dummy MAC address, padded with 0s
+    const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0,
+                                     0, 0, 0, 0, 0, 0, 0, 0 };
+
+    // Let's use some creative test content here (128 chars + \0)
+    const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur "
+        "adipiscing elit. Proin mollis placerat metus, at "
+        "lacinia orci ornare vitae. Mauris amet.";
+
+    // Yet another type of test content (64 chars + \0)
+    const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
+        "adipiscing elit posuere.";
+
+/// @brief a class dedicated to Hooks testing in DHCPv4 server
+///
+/// This class has a number of static members, because each non-static
+/// method has implicit 'this' parameter, so it does not match callout
+/// signature and couldn't be registered. Furthermore, static methods
+/// can't modify non-static members (for obvious reasons), so many
+/// fields are declared static. It is still better to keep them as
+/// one class rather than unrelated collection of global objects.
+class HooksDhcpv4SrvTest : public Dhcpv4SrvTest {
+
+public:
+
+    /// @brief creates Dhcpv4Srv and prepares buffers for callouts
+    HooksDhcpv4SrvTest() {
+
+        // Allocate new DHCPv6 Server
+        srv_ = new NakedDhcpv4Srv(0);
+
+        // clear static buffers
+        resetCalloutBuffers();
+    }
+
+    /// @brief destructor (deletes Dhcpv4Srv)
+    virtual ~HooksDhcpv4SrvTest() {
+
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("pkt4_receive");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("pkt4_send");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("subnet4_select");
+
+        delete srv_;
+    }
+
+    /// @brief creates an option with specified option code
+    ///
+    /// This method is static, because it is used from callouts
+    /// that do not have a pointer to HooksDhcpv4SSrvTest object
+    ///
+    /// @param option_code code of option to be created
+    ///
+    /// @return pointer to create option object
+    static OptionPtr createOption(uint16_t option_code) {
+
+        char payload[] = {
+            0xa, 0xb, 0xc, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14
+        };
+
+        OptionBuffer tmp(payload, payload + sizeof(payload));
+        return OptionPtr(new Option(Option::V4, option_code, tmp));
+    }
+
+    /// @brief Generates test packet.
+    ///
+    /// Allocates and generates on-wire buffer that represents test packet, with all
+    /// fixed fields set to non-zero values.  Content is not always reasonable.
+    ///
+    /// See generateTestPacket1() function that returns exactly the same packet as
+    /// Pkt4 object.
+    ///
+    /// @return pointer to allocated Pkt4 object
+    // Returns a vector containing a DHCPv4 packet header.
+    Pkt4Ptr
+    generateSimpleDiscover() {
+
+        // That is only part of the header. It contains all "short" fields,
+        // larger fields are constructed separately.
+        uint8_t hdr[] = {
+            1, 6, 6, 13,            // op, htype, hlen, hops,
+            0x12, 0x34, 0x56, 0x78, // transaction-id
+            0, 42, 0x80, 0x00,      // 42 secs, BROADCAST flags
+            192, 0, 2, 1,           // ciaddr
+            1, 2, 3, 4,             // yiaddr
+            192, 0, 2, 255,         // siaddr
+            255, 255, 255, 255,     // giaddr
+        };
+
+        // Initialize the vector with the header fields defined above.
+        vector<uint8_t> buf(hdr, hdr + sizeof(hdr));
+
+        // Append the large header fields.
+        copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN, back_inserter(buf));
+        copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN, back_inserter(buf));
+        copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN, back_inserter(buf));
+
+        // Should now have all the header, so check.  The "static_cast" is used
+        // to get round an odd bug whereby the linker appears not to find the
+        // definition of DHCPV4_PKT_HDR_LEN if it appears within an EXPECT_EQ().
+        EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), buf.size());
+
+        // Add magic cookie
+        buf.push_back(0x63);
+        buf.push_back(0x82);
+        buf.push_back(0x53);
+        buf.push_back(0x63);
+
+        // Add message type DISCOVER
+        buf.push_back(static_cast<uint8_t>(DHO_DHCP_MESSAGE_TYPE));
+        buf.push_back(1); // length (just one byte)
+        buf.push_back(static_cast<uint8_t>(DHCPDISCOVER));
+
+        return (Pkt4Ptr(new Pkt4(&buf[0], buf.size())));
+    }
+
+    /// test callback that stores received callout name and pkt4 value
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt4_receive_callout(CalloutHandle& callout_handle) {
+        callback_name_ = string("pkt4_receive");
+
+        callout_handle.getArgument("query4", callback_pkt4_);
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    /// test callback that changes client-id value
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt4_receive_change_clientid(CalloutHandle& callout_handle) {
+
+        Pkt4Ptr pkt;
+        callout_handle.getArgument("query4", pkt);
+
+        // get rid of the old client-id
+        pkt->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
+
+        // add a new option
+        pkt->addOption(createOption(DHO_DHCP_CLIENT_IDENTIFIER));
+
+        // carry on as usual
+        return pkt4_receive_callout(callout_handle);
+    }
+
+    /// test callback that deletes client-id
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt4_receive_delete_clientid(CalloutHandle& callout_handle) {
+
+        Pkt4Ptr pkt;
+        callout_handle.getArgument("query4", pkt);
+
+        // get rid of the old client-id (and no HWADDR)
+        vector<uint8_t> mac;
+        pkt->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
+        pkt->setHWAddr(1, 0, mac); // HWtype 1, hwardware len = 0
+
+        // carry on as usual
+        return pkt4_receive_callout(callout_handle);
+    }
+
+    /// test callback that sets skip flag
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt4_receive_skip(CalloutHandle& callout_handle) {
+
+        Pkt4Ptr pkt;
+        callout_handle.getArgument("query4", pkt);
+
+        callout_handle.setSkip(true);
+
+        // carry on as usual
+        return pkt4_receive_callout(callout_handle);
+    }
+
+    /// Test callback that stores received callout name and pkt4 value
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt4_send_callout(CalloutHandle& callout_handle) {
+        callback_name_ = string("pkt4_send");
+
+        callout_handle.getArgument("response4", callback_pkt4_);
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    // Test callback that changes server-id
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt4_send_change_serverid(CalloutHandle& callout_handle) {
+
+        Pkt4Ptr pkt;
+        callout_handle.getArgument("response4", pkt);
+
+        // get rid of the old server-id
+        pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER);
+
+        // add a new option
+        pkt->addOption(createOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+        // carry on as usual
+        return pkt4_send_callout(callout_handle);
+    }
+
+    /// test callback that deletes server-id
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt4_send_delete_serverid(CalloutHandle& callout_handle) {
+
+        Pkt4Ptr pkt;
+        callout_handle.getArgument("response4", pkt);
+
+        // get rid of the old client-id
+        pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER);
+
+        // carry on as usual
+        return pkt4_send_callout(callout_handle);
+    }
+
+    /// Test callback that sets skip flag
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt4_send_skip(CalloutHandle& callout_handle) {
+
+        Pkt4Ptr pkt;
+        callout_handle.getArgument("response4", pkt);
+
+        callout_handle.setSkip(true);
+
+        // carry on as usual
+        return pkt4_send_callout(callout_handle);
+    }
+
+    /// Test callback that stores received callout name and subnet4 values
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    subnet4_select_callout(CalloutHandle& callout_handle) {
+        callback_name_ = string("subnet4_select");
+
+        callout_handle.getArgument("query4", callback_pkt4_);
+        callout_handle.getArgument("subnet4", callback_subnet4_);
+        callout_handle.getArgument("subnet4collection", callback_subnet4collection_);
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    /// Test callback that picks the other subnet if possible.
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    subnet4_select_different_subnet_callout(CalloutHandle& callout_handle) {
+
+        // Call the basic calllout to record all passed values
+        subnet4_select_callout(callout_handle);
+
+        const Subnet4Collection* subnets;
+        Subnet4Ptr subnet;
+        callout_handle.getArgument("subnet4", subnet);
+        callout_handle.getArgument("subnet4collection", subnets);
+
+        // Let's change to a different subnet
+        if (subnets->size() > 1) {
+            subnet = (*subnets)[1]; // Let's pick the other subnet
+            callout_handle.setArgument("subnet4", subnet);
+        }
+
+        return (0);
+    }
+
+    /// resets buffers used to store data received by callouts
+    void resetCalloutBuffers() {
+        callback_name_ = string("");
+        callback_pkt4_.reset();
+        callback_subnet4_.reset();
+        callback_subnet4collection_ = NULL;
+        callback_argument_names_.clear();
+    }
+
+    /// pointer to Dhcpv4Srv that is used in tests
+    NakedDhcpv4Srv* srv_;
+
+    // The following fields are used in testing pkt4_receive_callout
+
+    /// String name of the received callout
+    static string callback_name_;
+
+    /// Pkt4 structure returned in the callout
+    static Pkt4Ptr callback_pkt4_;
+
+    /// Pointer to a subnet received by callout
+    static Subnet4Ptr callback_subnet4_;
+
+    /// A list of all available subnets (received by callout)
+    static const Subnet4Collection* callback_subnet4collection_;
+
+    /// A list of all received arguments
+    static vector<string> callback_argument_names_;
+};
+
+// The following fields are used in testing pkt4_receive_callout.
+// See fields description in the class for details
+string HooksDhcpv4SrvTest::callback_name_;
+Pkt4Ptr HooksDhcpv4SrvTest::callback_pkt4_;
+Subnet4Ptr HooksDhcpv4SrvTest::callback_subnet4_;
+const Subnet4Collection* HooksDhcpv4SrvTest::callback_subnet4collection_;
+vector<string> HooksDhcpv4SrvTest::callback_argument_names_;
+
+
+// Checks if callouts installed on pkt4_received are indeed called and the
+// all necessary parameters are passed.
+//
+// Note that the test name does not follow test naming convention,
+// but the proper hook name is "pkt4_receive".
+TEST_F(HooksDhcpv4SrvTest, simple_pkt4_receive) {
+
+    // Install pkt4_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt4_receive", pkt4_receive_callout));
+
+    // Let's create a simple DISCOVER
+    Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive4(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt4_receive callback.
+    srv_->run();
+
+    // check that the callback called is indeed the one we installed
+    EXPECT_EQ("pkt4_receive", callback_name_);
+
+    // check that pkt4 argument passing was successful and returned proper value
+    EXPECT_TRUE(callback_pkt4_.get() == sol.get());
+
+    // Check that all expected parameters are there
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back(string("query4"));
+
+    EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+}
+
+// Checks if callouts installed on pkt4_received is able to change
+// the values and the parameters are indeed used by the server.
+TEST_F(HooksDhcpv4SrvTest, valueChange_pkt4_receive) {
+
+    // Install pkt4_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt4_receive", pkt4_receive_change_clientid));
+
+    // Let's create a simple DISCOVER
+    Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive4(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt4_receive callback.
+    srv_->run();
+
+    // check that the server did send a reposonce
+    ASSERT_EQ(1, srv_->fake_sent_.size());
+
+    // Make sure that we received a response
+    Pkt4Ptr adv = srv_->fake_sent_.front();
+    ASSERT_TRUE(adv);
+
+    // Get client-id...
+    OptionPtr clientid = adv->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+
+    // ... and check if it is the modified value
+    OptionPtr expected = createOption(DHO_DHCP_CLIENT_IDENTIFIER);
+    EXPECT_TRUE(clientid->equal(expected));
+}
+
+// Checks if callouts installed on pkt4_received is able to delete
+// existing options and that change impacts server processing (mandatory
+// client-id option is deleted, so the packet is expected to be dropped)
+TEST_F(HooksDhcpv4SrvTest, deleteClientId_pkt4_receive) {
+
+    // Install pkt4_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt4_receive", pkt4_receive_delete_clientid));
+
+    // Let's create a simple DISCOVER
+    Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive4(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt4_receive callback.
+    srv_->run();
+
+    // Check that the server dropped the packet and did not send a response
+    ASSERT_EQ(0, srv_->fake_sent_.size());
+}
+
+// Checks if callouts installed on pkt4_received is able to set skip flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv4SrvTest, skip_pkt4_receive) {
+
+    // Install pkt4_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt4_receive", pkt4_receive_skip));
+
+    // Let's create a simple DISCOVER
+    Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive4(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt4_receive callback.
+    srv_->run();
+
+    // check that the server dropped the packet and did not produce any response
+    ASSERT_EQ(0, srv_->fake_sent_.size());
+}
+
+
+// Checks if callouts installed on pkt4_send are indeed called and the
+// all necessary parameters are passed.
+TEST_F(HooksDhcpv4SrvTest, simple_pkt4_send) {
+
+    // Install pkt4_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt4_send", pkt4_send_callout));
+
+    // Let's create a simple DISCOVER
+    Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive4(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt4_receive callback.
+    srv_->run();
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("pkt4_send", callback_name_);
+
+    // Check that there is one packet sent
+    ASSERT_EQ(1, srv_->fake_sent_.size());
+    Pkt4Ptr adv = srv_->fake_sent_.front();
+
+    // Check that pkt4 argument passing was successful and returned proper value
+    EXPECT_TRUE(callback_pkt4_.get() == adv.get());
+
+    // Check that all expected parameters are there
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back(string("response4"));
+    EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+}
+
+// Checks if callouts installed on pkt4_send is able to change
+// the values and the packet sent contains those changes
+TEST_F(HooksDhcpv4SrvTest, valueChange_pkt4_send) {
+
+    // Install pkt4_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt4_send", pkt4_send_change_serverid));
+
+    // Let's create a simple DISCOVER
+    Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive4(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt4_receive callback.
+    srv_->run();
+
+    // check that the server did send a reposonce
+    ASSERT_EQ(1, srv_->fake_sent_.size());
+
+    // Make sure that we received a response
+    Pkt4Ptr adv = srv_->fake_sent_.front();
+    ASSERT_TRUE(adv);
+
+    // Get client-id...
+    OptionPtr clientid = adv->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+
+    // ... and check if it is the modified value
+    OptionPtr expected = createOption(DHO_DHCP_SERVER_IDENTIFIER);
+    EXPECT_TRUE(clientid->equal(expected));
+}
+
+// Checks if callouts installed on pkt4_send is able to delete
+// existing options and that server applies those changes. In particular,
+// we are trying to send a packet without server-id. The packet should
+// be sent
+TEST_F(HooksDhcpv4SrvTest, deleteServerId_pkt4_send) {
+
+    // Install pkt4_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt4_send", pkt4_send_delete_serverid));
+
+    // Let's create a simple DISCOVER
+    Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive4(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt4_receive callback.
+    srv_->run();
+
+    // Check that the server indeed sent a malformed ADVERTISE
+    ASSERT_EQ(1, srv_->fake_sent_.size());
+
+    // Get that ADVERTISE
+    Pkt4Ptr adv = srv_->fake_sent_.front();
+    ASSERT_TRUE(adv);
+
+    // Make sure that it does not have server-id
+    EXPECT_FALSE(adv->getOption(DHO_DHCP_SERVER_IDENTIFIER));
+}
+
+// Checks if callouts installed on pkt4_skip is able to set skip flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv4SrvTest, skip_pkt4_send) {
+
+    // Install pkt4_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt4_send", pkt4_send_skip));
+
+    // Let's create a simple REQUEST
+    Pkt4Ptr sol = Pkt4Ptr(generateSimpleDiscover());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive4(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt4_receive callback.
+    srv_->run();
+
+    // check that the server dropped the packet and did not produce any response
+    ASSERT_EQ(0, srv_->fake_sent_.size());
+}
+
+// This test checks if subnet4_select callout is triggered and reports
+// valid parameters
+TEST_F(HooksDhcpv4SrvTest, subnet4_select) {
+
+    // Install pkt4_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "subnet4_select", subnet4_select_callout));
+
+    // Configure 2 subnets, both directly reachable over local interface
+    // (let's not complicate the matter with relays)
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.0/25\" ],"
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"interface\": \"" + valid_iface_ + "\" "
+        " }, {"
+        "    \"pool\": [ \"192.0.3.0/25\" ],"
+        "    \"subnet\": \"192.0.3.0/24\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    comment_ = config::parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    // Prepare discover packet. Server should select first subnet for it
+    Pkt4Ptr sol = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    sol->setRemoteAddr(IOAddress("192.0.2.1"));
+    sol->setIface(valid_iface_);
+    OptionPtr clientid = generateClientId();
+    sol->addOption(clientid);
+
+    // Pass it to the server and get an advertise
+    Pkt4Ptr adv = srv_->processDiscover(sol);
+
+    // check if we get response at all
+    ASSERT_TRUE(adv);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("subnet4_select", callback_name_);
+
+    // Check that pkt4 argument passing was successful and returned proper value
+    EXPECT_TRUE(callback_pkt4_.get() == sol.get());
+
+    const Subnet4Collection* exp_subnets = CfgMgr::instance().getSubnets4();
+
+    // The server is supposed to pick the first subnet, because of matching
+    // interface. Check that the value is reported properly.
+    ASSERT_TRUE(callback_subnet4_);
+    EXPECT_EQ(exp_subnets->front().get(), callback_subnet4_.get());
+
+    // Server is supposed to report two subnets
+    ASSERT_EQ(exp_subnets->size(), callback_subnet4collection_->size());
+
+    // Compare that the available subnets are reported as expected
+    EXPECT_TRUE((*exp_subnets)[0].get() == (*callback_subnet4collection_)[0].get());
+    EXPECT_TRUE((*exp_subnets)[1].get() == (*callback_subnet4collection_)[1].get());
+}
+
+// This test checks if callout installed on subnet4_select hook point can pick
+// a different subnet.
+TEST_F(HooksDhcpv4SrvTest, subnet_select_change) {
+
+    // Install pkt4_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "subnet4_select", subnet4_select_different_subnet_callout));
+
+    // Configure 2 subnets, both directly reachable over local interface
+    // (let's not complicate the matter with relays)
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.0/25\" ],"
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"interface\": \"" + valid_iface_ + "\" "
+        " }, {"
+        "    \"pool\": [ \"192.0.3.0/25\" ],"
+        "    \"subnet\": \"192.0.3.0/24\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    comment_ = config::parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    // Prepare discover packet. Server should select first subnet for it
+    Pkt4Ptr sol = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    sol->setRemoteAddr(IOAddress("192.0.2.1"));
+    sol->setIface(valid_iface_);
+    OptionPtr clientid = generateClientId();
+    sol->addOption(clientid);
+
+    // Pass it to the server and get an advertise
+    Pkt4Ptr adv = srv_->processDiscover(sol);
+
+    // check if we get response at all
+    ASSERT_TRUE(adv);
+
+    // The response should have an address from second pool, so let's check it
+    IOAddress addr = adv->getYiaddr();
+    EXPECT_NE("0.0.0.0", addr.toText());
+
+    // Get all subnets and use second subnet for verification
+    const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
+    ASSERT_EQ(2, subnets->size());
+
+    // Advertised address must belong to the second pool (in subnet's range,
+    // in dynamic pool)
+    EXPECT_TRUE((*subnets)[1]->inRange(addr));
+    EXPECT_TRUE((*subnets)[1]->inPool(addr));
+}
+
+
+
 } // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/marker_file.h b/src/bin/dhcp4/tests/marker_file.h
new file mode 100644
index 0000000..03db9d6
--- /dev/null
+++ b/src/bin/dhcp4/tests/marker_file.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef MARKER_FILE_H
+#define MARKER_FILE_H
+
+/// @file
+/// Define a marker file that is used in tests to prove that an "unload"
+/// function has been called
+
+namespace {
+const char* const LOAD_MARKER_FILE = "/home/marcin/devel/bind10/src/bin/dhcp4/tests/load_marker.txt";
+const char* const UNLOAD_MARKER_FILE = "/home/marcin/devel/bind10/src/bin/dhcp4/tests/unload_marker.txt";
+}
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Check marker file
+///
+/// This function is used in some of the DHCP server tests.
+///
+/// Marker files are used by the load/unload functions in the hooks
+/// libraries in these tests to signal whether they have been loaded or
+/// unloaded.  The file (if present) contains a single line holding
+/// a set of characters.
+///
+/// This convenience function checks the file to see if the characters
+/// are those expected.
+///
+/// @param name Name of the marker file.
+/// @param expected Characters expected.  If a marker file is present,
+///        it is expected to contain characters.
+///
+/// @return true if all tests pass, false if not (in which case a failure
+///         will have been logged).
+bool
+checkMarkerFile(const char* name, const char* expected);
+
+/// @brief Check marker file exists
+///
+/// This function is used in some of the DHCP server tests.
+///
+/// Checkes that the specified file does NOT exist.
+///
+/// @param name Name of the marker file.
+///
+/// @return true if file exists, false if not.
+bool
+checkMarkerFileExists(const char* name);
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // MARKER_FILE_H
+
diff --git a/src/bin/dhcp4/tests/test_libraries.h b/src/bin/dhcp4/tests/test_libraries.h
new file mode 100644
index 0000000..7fe89f2
--- /dev/null
+++ b/src/bin/dhcp4/tests/test_libraries.h
@@ -0,0 +1,51 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TEST_LIBRARIES_H
+#define TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+
+// Take care of differences in DLL naming between operating systems.
+
+#ifdef OS_OSX
+#define DLL_SUFFIX ".dylib"
+
+#else
+#define DLL_SUFFIX ".so"
+
+#endif
+
+
+// Names of the libraries used in these tests.  These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// shared library.
+
+// Library with load/unload functions creating marker files to check their
+// operation.
+const char* const CALLOUT_LIBRARY_1 = "/home/marcin/devel/bind10/src/bin/dhcp4/tests/.libs/libco1"
+                                           DLL_SUFFIX;
+const char* const CALLOUT_LIBRARY_2 = "/home/marcin/devel/bind10/src/bin/dhcp4/tests/.libs/libco2"
+                                           DLL_SUFFIX;
+
+// Name of a library which is not present.
+const char* const NOT_PRESENT_LIBRARY = "/home/marcin/devel/bind10/src/bin/dhcp4/tests/.libs/libnothere"
+                                         DLL_SUFFIX;
+} // anonymous namespace
+
+
+#endif // TEST_LIBRARIES_H
diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am
index 6f2c694..9b7972b 100644
--- a/src/bin/dhcp6/Makefile.am
+++ b/src/bin/dhcp6/Makefile.am
@@ -54,11 +54,6 @@ b10_dhcp6_SOURCES += config_parser.cc config_parser.h
 b10_dhcp6_SOURCES += dhcp6_log.cc dhcp6_log.h
 b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
 
-# Temporarily compile this file here. It will be removed once libdhcp-ddns
-# is implemented which will include this file and other files required
-# by DHCPv6.
-b10_dhcp6_SOURCES += ../d2/ncr_msg.cc ../d2/ncr_msg.h
-
 nodist_b10_dhcp6_SOURCES = dhcp6_messages.h dhcp6_messages.cc
 EXTRA_DIST += dhcp6_messages.mes
 
@@ -66,10 +61,12 @@ b10_dhcp6_LDADD  = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
 
 b10_dhcp6dir = $(pkgdatadir)
 b10_dhcp6_DATA = dhcp6.spec
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 74012ac..cc6bd02 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -67,10 +67,10 @@ public:
     /// @param dummy first param, option names are always "Dhcp6/option-data[n]"
     /// @param options is the option storage in which to store the parsed option
     /// upon "commit".
-    /// @param global_context is a pointer to the global context which 
+    /// @param global_context is a pointer to the global context which
     /// stores global scope parameters, options, option defintions.
-    Dhcp6OptionDataParser(const std::string&, OptionStoragePtr options, 
-                         ParserContextPtr global_context) 
+    Dhcp6OptionDataParser(const std::string&, OptionStoragePtr options,
+                         ParserContextPtr global_context)
         :OptionDataParser("", options, global_context) {
     }
 
@@ -78,7 +78,7 @@ public:
     ///
     /// @param param_name name of the parameter to be parsed.
     /// @param options storage where the parameter value is to be stored.
-    /// @param global_context is a pointer to the global context which 
+    /// @param global_context is a pointer to the global context which
     /// stores global scope parameters, options, option defintions.
     /// @return returns a pointer to a new OptionDataParser. Caller is
     /// is responsible for deleting it when it is no longer needed.
@@ -90,16 +90,16 @@ public:
 
 protected:
     /// @brief Finds an option definition within the server's option space
-    /// 
-    /// Given an option space and an option code, find the correpsonding 
+    ///
+    /// Given an option space and an option code, find the correpsonding
     /// option defintion within the server's option defintion storage.
     ///
-    /// @param option_space name of the parameter option space 
-    /// @param option_code numeric value of the parameter to find 
-    /// @return OptionDefintionPtr of the option defintion or an 
+    /// @param option_space name of the parameter option space
+    /// @param option_code numeric value of the parameter to find
+    /// @return OptionDefintionPtr of the option defintion or an
     /// empty OptionDefinitionPtr if not found.
-    /// @throw DhcpConfigError if the option space requested is not valid 
-    /// for this server. 
+    /// @throw DhcpConfigError if the option space requested is not valid
+    /// for this server.
     virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
                             std::string& option_space, uint32_t option_code) {
         OptionDefinitionPtr def;
@@ -115,11 +115,11 @@ protected:
     }
 };
 
-/// @brief Parser for IPv4 pool definitions.  
+/// @brief Parser for IPv4 pool definitions.
 ///
-/// This is the IPv6 derivation of the PoolParser class and handles pool 
-/// definitions, i.e. a list of entries of one of two syntaxes: min-max and 
-/// prefix/len for IPv6 pools. Pool6 objects are created and stored in chosen 
+/// This is the IPv6 derivation of the PoolParser class and handles pool
+/// definitions, i.e. a list of entries of one of two syntaxes: min-max and
+/// prefix/len for IPv6 pools. Pool6 objects are created and stored in chosen
 /// PoolStorage container.
 ///
 /// It is useful for parsing Dhcp6/subnet6[X]/pool parameters.
@@ -142,9 +142,9 @@ protected:
     /// @param addr is the IPv6 prefix of the pool.
     /// @param len is the prefix length.
     /// @param ptype is the type of IPv6 pool (Pool6::Pool6Type). Note this is
-    /// passed in as an int32_t and cast to Pool6Type to accommodate a 
+    /// passed in as an int32_t and cast to Pool6Type to accommodate a
     /// polymorphic interface.
-    /// @return returns a PoolPtr to the new Pool4 object. 
+    /// @return returns a PoolPtr to the new Pool4 object.
     PoolPtr poolMaker (IOAddress &addr, uint32_t len, int32_t ptype)
     {
         return (PoolPtr(new Pool6(static_cast<isc::dhcp::Pool6::Pool6Type>
@@ -156,9 +156,9 @@ protected:
     /// @param min is the first IPv6 address in the pool.
     /// @param max is the last IPv6 address in the pool.
     /// @param ptype is the type of IPv6 pool (Pool6::Pool6Type). Note this is
-    /// passed in as an int32_t and cast to Pool6Type to accommodate a 
+    /// passed in as an int32_t and cast to Pool6Type to accommodate a
     /// polymorphic interface.
-    /// @return returns a PoolPtr to the new Pool4 object. 
+    /// @return returns a PoolPtr to the new Pool4 object.
     PoolPtr poolMaker (IOAddress &min, IOAddress &max, int32_t ptype)
     {
         return (PoolPtr(new Pool6(static_cast<isc::dhcp::Pool6::Pool6Type>
@@ -168,8 +168,8 @@ protected:
 
 /// @brief This class parses a single IPv6 subnet.
 ///
-/// This is the IPv6 derivation of the SubnetConfigParser class and it parses 
-/// the whole subnet definition. It creates parsersfor received configuration 
+/// This is the IPv6 derivation of the SubnetConfigParser class and it parses
+/// the whole subnet definition. It creates parsersfor received configuration
 /// parameters as needed.
 class Subnet6ConfigParser : public SubnetConfigParser {
 public:
@@ -178,7 +178,7 @@ public:
     ///
     /// @param ignored first parameter
     /// stores global scope parameters, options, option defintions.
-    Subnet6ConfigParser(const std::string&) 
+    Subnet6ConfigParser(const std::string&)
         :SubnetConfigParser("", globalContext()) {
     }
 
@@ -220,7 +220,7 @@ protected:
         } else if (config_id.compare("pool") == 0) {
             parser = new Pool6Parser(config_id, pools_);
         } else if (config_id.compare("option-data") == 0) {
-           parser = new OptionDataListParser(config_id, options_, 
+           parser = new OptionDataListParser(config_id, options_,
                                              global_context_,
                                              Dhcp6OptionDataParser::factory);
         } else {
@@ -233,14 +233,14 @@ protected:
 
 
     /// @brief Determines if the given option space name and code describe
-    /// a standard option for the DHCP6 server. 
+    /// a standard option for the DHCP6 server.
     ///
     /// @param option_space is the name of the option space to consider
     /// @param code is the numeric option code to consider
     /// @return returns true if the space and code are part of the server's
     /// standard options.
     bool isServerStdOption(std::string option_space, uint32_t code) {
-        return ((option_space.compare("dhcp6") == 0) 
+        return ((option_space.compare("dhcp6") == 0)
                 && LibDHCP::isStandardOption(Option::V6, code));
     }
 
@@ -253,23 +253,23 @@ protected:
     }
 
     /// @brief Issues a DHCP6 server specific warning regarding duplicate subnet
-    /// options. 
-    /// 
+    /// options.
+    ///
     /// @param code is the numeric option code of the duplicate option
     /// @param addr is the subnet address
     /// @todo A means to know the correct logger and perhaps a common
     /// message would allow this message to be emitted by the base class.
-    virtual void duplicate_option_warning(uint32_t code, 
+    virtual void duplicate_option_warning(uint32_t code,
                                          isc::asiolink::IOAddress& addr) {
         LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
             .arg(code).arg(addr.toText());
     }
 
     /// @brief Instantiates the IPv6 Subnet based on a given IPv6 address
-    /// and prefix length.  
-    /// 
+    /// and prefix length.
+    ///
     /// @param addr is IPv6 prefix of the subnet.
-    /// @param len is the prefix length 
+    /// @param len is the prefix length
     void initSubnet(isc::asiolink::IOAddress addr, uint8_t len) {
         // Get all 'time' parameters using inheritance.
         // If the subnet-specific value is defined then use it, else
@@ -292,13 +292,13 @@ protected:
 
         // Specifying both interface for locally reachable subnets and
         // interface id for relays is mutually exclusive. Need to test for
-        // this condition. 
+        // this condition.
         if (!ifaceid.empty()) {
             std::string iface;
             try {
                 iface = string_values_->getParam("interface");
             } catch (const DhcpConfigError &) {
-                // iface not mandatory 
+                // iface not mandatory
             }
 
             if (!iface.empty()) {
@@ -403,7 +403,7 @@ namespace dhcp {
 ///
 /// @param config_id pointer to received global configuration entry
 /// @return parser for specified global DHCPv6 parameter
-/// @throw NotImplemented if trying to create a parser for unknown config 
+/// @throw NotImplemented if trying to create a parser for unknown config
 /// element
 DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id) {
     DhcpConfigParser* parser = NULL;
@@ -411,22 +411,22 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id) {
         (config_id.compare("valid-lifetime") == 0)  ||
         (config_id.compare("renew-timer") == 0)  ||
         (config_id.compare("rebind-timer") == 0))  {
-        parser = new Uint32Parser(config_id, 
+        parser = new Uint32Parser(config_id,
                                  globalContext()->uint32_values_);
-    } else if (config_id.compare("interface") == 0) {
+    } else if (config_id.compare("interfaces") == 0) {
         parser = new InterfaceListConfigParser(config_id);
     } else if (config_id.compare("subnet6") == 0) {
         parser = new Subnets6ListConfigParser(config_id);
     } else if (config_id.compare("option-data") == 0) {
-        parser = new OptionDataListParser(config_id, 
-                                          globalContext()->options_, 
+        parser = new OptionDataListParser(config_id,
+                                          globalContext()->options_,
                                           globalContext(),
                                           Dhcp6OptionDataParser::factory);
     } else if (config_id.compare("option-def") == 0) {
-        parser  = new OptionDefListParser(config_id, 
+        parser  = new OptionDefListParser(config_id,
                                           globalContext()->option_defs_);
     } else if (config_id.compare("version") == 0) {
-        parser  = new StringParser(config_id, 
+        parser  = new StringParser(config_id,
                                    globalContext()->string_values_);
     } else if (config_id.compare("lease-database") == 0) {
         parser = new DbAccessParser(config_id);
@@ -450,7 +450,7 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
     /// @todo: Append most essential info here (like "2 new subnets configured")
     string config_details;
 
-    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, 
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND,
               DHCP6_CONFIG_START).arg(config_set->str());
 
     // Some of the values specified in the configuration depend on
@@ -463,6 +463,7 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
     ParserCollection independent_parsers;
     ParserPtr subnet_parser;
     ParserPtr option_parser;
+    ParserPtr iface_parser;
 
     // The subnet parsers implement data inheritance by directly
     // accessing global storage. For this reason the global data
@@ -495,6 +496,11 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
                 subnet_parser = parser;
             } else if (config_pair.first == "option-data") {
                 option_parser = parser;
+            } else if (config_pair.first == "interfaces") {
+                // The interface parser is independent from any other parser and
+                // can be run here before other parsers.
+                parser->build(config_pair.second);
+                iface_parser = parser;
             } else {
                 // Those parsers should be started before other
                 // parsers so we can call build straight away.
@@ -548,6 +554,10 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
             if (subnet_parser) {
                 subnet_parser->commit();
             }
+
+            if (iface_parser) {
+                iface_parser->commit();
+            }
         }
         catch (const isc::Exception& ex) {
             LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_FAIL).arg(ex.what());
diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/config_parser.h
index 2dce79e..bd571af 100644
--- a/src/bin/dhcp6/config_parser.h
+++ b/src/bin/dhcp6/config_parser.h
@@ -31,8 +31,8 @@ class Dhcpv6Srv;
 
 /// @brief Configures DHCPv6 server
 ///
-/// This function is called every time a new configuration is received. The 
-/// extra parameter is a reference to DHCPv6 server component. It is currently 
+/// This function is called every time a new configuration is received. The
+/// extra parameter is a reference to DHCPv6 server component. It is currently
 /// not used and CfgMgr::instance() is accessed instead.
 ///
 /// This method does not throw. It catches all exceptions and returns them as
@@ -53,7 +53,7 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set);
 ///
 /// @returns a reference to the global context
 ParserContextPtr& globalContext();
- 
+
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
 
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index c3488e5..dffac61 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -20,6 +20,7 @@
 #include <config/ccsession.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <dhcp6/config_parser.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/dhcp6_log.h>
@@ -100,7 +101,27 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
     }
 
     // Configure the server.
-    return (configureDhcp6Server(*server_, merged_config));
+    ConstElementPtr answer = configureDhcp6Server(*server_, merged_config);
+
+    // Check that configuration was successful. If not, do not reopen sockets.
+    int rcode = 0;
+    parseAnswer(rcode, answer);
+    if (rcode != 0) {
+        return (answer);
+    }
+
+    // Configuration may change active interfaces. Therefore, we have to reopen
+    // sockets according to new configuration. This operation is not exception
+    // safe and we really don't want to emit exceptions to the callback caller.
+    // Instead, catch an exception and create appropriate answer.
+    try {
+        server_->openActiveSockets(server_->getPort());
+    } catch (const std::exception& ex) {
+        std::ostringstream err;
+        err << "failed to open sockets after server reconfiguration: " << ex.what();
+        answer = isc::config::createAnswer(1, err.str());
+    }
+    return (answer);
 }
 
 ConstElementPtr
@@ -172,8 +193,13 @@ void ControlledDhcpv6Srv::establishSession() {
     try {
         // Pull the full configuration out from the session.
         configureDhcp6Server(*this, config_session_->getFullConfig());
+        // Configuration may disable or enable interfaces so we have to
+        // reopen sockets according to new configuration.
+        openActiveSockets(getPort());
+
     } catch (const DhcpConfigError& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
+
     }
 
     /// Integrate the asynchronous I/O model of BIND 10 configuration
@@ -228,6 +254,5 @@ ControlledDhcpv6Srv::execDhcpv6ServerCommand(const std::string& command_id,
     }
 }
 
-
 };
 };
diff --git a/src/bin/dhcp6/dhcp6.dox b/src/bin/dhcp6/dhcp6.dox
index fb44366..84260e6 100644
--- a/src/bin/dhcp6/dhcp6.dox
+++ b/src/bin/dhcp6/dhcp6.dox
@@ -106,8 +106,8 @@ to the client. The implementation of this feature is planned for the future rele
 
 The bind10-d2 process is responsible for the actual communication with the DNS
 server, i.e. to send DNS Update messages. The bind10-dhcp6 module is responsible
-for generating so called @ref isc::d2::NameChangeRequest and sending it to the
-bind10-d2 module. The @ref isc::d2::NameChangeRequest object represents changes to the
+for generating so called @ref isc::dhcp_ddns::NameChangeRequest and sending it to the
+bind10-d2 module. The @ref isc::dhcp_ddns::NameChangeRequest object represents changes to the
 DNS bindings, related to acquisition, renewal or release of the lease. The bind10-dhcp6
 module implements the simple FIFO queue of the NameChangeRequest objects. The module
 logic, which processes the incoming DHCPv6 Client FQDN Options puts these requests
@@ -168,4 +168,8 @@ Once the configuration is implemented, these constants will be removed.
 
  @todo Add section about setting up options and their definitions with bindctl.
 
+ @section dhcpv6Other Other DHCPv6 topics
+
+ For hooks API support in DHCPv6, see @ref dhcpv6Hooks.
+
  */
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index bb5de0a..b61e811 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -3,16 +3,16 @@
     "module_name": "Dhcp6",
     "module_description": "DHCPv6 server daemon",
     "config_data": [
-      { "item_name": "interface",
+      { "item_name": "interfaces",
         "item_type": "list",
         "item_optional": false,
-        "item_default": [ "all" ],
+        "item_default": [ "*" ],
         "list_item_spec":
         {
           "item_name": "interface_name",
           "item_type": "string",
           "item_optional": false,
-          "item_default": "all"
+          "item_default": "*"
         }
       } ,
 
diff --git a/src/bin/dhcp6/dhcp6_hooks.dox b/src/bin/dhcp6/dhcp6_hooks.dox
new file mode 100644
index 0000000..b21ad74
--- /dev/null
+++ b/src/bin/dhcp6/dhcp6_hooks.dox
@@ -0,0 +1,239 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/**
+ @page dhcpv6Hooks The Hooks API for the DHCPv6 Server
+
+ @section dhcpv6HooksIntroduction Introduction
+ BIND10 features an API (the "Hooks" API) that allows user-written code to
+ be integrated into BIND 10 and called at specific points in its processing.
+ An overview of the API and a tutorial for writing such code can be found in
+ the @ref hooksdgDevelopersGuide.  Information for BIND 10 maintainers can be
+ found in the @ref hooksComponentDeveloperGuide.
+
+ This manual is more specialised and is aimed at developers of hook
+ code for the DHCPv6 server. It describes each hook point, what the callouts
+ attached to the hook are able to do, and the arguments passed to the
+ callouts.  Each entry in this manual has the following information:
+
+ - Name of the hook point.
+ - Arguments for the callout.  As well as the argument name and data type, the
+   information includes the direction, which can be one of:
+   - @b in - the server passes values to the callout but ignored any data
+     returned.
+   - @b out - the callout is expected to set this value.
+   - <b>in/out</b> - the server passes a value to the callout and uses whatever
+     value the callout sends back.  Note that the callout may choose not to
+     do any modification, in which case the server will use whatever value
+     it sent to the callout.
+ - Description of the hook. This explains where in the processing the hook
+   is located, the possible actions a callout attached to this hook could take,
+   and a description of the data passed to the callouts.
+ - Skip flag action: the action taken by the server if a callout chooses to set
+    the "skip" flag.
+
+ at section dhcpv6HooksHookPoints Hooks in the DHCPv6 Server
+
+The following list is ordered by appearance of specific hook points during
+packet processing. Hook points that are not specific to packet processing
+(e.g. lease expiration) will be added to the end of this list.
+
+ @subsection dhcpv6HooksBuffer6Receive buffer6_receive
+
+ - @b Arguments:
+   - name: @b query6, type: isc::dhcp::Pkt6Ptr, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when an incoming DHCPv6
+   packet is received and the data stored in a buffer. The sole argument -
+   query6 - contains a pointer to an isc::dhcp::Pkt6 object that contains
+   the received information stored in the data_ field. Basic information
+   like protocol, source/destination addresses and ports are set, but
+   the contents of the buffer have not yet been parsed.  That means that
+   the options_ field (that will eventually contain a list of objects
+   representing the received options) is empty, so none of the methods
+   that operate on it (e.g., getOption()) will work. The primary purpose
+   of this early call is to offer the ability to modify incoming packets
+   in their raw form. Unless you need to access to the raw data, it is
+   usually better to install your callout on the pkt6_receive hook point.
+
+ - <b>Skip flag action</b>: If any callout sets the skip flag, the
+   server will assume that the callout parsed the buffer and added then
+   necessary option objects to the options_ field; the server will not
+   do any parsing. If the callout sets the skip flag but does not parse
+   the buffer, the server will most probably drop the packet due to
+   the absence of mandatory options. If you want to drop the packet,
+   see the description of the skip flag in the pkt6_receive hook point.
+
+ @subsection dhcpv6HooksPkt6Receive pkt6_receive
+
+ - @b Arguments:
+   - name: @b query6, type: isc::dhcp::Pkt6Ptr, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when an incoming DHCPv6
+   packet is received and its content is parsed. The sole argument -
+   query6 - contains a pointer to an isc::dhcp::Pkt6 object that contains
+   all information regarding incoming packet, including its source and
+   destination addresses, the interface over which it was received, a list
+   of all options present within and relay information.  All fields of
+   the Pkt6 object can be modified at this time, except data_. (data_
+   contains the incoming packet as raw buffer. By the time this hook is
+   reached, that information has already been parsed and is available though
+   other fields in the Pkt6 object.  For this reason, it doesn't make
+   sense to modify it.)
+
+ - <b>Skip flag action</b>: If any callout sets the skip flag, the server will
+   drop the packet and start processing the next one.  The reason for the drop
+   will be logged if logging is set to the appropriate debug level.
+
+ at subsection dhcpv6HooksSubnet6Select subnet6_select
+
+ - @b Arguments:
+   - name: @b query6, type: isc::dhcp::Pkt6Ptr, direction: <b>in/out</b>
+   - name: @b subnet6, type: isc::dhcp::Subnet6Ptr, direction: <b>in/out</b>
+   - name: @b subnet6collection, type: const isc::dhcp::Subnet6Collection *, direction: <b>in</b>
+
+ - @b Description: This callout is executed when a subnet is being
+   selected for the incoming packet. All parameters, addresses and
+   prefixes will be assigned from that subnet. A callout can select a
+   different subnet if it wishes so, the list of all subnets currently
+   configured being provided as 'subnet6collection'. The list itself must
+   not be modified.
+
+ - <b>Skip flag action</b>: If any callout installed on 'subnet6_select'
+   sets the skip flag, the server will not select any subnet. Packet processing
+   will continue, but will be severely limited (i.e. only global options
+   will be assigned).
+
+ at subsection dhcpv6HooksLease6Select lease6_select
+
+ - @b Arguments:
+   - name: @b subnet6, type: isc::dhcp::Subnet6Ptr, direction: <b>in</b>
+   - name: @b fake_allocation, type: bool, direction: <b>in</b>
+   - name: @b lease6, type: isc::dhcp::Lease6Ptr, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed after the server engine
+   has selected a lease for client's request but before the lease
+   has been inserted into the database. Any modifications made to the
+   isc::dhcp::Lease6 object will be stored in the lease's record in the
+   database. The callout should make sure that any modifications are
+   sanity checked as the server will use that data as is with no further
+   checking.\n\n The server processes lease requests for SOLICIT and
+   REQUEST in a very similar way. The only major difference is that
+   for SOLICIT the lease is just selected; it is not inserted into
+   the database.  It is possible to distinguish between SOLICIT and
+   REQUEST by checking value of the fake_allocation flag: a value of true
+   means that the lease won't be inserted into the database (SOLICIT),
+   a value of false means that it will (REQUEST).
+
+ - <b>Skip flag action</b>: If any callout installed on 'lease6_select'
+   sets the skip flag, the server will not assign that particular lease.
+   Packet processing will continue and the client may get other addresses
+   or prefixes if it requested more than one address and/or prefix.
+
+ at subsection dhcpv6HooksLease6Renew lease6_renew
+
+ - @b Arguments:
+   - name: @b query6, type: isc::dhcp::PktPtr, direction: <b>in</b>
+   - name: @b lease6, type: isc::dhcp::Lease6Ptr, direction: <b>in/out</b>
+   - name: @b ia_na, type: boost::shared_ptr<Option6IA>, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when the server engine is
+   about to renew an existing lease. The client's request is provided as
+   the query6 argument and the existing lease with the appropriate fields
+   already modified is given in the lease6 argument. The final argument,
+   ia_na, is the IA_NA option that will be sent back to the client.
+   Callouts installed on the lease6_renew may modify the content of
+   the lease6 object. Care should be taken however, as that modified
+   information will be written to the database without any further
+   checking. \n\n Although the envisaged usage assumes modification of T1,
+   T2, preferred and valid lifetimes only, other parameters associated
+   with the lease may be modified as well. The only exception is the addr_
+   field, which must not be modified as it is used by the database to
+   select the existing lease to be updated. Care should also be taken to
+   modify the ia_na argument to match any changes in the lease6 argument.
+   If a client sends more than one IA_NA option, callouts will be called
+   separately for each IA_NA instance. The callout will be called only
+   when the update is valid, i.e. conditions such as an invalid addresses
+   or invalid iaid renewal attempts will not trigger this hook point.
+
+ - <b>Skip flag action</b>: If any callout installed on 'lease6_renew'
+   sets the skip flag, the server will not renew the lease. Under these
+   circumstances, the callout should modify the ia_na argument to reflect
+   this fact; otherwise the client will think the lease was renewed and continue
+   to operate under this assumption.
+
+ at subsection dhcpv6HooksLease6Release lease6_release
+
+ - @b Arguments:
+   - name: @b query6, type: isc::dhcp::PktPtr, direction: <b>in</b>
+   - name: @b lease6, type: isc::dhcp::Lease6Ptr, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when the server engine is
+   about to release an existing lease. The client's request is provided
+   as the query6 argument and the existing lease is given in the lease6
+   argument.  Although the lease6 structure may be modified, it doesn't
+   make sense to do so as it will be destroyed immediately the callouts
+   finish execution.
+
+ - <b>Skip flag action</b>: If any callout installed on 'lease6_release'
+   sets the skip flag, the server will not delete the lease, which will
+   remain in the database until it expires. However, the server will send out
+   the response back to the client as if it did.
+
+ at subsection dhcpv6HooksPkt6Send pkt6_send
+
+ - @b Arguments:
+   - name: @b response6, type: isc::dhcp::Pkt6Ptr, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when server's response
+   is about to be send back to the client. The sole argument - response6 -
+   contains a pointer to an isc::dhcp::Pkt6 object that contains the
+   packet, with set source and destination addresses, interface over which
+   it will be send, list of all options and relay information.  All fields
+   of the Pkt6 object can be modified at this time.  It should be noted that
+   unless the callout sets the skip flag (see below), anything placed in the
+   bufferOut_ field will be overwritten when the callout returns.
+   (bufferOut_ is scratch space used for constructing the packet.)
+
+ - <b>Skip flag action</b>: If any callout sets the skip flag, the server
+   will assume that the callout did pack the transaction-id, message type and
+   option objects into the bufferOut_ field and will skip packing part.
+   Note that if the callout sets skip flag, but did not prepare the
+   output buffer, the server will send a zero sized message that will be
+   ignored by the client. If you want to drop the packet, please see
+   skip flag in the buffer6_send hook point.
+
+ at subsection dhcpv6HooksBuffer6Send buffer6_send
+
+ - @b Arguments:
+   - name: @b response6, type: isc::dhcp::Pkt6Ptr, direction: <b>in/out</b>
+
+ - @b Description: This callout is executed when server's response is
+   assembled into binary form and is about to be send back to the
+   client. The sole argument - response6 - contains a pointer to an
+   isc::dhcp::Pkt6 object that contains the packet, with set source and
+   destination addresses, interface over which it will be sent, list of
+   all options and relay information. All options are already encoded
+   in bufferOut_ field. It doesn't make sense to modify anything but the
+   contents of bufferOut_ at this time (although if it is a requirement
+   to modify that data, it will probably be found easier to modify the
+   option objects in a callout attached to the pkt6_send hook).
+
+ - <b>Skip flag action</b>: If any callout sets the skip flag, the server
+   will drop this response packet. However, the original request packet
+   from a client has been processed, so server's state has most likely changed
+   (e.g. lease was allocated). Setting this flag merely stops the change
+   being communicated to the client.
+
+*/
diff --git a/src/bin/dhcp6/dhcp6_log.h b/src/bin/dhcp6/dhcp6_log.h
index 23202da..244151c 100644
--- a/src/bin/dhcp6/dhcp6_log.h
+++ b/src/bin/dhcp6/dhcp6_log.h
@@ -38,6 +38,9 @@ const int DBG_DHCP6_COMMAND = DBGLVL_COMMAND;
 // Trace basic operations within the code.
 const int DBG_DHCP6_BASIC = DBGLVL_TRACE_BASIC;
 
+// Trace hook related operations
+const int DBG_DHCP6_HOOKS = DBGLVL_TRACE_BASIC;
+
 // Trace detailed operations, including errors raised when processing invalid
 // packets.  (These are not logged at severities of WARN or higher for fear
 // that a set of deliberately invalid packets set to the server could overwhelm
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index bd50d5c..2aa327f 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -14,6 +14,11 @@
 
 $NAMESPACE isc::dhcp
 
+% DHCP6_ACTIVATE_INTERFACE activating interface %1
+This message is printed when DHCPv6 server enabled an interface to be used
+to receive DHCPv6 traffic. IPv6 socket on this interface will be opened once
+Interface Manager starts up procedure of opening sockets.
+
 % DHCP6_CCSESSION_STARTED control channel session started on socket %1
 A debug message issued during startup after the IPv6 DHCP server has
 successfully established a session with the BIND 10 control channel.
@@ -82,6 +87,11 @@ New  values: hostname = %2, reverse mapping = %3, forward mapping = %4
 This debug message is logged when FQDN mapping for a particular lease has been
 changed by the recent Renew message. This mapping will be changed in DNS.
 
+% DHCP6_DEACTIVATE_INTERFACE deactivate interface %1
+This message is printed when DHCPv6 server disables an interface from being
+used to receive DHCPv6 traffic. Sockets on this interface will not be opened
+by the Interface Manager until interface is enabled.
+
 % DHCP6_DDNS_SEND_FQDN sending DHCPv6 Client FQDN Option to the client: %1
 This debug message is logged when server includes an DHCPv6 Client FQDN Option
 in its response to the client.
@@ -102,6 +112,55 @@ that the DNS Update has been performed for it, but the FQDN held in the lease
 database has invalid format and can't be transformed to the canonical on-wire
 format.
 
+% DHCP6_HOOK_BUFFER_RCVD_SKIP received DHCPv6 buffer was dropped because a callout set the skip flag.
+This debug message is printed when a callout installed on buffer6_receive
+hook point set the skip flag. For this particular hook point, the
+setting of the flag by a callout instructs the server to drop the packet.
+
+% DHCP6_HOOK_BUFFER_SEND_SKIP prepared DHCPv6 response was dropped because a callout set the skip flag.
+This debug message is printed when a callout installed on buffer6_receive
+hook point set the skip flag. For this particular hook point, the
+setting of the flag by a callout instructs the server to drop the packet.
+Server completed all the processing (e.g. may have assigned, updated
+or released leases), but the response will not be send to the client.
+
+% DHCP6_HOOK_LEASE6_RENEW_SKIP DHCPv6 lease was not renewed because a callout set the skip flag.
+This debug message is printed when a callout installed on lease6_renew
+hook point set the skip flag. For this particular hook point, the setting
+of the flag by a callout instructs the server to not renew a lease. If
+client requested renewal of multiples leases (by sending multiple IA
+options), the server will skip the renewal of the one in question and
+will proceed with other renewals as usual.
+
+% DHCP6_HOOK_LEASE6_RELEASE_SKIP DHCPv6 lease was not released because a callout set the skip flag.
+This debug message is printed when a callout installed on lease6_release
+hook point set the skip flag. For this particular hook point, the
+setting of the flag by a callout instructs the server to not release
+a lease. If client requested release of multiples leases (by sending
+multiple IA options), the server will retains this particular lease and
+will proceed with other renewals as usual.
+
+% DHCP6_HOOK_PACKET_RCVD_SKIP received DHCPv6 packet was dropped because a callout set the skip flag.
+This debug message is printed when a callout installed on the pkt6_receive
+hook point set the skip flag. For this particular hook point, the
+setting of the flag by a callout instructs the server to drop the packet.
+
+% DHCP6_HOOK_PACKET_SEND_SKIP prepared DHCPv6 response was not sent because a callout set the skip flag.
+This debug message is printed when a callout installed on the pkt6_send
+hook point set the skip flag. For this particular hook point, the setting
+of the flag by a callout instructs the server to drop the packet. This
+effectively means that the client will not get any response, even though
+the server processed client's request and acted on it (e.g. possibly
+allocated a lease).
+
+% DHCP6_HOOK_SUBNET6_SELECT_SKIP no subnet was selected because a callout set the skip flag.
+This debug message is printed when a callout installed on the
+subnet6_select hook point set the skip flag. For this particular hook
+point, the setting of the flag instructs the server not to choose a
+subnet, an action that severely limits further processing; the server
+will be only able to offer global options - no addresses or prefixes
+will be assigned.
+
 % DHCP6_LEASE_ADVERT lease %1 advertised (client duid=%2, iaid=%3)
 This debug message indicates that the server successfully advertised
 a lease. It is up to the client to choose one server out of the
@@ -138,6 +197,11 @@ IPv6 DHCP server but it is not running.
 During startup the IPv6 DHCP server failed to detect any network
 interfaces and is therefore shutting down.
 
+% DHCP6_NO_SOCKETS_OPEN no interface configured to listen to DHCP traffic
+This warning message is issued when current server configuration specifies
+no interfaces that server should listen on, or specified interfaces are not
+configured to receive the traffic.
+
 % DHCP6_OPEN_SOCKET opening sockets on port %1
 A debug message issued during startup, this indicates that the IPv6 DHCP
 server is about to open sockets on the specified port.
@@ -196,10 +260,10 @@ actions and committal of changes failed.  The message has been output in
 response to a non-BIND 10 exception being raised.  Additional messages
 may give further information.
 
-The most likely cause of this is that the specification file for the server
-(which details the allowable contents of the configuration) is not correct for
-this version of BIND 10.  This former may be the result of an interrupted
-installation of an update to BIND 10.
+The most likely cause of this is that the specification file for the
+server (which details the allowable contents of the configuration) is
+not correct for this version of BIND 10.  This may be the result of an
+interrupted installation of an update to BIND 10.
 
 % DHCP6_PARSER_FAIL failed to create or run parser for configuration element %1: %2
 On receipt of message containing details to a change of its configuration,
@@ -345,6 +409,12 @@ to a misconfiguration of the server. The packet processing will continue, but
 the response will only contain generic configuration parameters and no
 addresses or prefixes.
 
+% DHCP6_UNKNOWN_MSG_RECEIVED received unknown message (type %d) on interface %2
+This debug message is printed when server receives a message of unknown type.
+That could either mean missing functionality or invalid or broken relay or client.
+The list of formally defined message types is available here:
+www.iana.org/assignments/dhcpv6-parameters.
+
 % DHCP6_UNKNOWN_RELEASE received RELEASE from unknown client (duid=%1, iaid=%2)
 This warning message is printed when client attempts to release a lease,
 but no such lease is known by the server. See DHCP6_UNKNOWN_RENEW for
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index da892b1..56addba 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -15,7 +15,7 @@
 #include <config.h>
 
 #include <asiolink/io_address.h>
-#include <d2/ncr_msg.h>
+#include <dhcp_ddns/ncr_msg.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/duid.h>
 #include <dhcp/iface_mgr.h>
@@ -39,6 +39,8 @@
 #include <util/io_utilities.h>
 #include <util/range_utilities.h>
 #include <util/encode/hex.h>
+#include <hooks/hooks_manager.h>
+#include <hooks/callout_handle.h>
 
 #include <boost/foreach.hpp>
 #include <boost/tokenizer.hpp>
@@ -51,11 +53,44 @@
 
 using namespace isc;
 using namespace isc::asiolink;
-using namespace isc::d2;
+using namespace isc::dhcp_ddns;
 using namespace isc::dhcp;
+using namespace isc::hooks;
 using namespace isc::util;
 using namespace std;
 
+namespace {
+
+/// Structure that holds registered hook indexes
+struct Dhcp6Hooks {
+    int hook_index_buffer6_receive_;///< index for "buffer6_receive" hook point
+    int hook_index_pkt6_receive_;   ///< index for "pkt6_receive" hook point
+    int hook_index_subnet6_select_; ///< index for "subnet6_select" hook point
+    int hook_index_lease6_renew_;   ///< index for "lease6_renew" hook point
+    int hook_index_lease6_release_; ///< index for "lease6_release" hook point
+    int hook_index_pkt6_send_;      ///< index for "pkt6_send" hook point
+    int hook_index_buffer6_send_;   ///< index for "buffer6_send" hook point
+
+    /// Constructor that registers hook points for DHCPv6 engine
+    Dhcp6Hooks() {
+        hook_index_buffer6_receive_= HooksManager::registerHook("buffer6_receive");
+        hook_index_pkt6_receive_   = HooksManager::registerHook("pkt6_receive");
+        hook_index_subnet6_select_ = HooksManager::registerHook("subnet6_select");
+        hook_index_lease6_renew_   = HooksManager::registerHook("lease6_renew");
+        hook_index_lease6_release_ = HooksManager::registerHook("lease6_release");
+        hook_index_pkt6_send_      = HooksManager::registerHook("pkt6_send");
+        hook_index_buffer6_send_   = HooksManager::registerHook("buffer6_send");
+    }
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+Dhcp6Hooks Hooks;
+
+}; // anonymous namespace
+
 namespace isc {
 namespace dhcp {
 
@@ -98,7 +133,8 @@ const bool FQDN_REPLACE_CLIENT_NAME = false;
 static const char* SERVER_DUID_FILE = "b10-dhcp6-serverid";
 
 Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
-    : alloc_engine_(), serverid_(), shutdown_(true) {
+:alloc_engine_(), serverid_(), shutdown_(true), port_(port)
+{
 
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
 
@@ -113,7 +149,7 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
                 LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
                 return;
             }
-            IfaceMgr::instance().openSockets6(port);
+            IfaceMgr::instance().openSockets6(port_);
         }
 
         string duid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_DUID_FILE);
@@ -131,12 +167,13 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
                 LOG_WARN(dhcp6_logger, DHCP6_SERVERID_WRITE_FAIL)
                     .arg(duid_file);
             }
-
         }
 
         // Instantiate allocation engine
         alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
 
+        /// @todo call loadLibraries() when handling configuration changes
+
     } catch (const std::exception &e) {
         LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what());
         return;
@@ -157,9 +194,17 @@ void Dhcpv6Srv::shutdown() {
     shutdown_ = true;
 }
 
+Pkt6Ptr Dhcpv6Srv::receivePacket(int timeout) {
+    return (IfaceMgr::instance().receive6(timeout));
+}
+
+void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) {
+    IfaceMgr::instance().send(packet);
+}
+
 bool Dhcpv6Srv::run() {
     while (!shutdown_) {
-        /// @todo: calculate actual timeout to the next event (e.g. lease
+        /// @todo Calculate actual timeout to the next event (e.g. lease
         /// expiration) once we have lease database. The idea here is that
         /// it is possible to do everything in a single process/thread.
         /// For now, we are just calling select for 1000 seconds. There
@@ -173,109 +218,243 @@ bool Dhcpv6Srv::run() {
         Pkt6Ptr rsp;
 
         try {
-            query = IfaceMgr::instance().receive6(timeout);
+            query = receivePacket(timeout);
         } catch (const std::exception& e) {
             LOG_ERROR(dhcp6_logger, DHCP6_PACKET_RECEIVE_FAIL).arg(e.what());
         }
 
-        if (query) {
+        // Timeout may be reached or signal received, which breaks select() with no packet received
+        if (!query) {
+            continue;
+        }
+
+        bool skip_unpack = false;
+
+        // The packet has just been received so contains the uninterpreted wire
+        // data; execute callouts registered for buffer6_receive.
+        if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_buffer6_receive_)) {
+            CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+            // Delete previously set arguments
+            callout_handle->deleteAllArguments();
+
+            // Pass incoming packet as argument
+            callout_handle->setArgument("query6", query);
+
+            // Call callouts
+            HooksManager::callCallouts(Hooks.hook_index_buffer6_receive_, *callout_handle);
+
+            // Callouts decided to skip the next processing step. The next
+            // processing step would to parse the packet, so skip at this
+            // stage means that callouts did the parsing already, so server
+            // should skip parsing.
+            if (callout_handle->getSkip()) {
+                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_RCVD_SKIP);
+                skip_unpack = true;
+            }
+
+            callout_handle->getArgument("query6", query);
+        }
+
+        // Unpack the packet information unless the buffer6_receive callouts
+        // indicated they did it
+        if (!skip_unpack) {
             if (!query->unpack()) {
                 LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
                           DHCP6_PACKET_PARSE_FAIL);
                 continue;
             }
-            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
-                      .arg(query->getName());
-            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
-                      .arg(static_cast<int>(query->getType()))
-                      .arg(query->getBuffer().getLength())
-                      .arg(query->toText());
-
-            try {
-                NameChangeRequestPtr ncr;
-                switch (query->getType()) {
-                case DHCPV6_SOLICIT:
-                    rsp = processSolicit(query);
-                    break;
-
-                case DHCPV6_REQUEST:
-                    rsp = processRequest(query);
-                    break;
-
-                case DHCPV6_RENEW:
-                    rsp = processRenew(query);
-                    break;
-
-                case DHCPV6_REBIND:
-                    rsp = processRebind(query);
-                    break;
+        }
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
+            .arg(query->getName());
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
+            .arg(static_cast<int>(query->getType()))
+            .arg(query->getBuffer().getLength())
+            .arg(query->toText());
+
+        // At this point the information in the packet has been unpacked into
+        // the various packet fields and option objects has been cretated.
+        // Execute callouts registered for packet6_receive.
+        if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_pkt6_receive_)) {
+            CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+            // Delete previously set arguments
+            callout_handle->deleteAllArguments();
+
+            // Pass incoming packet as argument
+            callout_handle->setArgument("query6", query);
+
+            // Call callouts
+            HooksManager::callCallouts(Hooks.hook_index_pkt6_receive_, *callout_handle);
+
+            // Callouts decided to skip the next processing step. The next
+            // processing step would to process the packet, so skip at this
+            // stage means drop.
+            if (callout_handle->getSkip()) {
+                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_RCVD_SKIP);
+                continue;
+            }
 
-                case DHCPV6_CONFIRM:
-                    rsp = processConfirm(query);
-                    break;
+            callout_handle->getArgument("query6", query);
+        }
 
-                case DHCPV6_RELEASE:
-                    rsp = processRelease(query);
+        try {
+                NameChangeRequestPtr ncr;
+            switch (query->getType()) {
+            case DHCPV6_SOLICIT:
+                rsp = processSolicit(query);
                     break;
 
-                case DHCPV6_DECLINE:
-                    rsp = processDecline(query);
-                    break;
+            case DHCPV6_REQUEST:
+                rsp = processRequest(query);
+                break;
+
+            case DHCPV6_RENEW:
+                rsp = processRenew(query);
+                break;
+
+            case DHCPV6_REBIND:
+                rsp = processRebind(query);
+                break;
+
+            case DHCPV6_CONFIRM:
+                rsp = processConfirm(query);
+                break;
+
+            case DHCPV6_RELEASE:
+                rsp = processRelease(query);
+                break;
+
+            case DHCPV6_DECLINE:
+                rsp = processDecline(query);
+                break;
+
+            case DHCPV6_INFORMATION_REQUEST:
+                rsp = processInfRequest(query);
+                break;
+
+            default:
+                // We received a packet type that we do not recognize.
+                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_UNKNOWN_MSG_RECEIVED)
+                    .arg(static_cast<int>(query->getType()))
+                    .arg(query->getIface());
+                // Only action is to output a message if debug is enabled,
+                // and that will be covered by the debug statement before
+                // the "switch" statement.
+                ;
+            }
 
-                case DHCPV6_INFORMATION_REQUEST:
-                    rsp = processInfRequest(query);
-                    break;
+        } catch (const RFCViolation& e) {
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL)
+                .arg(query->getName())
+                .arg(query->getRemoteAddr().toText())
+                .arg(e.what());
+
+        } catch (const isc::Exception& e) {
+
+            // Catch-all exception (at least for ones based on the isc
+            // Exception class, which covers more or less all that
+            // are explicitly raised in the BIND 10 code).  Just log
+            // the problem and ignore the packet. (The problem is logged
+            // as a debug message because debug is disabled by default -
+            // it prevents a DDOS attack based on the sending of problem
+            // packets.)
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_PACKET_PROCESS_FAIL)
+                .arg(query->getName())
+                .arg(query->getRemoteAddr().toText())
+                .arg(e.what());
+        }
 
-                default:
-                    // Only action is to output a message if debug is enabled,
-                    // and that will be covered by the debug statement before
-                    // the "switch" statement.
-                    ;
+        if (rsp) {
+            rsp->setRemoteAddr(query->getRemoteAddr());
+            rsp->setLocalAddr(query->getLocalAddr());
+            rsp->setRemotePort(DHCP6_CLIENT_PORT);
+            rsp->setLocalPort(DHCP6_SERVER_PORT);
+            rsp->setIndex(query->getIndex());
+            rsp->setIface(query->getIface());
+
+            // Specifies if server should do the packing
+            bool skip_pack = false;
+
+            // Server's reply packet now has all options and fields set.
+            // Options are represented by individual objects, but the
+            // output wire data has not been prepared yet.
+            // Execute all callouts registered for packet6_send
+            if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_pkt6_send_)) {
+                CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+                // Delete all previous arguments
+                callout_handle->deleteAllArguments();
+
+                // Set our response
+                callout_handle->setArgument("response6", rsp);
+
+                // Call all installed callouts
+                HooksManager::callCallouts(Hooks.hook_index_pkt6_send_, *callout_handle);
+
+                // Callouts decided to skip the next processing step. The next
+                // processing step would to pack the packet (create wire data).
+                // That step will be skipped if any callout sets skip flag.
+                // It essentially means that the callout already did packing,
+                // so the server does not have to do it again.
+                if (callout_handle->getSkip()) {
+                    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_PACKET_SEND_SKIP);
+                    skip_pack = true;
                 }
+            }
 
-            } catch (const RFCViolation& e) {
-                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL)
-                    .arg(query->getName())
-                    .arg(query->getRemoteAddr())
-                    .arg(e.what());
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
+                      DHCP6_RESPONSE_DATA)
+                .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());
+
+            if (!skip_pack) {
+                try {
+                    rsp->pack();
+                } catch (const std::exception& e) {
+                    LOG_ERROR(dhcp6_logger, DHCP6_PACK_FAIL)
+                        .arg(e.what());
+                    continue;
+                }
 
-            } catch (const isc::Exception& e) {
-
-                // Catch-all exception (at least for ones based on the isc
-                // Exception class, which covers more or less all that
-                // are explicitly raised in the BIND 10 code).  Just log
-                // the problem and ignore the packet. (The problem is logged
-                // as a debug message because debug is disabled by default -
-                // it prevents a DDOS attack based on the sending of problem
-                // packets.)
-                LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_PACKET_PROCESS_FAIL)
-                    .arg(query->getName())
-                    .arg(query->getRemoteAddr())
-                    .arg(e.what());
             }
 
-            if (rsp) {
-                rsp->setRemoteAddr(query->getRemoteAddr());
-                rsp->setLocalAddr(query->getLocalAddr());
-                rsp->setRemotePort(DHCP6_CLIENT_PORT);
-                rsp->setLocalPort(DHCP6_SERVER_PORT);
-                rsp->setIndex(query->getIndex());
-                rsp->setIface(query->getIface());
+            try {
+
+                // Now all fields and options are constructed into output wire buffer.
+                // Option objects modification does not make sense anymore. Hooks
+                // can only manipulate wire buffer at this stage.
+                // Let's execute all callouts registered for buffer6_send
+                if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_buffer6_send_)) {
+                    CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+                    // Delete previously set arguments
+                    callout_handle->deleteAllArguments();
+
+                    // Pass incoming packet as argument
+                    callout_handle->setArgument("response6", rsp);
+                    
+                    // Call callouts
+                    HooksManager::callCallouts(Hooks.hook_index_buffer6_send_, *callout_handle);
+                    
+                    // Callouts decided to skip the next processing step. The next
+                    // processing step would to parse the packet, so skip at this
+                    // stage means drop.
+                    if (callout_handle->getSkip()) {
+                        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_BUFFER_SEND_SKIP);
+                        continue;
+                    }
+                    
+                    callout_handle->getArgument("response6", rsp);
+                }
 
                 LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA,
                           DHCP6_RESPONSE_DATA)
                     .arg(static_cast<int>(rsp->getType())).arg(rsp->toText());
 
-                if (rsp->pack()) {
-                    try {
-                        IfaceMgr::instance().send(rsp);
-                    } catch (const std::exception& e) {
-                        LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL).arg(e.what());
-                    }
-
-                } else {
-                    LOG_ERROR(dhcp6_logger, DHCP6_PACK_FAIL);
-                }
+                sendPacket(rsp);
+            } catch (const std::exception& e) {
+                LOG_ERROR(dhcp6_logger, DHCP6_PACKET_SEND_FAIL)
+                    .arg(e.what());
             }
 
             // Although we don't support sending the NameChangeRequests to
@@ -598,6 +777,37 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
         }
     }
 
+    // Let's execute all callouts registered for subnet6_receive
+    if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_subnet6_select_)) {
+        CalloutHandlePtr callout_handle = getCalloutHandle(question);
+
+        // We're reusing callout_handle from previous calls
+        callout_handle->deleteAllArguments();
+
+        // Set new arguments
+        callout_handle->setArgument("query6", question);
+        callout_handle->setArgument("subnet6", subnet);
+
+        // We pass pointer to const collection for performance reasons.
+        // Otherwise we would get a non-trivial performance penalty each
+        // time subnet6_select is called.
+        callout_handle->setArgument("subnet6collection", CfgMgr::instance().getSubnets6());
+
+        // Call user (and server-side) callouts
+        HooksManager::callCallouts(Hooks.hook_index_subnet6_select_, *callout_handle);
+
+        // Callouts decided to skip this step. This means that no subnet will be
+        // selected. Packet processing will continue, but it will be severly limited
+        // (i.e. only global options will be assigned)
+        if (callout_handle->getSkip()) {
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_SUBNET6_SELECT_SKIP);
+            return (Subnet6Ptr());
+        }
+
+        // Use whatever subnet was specified by the callout
+        callout_handle->getArgument("subnet6", subnet);
+    }
+
     return (subnet);
 }
 
@@ -831,7 +1041,7 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
     // However, it will be faster if we used a pointer name_data.
     std::vector<uint8_t> buf_vec(name_data, name_data + name_buf.getLength());
     // Compute DHCID from Client Identifier and FQDN.
-    isc::d2::D2Dhcid dhcid(*duid, buf_vec);
+    isc::dhcp_ddns::D2Dhcid dhcid(*duid, buf_vec);
 
     // Get all IAs from the answer. For each IA, holding an address we will
     // create a corresponding NameChangeRequest.
@@ -851,7 +1061,7 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
         // Get the IP address from the lease. Also, use the S flag to determine
         // if forward change should be performed. This flag will always be
         // set if server has taken responsibility for the forward update.
-        NameChangeRequest ncr(isc::d2::CHG_ADD,
+        NameChangeRequest ncr(isc::dhcp_ddns::CHG_ADD,
                               opt_fqdn->getFlag(Option6ClientFqdn::FLAG_S),
                               true, opt_fqdn->getDomainName(),
                               iaaddr->getAddress().toText(),
@@ -904,10 +1114,10 @@ Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) {
                   << lease->addr_.toText());
 
     }
-    isc::d2::D2Dhcid dhcid(*lease->duid_, hostname_wire);
+    isc::dhcp_ddns::D2Dhcid dhcid(*lease->duid_, hostname_wire);
 
     // Create a NameChangeRequest to remove the entry.
-    NameChangeRequest ncr(isc::d2::CHG_REMOVE,
+    NameChangeRequest ncr(isc::dhcp_ddns::CHG_REMOVE,
                           lease->fqdn_fwd_, lease->fqdn_rev_,
                           lease->hostname_,
                           lease->addr_.toText(),
@@ -932,7 +1142,7 @@ Dhcpv6Srv::sendNameChangeRequests() {
 
 OptionPtr
 Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                       Pkt6Ptr question, Option6IAPtr ia,
+                       const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia,
                        const Option6ClientFqdnPtr& fqdn) {
     // If there is no subnet selected for handling this IA_NA, the only thing to do left is
     // to say that we are sorry, but the user won't get an address. As a convenience, we
@@ -972,11 +1182,13 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     // it should include this hint. That will help us during the actual lease
     // allocation.
     bool fake_allocation = false;
-    if (question->getType() == DHCPV6_SOLICIT) {
+    if (query->getType() == DHCPV6_SOLICIT) {
         /// @todo: Check if we support rapid commit
         fake_allocation = true;
     }
 
+    CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
     // At this point, we have to make make some decisions with respect to the
     // FQDN option that we have generated as a result of receiving client's
     // FQDN. In particular, we have to get to know if the DNS update will be
@@ -1011,7 +1223,8 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
                                                       hint,
                                                       do_fwd, do_rev,
                                                       hostname,
-                                                      fake_allocation);
+                                                      fake_allocation,
+                                                      callout_handle);
 
     // Create IA_NA that we will put in the response.
     // Do not use OptionDefinition to create option's instance so
@@ -1081,7 +1294,7 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
 
 OptionPtr
 Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                      Pkt6Ptr /* question */, boost::shared_ptr<Option6IA> ia,
+                      const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia,
                       const Option6ClientFqdnPtr& fqdn) {
     if (!subnet) {
         // There's no subnet select for this client. There's nothing to renew.
@@ -1119,6 +1332,9 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
         return (ia_rsp);
     }
 
+    // Keep the old data in case the callout tells us to skip update
+    Lease6 old_data = *lease;
+
     // At this point, we have to make make some decisions with respect to the
     // FQDN option that we have generated as a result of receiving client's
     // FQDN. In particular, we have to get to know if the DNS update will be
@@ -1164,8 +1380,6 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     lease->fqdn_fwd_ = do_fwd;
     lease->fqdn_rev_ = do_rev;
 
-    LeaseMgrFactory::instance().updateLease6(lease);
-
     // Create empty IA_NA option with IAID matching the request.
     boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
 
@@ -1176,6 +1390,46 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
                                           lease->addr_, lease->preferred_lft_,
                                           lease->valid_lft_));
     ia_rsp->addOption(addr);
+
+    bool skip = false;
+    // Execute all callouts registered for packet6_send
+    if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_lease6_renew_)) {
+        CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+        // Delete all previous arguments
+        callout_handle->deleteAllArguments();
+
+        // Pass the original packet
+        callout_handle->setArgument("query6", query);
+
+        // Pass the lease to be updated
+        callout_handle->setArgument("lease6", lease);
+
+        // Pass the IA option to be sent in response
+        callout_handle->setArgument("ia_na", ia_rsp);
+
+        // Call all installed callouts
+        HooksManager::callCallouts(Hooks.hook_index_lease6_renew_, *callout_handle);
+
+        // Callouts decided to skip the next processing step. The next
+        // processing step would to actually renew the lease, so skip at this
+        // stage means "keep the old lease as it is".
+        if (callout_handle->getSkip()) {
+            skip = true;
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_LEASE6_RENEW_SKIP);
+        }
+    }
+
+    if (!skip) {
+        LeaseMgrFactory::instance().updateLease6(lease);
+    } else {
+        // Copy back the original date to the lease. For MySQL it doesn't make
+        // much sense, but for memfile, the Lease6Ptr points to the actual lease
+        // in memfile, so the actual update is performed when we manipulate fields
+        // of returned Lease6Ptr, the actual updateLease6() is no-op.
+        *lease = old_data;
+    }
+
     return (ia_rsp);
 }
 
@@ -1297,7 +1551,7 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
 }
 
 OptionPtr
-Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr /* question */,
+Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query,
                         int& general_status, boost::shared_ptr<Option6IA> ia) {
     // Release can be done in one of two ways:
     // Approach 1: extract address from client's IA_NA and see if it belongs
@@ -1381,9 +1635,43 @@ Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr /* question */,
     // It is not necessary to check if the address matches as we used
     // getLease6(addr) method that is supposed to return a proper lease.
 
+    bool skip = false;
+    // Execute all callouts registered for packet6_send
+    if (HooksManager::getHooksManager().calloutsPresent(Hooks.hook_index_lease6_release_)) {
+        CalloutHandlePtr callout_handle = getCalloutHandle(query);
+
+        // Delete all previous arguments
+        callout_handle->deleteAllArguments();
+
+        // Pass the original packet
+        callout_handle->setArgument("query6", query);
+
+        // Pass the lease to be updated
+        callout_handle->setArgument("lease6", lease);
+
+        // Call all installed callouts
+        HooksManager::callCallouts(Hooks.hook_index_lease6_release_, *callout_handle);
+
+        // Callouts decided to skip the next processing step. The next
+        // processing step would to send the packet, so skip at this
+        // stage means "drop response".
+        if (callout_handle->getSkip()) {
+            skip = true;
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_HOOKS, DHCP6_HOOK_LEASE6_RELEASE_SKIP);
+        }
+    }
+
     // Ok, we've passed all checks. Let's release this address.
+    bool success = false; // was the removal operation succeessful?
+
+    if (!skip) {
+        success = LeaseMgrFactory::instance().deleteLease(lease->addr_);
+    }
+
+    // Here the success should be true if we removed lease successfully
+    // and false if skip flag was set or the removal failed for whatever reason
 
-    if (!LeaseMgrFactory::instance().deleteLease(lease->addr_)) {
+    if (!success) {
         ia_rsp->addOption(createStatusCode(STATUS_UnspecFail,
                           "Server failed to release a lease"));
 
@@ -1518,5 +1806,74 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& infRequest) {
     return reply;
 }
 
+isc::hooks::CalloutHandlePtr Dhcpv6Srv::getCalloutHandle(const Pkt6Ptr& pkt) {
+    // This method returns a CalloutHandle for a given packet. It is guaranteed
+    // to return the same callout_handle (so user library contexts are
+    // preserved). This method works well if the server processes one packet
+    // at a time. Once the server architecture is extended to cover parallel
+    // packets processing (e.g. delayed-ack, some form of buffering etc.), this
+    // method has to be extended (e.g. store callouts in a map and use pkt as
+    // a key). Additional code would be required to release the callout handle
+    // once the server finished processing.
+
+    CalloutHandlePtr callout_handle;
+    static Pkt6Ptr old_pointer;
+
+    if (!callout_handle ||
+        old_pointer != pkt) {
+        // This is the first packet or a different packet than previously
+        // passed to getCalloutHandle()
+
+        // Remember the pointer to this packet
+        old_pointer = pkt;
+
+        callout_handle = HooksManager::getHooksManager().createCalloutHandle();
+    }
+
+    return (callout_handle);
+}
+
+void
+Dhcpv6Srv::openActiveSockets(const uint16_t port) {
+    IfaceMgr::instance().closeSockets();
+
+    // Get the reference to the collection of interfaces. This reference should be
+    // valid as long as the program is run because IfaceMgr is a singleton.
+    // Therefore we can safely iterate over instances of all interfaces and modify
+    // their flags. Here we modify flags which indicate wheter socket should be
+    // open for a particular interface or not.
+    const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+    for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
+         iface != ifaces.end(); ++iface) {
+        Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
+        if (iface_ptr == NULL) {
+            isc_throw(isc::Unexpected, "Interface Manager returned NULL"
+                      << " instance of the interface when DHCPv6 server was"
+                      << " trying to reopen sockets after reconfiguration");
+        }
+        if (CfgMgr::instance().isActiveIface(iface->getName())) {
+            iface_ptr->inactive4_ = false;
+            LOG_INFO(dhcp6_logger, DHCP6_ACTIVATE_INTERFACE)
+                .arg(iface->getFullName());
+
+        } else {
+            // For deactivating interface, it should be sufficient to log it
+            // on the debug level because it is more useful to know what
+            // interface is activated which is logged on the info level.
+            LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC,
+                      DHCP6_DEACTIVATE_INTERFACE).arg(iface->getName());
+            iface_ptr->inactive6_ = true;
+
+        }
+    }
+    // Let's reopen active sockets. openSockets6 will check internally whether
+    // sockets are marked active or inactive.
+    // @todo Optimization: we should not reopen all sockets but rather select
+    // those that have been affected by the new configuration.
+    if (!IfaceMgr::instance().openSockets6(port)) {
+        LOG_WARN(dhcp6_logger, DHCP6_NO_SOCKETS_OPEN);
+    }
+}
+
 };
 };
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index 3df5a21..d6a1151 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -15,7 +15,7 @@
 #ifndef DHCPV6_SRV_H
 #define DHCPV6_SRV_H
 
-#include <d2/ncr_msg.h>
+#include <dhcp_ddns/ncr_msg.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/duid.h>
 #include <dhcp/option.h>
@@ -25,6 +25,7 @@
 #include <dhcp/pkt6.h>
 #include <dhcpsrv/alloc_engine.h>
 #include <dhcpsrv/subnet.h>
+#include <hooks/callout_handle.h>
 
 #include <boost/noncopyable.hpp>
 
@@ -90,6 +91,32 @@ public:
     /// @brief Instructs the server to shut down.
     void shutdown();
 
+    /// @brief Get UDP port on which server should listen.
+    ///
+    /// Typically, server listens on UDP port 547. Other ports are only
+    /// used for testing purposes.
+    ///
+    /// This accessor must be public because sockets are reopened from the
+    /// static configuration callback handler. This callback handler invokes
+    /// @c ControlledDhcpv4Srv::openActiveSockets which requires port parameter
+    /// which has to be retrieved from the @c ControlledDhcpv4Srv object.
+    /// They are retrieved using this public function.
+    ///
+    /// @return UDP port on which server should listen.
+    uint16_t getPort() const {
+        return (port_);
+    }
+
+    /// @brief Open sockets which are marked as active in @c CfgMgr.
+    ///
+    /// This function reopens sockets according to the current settings in the
+    /// Configuration Manager. It holds the list of the interfaces which server
+    /// should listen on. This function will open sockets on these interfaces
+    /// only. This function is not exception safe.
+    ///
+    /// @param port UDP port on which server should listen.
+    static void openActiveSockets(const uint16_t port);
+
 protected:
 
     /// @brief verifies if specified packet meets RFC requirements
@@ -186,14 +213,14 @@ protected:
     ///
     /// @param subnet subnet the client is connected to
     /// @param duid client's duid
-    /// @param question client's message (typically SOLICIT or REQUEST)
+    /// @param query client's message (typically SOLICIT or REQUEST)
     /// @param ia pointer to client's IA_NA option (client's request)
     /// @param fqdn A DHCPv6 Client FQDN %Option generated in a response to the
     /// FQDN option sent by a client.
     /// @return IA_NA option (server's response)
     OptionPtr assignIA_NA(const isc::dhcp::Subnet6Ptr& subnet,
                           const isc::dhcp::DuidPtr& duid,
-                          isc::dhcp::Pkt6Ptr question,
+                          const isc::dhcp::Pkt6Ptr& query,
                           Option6IAPtr ia,
                           const Option6ClientFqdnPtr& fqdn);
 
@@ -205,12 +232,12 @@ protected:
     ///
     /// @param subnet subnet the sender belongs to
     /// @param duid client's duid
-    /// @param question client's message
+    /// @param query client's message
     /// @param ia IA_NA option that is being renewed
     /// @param fqdn DHCPv6 Client FQDN Option included in the server's response
     /// @return IA_NA option (server's response)
     OptionPtr renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                         Pkt6Ptr question, boost::shared_ptr<Option6IA> ia,
+                         const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia,
                          const Option6ClientFqdnPtr& fqdn);
 
     /// @brief Releases specific IA_NA option
@@ -226,11 +253,11 @@ protected:
     /// release process fails.
     ///
     /// @param duid client's duid
-    /// @param question client's message
+    /// @param query client's message
     /// @param general_status a global status (it may be updated in case of errors)
     /// @param ia IA_NA option that is being renewed
     /// @return IA_NA option (server's response)
-    OptionPtr releaseIA_NA(const DuidPtr& duid, Pkt6Ptr question,
+    OptionPtr releaseIA_NA(const DuidPtr& duid, const Pkt6Ptr& query,
                            int& general_status,
                            boost::shared_ptr<Option6IA> ia);
 
@@ -312,17 +339,17 @@ protected:
                           Pkt6Ptr& answer,
                           const Option6ClientFqdnPtr& fqdn);
 
-    /// @brief Creates a number of @c isc::d2::NameChangeRequest objects based
-    /// on the DHCPv6 Client FQDN %Option.
+    /// @brief Creates a number of @c isc::dhcp_ddns::NameChangeRequest objects
+    /// based on the DHCPv6 Client FQDN %Option.
     ///
-    /// The @c isc::d2::NameChangeRequest class encapsulates the request from
-    /// the DHCPv6 server to the DHCP-DDNS module to perform DNS Update. The
-    /// FQDN option carries response to the client about DNS updates that
+    /// The @c isc::dhcp_ddns::NameChangeRequest class encapsulates the request
+    /// from the DHCPv6 server to the DHCP-DDNS module to perform DNS Update.
+    /// The FQDN option carries response to the client about DNS updates that
     /// server intents to perform for the DNS client. Based on this, the
-    /// function will create zero or more @c isc::d2::NameChangeRequest objects
-    /// and store them in the internal queue. Requests created by this function
-    /// are only adding or updating DNS records. In order to generate requests
-    /// for DNS records removal, use @c createRemovalNameChangeRequest.
+    /// function will create zero or more @c isc::dhcp_ddns::NameChangeRequest
+    /// objects and store them in the internal queue. Requests created by this
+    /// function are only adding or updating DNS records. In order to generate
+    /// requests for DNS records removal, use @c createRemovalNameChangeRequest.
     ///
     /// @param answer A message beging sent to the Client.
     /// @param fqdn_answer A DHCPv6 Client FQDN %Option which is included in the
@@ -330,15 +357,16 @@ protected:
     void createNameChangeRequests(const Pkt6Ptr& answer,
                                   const Option6ClientFqdnPtr& fqdn_answer);
 
-    /// @brief Creates a @c isc::d2::NameChangeRequest which requests removal
-    /// of DNS entries for a particular lease.
+    /// @brief Creates a @c isc::dhcp_ddns::NameChangeRequest which requests
+    /// removal of DNS entries for a particular lease.
     ///
     /// This function should be called upon removal of the lease from the lease
     /// database, i.e, when client sent Release or Decline message. It will
-    /// create a single @isc::d2::NameChangeRequest which removes the existing
-    /// DNS records for the lease, which server is responsible for. Note that
-    /// this function will not remove the entries which server hadn't added.
-    /// This is the case, when client performs forward DNS update on its own.
+    /// create a single @c isc::dhcp_ddns::NameChangeRequest which removes the
+    /// existing DNS records for the lease, which server is responsible for.
+    /// Note that this function will not remove the entries which server hadn't
+    /// added. This is the case, when client performs forward DNS update on its
+    /// own.
     ///
     /// @param lease A lease for which the the removal of corresponding DNS
     /// records will be performed.
@@ -347,7 +375,7 @@ protected:
     /// @brief Sends all outstanding NameChangeRequests to bind10-d2 module.
     ///
     /// The purpose of this function is to pick all outstanding
-    /// NameChangeRequests from the FIFO queue and send them to bind10-d2
+    /// NameChangeRequests from the FIFO queue and send them to bind10-dhcp-ddns
     /// module.
     ///
     /// @todo Currently this function simply removes all requests from the
@@ -415,6 +443,19 @@ protected:
     /// @return string representation
     static std::string duidToString(const OptionPtr& opt);
 
+
+    /// @brief dummy wrapper around IfaceMgr::receive6
+    ///
+    /// This method is useful for testing purposes, where its replacement
+    /// simulates reception of a packet. For that purpose it is protected.
+    virtual Pkt6Ptr receivePacket(int timeout);
+
+    /// @brief dummy wrapper around IfaceMgr::send()
+    ///
+    /// This method is useful for testing purposes, where its replacement
+    /// simulates transmission of a packet. For that purpose it is protected.
+    virtual void sendPacket(const Pkt6Ptr& pkt);
+
 private:
     /// @brief Allocation Engine.
     /// Pointer to the allocation engine that we are currently using
@@ -429,11 +470,21 @@ private:
     /// initiate server shutdown procedure.
     volatile bool shutdown_;
 
+    /// @brief returns callout handle for specified packet
+    ///
+    /// @param pkt packet for which the handle should be returned
+    ///
+    /// @return a callout handle to be used in hooks related to said packet
+    isc::hooks::CalloutHandlePtr getCalloutHandle(const Pkt6Ptr& pkt);
+
+    /// UDP port number on which server listens.
+    uint16_t port_;
+
 protected:
 
-    /// Holds a list of @c isc::d2::NameChangeRequest objects, which
-    /// are waiting for sending to D2 module.
-    std::queue<isc::d2::NameChangeRequest> name_change_reqs_;
+    /// Holds a list of @c isc::dhcp_ddns::NameChangeRequest objects, which
+    /// are waiting for sending to b10-dhcp-ddns module.
+    std::queue<isc::dhcp_ddns::NameChangeRequest> name_change_reqs_;
 };
 
 }; // namespace isc::dhcp
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index 0fec35b..b53393f 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -49,17 +49,14 @@ TESTS += dhcp6_unittests
 
 dhcp6_unittests_SOURCES  = dhcp6_unittests.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
+dhcp6_unittests_SOURCES += hooks_unittest.cc
+dhcp6_unittests_SOURCES += dhcp6_test_utils.h
 dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += config_parser_unittest.cc
 dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
 dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../config_parser.cc ../config_parser.h
-
-# Temporarily compile this file here. It will be removed once libdhcp-ddns
-# is implemented which will include this file and other files required
-# by DHCPv6.
-dhcp6_unittests_SOURCES += ../../d2/ncr_msg.cc ../../d2/ncr_msg.h
 nodist_dhcp6_unittests_SOURCES = ../dhcp6_messages.h ../dhcp6_messages.cc
 
 dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
@@ -69,10 +66,12 @@ dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
 endif
 
 noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index d9c5859..cda9066 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -66,11 +66,12 @@ public:
                           << " while the test assumes that it doesn't, to execute"
                           << " some negative scenarios. Can't continue this test.";
         }
+
+        // Reset configuration for each test.
+        resetConfiguration();
     }
 
     ~Dhcp6ParserTest() {
-        // Reset configuration database after each test.
-        resetConfiguration();
     };
 
     // Checks if config_result (result of DHCP server configuration) has
@@ -133,7 +134,7 @@ public:
                                        std::string>& params)
     {
         std::ostringstream stream;
-        stream << "{ \"interface\": [ \"all\" ],"
+        stream << "{ \"interfaces\": [ \"*\" ],"
             "\"preferred-lifetime\": 3000,"
             "\"rebind-timer\": 2000, "
             "\"renew-timer\": 1000, "
@@ -173,13 +174,13 @@ public:
     ///
     /// This function resets configuration data base by
     /// removing all subnets and option-data. Reset must
-    /// be performed after each test to make sure that
+    /// be performed before each test to make sure that
     /// contents of the database do not affect result of
-    /// subsequent tests.
+    /// the test being executed.
     void resetConfiguration() {
         ConstElementPtr status;
 
-        string config = "{ \"interface\": [ \"all\" ],"
+        string config = "{ \"interfaces\": [ \"*\" ],"
             "\"preferred-lifetime\": 3000,"
             "\"rebind-timer\": 2000, "
             "\"renew-timer\": 1000, "
@@ -213,6 +214,12 @@ public:
                    << " after the test. Configuration function returned"
                    << " error code " << rcode_ << std::endl;
         }
+
+        // The default setting is to listen on all interfaces. In order to
+        // properly test interface configuration we disable listening on
+        // all interfaces before each test and later check that this setting
+        // has been overriden by the configuration used in the test.
+        CfgMgr::instance().deleteActiveIfaces();
     }
 
     /// @brief Test invalid option parameter value.
@@ -324,7 +331,7 @@ TEST_F(Dhcp6ParserTest, emptySubnet) {
     ConstElementPtr status;
 
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
-                    Element::fromJSON("{ \"interface\": [ \"all\" ],"
+                    Element::fromJSON("{ \"interfaces\": [ \"*\" ],"
                                       "\"preferred-lifetime\": 3000,"
                                       "\"rebind-timer\": 2000, "
                                       "\"renew-timer\": 1000, "
@@ -343,7 +350,7 @@ TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
 
     ConstElementPtr status;
 
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -377,7 +384,7 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
 
     ConstElementPtr status;
 
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -415,7 +422,7 @@ TEST_F(Dhcp6ParserTest, subnetInterface) {
 
     // There should be at least one interface
 
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -448,7 +455,7 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {
 
     // There should be at least one interface
 
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -479,7 +486,7 @@ TEST_F(Dhcp6ParserTest, interfaceGlobal) {
 
     ConstElementPtr status;
 
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -549,7 +556,7 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceId) {
 // parameter.
 TEST_F(Dhcp6ParserTest, interfaceIdGlobal) {
 
-    const string config = "{ \"interface\": [ \"all\" ],"
+    const string config = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -604,7 +611,7 @@ TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
 
     ConstElementPtr status;
 
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -632,7 +639,7 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
 
     ConstElementPtr x;
 
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -1152,7 +1159,7 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
 // configuration does not include options configuration.
 TEST_F(Dhcp6ParserTest, optionDataDefaults) {
     ConstElementPtr x;
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
@@ -1234,7 +1241,7 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
     // The definition is not required for the option that
     // belongs to the 'dhcp6' option space as it is the
     // standard option.
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -1312,7 +1319,7 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
     // at the very end (when all other parameters are configured).
 
     // Starting stage 1. Configure sub-options and their definitions.
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -1361,7 +1368,7 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
     // the configuration from the stage 2 is repeated because BIND
     // configuration manager sends whole configuration for the lists
     // where at least one element is being modified or added.
-    config = "{ \"interface\": [ \"all\" ],"
+    config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -1455,7 +1462,7 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
 // for multiple subnets.
 TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
     ConstElementPtr x;
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -1698,7 +1705,7 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
     // In the first stahe we create definitions of suboptions
     // that we will add to the base option.
     // Let's create some dummy options: foo and foo2.
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -1751,7 +1758,7 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
     // We add our dummy options to this option space and thus
     // they should be included as sub-options in the 'vendor-opts'
     // option.
-    config = "{ \"interface\": [ \"all\" ],"
+    config = "{ \"interfaces\": [ \"*\" ],"
         "\"rebind-timer\": 2000,"
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
@@ -1850,4 +1857,77 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
     EXPECT_FALSE(desc.option->getOption(112));
 }
 
+// This test verifies that it is possible to select subset of interfaces on
+// which server should listen.
+TEST_F(Dhcp6ParserTest, selectedInterfaces) {
+
+    // Make sure there is no garbage interface configuration in the CfgMgr.
+    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
+    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
+    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+
+    ConstElementPtr status;
+
+    string config = "{ \"interfaces\": [ \"eth0\", \"eth1\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"valid-lifetime\": 4000 }";
+
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+    // returned value must be 1 (values error)
+    // as the pool does not belong to that subnet
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(0, rcode_);
+
+    // eth0 and eth1 were explicitly selected. eth2 was not.
+    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
+    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
+    EXPECT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+}
+
+// This test verifies that it is possible to configure the server to listen on
+// all interfaces.
+TEST_F(Dhcp6ParserTest, allInterfaces) {
+
+    // Make sure there is no garbage interface configuration in the CfgMgr.
+    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
+    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
+    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
+
+    ConstElementPtr status;
+
+    // This configuration specifies two interfaces on which server should listen
+    // bu also includes keyword 'all'. This keyword switches server into the
+    // mode when it listens on all interfaces regardless of what interface names
+    // were specified in the "interfaces" parameter.
+    string config = "{ \"interfaces\": [ \"eth0\", \"eth1\", \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"valid-lifetime\": 4000 }";
+
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+    // returned value must be 1 (values error)
+    // as the pool does not belong to that subnet
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(0, rcode_);
+
+    // All interfaces should be now active.
+    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
+    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
+    EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2"));
+}
+
+
 };
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index 09ed4e5..17cff6d 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -15,8 +15,7 @@
 #include <config.h>
 
 #include <asiolink/io_address.h>
-#include <config/ccsession.h>
-#include <d2/ncr_msg.h>
+#include <dhcp_ddns/ncr_msg.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/duid.h>
 #include <dhcp/option.h>
@@ -26,15 +25,18 @@
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/iface_mgr.h>
 #include <dhcp6/config_parser.h>
-#include <dhcp6/dhcp6_srv.h>
+#include <dhcp/dhcp6.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/utils.h>
 #include <util/buffer.h>
 #include <util/range_utilities.h>
+#include <hooks/server_hooks.h>
 
+#include <dhcp6/tests/dhcp6_test_utils.h>
 #include <boost/pointer_cast.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
@@ -44,12 +46,12 @@
 #include <sstream>
 
 using namespace isc;
+using namespace isc::test;
 using namespace isc::asiolink;
-using namespace isc::config;
-using namespace isc::d2;
-using namespace isc::data;
 using namespace isc::dhcp;
+using namespace isc::dhcp_ddns;
 using namespace isc::util;
+using namespace isc::hooks;
 using namespace std;
 
 // namespace has to be named, because friends are defined in Dhcpv6Srv class
@@ -60,290 +62,6 @@ const uint8_t FQDN_FLAG_S = 0x1;
 const uint8_t FQDN_FLAG_O = 0x2;
 const uint8_t FQDN_FLAG_N = 0x4;
 
-class NakedDhcpv6Srv: public Dhcpv6Srv {
-    // "naked" Interface Manager, exposes internal members
-public:
-    NakedDhcpv6Srv(uint16_t port) : Dhcpv6Srv(port) {
-        // Open the "memfile" database for leases
-        std::string memfile = "type=memfile";
-        LeaseMgrFactory::create(memfile);
-    }
-
-    virtual ~NakedDhcpv6Srv() {
-        // Close the lease database
-        LeaseMgrFactory::destroy();
-    }
-
-    using Dhcpv6Srv::processSolicit;
-    using Dhcpv6Srv::processRequest;
-    using Dhcpv6Srv::processRenew;
-    using Dhcpv6Srv::processRelease;
-    using Dhcpv6Srv::processClientFqdn;
-    using Dhcpv6Srv::createNameChangeRequests;
-    using Dhcpv6Srv::createRemovalNameChangeRequest;
-    using Dhcpv6Srv::createStatusCode;
-    using Dhcpv6Srv::selectSubnet;
-    using Dhcpv6Srv::sanityCheck;
-    using Dhcpv6Srv::loadServerID;
-    using Dhcpv6Srv::writeServerID;
-    using Dhcpv6Srv::name_change_reqs_;
-};
-
-static const char* DUID_FILE = "server-id-test.txt";
-
-// test fixture for any tests requiring blank/empty configuration
-// serves as base class for additional tests
-class NakedDhcpv6SrvTest : public ::testing::Test {
-public:
-
-    NakedDhcpv6SrvTest() : rcode_(-1) {
-        // it's ok if that fails. There should not be such a file anyway
-        unlink(DUID_FILE);
-    }
-
-    // Generate IA_NA option with specified parameters
-    boost::shared_ptr<Option6IA> generateIA(uint32_t iaid, uint32_t t1, uint32_t t2) {
-        boost::shared_ptr<Option6IA> ia =
-            boost::shared_ptr<Option6IA>(new Option6IA(D6O_IA_NA, iaid));
-        ia->setT1(t1);
-        ia->setT2(t2);
-        return (ia);
-    }
-
-    /// @brief generates interface-id option, based on text
-    ///
-    /// @param iface_id textual representation of the interface-id content
-    ///
-    /// @return pointer to the option object
-    OptionPtr generateInterfaceId(const string& iface_id) {
-        OptionBuffer tmp(iface_id.begin(), iface_id.end());
-        return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
-    }
-
-    // Generate client-id option
-    OptionPtr generateClientId(size_t duid_size = 32) {
-
-        OptionBuffer clnt_duid(duid_size);
-        for (int i = 0; i < duid_size; i++) {
-            clnt_duid[i] = 100 + i;
-        }
-
-        duid_ = DuidPtr(new DUID(clnt_duid));
-
-        return (OptionPtr(new Option(Option::V6, D6O_CLIENTID,
-                                     clnt_duid.begin(),
-                                     clnt_duid.begin() + duid_size)));
-    }
-
-    // Checks if server response (ADVERTISE or REPLY) includes proper server-id.
-    void checkServerId(const Pkt6Ptr& rsp, const OptionPtr& expected_srvid) {
-        // check that server included its server-id
-        OptionPtr tmp = rsp->getOption(D6O_SERVERID);
-        EXPECT_EQ(tmp->getType(), expected_srvid->getType() );
-        ASSERT_EQ(tmp->len(), expected_srvid->len() );
-        EXPECT_TRUE(tmp->getData() == expected_srvid->getData());
-    }
-
-    // Checks if server response (ADVERTISE or REPLY) includes proper client-id.
-    void checkClientId(const Pkt6Ptr& rsp, const OptionPtr& expected_clientid) {
-        // check that server included our own client-id
-        OptionPtr tmp = rsp->getOption(D6O_CLIENTID);
-        ASSERT_TRUE(tmp);
-        EXPECT_EQ(expected_clientid->getType(), tmp->getType());
-        ASSERT_EQ(expected_clientid->len(), tmp->len());
-
-        // check that returned client-id is valid
-        EXPECT_TRUE(expected_clientid->getData() == tmp->getData());
-    }
-
-    // Checks if server response is a NAK
-    void checkNakResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
-                          uint32_t expected_transid,
-                          uint16_t expected_status_code) {
-        // Check if we get response at all
-        checkResponse(rsp, expected_message_type, expected_transid);
-
-        // Check that IA_NA was returned
-        OptionPtr option_ia_na = rsp->getOption(D6O_IA_NA);
-        ASSERT_TRUE(option_ia_na);
-
-        // check that the status is no address available
-        boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(option_ia_na);
-        ASSERT_TRUE(ia);
-
-        checkIA_NAStatusCode(ia, expected_status_code);
-    }
-
-    // Checks that server rejected IA_NA, i.e. that it has no addresses and
-    // that expected status code really appears there. In some limited cases
-    // (reply to RELEASE) it may be used to verify positive case, where
-    // IA_NA response is expected to not include address.
-    //
-    // Status code indicates type of error encountered (in theory it can also
-    // indicate success, but servers typically don't send success status
-    // as this is the default result and it saves bandwidth)
-    void checkIA_NAStatusCode(const boost::shared_ptr<Option6IA>& ia,
-                            uint16_t expected_status_code) {
-        // Make sure there is no address assigned.
-        EXPECT_FALSE(ia->getOption(D6O_IAADDR));
-
-        // T1, T2 should be zeroed
-        EXPECT_EQ(0, ia->getT1());
-        EXPECT_EQ(0, ia->getT2());
-
-        OptionCustomPtr status =
-            boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
-
-        // It is ok to not include status success as this is the default behavior
-        if (expected_status_code == STATUS_Success && !status) {
-            return;
-        }
-
-        EXPECT_TRUE(status);
-
-        if (status) {
-            // We don't have dedicated class for status code, so let's just interpret
-            // first 2 bytes as status. Remainder of the status code option content is
-            // just a text explanation what went wrong.
-            EXPECT_EQ(static_cast<uint16_t>(expected_status_code),
-                      status->readInteger<uint16_t>(0));
-        }
-    }
-
-    void checkMsgStatusCode(const Pkt6Ptr& msg, uint16_t expected_status) {
-        OptionCustomPtr status =
-            boost::dynamic_pointer_cast<OptionCustom>(msg->getOption(D6O_STATUS_CODE));
-
-        // It is ok to not include status success as this is the default behavior
-        if (expected_status == STATUS_Success && !status) {
-            return;
-        }
-
-        EXPECT_TRUE(status);
-        if (status) {
-            // We don't have dedicated class for status code, so let's just interpret
-            // first 2 bytes as status. Remainder of the status code option content is
-            // just a text explanation what went wrong.
-            EXPECT_EQ(static_cast<uint16_t>(expected_status),
-                      status->readInteger<uint16_t>(0));
-        }
-    }
-
-    // Basic checks for generated response (message type and transaction-id).
-    void checkResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
-                       uint32_t expected_transid) {
-        ASSERT_TRUE(rsp);
-        EXPECT_EQ(expected_message_type, rsp->getType());
-        EXPECT_EQ(expected_transid, rsp->getTransid());
-    }
-
-    // Generates client's packet holding an FQDN option.
-
-    virtual ~NakedDhcpv6SrvTest() {
-        // Let's clean up if there is such a file.
-        unlink(DUID_FILE);
-    };
-
-    // A DUID used in most tests (typically as client-id)
-    DuidPtr duid_;
-
-    int rcode_;
-    ConstElementPtr comment_;
-};
-
-// Provides suport for tests against a preconfigured subnet6
-// extends upon NakedDhcp6SrvTest
-class Dhcpv6SrvTest : public NakedDhcpv6SrvTest {
-public:
-    /// Name of the server-id file (used in server-id tests)
-
-    // these are empty for now, but let's keep them around
-    Dhcpv6SrvTest() {
-        subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
-                                         2000, 3000, 4000));
-        pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
-        subnet_->addPool(pool_);
-
-        CfgMgr::instance().deleteSubnets6();
-        CfgMgr::instance().addSubnet6(subnet_);
-    }
-
-    // Checks that server response (ADVERTISE or REPLY) contains proper IA_NA option
-    // It returns IAADDR option for each chaining with checkIAAddr method.
-    boost::shared_ptr<Option6IAAddr> checkIA_NA(const Pkt6Ptr& rsp, uint32_t expected_iaid,
-                                            uint32_t expected_t1, uint32_t expected_t2) {
-        OptionPtr tmp = rsp->getOption(D6O_IA_NA);
-        // Can't use ASSERT_TRUE() in method that returns something
-        if (!tmp) {
-            ADD_FAILURE() << "IA_NA option not present in response";
-            return (boost::shared_ptr<Option6IAAddr>());
-        }
-
-        boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
-        if (!ia) {
-            ADD_FAILURE() << "IA_NA cannot convert option ptr to Option6";
-            return (boost::shared_ptr<Option6IAAddr>());
-        }
-
-        EXPECT_EQ(expected_iaid, ia->getIAID());
-        EXPECT_EQ(expected_t1, ia->getT1());
-        EXPECT_EQ(expected_t2, ia->getT2());
-
-        tmp = ia->getOption(D6O_IAADDR);
-        boost::shared_ptr<Option6IAAddr> addr = boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
-        return (addr);
-    }
-
-    // Check that generated IAADDR option contains expected address
-    // and lifetime values match the configured subnet
-    void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
-                     const IOAddress& expected_addr,
-                     uint32_t /* expected_preferred */,
-                     uint32_t /* expected_valid */) {
-
-        // Check that the assigned address is indeed from the configured pool.
-        // Note that when comparing addresses, we compare the textual
-        // representation. IOAddress does not support being streamed to
-        // an ostream, which means it can't be used in EXPECT_EQ.
-        EXPECT_TRUE(subnet_->inPool(addr->getAddress()));
-        EXPECT_EQ(expected_addr.toText(), addr->getAddress().toText());
-        EXPECT_EQ(addr->getPreferred(), subnet_->getPreferred());
-        EXPECT_EQ(addr->getValid(), subnet_->getValid());
-    }
-
-    // Checks if the lease sent to client is present in the database
-    // and is valid when checked agasint the configured subnet
-    Lease6Ptr checkLease(const DuidPtr& duid, const OptionPtr& ia_na,
-                         boost::shared_ptr<Option6IAAddr> addr) {
-        boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(ia_na);
-
-        Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(addr->getAddress());
-        if (!lease) {
-            cout << "Lease for " << addr->getAddress().toText()
-                 << " not found in the database backend.";
-            return (Lease6Ptr());
-        }
-
-        EXPECT_EQ(addr->getAddress().toText(), lease->addr_.toText());
-        EXPECT_TRUE(*lease->duid_ == *duid);
-        EXPECT_EQ(ia->getIAID(), lease->iaid_);
-        EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
-
-        return (lease);
-    }
-
-    ~Dhcpv6SrvTest() {
-        CfgMgr::instance().deleteSubnets6();
-    };
-
-    // A subnet used in most tests
-    Subnet6Ptr subnet_;
-
-    // A pool used in most tests
-    Pool6Ptr pool_;
-
-};
-
 class FqdnDhcpv6SrvTest : public Dhcpv6SrvTest {
 public:
     FqdnDhcpv6SrvTest()
@@ -562,7 +280,7 @@ public:
 
     // Verify that NameChangeRequest holds valid values.
     void verifyNameChangeRequest(NakedDhcpv6Srv& srv,
-                                 const isc::d2::NameChangeType type,
+                                 const isc::dhcp_ddns::NameChangeType type,
                                  const bool reverse, const bool forward,
                                  const std::string& addr,
                                  const std::string& dhcid,
@@ -576,11 +294,12 @@ public:
         EXPECT_EQ(dhcid, ncr.getDhcid().toStr());
         EXPECT_EQ(expires, ncr.getLeaseExpiresOn());
         EXPECT_EQ(len, ncr.getLeaseLength());
-        EXPECT_EQ(isc::d2::ST_NEW, ncr.getStatus());
+        EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr.getStatus());
         srv.name_change_reqs_.pop();
     }
 
     Lease6Ptr lease_;
+
 };
 
 // This test verifies that incoming SOLICIT can be handled properly when
@@ -734,7 +453,7 @@ TEST_F(Dhcpv6SrvTest, DUID) {
 
     boost::scoped_ptr<Dhcpv6Srv> srv;
     ASSERT_NO_THROW( {
-        srv.reset(new Dhcpv6Srv(0));
+        srv.reset(new NakedDhcpv6Srv(0));
     });
 
     OptionPtr srvid = srv->getServerID();
@@ -809,7 +528,7 @@ TEST_F(Dhcpv6SrvTest, DUID) {
 // and the requested options are actually assigned.
 TEST_F(Dhcpv6SrvTest, advertiseOptions) {
     ConstElementPtr x;
-    string config = "{ \"interface\": [ \"all\" ],"
+    string config = "{ \"interfaces\": [ \"all\" ],"
         "\"preferred-lifetime\": 3000,"
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
@@ -2142,17 +1861,20 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
     // Verify that NameChangeRequests are correct. Each call to the
     // verifyNameChangeRequest will pop verified request from the queue.
 
-    verifyNameChangeRequest(srv, isc::d2::CHG_ADD, true, true, "2001:db8:1::1",
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1::1",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
                             0, 500);
 
-    verifyNameChangeRequest(srv, isc::d2::CHG_ADD, true, true, "2001:db8:1::2",
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1::2",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
                             0, 500);
 
-    verifyNameChangeRequest(srv, isc::d2::CHG_ADD, true, true, "2001:db8:1::3",
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1::3",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
                             0, 500);
@@ -2172,7 +1894,7 @@ TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestFwdRev) {
 
     ASSERT_EQ(1, srv.name_change_reqs_.size());
 
-    verifyNameChangeRequest(srv, isc::d2::CHG_REMOVE, true, true,
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1::1",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -2193,7 +1915,7 @@ TEST_F(FqdnDhcpv6SrvTest, createRemovalNameChangeRequestRev) {
 
     ASSERT_EQ(1, srv.name_change_reqs_.size());
 
-    verifyNameChangeRequest(srv, isc::d2::CHG_REMOVE, true, false,
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, false,
                             "2001:db8:1::1",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -2272,7 +1994,7 @@ TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
     // to add both reverse and forward mapping to DNS.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
     ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::d2::CHG_ADD, true, true,
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -2287,12 +2009,12 @@ TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
     // remove the existing entries, one to add new entries.
     testProcessMessage(DHCPV6_REQUEST, "otherhost.example.com", srv);
     ASSERT_EQ(2, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::d2::CHG_REMOVE, true, true,
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
                             0, 4000);
-    verifyNameChangeRequest(srv, isc::d2::CHG_ADD, true, true,
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201D422AA463306223D269B6CB7AFE7AAD265FC"
                             "EA97F93623019B2E0D14E5323D5A",
@@ -2314,7 +2036,7 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
     // to add both reverse and forward mapping to DNS.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
     ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::d2::CHG_ADD, true, true,
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -2329,12 +2051,12 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
     // remove the existing entries, one to add new entries.
     testProcessMessage(DHCPV6_RENEW, "otherhost.example.com", srv);
     ASSERT_EQ(2, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::d2::CHG_REMOVE, true, true,
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
                             0, 4000);
-    verifyNameChangeRequest(srv, isc::d2::CHG_ADD, true, true,
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201D422AA463306223D269B6CB7AFE7AAD265FC"
                             "EA97F93623019B2E0D14E5323D5A",
@@ -2351,7 +2073,7 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
     // to add both reverse and forward mapping to DNS.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
     ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::d2::CHG_ADD, true, true,
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
@@ -2363,7 +2085,7 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
     // remove DNS entries is generated.
     testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com", srv);
     ASSERT_EQ(1, srv.name_change_reqs_.size());
-    verifyNameChangeRequest(srv, isc::d2::CHG_REMOVE, true, true,
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.h b/src/bin/dhcp6/tests/dhcp6_test_utils.h
new file mode 100644
index 0000000..5032857
--- /dev/null
+++ b/src/bin/dhcp6/tests/dhcp6_test_utils.h
@@ -0,0 +1,407 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @file   dhcp6_test_utils.h
+///
+/// @brief  This file contains utility classes used for DHCPv6 server testing
+
+#include <gtest/gtest.h>
+
+#include <dhcp/pkt6.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcp6/dhcp6_srv.h>
+#include <hooks/hooks_manager.h>
+#include <config/ccsession.h>
+
+#include <list>
+
+using namespace isc::dhcp;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace isc::asiolink;
+using namespace isc::util;
+using namespace isc::hooks;
+
+namespace isc {
+namespace test {
+
+/// @brief "naked" Dhcpv6Srv class that exposes internal members
+class NakedDhcpv6Srv: public isc::dhcp::Dhcpv6Srv {
+public:
+    NakedDhcpv6Srv(uint16_t port) : Dhcpv6Srv(port) {
+        // Open the "memfile" database for leases
+        std::string memfile = "type=memfile";
+        LeaseMgrFactory::create(memfile);
+    }
+
+    /// @brief fakes packet reception
+    /// @param timeout ignored
+    ///
+    /// The method receives all packets queued in receive
+    /// queue, one after another. Once the queue is empty,
+    /// it initiates the shutdown procedure.
+    ///
+    /// See fake_received_ field for description
+    virtual isc::dhcp::Pkt6Ptr receivePacket(int /*timeout*/) {
+
+        // If there is anything prepared as fake incoming
+        // traffic, use it
+        if (!fake_received_.empty()) {
+            Pkt6Ptr pkt = fake_received_.front();
+            fake_received_.pop_front();
+            return (pkt);
+        }
+
+        // If not, just trigger shutdown and
+        // return immediately
+        shutdown();
+        return (Pkt6Ptr());
+    }
+
+    /// @brief fake packet sending
+    ///
+    /// Pretend to send a packet, but instead just store
+    /// it in fake_send_ list where test can later inspect
+    /// server's response.
+    virtual void sendPacket(const Pkt6Ptr& pkt) {
+        fake_sent_.push_back(pkt);
+    }
+
+    /// @brief adds a packet to fake receive queue
+    ///
+    /// See fake_received_ field for description
+    void fakeReceive(const Pkt6Ptr& pkt) {
+        fake_received_.push_back(pkt);
+    }
+
+    virtual ~NakedDhcpv6Srv() {
+        // Close the lease database
+        LeaseMgrFactory::destroy();
+    }
+
+    using Dhcpv6Srv::processSolicit;
+    using Dhcpv6Srv::processRequest;
+    using Dhcpv6Srv::processRenew;
+    using Dhcpv6Srv::processRelease;
+    using Dhcpv6Srv::processClientFqdn;
+    using Dhcpv6Srv::createNameChangeRequests;
+    using Dhcpv6Srv::createRemovalNameChangeRequest;
+    using Dhcpv6Srv::createStatusCode;
+    using Dhcpv6Srv::selectSubnet;
+    using Dhcpv6Srv::sanityCheck;
+    using Dhcpv6Srv::loadServerID;
+    using Dhcpv6Srv::writeServerID;
+    using Dhcpv6Srv::name_change_reqs_;
+
+    /// @brief packets we pretend to receive
+    ///
+    /// Instead of setting up sockets on interfaces that change between OSes, it
+    /// is much easier to fake packet reception. This is a list of packets that
+    /// we pretend to have received. You can schedule new packets to be received
+    /// using fakeReceive() and NakedDhcpv6Srv::receivePacket() methods.
+    std::list<Pkt6Ptr> fake_received_;
+
+    std::list<Pkt6Ptr> fake_sent_;
+};
+
+static const char* DUID_FILE = "server-id-test.txt";
+
+// test fixture for any tests requiring blank/empty configuration
+// serves as base class for additional tests
+class NakedDhcpv6SrvTest : public ::testing::Test {
+public:
+
+    NakedDhcpv6SrvTest() : rcode_(-1) {
+        // it's ok if that fails. There should not be such a file anyway
+        unlink(DUID_FILE);
+
+        const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+        // There must be some interface detected
+        if (ifaces.empty()) {
+            // We can't use ASSERT in constructor
+            ADD_FAILURE() << "No interfaces detected.";
+        }
+
+        valid_iface_ = ifaces.begin()->getName();
+    }
+
+    // Generate IA_NA option with specified parameters
+    boost::shared_ptr<Option6IA> generateIA(uint32_t iaid, uint32_t t1, uint32_t t2) {
+        boost::shared_ptr<Option6IA> ia =
+            boost::shared_ptr<Option6IA>(new Option6IA(D6O_IA_NA, iaid));
+        ia->setT1(t1);
+        ia->setT2(t2);
+        return (ia);
+    }
+
+    /// @brief generates interface-id option, based on text
+    ///
+    /// @param iface_id textual representation of the interface-id content
+    ///
+    /// @return pointer to the option object
+    OptionPtr generateInterfaceId(const std::string& iface_id) {
+        OptionBuffer tmp(iface_id.begin(), iface_id.end());
+        return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+    }
+
+    // Generate client-id option
+    OptionPtr generateClientId(size_t duid_size = 32) {
+
+        OptionBuffer clnt_duid(duid_size);
+        for (int i = 0; i < duid_size; i++) {
+            clnt_duid[i] = 100 + i;
+        }
+
+        duid_ = DuidPtr(new DUID(clnt_duid));
+
+        return (OptionPtr(new Option(Option::V6, D6O_CLIENTID,
+                                     clnt_duid.begin(),
+                                     clnt_duid.begin() + duid_size)));
+    }
+
+    // Checks if server response (ADVERTISE or REPLY) includes proper server-id.
+    void checkServerId(const Pkt6Ptr& rsp, const OptionPtr& expected_srvid) {
+        // check that server included its server-id
+        OptionPtr tmp = rsp->getOption(D6O_SERVERID);
+        EXPECT_EQ(tmp->getType(), expected_srvid->getType() );
+        ASSERT_EQ(tmp->len(), expected_srvid->len() );
+        EXPECT_TRUE(tmp->getData() == expected_srvid->getData());
+    }
+
+    // Checks if server response (ADVERTISE or REPLY) includes proper client-id.
+    void checkClientId(const Pkt6Ptr& rsp, const OptionPtr& expected_clientid) {
+        // check that server included our own client-id
+        OptionPtr tmp = rsp->getOption(D6O_CLIENTID);
+        ASSERT_TRUE(tmp);
+        EXPECT_EQ(expected_clientid->getType(), tmp->getType());
+        ASSERT_EQ(expected_clientid->len(), tmp->len());
+
+        // check that returned client-id is valid
+        EXPECT_TRUE(expected_clientid->getData() == tmp->getData());
+    }
+
+    // Checks if server response is a NAK
+    void checkNakResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
+                          uint32_t expected_transid,
+                          uint16_t expected_status_code) {
+        // Check if we get response at all
+        checkResponse(rsp, expected_message_type, expected_transid);
+
+        // Check that IA_NA was returned
+        OptionPtr option_ia_na = rsp->getOption(D6O_IA_NA);
+        ASSERT_TRUE(option_ia_na);
+
+        // check that the status is no address available
+        boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(option_ia_na);
+        ASSERT_TRUE(ia);
+
+        checkIA_NAStatusCode(ia, expected_status_code);
+    }
+
+    // Checks that server rejected IA_NA, i.e. that it has no addresses and
+    // that expected status code really appears there. In some limited cases
+    // (reply to RELEASE) it may be used to verify positive case, where
+    // IA_NA response is expected to not include address.
+    //
+    // Status code indicates type of error encountered (in theory it can also
+    // indicate success, but servers typically don't send success status
+    // as this is the default result and it saves bandwidth)
+    void checkIA_NAStatusCode(const boost::shared_ptr<Option6IA>& ia,
+                            uint16_t expected_status_code) {
+        // Make sure there is no address assigned.
+        EXPECT_FALSE(ia->getOption(D6O_IAADDR));
+
+        // T1, T2 should be zeroed
+        EXPECT_EQ(0, ia->getT1());
+        EXPECT_EQ(0, ia->getT2());
+
+        OptionCustomPtr status =
+            boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
+
+        // It is ok to not include status success as this is the default behavior
+        if (expected_status_code == STATUS_Success && !status) {
+            return;
+        }
+
+        EXPECT_TRUE(status);
+
+        if (status) {
+            // We don't have dedicated class for status code, so let's just interpret
+            // first 2 bytes as status. Remainder of the status code option content is
+            // just a text explanation what went wrong.
+            EXPECT_EQ(static_cast<uint16_t>(expected_status_code),
+                      status->readInteger<uint16_t>(0));
+        }
+    }
+
+    void checkMsgStatusCode(const Pkt6Ptr& msg, uint16_t expected_status) {
+        OptionCustomPtr status =
+            boost::dynamic_pointer_cast<OptionCustom>(msg->getOption(D6O_STATUS_CODE));
+
+        // It is ok to not include status success as this is the default behavior
+        if (expected_status == STATUS_Success && !status) {
+            return;
+        }
+
+        EXPECT_TRUE(status);
+        if (status) {
+            // We don't have dedicated class for status code, so let's just interpret
+            // first 2 bytes as status. Remainder of the status code option content is
+            // just a text explanation what went wrong.
+            EXPECT_EQ(static_cast<uint16_t>(expected_status),
+                      status->readInteger<uint16_t>(0));
+        }
+    }
+
+    // Basic checks for generated response (message type and transaction-id).
+    void checkResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
+                       uint32_t expected_transid) {
+        ASSERT_TRUE(rsp);
+        EXPECT_EQ(expected_message_type, rsp->getType());
+        EXPECT_EQ(expected_transid, rsp->getTransid());
+    }
+
+    virtual ~NakedDhcpv6SrvTest() {
+        // Let's clean up if there is such a file.
+        unlink(DUID_FILE);
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+                                                 "buffer6_receive");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+                                                 "buffer6_send");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+                                                 "lease6_renew");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+                                                 "lease6_release");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+                                                 "pkt6_receive");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+                                                 "pkt6_send");
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+                                                 "subnet6_select");
+
+    };
+
+    // A DUID used in most tests (typically as client-id)
+    DuidPtr duid_;
+
+    int rcode_;
+    ConstElementPtr comment_;
+
+    // Name of a valid network interface
+    std::string valid_iface_;
+};
+
+// Provides suport for tests against a preconfigured subnet6
+// extends upon NakedDhcp6SrvTest
+class Dhcpv6SrvTest : public NakedDhcpv6SrvTest {
+public:
+    /// Name of the server-id file (used in server-id tests)
+
+    // these are empty for now, but let's keep them around
+    Dhcpv6SrvTest() {
+        subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
+                                         2000, 3000, 4000));
+        pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
+        subnet_->addPool(pool_);
+
+        CfgMgr::instance().deleteSubnets6();
+        CfgMgr::instance().addSubnet6(subnet_);
+    }
+
+    // Checks that server response (ADVERTISE or REPLY) contains proper IA_NA option
+    // It returns IAADDR option for each chaining with checkIAAddr method.
+    boost::shared_ptr<Option6IAAddr> checkIA_NA(const Pkt6Ptr& rsp, uint32_t expected_iaid,
+                                            uint32_t expected_t1, uint32_t expected_t2) {
+        OptionPtr tmp = rsp->getOption(D6O_IA_NA);
+        // Can't use ASSERT_TRUE() in method that returns something
+        if (!tmp) {
+            ADD_FAILURE() << "IA_NA option not present in response";
+            return (boost::shared_ptr<Option6IAAddr>());
+        }
+
+        boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+        if (!ia) {
+            ADD_FAILURE() << "IA_NA cannot convert option ptr to Option6";
+            return (boost::shared_ptr<Option6IAAddr>());
+        }
+
+        EXPECT_EQ(expected_iaid, ia->getIAID());
+        EXPECT_EQ(expected_t1, ia->getT1());
+        EXPECT_EQ(expected_t2, ia->getT2());
+
+        tmp = ia->getOption(D6O_IAADDR);
+        boost::shared_ptr<Option6IAAddr> addr = boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
+        return (addr);
+    }
+
+    // Check that generated IAADDR option contains expected address
+    // and lifetime values match the configured subnet
+    void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
+                     const IOAddress& expected_addr,
+                     uint32_t /* expected_preferred */,
+                     uint32_t /* expected_valid */) {
+
+        // Check that the assigned address is indeed from the configured pool.
+        // Note that when comparing addresses, we compare the textual
+        // representation. IOAddress does not support being streamed to
+        // an ostream, which means it can't be used in EXPECT_EQ.
+        EXPECT_TRUE(subnet_->inPool(addr->getAddress()));
+        EXPECT_EQ(expected_addr.toText(), addr->getAddress().toText());
+        EXPECT_EQ(addr->getPreferred(), subnet_->getPreferred());
+        EXPECT_EQ(addr->getValid(), subnet_->getValid());
+    }
+
+    // Checks if the lease sent to client is present in the database
+    // and is valid when checked agasint the configured subnet
+    Lease6Ptr checkLease(const DuidPtr& duid, const OptionPtr& ia_na,
+                         boost::shared_ptr<Option6IAAddr> addr) {
+        boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(ia_na);
+
+        Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(addr->getAddress());
+        if (!lease) {
+            std::cout << "Lease for " << addr->getAddress().toText()
+                      << " not found in the database backend.";
+            return (Lease6Ptr());
+        }
+
+        EXPECT_EQ(addr->getAddress().toText(), lease->addr_.toText());
+        EXPECT_TRUE(*lease->duid_ == *duid);
+        EXPECT_EQ(ia->getIAID(), lease->iaid_);
+        EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
+
+        return (lease);
+    }
+
+    ~Dhcpv6SrvTest() {
+        CfgMgr::instance().deleteSubnets6();
+    };
+
+    /// A subnet used in most tests
+    Subnet6Ptr subnet_;
+
+    /// A pool used in most tests
+    Pool6Ptr pool_;
+};
+
+}; // end of isc::test namespace
+}; // end of isc namespace
diff --git a/src/bin/dhcp6/tests/hooks_unittest.cc b/src/bin/dhcp6/tests/hooks_unittest.cc
new file mode 100644
index 0000000..717de08
--- /dev/null
+++ b/src/bin/dhcp6/tests/hooks_unittest.cc
@@ -0,0 +1,1457 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/duid.h>
+#include <dhcp6/config_parser.h>
+#include <dhcp/dhcp6.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/utils.h>
+#include <util/buffer.h>
+#include <util/range_utilities.h>
+#include <hooks/server_hooks.h>
+
+#include <dhcp6/tests/dhcp6_test_utils.h>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+#include <unistd.h>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+using namespace isc;
+using namespace isc::test;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace isc::hooks;
+using namespace std;
+
+// namespace has to be named, because friends are defined in Dhcpv6Srv class
+// Maybe it should be isc::test?
+namespace {
+
+// Checks if hooks are implemented properly.
+TEST_F(Dhcpv6SrvTest, Hooks) {
+    NakedDhcpv6Srv srv(0);
+
+    // check if appropriate hooks are registered
+    int hook_index_buffer6_receive = -1;
+    int hook_index_buffer6_send    = -1;
+    int hook_index_lease6_renew    = -1;
+    int hook_index_lease6_release  = -1;
+    int hook_index_pkt6_received   = -1;
+    int hook_index_select_subnet   = -1;
+    int hook_index_pkt6_send       = -1;
+
+    // check if appropriate indexes are set
+    EXPECT_NO_THROW(hook_index_buffer6_receive = ServerHooks::getServerHooks()
+                    .getIndex("buffer6_receive"));
+    EXPECT_NO_THROW(hook_index_buffer6_send = ServerHooks::getServerHooks()
+                    .getIndex("buffer6_send"));
+    EXPECT_NO_THROW(hook_index_lease6_renew = ServerHooks::getServerHooks()
+                    .getIndex("lease6_renew"));
+    EXPECT_NO_THROW(hook_index_lease6_release = ServerHooks::getServerHooks()
+                    .getIndex("lease6_release"));
+    EXPECT_NO_THROW(hook_index_pkt6_received = ServerHooks::getServerHooks()
+                    .getIndex("pkt6_receive"));
+    EXPECT_NO_THROW(hook_index_select_subnet = ServerHooks::getServerHooks()
+                    .getIndex("subnet6_select"));
+    EXPECT_NO_THROW(hook_index_pkt6_send     = ServerHooks::getServerHooks()
+                    .getIndex("pkt6_send"));
+
+    EXPECT_TRUE(hook_index_pkt6_received   > 0);
+    EXPECT_TRUE(hook_index_select_subnet   > 0);
+    EXPECT_TRUE(hook_index_pkt6_send       > 0);
+    EXPECT_TRUE(hook_index_buffer6_receive > 0);
+    EXPECT_TRUE(hook_index_buffer6_send    > 0);
+    EXPECT_TRUE(hook_index_lease6_renew    > 0);
+    EXPECT_TRUE(hook_index_lease6_release  > 0);
+}
+
+// This function returns buffer for very simple Solicit
+Pkt6* captureSimpleSolicit() {
+    Pkt6* pkt;
+    uint8_t data[] = {
+        1,  // type 1 = SOLICIT
+        0xca, 0xfe, 0x01, // trans-id = 0xcafe01
+        0, 1, // option type 1 (client-id)
+        0, 10, // option lenth 10
+        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // DUID
+        0, 3, // option type 3 (IA_NA)
+        0, 12, // option length 12
+        0, 0, 0, 1, // iaid = 1
+        0, 0, 0, 0, // T1 = 0
+        0, 0, 0, 0  // T2 = 0
+    };
+
+    pkt = new Pkt6(data, sizeof(data));
+    pkt->setRemotePort(546);
+    pkt->setRemoteAddr(IOAddress("fe80::1"));
+    pkt->setLocalPort(0);
+    pkt->setLocalAddr(IOAddress("ff02::1:2"));
+    pkt->setIndex(2);
+    pkt->setIface("eth0");
+
+    return (pkt);
+}
+
+/// @brief a class dedicated to Hooks testing in DHCPv6 server
+///
+/// This class has a number of static members, because each non-static
+/// method has implicit 'this' parameter, so it does not match callout
+/// signature and couldn't be registered. Furthermore, static methods
+/// can't modify non-static members (for obvious reasons), so many
+/// fields are declared static. It is still better to keep them as
+/// one class rather than unrelated collection of global objects.
+class HooksDhcpv6SrvTest : public Dhcpv6SrvTest {
+
+public:
+
+    /// @brief creates Dhcpv6Srv and prepares buffers for callouts
+    HooksDhcpv6SrvTest() {
+
+        // Allocate new DHCPv6 Server
+        srv_.reset(new NakedDhcpv6Srv(0));
+
+        // Clear static buffers
+        resetCalloutBuffers();
+    }
+
+    /// @brief destructor (deletes Dhcpv6Srv)
+    ~HooksDhcpv6SrvTest() {
+    }
+
+    /// @brief creates an option with specified option code
+    ///
+    /// This method is static, because it is used from callouts
+    /// that do not have a pointer to HooksDhcpv6SSrvTest object
+    ///
+    /// @param option_code code of option to be created
+    ///
+    /// @return pointer to create option object
+    static OptionPtr createOption(uint16_t option_code) {
+
+        uint8_t payload[] = {
+            0xa, 0xb, 0xc, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14
+        };
+
+        OptionBuffer tmp(payload, payload + sizeof(payload));
+        return OptionPtr(new Option(Option::V6, option_code, tmp));
+    }
+
+    /// test callback that stores received callout name and pkt6 value
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt6_receive_callout(CalloutHandle& callout_handle) {
+        callback_name_ = string("pkt6_receive");
+
+        callout_handle.getArgument("query6", callback_pkt6_);
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    /// test callback that changes client-id value
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt6_receive_change_clientid(CalloutHandle& callout_handle) {
+
+        Pkt6Ptr pkt;
+        callout_handle.getArgument("query6", pkt);
+
+        // Get rid of the old client-id
+        pkt->delOption(D6O_CLIENTID);
+
+        // Add a new option
+        pkt->addOption(createOption(D6O_CLIENTID));
+
+        // Carry on as usual
+        return pkt6_receive_callout(callout_handle);
+    }
+
+    /// Test callback that deletes client-id
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt6_receive_delete_clientid(CalloutHandle& callout_handle) {
+
+        Pkt6Ptr pkt;
+        callout_handle.getArgument("query6", pkt);
+
+        // Get rid of the old client-id
+        pkt->delOption(D6O_CLIENTID);
+
+        // Carry on as usual
+        return pkt6_receive_callout(callout_handle);
+    }
+
+    /// Test callback that sets skip flag
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt6_receive_skip(CalloutHandle& callout_handle) {
+
+        Pkt6Ptr pkt;
+        callout_handle.getArgument("query6", pkt);
+
+        callout_handle.setSkip(true);
+
+        // Carry on as usual
+        return pkt6_receive_callout(callout_handle);
+    }
+
+    /// Test callback that stores received callout name and pkt6 value
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    buffer6_receive_callout(CalloutHandle& callout_handle) {
+        callback_name_ = string("buffer6_receive");
+
+        callout_handle.getArgument("query6", callback_pkt6_);
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    /// Test callback that changes first byte of client-id value
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    buffer6_receive_change_clientid(CalloutHandle& callout_handle) {
+
+        Pkt6Ptr pkt;
+        callout_handle.getArgument("query6", pkt);
+
+        // If there is at least one option with data
+        if (pkt->data_.size() > Pkt6::DHCPV6_PKT_HDR_LEN + Option::OPTION6_HDR_LEN) {
+            // Offset of the first byte of the first option. Let's set this byte
+            // to some new value that we could later check
+            pkt->data_[Pkt6::DHCPV6_PKT_HDR_LEN + Option::OPTION6_HDR_LEN] = 0xff;
+        }
+
+        // Carry on as usual
+        return buffer6_receive_callout(callout_handle);
+    }
+
+    /// Test callback that deletes client-id
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    buffer6_receive_delete_clientid(CalloutHandle& callout_handle) {
+
+        Pkt6Ptr pkt;
+        callout_handle.getArgument("query6", pkt);
+
+        // this is modified SOLICIT (with missing mandatory client-id)
+        uint8_t data[] = {
+        1,  // type 1 = SOLICIT
+        0xca, 0xfe, 0x01, // trans-id = 0xcafe01
+        0, 3, // option type 3 (IA_NA)
+        0, 12, // option length 12
+        0, 0, 0, 1, // iaid = 1
+        0, 0, 0, 0, // T1 = 0
+        0, 0, 0, 0  // T2 = 0
+        };
+
+        OptionBuffer modifiedMsg(data, data + sizeof(data));
+
+        pkt->data_ = modifiedMsg;
+
+        // carry on as usual
+        return buffer6_receive_callout(callout_handle);
+    }
+
+    /// Test callback that sets skip flag
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    buffer6_receive_skip(CalloutHandle& callout_handle) {
+
+        Pkt6Ptr pkt;
+        callout_handle.getArgument("query6", pkt);
+
+        callout_handle.setSkip(true);
+
+        // Carry on as usual
+        return buffer6_receive_callout(callout_handle);
+    }
+
+    /// Test callback that stores received callout name and pkt6 value
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt6_send_callout(CalloutHandle& callout_handle) {
+        callback_name_ = string("pkt6_send");
+
+        callout_handle.getArgument("response6", callback_pkt6_);
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    // Test callback that changes server-id
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt6_send_change_serverid(CalloutHandle& callout_handle) {
+
+        Pkt6Ptr pkt;
+        callout_handle.getArgument("response6", pkt);
+
+        // Get rid of the old server-id
+        pkt->delOption(D6O_SERVERID);
+
+        // Add a new option
+        pkt->addOption(createOption(D6O_SERVERID));
+
+        // Carry on as usual
+        return pkt6_send_callout(callout_handle);
+    }
+
+    /// Test callback that deletes server-id
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt6_send_delete_serverid(CalloutHandle& callout_handle) {
+
+        Pkt6Ptr pkt;
+        callout_handle.getArgument("response6", pkt);
+
+        // Get rid of the old client-id
+        pkt->delOption(D6O_SERVERID);
+
+        // Carry on as usual
+        return pkt6_send_callout(callout_handle);
+    }
+
+    /// Test callback that sets skip flag
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    pkt6_send_skip(CalloutHandle& callout_handle) {
+
+        Pkt6Ptr pkt;
+        callout_handle.getArgument("response6", pkt);
+
+        callout_handle.setSkip(true);
+
+        // carry on as usual
+        return pkt6_send_callout(callout_handle);
+    }
+
+    /// Test callback that stores received callout name and subnet6 values
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    subnet6_select_callout(CalloutHandle& callout_handle) {
+        callback_name_ = string("subnet6_select");
+
+        callout_handle.getArgument("query6", callback_pkt6_);
+        callout_handle.getArgument("subnet6", callback_subnet6_);
+        callout_handle.getArgument("subnet6collection", callback_subnet6collection_);
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    /// Test callback that picks the other subnet if possible.
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    subnet6_select_different_subnet_callout(CalloutHandle& callout_handle) {
+
+        // Call the basic calllout to record all passed values
+        subnet6_select_callout(callout_handle);
+
+        const Subnet6Collection* subnets;
+        Subnet6Ptr subnet;
+        callout_handle.getArgument("subnet6", subnet);
+        callout_handle.getArgument("subnet6collection", subnets);
+
+        // Let's change to a different subnet
+        if (subnets->size() > 1) {
+            subnet = (*subnets)[1]; // Let's pick the other subnet
+            callout_handle.setArgument("subnet6", subnet);
+        }
+
+        return (0);
+    }
+
+    /// Test callback that stores received callout name and pkt6 value
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    lease6_renew_callout(CalloutHandle& callout_handle) {
+        callback_name_ = string("lease6_renew");
+
+        callout_handle.getArgument("query6", callback_pkt6_);
+        callout_handle.getArgument("lease6", callback_lease6_);
+        callout_handle.getArgument("ia_na", callback_ia_na_);
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    /// The following values are used by the callout to override
+    /// renewed lease parameters
+    static const uint32_t override_iaid_;
+    static const uint32_t override_t1_;
+    static const uint32_t override_t2_;
+    static const uint32_t override_preferred_;
+    static const uint32_t override_valid_;
+
+    /// Test callback that overrides received lease. It updates
+    /// T1, T2, preferred and valid lifetimes
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    lease6_renew_update_callout(CalloutHandle& callout_handle) {
+        callback_name_ = string("lease6_renew");
+
+        callout_handle.getArgument("query6", callback_pkt6_);
+        callout_handle.getArgument("lease6", callback_lease6_);
+        callout_handle.getArgument("ia_na", callback_ia_na_);
+
+        // Let's override some values in the lease
+        callback_lease6_->iaid_          = override_iaid_;
+        callback_lease6_->t1_            = override_t1_;
+        callback_lease6_->t2_            = override_t2_;
+        callback_lease6_->preferred_lft_ = override_preferred_;
+        callback_lease6_->valid_lft_     = override_valid_;
+
+        // Override the values to be sent to the client as well
+        callback_ia_na_->setIAID(override_iaid_);
+        callback_ia_na_->setT1(override_t1_);
+        callback_ia_na_->setT2(override_t2_);
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    /// Test callback that sets the skip flag
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    lease6_renew_skip_callout(CalloutHandle& callout_handle) {
+        callback_name_ = string("lease6_renew");
+
+        callout_handle.setSkip(true);
+
+        return (0);
+    }
+
+    /// Test callback that stores received callout name passed parameters
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    lease6_release_callout(CalloutHandle& callout_handle) {
+        callback_name_ = string("lease6_release");
+
+        callout_handle.getArgument("query6", callback_pkt6_);
+        callout_handle.getArgument("lease6", callback_lease6_);
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    /// Test callback that sets the skip flag
+    /// @param callout_handle handle passed by the hooks framework
+    /// @return always 0
+    static int
+    lease6_release_skip_callout(CalloutHandle& callout_handle) {
+        callback_name_ = string("lease6_release");
+
+        callout_handle.setSkip(true);
+
+        return (0);
+    }
+
+    /// Resets buffers used to store data received by callouts
+    void resetCalloutBuffers() {
+        callback_name_ = string("");
+        callback_pkt6_.reset();
+        callback_subnet6_.reset();
+        callback_lease6_.reset();
+        callback_ia_na_.reset();
+        callback_subnet6collection_ = NULL;
+        callback_argument_names_.clear();
+    }
+
+    /// Pointer to Dhcpv6Srv that is used in tests
+    boost::scoped_ptr<NakedDhcpv6Srv> srv_;
+
+    // The following fields are used in testing pkt6_receive_callout
+
+    /// String name of the received callout
+    static string callback_name_;
+
+    /// Pkt6 structure returned in the callout
+    static Pkt6Ptr callback_pkt6_;
+
+    /// Pointer to lease6
+    static Lease6Ptr callback_lease6_;
+
+    /// Pointer to IA_NA option being renewed
+    static boost::shared_ptr<Option6IA> callback_ia_na_;
+
+    /// Pointer to a subnet received by callout
+    static Subnet6Ptr callback_subnet6_;
+
+    /// A list of all available subnets (received by callout)
+    static const Subnet6Collection* callback_subnet6collection_;
+
+    /// A list of all received arguments
+    static vector<string> callback_argument_names_;
+};
+
+// The following parameters are used by callouts to override
+// renewed lease parameters
+const uint32_t HooksDhcpv6SrvTest::override_iaid_ = 1000;
+const uint32_t HooksDhcpv6SrvTest::override_t1_ = 1001;
+const uint32_t HooksDhcpv6SrvTest::override_t2_ = 1002;
+const uint32_t HooksDhcpv6SrvTest::override_preferred_ = 1003;
+const uint32_t HooksDhcpv6SrvTest::override_valid_ = 1004;
+
+// The following fields are used in testing pkt6_receive_callout.
+// See fields description in the class for details
+string HooksDhcpv6SrvTest::callback_name_;
+Pkt6Ptr HooksDhcpv6SrvTest::callback_pkt6_;
+Subnet6Ptr HooksDhcpv6SrvTest::callback_subnet6_;
+const Subnet6Collection* HooksDhcpv6SrvTest::callback_subnet6collection_;
+vector<string> HooksDhcpv6SrvTest::callback_argument_names_;
+Lease6Ptr HooksDhcpv6SrvTest::callback_lease6_;
+boost::shared_ptr<Option6IA> HooksDhcpv6SrvTest::callback_ia_na_;
+
+// Checks if callouts installed on pkt6_receive are indeed called and the
+// all necessary parameters are passed.
+//
+// Note that the test name does not follow test naming convention,
+// but the proper hook name is "buffer6_receive".
+TEST_F(HooksDhcpv6SrvTest, simple_buffer6_receive) {
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "buffer6_receive", buffer6_receive_callout));
+
+    // Let's create a simple SOLICIT
+    Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive6(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt6_receive callback.
+    srv_->run();
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("buffer6_receive", callback_name_);
+
+    // Check that pkt6 argument passing was successful and returned proper value
+    EXPECT_TRUE(callback_pkt6_.get() == sol.get());
+
+    // Check that all expected parameters are there
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back(string("query6"));
+
+    EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+}
+
+// Checks if callouts installed on pkt6_received is able to change
+// the values and the parameters are indeed used by the server.
+TEST_F(HooksDhcpv6SrvTest, valueChange_buffer6_receive) {
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "buffer6_receive", buffer6_receive_change_clientid));
+
+    // Let's create a simple SOLICIT
+    Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive6(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt6_receive callback.
+    srv_->run();
+
+    // Check that the server did send a reposonce
+    ASSERT_EQ(1, srv_->fake_sent_.size());
+
+    // Make sure that we received a response
+    Pkt6Ptr adv = srv_->fake_sent_.front();
+    ASSERT_TRUE(adv);
+
+    // Get client-id...
+    OptionPtr clientid = adv->getOption(D6O_CLIENTID);
+
+    ASSERT_TRUE(clientid);
+
+    // ... and check if it is the modified value
+    EXPECT_EQ(0xff, clientid->getData()[0]);
+}
+
+// Checks if callouts installed on buffer6_receive is able to delete
+// existing options and that change impacts server processing (mandatory
+// client-id option is deleted, so the packet is expected to be dropped)
+TEST_F(HooksDhcpv6SrvTest, deleteClientId_buffer6_receive) {
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "buffer6_receive", buffer6_receive_delete_clientid));
+
+    // Let's create a simple SOLICIT
+    Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive6(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt6_receive callback.
+    srv_->run();
+
+    // Check that the server dropped the packet and did not send a response
+    ASSERT_EQ(0, srv_->fake_sent_.size());
+}
+
+// Checks if callouts installed on buffer6_received is able to set skip flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv6SrvTest, skip_buffer6_receive) {
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "buffer6_receive", buffer6_receive_skip));
+
+    // Let's create a simple SOLICIT
+    Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive6(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt6_receive callback.
+    srv_->run();
+
+    // Check that the server dropped the packet and did not produce any response
+    ASSERT_EQ(0, srv_->fake_sent_.size());
+}
+
+// Checks if callouts installed on pkt6_receive are indeed called and the
+// all necessary parameters are passed.
+//
+// Note that the test name does not follow test naming convention,
+// but the proper hook name is "pkt6_receive".
+TEST_F(HooksDhcpv6SrvTest, simple_pkt6_receive) {
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt6_receive", pkt6_receive_callout));
+
+    // Let's create a simple SOLICIT
+    Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive6(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt6_receive callback.
+    srv_->run();
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("pkt6_receive", callback_name_);
+
+    // Check that pkt6 argument passing was successful and returned proper value
+    EXPECT_TRUE(callback_pkt6_.get() == sol.get());
+
+    // Check that all expected parameters are there
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back(string("query6"));
+
+    EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+}
+
+// Checks if callouts installed on pkt6_received is able to change
+// the values and the parameters are indeed used by the server.
+TEST_F(HooksDhcpv6SrvTest, valueChange_pkt6_receive) {
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt6_receive", pkt6_receive_change_clientid));
+
+    // Let's create a simple SOLICIT
+    Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive6(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt6_receive callback.
+    srv_->run();
+
+    // Check that the server did send a reposonce
+    ASSERT_EQ(1, srv_->fake_sent_.size());
+
+    // Make sure that we received a response
+    Pkt6Ptr adv = srv_->fake_sent_.front();
+    ASSERT_TRUE(adv);
+
+    // Get client-id...
+    OptionPtr clientid = adv->getOption(D6O_CLIENTID);
+
+    // ... and check if it is the modified value
+    OptionPtr expected = createOption(D6O_CLIENTID);
+    EXPECT_TRUE(clientid->equal(expected));
+}
+
+// Checks if callouts installed on pkt6_received is able to delete
+// existing options and that change impacts server processing (mandatory
+// client-id option is deleted, so the packet is expected to be dropped)
+TEST_F(HooksDhcpv6SrvTest, deleteClientId_pkt6_receive) {
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt6_receive", pkt6_receive_delete_clientid));
+
+    // Let's create a simple SOLICIT
+    Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive6(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt6_receive callback.
+    srv_->run();
+
+    // Check that the server dropped the packet and did not send a response
+    ASSERT_EQ(0, srv_->fake_sent_.size());
+}
+
+// Checks if callouts installed on pkt6_received is able to set skip flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv6SrvTest, skip_pkt6_receive) {
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt6_receive", pkt6_receive_skip));
+
+    // Let's create a simple SOLICIT
+    Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive6(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt6_receive callback.
+    srv_->run();
+
+    // Check that the server dropped the packet and did not produce any response
+    ASSERT_EQ(0, srv_->fake_sent_.size());
+}
+
+
+// Checks if callouts installed on pkt6_send are indeed called and the
+// all necessary parameters are passed.
+TEST_F(HooksDhcpv6SrvTest, simple_pkt6_send) {
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt6_send", pkt6_send_callout));
+
+    // Let's create a simple SOLICIT
+    Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive6(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt6_receive callback.
+    srv_->run();
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("pkt6_send", callback_name_);
+
+    // Check that there is one packet sent
+    ASSERT_EQ(1, srv_->fake_sent_.size());
+    Pkt6Ptr adv = srv_->fake_sent_.front();
+
+    // Check that pkt6 argument passing was successful and returned proper value
+    EXPECT_TRUE(callback_pkt6_.get() == adv.get());
+
+    // Check that all expected parameters are there
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back(string("response6"));
+    EXPECT_TRUE(expected_argument_names == callback_argument_names_);
+}
+
+// Checks if callouts installed on pkt6_send is able to change
+// the values and the packet sent contains those changes
+TEST_F(HooksDhcpv6SrvTest, valueChange_pkt6_send) {
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt6_send", pkt6_send_change_serverid));
+
+    // Let's create a simple SOLICIT
+    Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive6(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt6_receive callback.
+    srv_->run();
+
+    // Check that the server did send a response
+    ASSERT_EQ(1, srv_->fake_sent_.size());
+
+    // Make sure that we received a response
+    Pkt6Ptr adv = srv_->fake_sent_.front();
+    ASSERT_TRUE(adv);
+
+    // Get client-id...
+    OptionPtr clientid = adv->getOption(D6O_SERVERID);
+
+    // ... and check if it is the modified value
+    OptionPtr expected = createOption(D6O_SERVERID);
+    EXPECT_TRUE(clientid->equal(expected));
+}
+
+// Checks if callouts installed on pkt6_send is able to delete
+// existing options and that server applies those changes. In particular,
+// we are trying to send a packet without server-id. The packet should
+// be sent
+TEST_F(HooksDhcpv6SrvTest, deleteServerId_pkt6_send) {
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt6_send", pkt6_send_delete_serverid));
+
+    // Let's create a simple SOLICIT
+    Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive6(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt6_receive callback.
+    srv_->run();
+
+    // Check that the server indeed sent a malformed ADVERTISE
+    ASSERT_EQ(1, srv_->fake_sent_.size());
+
+    // Get that ADVERTISE
+    Pkt6Ptr adv = srv_->fake_sent_.front();
+    ASSERT_TRUE(adv);
+
+    // Make sure that it does not have server-id
+    EXPECT_FALSE(adv->getOption(D6O_SERVERID));
+}
+
+// Checks if callouts installed on pkt6_skip is able to set skip flag that
+// will cause the server to not process the packet (drop), even though it is valid.
+TEST_F(HooksDhcpv6SrvTest, skip_pkt6_send) {
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "pkt6_send", pkt6_send_skip));
+
+    // Let's create a simple REQUEST
+    Pkt6Ptr sol = Pkt6Ptr(captureSimpleSolicit());
+
+    // Simulate that we have received that traffic
+    srv_->fakeReceive(sol);
+
+    // Server will now process to run its normal loop, but instead of calling
+    // IfaceMgr::receive6(), it will read all packets from the list set by
+    // fakeReceive()
+    // In particular, it should call registered pkt6_receive callback.
+    srv_->run();
+
+    // Check that the server send the packet
+    ASSERT_EQ(1, srv_->fake_sent_.size());
+
+    // But the sent packet should have 0 length (we told the server to
+    // skip pack(), but did not do packing outselves)
+    Pkt6Ptr sent = srv_->fake_sent_.front();
+
+    // The actual size of sent packet should be 0
+    EXPECT_EQ(0, sent->getBuffer().getLength());
+}
+
+// This test checks if subnet6_select callout is triggered and reports
+// valid parameters
+TEST_F(HooksDhcpv6SrvTest, subnet6_select) {
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "subnet6_select", subnet6_select_callout));
+
+    // Configure 2 subnets, both directly reachable over local interface
+    // (let's not complicate the matter with relays)
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/64\" ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface\": \"" + valid_iface_ + "\" "
+        " }, {"
+        "    \"pool\": [ \"2001:db8:2::/64\" ],"
+        "    \"subnet\": \"2001:db8:2::/48\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    // Prepare solicit packet. Server should select first subnet for it
+    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    sol->setRemoteAddr(IOAddress("fe80::abcd"));
+    sol->setIface(valid_iface_);
+    sol->addOption(generateIA(234, 1500, 3000));
+    OptionPtr clientid = generateClientId();
+    sol->addOption(clientid);
+
+    // Pass it to the server and get an advertise
+    Pkt6Ptr adv = srv_->processSolicit(sol);
+
+    // Check if we get response at all
+    ASSERT_TRUE(adv);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("subnet6_select", callback_name_);
+
+    // Check that pkt6 argument passing was successful and returned proper value
+    EXPECT_TRUE(callback_pkt6_.get() == sol.get());
+
+    const Subnet6Collection* exp_subnets = CfgMgr::instance().getSubnets6();
+
+    // The server is supposed to pick the first subnet, because of matching
+    // interface. Check that the value is reported properly.
+    ASSERT_TRUE(callback_subnet6_);
+    EXPECT_EQ(callback_subnet6_.get(), exp_subnets->front().get());
+
+    // Server is supposed to report two subnets
+    ASSERT_EQ(exp_subnets->size(), callback_subnet6collection_->size());
+
+    // Compare that the available subnets are reported as expected
+    EXPECT_TRUE((*exp_subnets)[0].get() == (*callback_subnet6collection_)[0].get());
+    EXPECT_TRUE((*exp_subnets)[1].get() == (*callback_subnet6collection_)[1].get());
+}
+
+// This test checks if callout installed on subnet6_select hook point can pick
+// a different subnet.
+TEST_F(HooksDhcpv6SrvTest, subnet_select_change) {
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "subnet6_select", subnet6_select_different_subnet_callout));
+
+    // Configure 2 subnets, both directly reachable over local interface
+    // (let's not complicate the matter with relays)
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/64\" ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface\": \"" + valid_iface_ + "\" "
+        " }, {"
+        "    \"pool\": [ \"2001:db8:2::/64\" ],"
+        "    \"subnet\": \"2001:db8:2::/48\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    // Prepare solicit packet. Server should select first subnet for it
+    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    sol->setRemoteAddr(IOAddress("fe80::abcd"));
+    sol->setIface(valid_iface_);
+    sol->addOption(generateIA(234, 1500, 3000));
+    OptionPtr clientid = generateClientId();
+    sol->addOption(clientid);
+
+    // Pass it to the server and get an advertise
+    Pkt6Ptr adv = srv_->processSolicit(sol);
+
+    // Check if we get response at all
+    ASSERT_TRUE(adv);
+
+    // The response should have an address from second pool, so let's check it
+    OptionPtr tmp = adv->getOption(D6O_IA_NA);
+    ASSERT_TRUE(tmp);
+    boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+    ASSERT_TRUE(ia);
+    tmp = ia->getOption(D6O_IAADDR);
+    ASSERT_TRUE(tmp);
+    boost::shared_ptr<Option6IAAddr> addr_opt =
+        boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
+    ASSERT_TRUE(addr_opt);
+
+    // Get all subnets and use second subnet for verification
+    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    ASSERT_EQ(2, subnets->size());
+
+    // Advertised address must belong to the second pool (in subnet's range,
+    // in dynamic pool)
+    EXPECT_TRUE((*subnets)[1]->inRange(addr_opt->getAddress()));
+    EXPECT_TRUE((*subnets)[1]->inPool(addr_opt->getAddress()));
+}
+
+// This test verifies that incoming (positive) RENEW can be handled properly,
+// and the lease6_renew callouts are triggered.
+TEST_F(HooksDhcpv6SrvTest, basic_lease6_renew) {
+    NakedDhcpv6Srv srv(0);
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease6_renew", lease6_renew_callout));
+
+    const IOAddress addr("2001:db8:1:1::cafe:babe");
+    const uint32_t iaid = 234;
+
+    // Generate client-id also duid_
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(addr));
+
+    // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+    // value on purpose. They should be updated during RENEW.
+    Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+                               501, 502, 503, 504, subnet_->getID(), 0));
+    lease->cltt_ = 1234;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Check that the lease is really in the database
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_TRUE(l);
+
+    // Check that T1, T2, preferred, valid and cltt really set and not using
+    // previous (500, 501, etc.) values
+    EXPECT_NE(l->t1_, subnet_->getT1());
+    EXPECT_NE(l->t2_, subnet_->getT2());
+    EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+    EXPECT_NE(l->valid_lft_, subnet_->getValid());
+    EXPECT_NE(l->cltt_, time(NULL));
+
+    // Let's create a RENEW
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+    OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+    ia->addOption(renewed_addr_opt);
+    req->addOption(ia);
+    req->addOption(clientid);
+
+    // Server-id is mandatory in RENEW
+    req->addOption(srv.getServerID());
+
+    // Pass it to the server and hope for a REPLY
+    Pkt6Ptr reply = srv.processRenew(req);
+    ASSERT_TRUE(reply);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("lease6_renew", callback_name_);
+
+    // Check that appropriate parameters are passed to the callouts
+    EXPECT_TRUE(callback_pkt6_);
+    EXPECT_TRUE(callback_lease6_);
+    EXPECT_TRUE(callback_ia_na_);
+
+    // Check if all expected parameters were really received
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back("query6");
+    expected_argument_names.push_back("lease6");
+    expected_argument_names.push_back("ia_na");
+
+    sort(callback_argument_names_.begin(), callback_argument_names_.end());
+    sort(expected_argument_names.begin(), expected_argument_names.end());
+
+    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+    // Check if we get response at all
+    checkResponse(reply, DHCPV6_REPLY, 1234);
+
+    OptionPtr tmp = reply->getOption(D6O_IA_NA);
+    ASSERT_TRUE(tmp);
+
+    // Check that IA_NA was returned and that there's an address included
+    boost::shared_ptr<Option6IAAddr> addr_opt = checkIA_NA(reply, 234, subnet_->getT1(),
+                                                           subnet_->getT2());
+
+    ASSERT_TRUE(addr_opt);
+    // Check that the lease is really in the database
+    l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+    ASSERT_TRUE(l);
+
+    // Check that the lease has been returned
+    ASSERT_TRUE(callback_lease6_);
+
+    // Check that the returned lease6 in callout is the same as the one in the
+    // database
+    EXPECT_TRUE(*callback_lease6_ == *l);
+}
+
+// This test verifies that incoming (positive) RENEW can be handled properly,
+// and the lease6_renew callouts are able to change the lease being updated.
+TEST_F(HooksDhcpv6SrvTest, leaseUpdate_lease6_renew) {
+    NakedDhcpv6Srv srv(0);
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease6_renew", lease6_renew_update_callout));
+
+    const IOAddress addr("2001:db8:1:1::cafe:babe");
+    const uint32_t iaid = 234;
+
+    // Generate client-id also duid_
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(addr));
+
+    // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+    // value on purpose. They should be updated during RENEW.
+    Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+                               501, 502, 503, 504, subnet_->getID(), 0));
+    lease->cltt_ = 1234;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Check that the lease is really in the database
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_TRUE(l);
+
+    // Check that T1, T2, preferred, valid and cltt really set and not using
+    // previous (500, 501, etc.) values
+    EXPECT_NE(l->t1_, subnet_->getT1());
+    EXPECT_NE(l->t2_, subnet_->getT2());
+    EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+    EXPECT_NE(l->valid_lft_, subnet_->getValid());
+    EXPECT_NE(l->cltt_, time(NULL));
+
+    // Let's create a RENEW
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+    OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+    ia->addOption(renewed_addr_opt);
+    req->addOption(ia);
+    req->addOption(clientid);
+
+    // Server-id is mandatory in RENEW
+    req->addOption(srv.getServerID());
+
+    // Pass it to the server and hope for a REPLY
+    Pkt6Ptr reply = srv.processRenew(req);
+    ASSERT_TRUE(reply);
+
+    // Check if we get response at all
+    checkResponse(reply, DHCPV6_REPLY, 1234);
+
+    OptionPtr tmp = reply->getOption(D6O_IA_NA);
+    ASSERT_TRUE(tmp);
+
+    // Check that IA_NA was returned and that there's an address included
+    boost::shared_ptr<Option6IAAddr> addr_opt = checkIA_NA(reply, 1000, 1001, 1002);
+
+    ASSERT_TRUE(addr_opt);
+    // Check that the lease is really in the database
+    l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+    ASSERT_TRUE(l);
+
+    // Check that we chose the distinct override values
+    ASSERT_NE(override_t1_,        subnet_->getT1());
+    ASSERT_NE(override_t2_,        subnet_->getT2());
+    ASSERT_NE(override_preferred_, subnet_->getPreferred());
+    EXPECT_NE(override_valid_,     subnet_->getValid());
+
+    // Check that T1, T2, preferred, valid were overridden the the callout
+    EXPECT_EQ(override_t1_, l->t1_);
+    EXPECT_EQ(override_t2_, l->t2_);
+    EXPECT_EQ(override_preferred_, l->preferred_lft_);
+    EXPECT_EQ(override_valid_, l->valid_lft_);
+
+    // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+    int32_t cltt = static_cast<int32_t>(l->cltt_);
+    int32_t expected = static_cast<int32_t>(time(NULL));
+    // Equality or difference by 1 between cltt and expected is ok.
+    EXPECT_GE(1, abs(cltt - expected));
+
+    EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr_opt->getAddress()));
+}
+
+// This test verifies that incoming (positive) RENEW can be handled properly,
+// and the lease6_renew callouts are able to set the skip flag that will
+// reject the renewal
+TEST_F(HooksDhcpv6SrvTest, skip_lease6_renew) {
+    NakedDhcpv6Srv srv(0);
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease6_renew", lease6_renew_skip_callout));
+
+    const IOAddress addr("2001:db8:1:1::cafe:babe");
+    const uint32_t iaid = 234;
+
+    // Generate client-id also duid_
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(addr));
+
+    // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+    // value on purpose. They should be updated during RENEW.
+    Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+                               501, 502, 503, 504, subnet_->getID(), 0));
+    lease->cltt_ = 1234;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Check that the lease is really in the database
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_TRUE(l);
+
+    // Check that T1, T2, preferred, valid and cltt really set and not using
+    // previous (500, 501, etc.) values
+    EXPECT_NE(l->t1_, subnet_->getT1());
+    EXPECT_NE(l->t2_, subnet_->getT2());
+    EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+    EXPECT_NE(l->valid_lft_, subnet_->getValid());
+    EXPECT_NE(l->cltt_, time(NULL));
+
+    // Let's create a RENEW
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+    OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+    ia->addOption(renewed_addr_opt);
+    req->addOption(ia);
+    req->addOption(clientid);
+
+    // Server-id is mandatory in RENEW
+    req->addOption(srv.getServerID());
+
+    // Pass it to the server and hope for a REPLY
+    Pkt6Ptr reply = srv.processRenew(req);
+    ASSERT_TRUE(reply);
+
+    // Check that our callback was called
+    EXPECT_EQ("lease6_renew", callback_name_);
+
+    l = LeaseMgrFactory::instance().getLease6(addr);
+
+    // Check that the old values are still there and they were not
+    // updated by the renewal
+    EXPECT_NE(l->t1_, subnet_->getT1());
+    EXPECT_NE(l->t2_, subnet_->getT2());
+    EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+    EXPECT_NE(l->valid_lft_, subnet_->getValid());
+    EXPECT_NE(l->cltt_, time(NULL));
+}
+
+// This test verifies that incoming (positive) RELEASE can be handled properly,
+// that a REPLY is generated, that the response has status code and that the
+// lease is indeed removed from the database.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that does not include an IAADDR
+// - lease is actually removed from LeaseMgr
+TEST_F(HooksDhcpv6SrvTest, basic_lease6_release) {
+    NakedDhcpv6Srv srv(0);
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease6_release", lease6_release_callout));
+
+    const IOAddress addr("2001:db8:1:1::cafe:babe");
+    const uint32_t iaid = 234;
+
+    // Generate client-id also duid_
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(addr));
+
+    // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+    // value on purpose. They should be updated during RENEW.
+    Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+                               501, 502, 503, 504, subnet_->getID(), 0));
+    lease->cltt_ = 1234;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Check that the lease is really in the database
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_TRUE(l);
+
+    // Let's create a RELEASE
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+    OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+    ia->addOption(released_addr_opt);
+    req->addOption(ia);
+    req->addOption(clientid);
+
+    // Server-id is mandatory in RELEASE
+    req->addOption(srv.getServerID());
+
+    // Pass it to the server and hope for a REPLY
+    Pkt6Ptr reply = srv.processRelease(req);
+
+    ASSERT_TRUE(reply);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("lease6_release", callback_name_);
+
+    // Check that appropriate parameters are passed to the callouts
+    EXPECT_TRUE(callback_pkt6_);
+    EXPECT_TRUE(callback_lease6_);
+
+    // Check if all expected parameters were really received
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back("query6");
+    expected_argument_names.push_back("lease6");
+    sort(callback_argument_names_.begin(), callback_argument_names_.end());
+    sort(expected_argument_names.begin(), expected_argument_names.end());
+    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+    // Check that the lease is really gone in the database
+    // get lease by address
+    l = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_FALSE(l);
+
+    // Get lease by subnetid/duid/iaid combination
+    l = LeaseMgrFactory::instance().getLease6(*duid_, iaid, subnet_->getID());
+    ASSERT_FALSE(l);
+}
+
+// This test verifies that incoming (positive) RELEASE can be handled properly,
+// that a REPLY is generated, that the response has status code and that the
+// lease is indeed removed from the database.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that does not include an IAADDR
+// - lease is actually removed from LeaseMgr
+TEST_F(HooksDhcpv6SrvTest, skip_lease6_release) {
+    NakedDhcpv6Srv srv(0);
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease6_release", lease6_release_skip_callout));
+
+    const IOAddress addr("2001:db8:1:1::cafe:babe");
+    const uint32_t iaid = 234;
+
+    // Generate client-id also duid_
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool
+    ASSERT_TRUE(subnet_->inPool(addr));
+
+    // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+    // value on purpose. They should be updated during RENEW.
+    Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+                               501, 502, 503, 504, subnet_->getID(), 0));
+    lease->cltt_ = 1234;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Check that the lease is really in the database
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_TRUE(l);
+
+    // Let's create a RELEASE
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+    OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+    ia->addOption(released_addr_opt);
+    req->addOption(ia);
+    req->addOption(clientid);
+
+    // Server-id is mandatory in RELEASE
+    req->addOption(srv.getServerID());
+
+    // Pass it to the server and hope for a REPLY
+    Pkt6Ptr reply = srv.processRelease(req);
+
+    ASSERT_TRUE(reply);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("lease6_release", callback_name_);
+
+    // Check that the lease is still there
+    // get lease by address
+    l = LeaseMgrFactory::instance().getLease6(addr);
+    ASSERT_TRUE(l);
+
+    // Get lease by subnetid/duid/iaid combination
+    l = LeaseMgrFactory::instance().getLease6(*duid_, iaid, subnet_->getID());
+    ASSERT_TRUE(l);
+}
+
+}   // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/marker_file.h b/src/bin/dhcp6/tests/marker_file.h
new file mode 100644
index 0000000..56b1a81
--- /dev/null
+++ b/src/bin/dhcp6/tests/marker_file.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef MARKER_FILE_H
+#define MARKER_FILE_H
+
+/// @file
+/// Define a marker file that is used in tests to prove that an "unload"
+/// function has been called
+
+namespace {
+const char* const LOAD_MARKER_FILE = "/home/marcin/devel/bind10/src/bin/dhcp6/tests/load_marker.txt";
+const char* const UNLOAD_MARKER_FILE = "/home/marcin/devel/bind10/src/bin/dhcp6/tests/unload_marker.txt";
+}
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Check marker file
+///
+/// This function is used in some of the DHCP server tests.
+///
+/// Marker files are used by the load/unload functions in the hooks
+/// libraries in these tests to signal whether they have been loaded or
+/// unloaded.  The file (if present) contains a single line holding
+/// a set of characters.
+///
+/// This convenience function checks the file to see if the characters
+/// are those expected.
+///
+/// @param name Name of the marker file.
+/// @param expected Characters expected.  If a marker file is present,
+///        it is expected to contain characters.
+///
+/// @return true if all tests pass, false if not (in which case a failure
+///         will have been logged).
+bool
+checkMarkerFile(const char* name, const char* expected);
+
+/// @brief Check marker file exists
+///
+/// This function is used in some of the DHCP server tests.
+///
+/// Checkes that the specified file does NOT exist.
+///
+/// @param name Name of the marker file.
+///
+/// @return true if file exists, false if not.
+bool
+checkMarkerFileExists(const char* name);
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+
+#endif // MARKER_FILE_H
+
diff --git a/src/bin/dhcp6/tests/test_libraries.h b/src/bin/dhcp6/tests/test_libraries.h
new file mode 100644
index 0000000..96462e7
--- /dev/null
+++ b/src/bin/dhcp6/tests/test_libraries.h
@@ -0,0 +1,51 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TEST_LIBRARIES_H
+#define TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+
+// Take care of differences in DLL naming between operating systems.
+
+#ifdef OS_OSX
+#define DLL_SUFFIX ".dylib"
+
+#else
+#define DLL_SUFFIX ".so"
+
+#endif
+
+
+// Names of the libraries used in these tests.  These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// shared library.
+
+// Library with load/unload functions creating marker files to check their
+// operation.
+const char* const CALLOUT_LIBRARY_1 = "/home/marcin/devel/bind10/src/bin/dhcp6/tests/.libs/libco1"
+                                           DLL_SUFFIX;
+const char* const CALLOUT_LIBRARY_2 = "/home/marcin/devel/bind10/src/bin/dhcp6/tests/.libs/libco2"
+                                           DLL_SUFFIX;
+
+// Name of a library which is not present.
+const char* const NOT_PRESENT_LIBRARY = "/home/marcin/devel/bind10/src/bin/dhcp6/tests/.libs/libnothere"
+                                         DLL_SUFFIX;
+} // anonymous namespace
+
+
+#endif // TEST_LIBRARIES_H
diff --git a/src/bin/memmgr/memmgr.py.in b/src/bin/memmgr/memmgr.py.in
index 5c9040f..4bf27fe 100755
--- a/src/bin/memmgr/memmgr.py.in
+++ b/src/bin/memmgr/memmgr.py.in
@@ -162,7 +162,7 @@ class Memmgr(BIND10Server):
         # This makes the MemorySegmentBuilder exit its main loop. It
         # should make the builder thread joinable.
         with self._builder_cv:
-            self._builder_command_queue.append('shutdown')
+            self._builder_command_queue.append(('shutdown',))
             self._builder_cv.notify_all()
 
         self._builder_thread.join()
diff --git a/src/bin/memmgr/tests/memmgr_test.py b/src/bin/memmgr/tests/memmgr_test.py
index 3a5c175..3dae17f 100755
--- a/src/bin/memmgr/tests/memmgr_test.py
+++ b/src/bin/memmgr/tests/memmgr_test.py
@@ -137,9 +137,24 @@ class TestMemmgr(unittest.TestCase):
         self.assertEqual(1, answer[0])
         self.assertIsNotNone(re.search('not a directory', answer[1]))
 
-        # Bad update: directory exists but is not readable.
-        os.mkdir(self.__test_mapped_file_dir, 0o500) # drop writable bit
+    @unittest.skipIf(os.getuid() == 0,
+                     'test cannot be run as root user')
+    def test_configure_bad_permissions(self):
+        self.__mgr._setup_ccsession()
+
+        # Pretend specified directories exist and writable
+        os.path.isdir = lambda x: True
+        os.access = lambda x, y: True
+
+        # Initial configuration.
+        self.assertEqual((0, None),
+                         parse_answer(self.__mgr._config_handler({})))
+
+        os.path.isdir = self.__orig_isdir
         os.access = self.__orig_os_access
+
+        # Bad update: directory exists but is not writable.
+        os.mkdir(self.__test_mapped_file_dir, 0o500) # drop writable bit
         user_cfg = {'mapped_file_dir': self.__test_mapped_file_dir}
         answer = parse_answer(self.__mgr._config_handler(user_cfg))
         self.assertEqual(1, answer[0])
diff --git a/src/bin/resolver/main.cc b/src/bin/resolver/main.cc
index 457f285..9149ebb 100644
--- a/src/bin/resolver/main.cc
+++ b/src/bin/resolver/main.cc
@@ -168,7 +168,6 @@ main(int argc, char* argv[]) {
         resolver = boost::shared_ptr<Resolver>(new Resolver());
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_CREATED);
 
-        SimpleCallback* checkin = resolver->getCheckinProvider();
         DNSLookup* lookup = resolver->getDNSLookupProvider();
         DNSAnswer* answer = resolver->getDNSAnswerProvider();
 
@@ -217,7 +216,7 @@ main(int argc, char* argv[]) {
         cache.update(root_a_rrset);
         cache.update(root_aaaa_rrset);
 
-        DNSService dns_service(io_service, checkin, lookup, answer);
+        DNSService dns_service(io_service, lookup, answer);
         resolver->setDNSService(dns_service);
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_SERVICE_CREATED);
 
diff --git a/src/bin/resolver/resolver.cc b/src/bin/resolver/resolver.cc
index 41ed7af..2ac638f 100644
--- a/src/bin/resolver/resolver.cc
+++ b/src/bin/resolver/resolver.cc
@@ -338,25 +338,9 @@ public:
     }
 };
 
-// This is a derived class of \c SimpleCallback, to serve
-// as a callback in the asiolink module.  It checks for queued
-// configuration messages, and executes them if found.
-class ConfigCheck : public SimpleCallback {
-public:
-    ConfigCheck(Resolver* srv) : server_(srv) {}
-    virtual void operator()(const IOMessage&) const {
-        if (server_->getConfigSession()->hasQueuedMsgs()) {
-            server_->getConfigSession()->checkCommand();
-        }
-    }
-private:
-    Resolver* server_;
-};
-
 Resolver::Resolver() :
     impl_(new ResolverImpl()),
     dnss_(NULL),
-    checkin_(NULL),
     dns_lookup_(NULL),
     dns_answer_(new MessageAnswer),
     nsas_(NULL),
@@ -365,13 +349,11 @@ Resolver::Resolver() :
     // Operations referring to "this" must be done in the constructor body
     // (some compilers will issue warnings if "this" is referred to in the
     // initialization list).
-    checkin_ = new ConfigCheck(this);
     dns_lookup_ = new MessageLookup(this);
 }
 
 Resolver::~Resolver() {
     delete impl_;
-    delete checkin_;
     delete dns_lookup_;
     delete dns_answer_;
 }
diff --git a/src/bin/resolver/resolver.h b/src/bin/resolver/resolver.h
index 725aa85..b1608c1 100644
--- a/src/bin/resolver/resolver.h
+++ b/src/bin/resolver/resolver.h
@@ -131,9 +131,6 @@ public:
     /// \brief Return pointer to the DNS Answer callback function
     isc::asiodns::DNSAnswer* getDNSAnswerProvider() { return (dns_answer_); }
 
-    /// \brief Return pointer to the Checkin callback function
-    isc::asiolink::SimpleCallback* getCheckinProvider() { return (checkin_); }
-
     /**
      * \brief Specify the list of upstream servers.
      *
@@ -259,7 +256,6 @@ public:
 private:
     ResolverImpl* impl_;
     isc::asiodns::DNSServiceBase* dnss_;
-    isc::asiolink::SimpleCallback* checkin_;
     isc::asiodns::DNSLookup* dns_lookup_;
     isc::asiodns::DNSAnswer* dns_answer_;
     isc::nsas::NameserverAddressStore* nsas_;
diff --git a/src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py b/src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py
index 0766b7c..2da418d 100644
--- a/src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py
+++ b/src/bin/usermgr/tests/b10-cmdctl-usermgr_test.py
@@ -399,6 +399,19 @@ Options:
                          'add', 'user1', 'pass1'
                        ])
 
+    @unittest.skipIf(os.getuid() == 0,
+                     'test cannot be run as root user')
+    def test_bad_file_permissions(self):
+        """
+        Check for graceful handling of bad file argument
+        """
+        # Create the test file
+        self.run_check(0, None, None,
+                       [ self.TOOL,
+                         '-f', self.OUTPUT_FILE,
+                         'add', 'user1', 'pass1'
+                       ])
+
         # Make it non-writable (don't worry about cleanup, the
         # file should be deleted after each test anyway
         os.chmod(self.OUTPUT_FILE, stat.S_IRUSR)
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index ec38952..683048e 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -28,7 +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.statistics.dns import Counters
 from isc.notify import notify_out
 import isc.util.process
 from isc.util.address_formatter import AddressFormatter
@@ -652,7 +652,8 @@ class XfrinConnection(asyncore.dispatcher):
             self.connect(self._master_addrinfo[2])
             return True
         except socket.error as e:
-            logger.error(XFRIN_CONNECT_MASTER, self._master_addrinfo[2],
+            logger.error(XFRIN_CONNECT_MASTER, self.tsig_key_name,
+                         self._master_addrinfo[2],
                          str(e))
             return False
 
@@ -767,14 +768,15 @@ class XfrinConnection(asyncore.dispatcher):
         '''
         Used as error callback below.
         '''
-        logger.error(XFRIN_ZONE_INVALID, self._zone_name, self._rrclass,
-                     reason)
+        logger.error(XFRIN_ZONE_INVALID, self._zone_name,
+                     self._rrclass, reason)
 
     def __validate_warning(self, reason):
         '''
         Used as warning callback below.
         '''
-        logger.warn(XFRIN_ZONE_WARN, self._zone_name, self._rrclass, reason)
+        logger.warn(XFRIN_ZONE_WARN, self._zone_name,
+                    self._rrclass, reason)
 
     def finish_transfer(self):
         """
@@ -965,17 +967,18 @@ class XfrinConnection(asyncore.dispatcher):
             # The log message doesn't contain the exception text, since there's
             # only one place where the exception is thrown now and it'd be the
             # same generic message every time.
-            logger.error(XFRIN_INVALID_ZONE_DATA, self.zone_str(),
+            logger.error(XFRIN_INVALID_ZONE_DATA,
+                         self.zone_str(),
                          format_addrinfo(self._master_addrinfo))
             ret = XFRIN_FAIL
         except XfrinProtocolError as e:
-            logger.info(XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION, req_str,
-                        self.zone_str(),
+            logger.info(XFRIN_XFR_TRANSFER_PROTOCOL_VIOLATION,
+                        req_str, self.zone_str(),
                         format_addrinfo(self._master_addrinfo), str(e))
             ret = XFRIN_FAIL
         except XfrinException as e:
-            logger.error(XFRIN_XFR_TRANSFER_FAILURE, req_str,
-                         self.zone_str(),
+            logger.error(XFRIN_XFR_TRANSFER_FAILURE,
+                         req_str, self.zone_str(),
                          format_addrinfo(self._master_addrinfo), str(e))
             ret = XFRIN_FAIL
         except Exception as e:
@@ -1142,12 +1145,12 @@ def __process_xfrin(server, zone_name, rrclass, datasrc_client, zone_soa,
                     # fallback.
                     if request_ixfr == ZoneInfo.REQUEST_IXFR_ONLY:
                         logger.warn(XFRIN_XFR_TRANSFER_FALLBACK_DISABLED,
-                                    conn.zone_str())
+                                    tsig_key, conn.zone_str())
                     else:
                         retry = True
                         request_type = RRType.AXFR
                         logger.warn(XFRIN_XFR_TRANSFER_FALLBACK,
-                                    conn.zone_str())
+                                    tsig_key, conn.zone_str())
                         conn.close()
                         conn = None
 
@@ -1688,7 +1691,8 @@ class Xfrin:
                 except isc.cc.session.SessionTimeout:
                     pass        # for now we just ignore the failure
             except socket.error as err:
-                logger.error(XFRIN_MSGQ_SEND_ERROR, XFROUT_MODULE_NAME, ZONE_MANAGER_MODULE_NAME)
+                logger.error(XFRIN_MSGQ_SEND_ERROR, self.tsig_key_name,
+                             XFROUT_MODULE_NAME, ZONE_MANAGER_MODULE_NAME)
 
         else:
             msg = create_command(notify_out.ZONE_XFRIN_FAILED, param)
@@ -1702,7 +1706,8 @@ class Xfrin:
                 except isc.cc.session.SessionTimeout:
                     pass        # for now we just ignore the failure
             except socket.error as err:
-                logger.error(XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER, ZONE_MANAGER_MODULE_NAME)
+                logger.error(XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER, self.tsig_key_name,
+                             ZONE_MANAGER_MODULE_NAME)
 
     def startup(self):
         logger.debug(DBG_PROCESS, XFRIN_STARTED)
diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes
index 9e4281a..4383324 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -56,7 +56,7 @@ most likely cause is that xfrin the msgq daemon is not running.
 There was an error while the given command was being processed. The
 error is given in the log message.
 
-% XFRIN_CONNECT_MASTER error connecting to master at %1: %2
+% XFRIN_CONNECT_MASTER (with TSIG %1) error connecting to master at %2: %3
 There was an error opening a connection to the master. The error is
 shown in the log message.
 
@@ -159,12 +159,12 @@ the primary server between the SOA and IXFR queries.  The client
 implementation confirms the whole response is this single SOA, and
 aborts the transfer just like a successful case.
 
-% XFRIN_MSGQ_SEND_ERROR error while contacting %1 and %2
+% XFRIN_MSGQ_SEND_ERROR (with TSIG %1) error while contacting %2 and %3
 There was a problem sending a message to the xfrout module or the
 zone manager. This most likely means that the msgq daemon has quit or
 was killed.
 
-% XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER error while contacting %1
+% XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER (with TSIG %1) error while contacting %2
 There was a problem sending a message to the zone manager. This most
 likely means that the msgq daemon has quit or was killed.
 
@@ -245,13 +245,13 @@ often.
 The XFR transfer for the given zone has failed due to an internal error.
 The error is shown in the log message.
 
-% XFRIN_XFR_TRANSFER_FALLBACK falling back from IXFR to AXFR for %1
+% XFRIN_XFR_TRANSFER_FALLBACK (with TSIG %1) falling back from IXFR to AXFR for %2
 The IXFR transfer of the given zone failed. This might happen in many cases,
 such that the remote server doesn't support IXFR, we don't have the SOA record
 (or the zone at all), we are out of sync, etc. In many of these situations,
 AXFR could still work. Therefore we try that one in case it helps.
 
-% XFRIN_XFR_TRANSFER_FALLBACK_DISABLED suppressing fallback from IXFR to AXFR for %1
+% XFRIN_XFR_TRANSFER_FALLBACK_DISABLED (with TSIG %1) suppressing fallback from IXFR to AXFR for %2
 An IXFR transfer of the given zone failed.  By default AXFR will be
 tried next, but this fallback is disabled by configuration, so the
 whole transfer attempt failed at that point.  If the reason for the
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index e26bc9a..bcb0d53 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -27,7 +27,7 @@ from socketserver import *
 import os
 from isc.config.ccsession import *
 from isc.cc import SessionError, SessionTimeout
-from isc.statistics import Counters
+from isc.statistics.dns import Counters
 from isc.notify import notify_out
 import isc.util.process
 import fcntl
@@ -1018,6 +1018,8 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
 
 class XfroutServer:
     def __init__(self):
+        self._default_notify_address = ''
+        self._default_notify_port = 53
         self._unix_socket_server = None
         self._listen_sock_file = UNIX_SOCKET_FILE
         self._shutdown_event = threading.Event()
@@ -1046,7 +1048,13 @@ class XfroutServer:
         self._notifier = notify_out.NotifyOut(datasrc)
         if 'also_notify' in self._config_data:
             for slave in self._config_data['also_notify']:
-                self._notifier.add_slave(slave['address'], slave['port'])
+                address = self._default_notify_address
+                if 'address' in slave:
+                    address = slave['address']
+                port = self._default_notify_port
+                if 'port' in slave:
+                    port = slave['port']
+                self._notifier.add_slave(address, port)
         self._notifier.dispatcher()
 
     def send_notify(self, zone_name, zone_class):
diff --git a/src/bin/xfrout/xfrout.spec.pre.in b/src/bin/xfrout/xfrout.spec.pre.in
index 570f73e..dd90fe4 100644
--- a/src/bin/xfrout/xfrout.spec.pre.in
+++ b/src/bin/xfrout/xfrout.spec.pre.in
@@ -43,7 +43,7 @@
                    "item_name": "port",
                    "item_type": "integer",
                    "item_optional": false,
-                   "item_default": 0
+                   "item_default": 53
                }
              ]
          }
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 79a8263..ddf369d 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -1,3 +1,3 @@
 SUBDIRS = exceptions util log hooks cryptolink dns cc config acl xfr bench \
           asiolink asiodns nsas cache resolve testutils datasrc \
-          server_common python dhcp dhcpsrv statistics
+          server_common python dhcp dhcp_ddns dhcpsrv statistics
diff --git a/src/lib/asiodns/README b/src/lib/asiodns/README
index 596d1df..ad27177 100644
--- a/src/lib/asiodns/README
+++ b/src/lib/asiodns/README
@@ -26,9 +26,6 @@ So, in simplified form, the behavior of a DNS Server is:
       if not parent:
         break
 
-    # This callback informs the caller that a packet has arrived, and
-    # gives it a chance to update configuration, etc
-    SimpleCallback(packet)
     YIELD answer = DNSLookup(packet, this)
     response = DNSAnswer(answer)
     YIELD send(response)
@@ -37,7 +34,7 @@ At each "YIELD" point, the coroutine initiates an asynchronous operation,
 then pauses and turns over control to some other task on the ASIO service
 queue.  When the operation completes, the coroutine resumes.
 
-DNSLookup, DNSAnswer and SimpleCallback define callback methods
+DNSLookup and DNSAnswer define callback methods
 used by a DNS Server to communicate with the module that called it.
 They are abstract-only classes whose concrete implementations
 are supplied by the calling module.
diff --git a/src/lib/asiodns/dns_service.cc b/src/lib/asiodns/dns_service.cc
index 972e397..58a9918 100644
--- a/src/lib/asiodns/dns_service.cc
+++ b/src/lib/asiodns/dns_service.cc
@@ -37,9 +37,9 @@ class DNSAnswer;
 
 class DNSServiceImpl {
 public:
-    DNSServiceImpl(IOService& io_service, SimpleCallback* checkin,
+    DNSServiceImpl(IOService& io_service,
                    DNSLookup* lookup, DNSAnswer* answer) :
-            io_service_(io_service), checkin_(checkin), lookup_(lookup),
+            io_service_(io_service), lookup_(lookup),
             answer_(answer), tcp_recv_timeout_(5000)
     {}
 
@@ -50,13 +50,12 @@ public:
     typedef boost::shared_ptr<TCPServer> TCPServerPtr;
     typedef boost::shared_ptr<DNSServer> DNSServerPtr;
     std::vector<DNSServerPtr> servers_;
-    SimpleCallback* checkin_;
     DNSLookup* lookup_;
     DNSAnswer* answer_;
     size_t tcp_recv_timeout_;
 
     template<class Ptr, class Server> void addServerFromFD(int fd, int af) {
-        Ptr server(new Server(io_service_.get_io_service(), fd, af, checkin_,
+        Ptr server(new Server(io_service_.get_io_service(), fd, af,
                               lookup_, answer_));
         startServer(server);
     }
@@ -88,9 +87,9 @@ private:
     }
 };
 
-DNSService::DNSService(IOService& io_service, SimpleCallback* checkin,
+DNSService::DNSService(IOService& io_service,
                        DNSLookup* lookup, DNSAnswer *answer) :
-    impl_(new DNSServiceImpl(io_service, checkin, lookup, answer)),
+    impl_(new DNSServiceImpl(io_service, lookup, answer)),
     io_service_(io_service)
 {
 }
diff --git a/src/lib/asiodns/dns_service.h b/src/lib/asiodns/dns_service.h
index 01b6310..00d7158 100644
--- a/src/lib/asiodns/dns_service.h
+++ b/src/lib/asiodns/dns_service.h
@@ -107,8 +107,8 @@ public:
 /// DNSService is the service that handles DNS queries and answers with
 /// a given IOService. This class is mainly intended to hold all the
 /// logic that is shared between the authoritative and the recursive
-/// server implementations. As such, it handles asio, including config
-/// updates (through the 'Checkinprovider'), and listening sockets.
+/// server implementations. As such, it handles asio and listening
+/// sockets.
 class DNSService : public DNSServiceBase {
     ///
     /// \name Constructors and Destructor
@@ -132,11 +132,9 @@ public:
     /// Use addServerTCPFromFD() or addServerUDPFromFD() to add some servers.
     ///
     /// \param io_service The IOService to work with
-    /// \param checkin Provider for cc-channel events (see \c SimpleCallback)
     /// \param lookup The lookup provider (see \c DNSLookup)
     /// \param answer The answer provider (see \c DNSAnswer)
     DNSService(asiolink::IOService& io_service,
-               isc::asiolink::SimpleCallback* checkin,
                DNSLookup* lookup, DNSAnswer* answer);
 
     /// \brief The destructor.
diff --git a/src/lib/asiodns/tcp_server.cc b/src/lib/asiodns/tcp_server.cc
index 00536f3..5f01d2f 100644
--- a/src/lib/asiodns/tcp_server.cc
+++ b/src/lib/asiodns/tcp_server.cc
@@ -14,8 +14,6 @@
 
 #include <config.h>
 
-#include <log/dummylog.h>
-
 #include <util/buffer.h>
 
 #include <asio.hpp>
@@ -49,11 +47,10 @@ namespace asiodns {
 ///
 /// The constructor
 TCPServer::TCPServer(io_service& io_service, int fd, int af,
-                     const SimpleCallback* checkin,
                      const DNSLookup* lookup,
                      const DNSAnswer* answer) :
     io_(io_service), done_(false),
-    checkin_callback_(checkin), lookup_callback_(lookup),
+    lookup_callback_(lookup),
     answer_callback_(answer)
 {
     if (af != AF_INET && af != AF_INET6) {
@@ -205,16 +202,6 @@ TCPServer::operator()(asio::error_code ec, size_t length) {
         io_message_.reset(new IOMessage(data_.get(), length, *iosock_,
                                         *peer_));
 
-        // Perform any necessary operations prior to processing the incoming
-        // packet (e.g., checking for queued configuration messages).
-        //
-        // (XXX: it may be a performance issue to have this called for
-        // every single incoming packet; we may wish to throttle it somehow
-        // in the future.)
-        if (checkin_callback_ != NULL) {
-            (*checkin_callback_)(*io_message_);
-        }
-
         // If we don't have a DNS Lookup provider, there's no point in
         // continuing; we exit the coroutine permanently.
         if (lookup_callback_ == NULL) {
diff --git a/src/lib/asiodns/tcp_server.h b/src/lib/asiodns/tcp_server.h
index 50e8717..3c01076 100644
--- a/src/lib/asiodns/tcp_server.h
+++ b/src/lib/asiodns/tcp_server.h
@@ -41,14 +41,12 @@ public:
     /// \param io_service the asio::io_service to work with
     /// \param fd the file descriptor of opened TCP socket
     /// \param af address family of the socket, either AF_INET or AF_INET6
-    /// \param checkin the callbackprovider for non-DNS events
     /// \param lookup the callbackprovider for DNS lookup events
     /// \param answer the callbackprovider for DNS answer events
     /// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6
     /// \throw isc::asiolink::IOError when a low-level error happens, like the
     ///     fd is not a valid descriptor or it can't be listened on.
     TCPServer(asio::io_service& io_service, int fd, int af,
-              const isc::asiolink::SimpleCallback* checkin = NULL,
               const DNSLookup* lookup = NULL, const DNSAnswer* answer = NULL);
 
     void operator()(asio::error_code ec = asio::error_code(),
@@ -125,7 +123,6 @@ private:
     bool done_;
 
     // Callback functions provided by the caller
-    const isc::asiolink::SimpleCallback* checkin_callback_;
     const DNSLookup* lookup_callback_;
     const DNSAnswer* answer_callback_;
 
diff --git a/src/lib/asiodns/tests/dns_server_unittest.cc b/src/lib/asiodns/tests/dns_server_unittest.cc
index 521be1b..d59bf5e 100644
--- a/src/lib/asiodns/tests/dns_server_unittest.cc
+++ b/src/lib/asiodns/tests/dns_server_unittest.cc
@@ -103,14 +103,6 @@ class ServerStopper {
         DNSServer* server_to_stop_;
 };
 
-// \brief no check logic at all,just provide a checkpoint to stop the server
-class DummyChecker : public SimpleCallback, public ServerStopper {
-    public:
-        virtual void operator()(const IOMessage&) const {
-            stopServer();
-        }
-};
-
 // \brief no lookup logic at all,just provide a checkpoint to stop the server
 class DummyLookup : public DNSLookup, public ServerStopper {
 public:
@@ -369,7 +361,6 @@ class DNSServerTestBase : public::testing::Test {
     protected:
         DNSServerTestBase() :
             server_address_(ip::address::from_string(server_ip)),
-            checker_(new DummyChecker()),
             lookup_(new DummyLookup()),
             sync_lookup_(new SyncDummyLookup()),
             answer_(new SimpleAnswer()),
@@ -390,7 +381,6 @@ class DNSServerTestBase : public::testing::Test {
             if (tcp_server_) {
                 tcp_server_->stop();
             }
-            delete checker_;
             delete lookup_;
             delete sync_lookup_;
             delete answer_;
@@ -436,7 +426,6 @@ class DNSServerTestBase : public::testing::Test {
 
         asio::io_service service;
         const ip::address server_address_;
-        DummyChecker* const checker_;
         DummyLookup* lookup_;     // we need to replace it in some cases
         SyncDummyLookup*  const sync_lookup_;
         SimpleAnswer* const answer_;
@@ -507,7 +496,7 @@ protected:
         this->tcp_server_ =
             boost::shared_ptr<TCPServer>(new TCPServer(
                                              this->service, fd_tcp, AF_INET6,
-                                             this->checker_, this->lookup_,
+                                             this->lookup_,
                                              this->answer_));
     }
 
@@ -516,7 +505,7 @@ protected:
     boost::shared_ptr<UDPServerClass> createServer(int fd, int af) {
         return (boost::shared_ptr<UDPServerClass>(
                     new UDPServerClass(this->service, fd, af,
-                                       this->checker_, this->lookup_,
+                                       this->lookup_,
                                        this->answer_)));
     }
 };
@@ -571,16 +560,6 @@ TYPED_TEST(DNSServerTest, stopUDPServerBeforeItStartServing) {
     EXPECT_TRUE(this->serverStopSucceed());
 }
 
-// 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());
-    EXPECT_TRUE(this->serverStopSucceed());
-}
-
 // Test whether udp server stopped successfully during query lookup
 TYPED_TEST(DNSServerTest, stopUDPServerDuringQueryLookup) {
     this->testStopServerByStopper(*this->udp_server_, this->udp_client_,
@@ -665,14 +644,6 @@ TYPED_TEST(DNSServerTest, stopTCPServerBeforeItStartServing) {
 }
 
 
-// Test whether tcp server stopped successfully during message check
-TYPED_TEST(DNSServerTest, stopTCPServerDuringMessageCheck) {
-    this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
-                                  this->checker_);
-    EXPECT_EQ(std::string(""), this->tcp_client_->getReceivedData());
-    EXPECT_TRUE(this->serverStopSucceed());
-}
-
 // Test whether tcp server stopped successfully during query lookup
 TYPED_TEST(DNSServerTest, stopTCPServerDuringQueryLookup) {
     this->testStopServerByStopper(*this->tcp_server_, this->tcp_client_,
@@ -709,7 +680,7 @@ TYPED_TEST(DNSServerTest, stopTCPServeMoreThanOnce) {
 TYPED_TEST(DNSServerTestBase, invalidFamily) {
     // We abuse DNSServerTestBase for this test, as we don't need the
     // initialization.
-    EXPECT_THROW(TCPServer(this->service, 0, AF_UNIX, this->checker_,
+    EXPECT_THROW(TCPServer(this->service, 0, AF_UNIX,
                            this->lookup_, this->answer_),
                  isc::InvalidParameter);
 }
@@ -728,10 +699,10 @@ TYPED_TEST(DNSServerTestBase, invalidTCPFD) {
      asio backend does fail as it tries to insert it right away, but
      not the others, maybe we could make it run this at last on epoll-based
      systems).
-    EXPECT_THROW(UDPServer(service, -1, AF_INET, checker_, lookup_,
+    EXPECT_THROW(UDPServer(service, -1, AF_INET, lookup_,
                            answer_), isc::asiolink::IOError);
     */
-    EXPECT_THROW(TCPServer(this->service, -1, AF_INET, this->checker_,
+    EXPECT_THROW(TCPServer(this->service, -1, AF_INET,
                            this->lookup_, this->answer_),
                  isc::asiolink::IOError);
 }
diff --git a/src/lib/asiodns/tests/dns_service_unittest.cc b/src/lib/asiodns/tests/dns_service_unittest.cc
index ce8eee9..c35897c 100644
--- a/src/lib/asiodns/tests/dns_service_unittest.cc
+++ b/src/lib/asiodns/tests/dns_service_unittest.cc
@@ -81,7 +81,7 @@ protected:
     UDPDNSServiceTest() :
         first_buffer_(NULL), second_buffer_(NULL),
         lookup(&first_buffer_, &second_buffer_, io_service),
-        dns_service(io_service, NULL, &lookup, NULL),
+        dns_service(io_service, &lookup, NULL),
         client_socket(io_service.get_io_service(), asio::ip::udp::v6()),
         server_ep(asio::ip::address::from_string(TEST_IPV6_ADDR),
                   lexical_cast<uint16_t>(TEST_SERVER_PORT)),
diff --git a/src/lib/asiodns/udp_server.cc b/src/lib/asiodns/udp_server.cc
index 7221296..649ea92 100644
--- a/src/lib/asiodns/udp_server.cc
+++ b/src/lib/asiodns/udp_server.cc
@@ -21,8 +21,6 @@
 
 #include <config.h>
 
-#include <log/dummylog.h>
-
 #include <asio.hpp>
 #include <asio/error.hpp>
 #include <asiolink/dummy_io_cb.h>
@@ -35,7 +33,6 @@
 
 using namespace asio;
 using asio::ip::udp;
-using isc::log::dlog;
 
 using namespace std;
 using namespace isc::dns;
@@ -60,9 +57,9 @@ struct UDPServer::Data {
      * first packet. But we do initialize the socket in here.
      */
     Data(io_service& io_service, const ip::address& addr, const uint16_t port,
-        SimpleCallback* checkin, DNSLookup* lookup, DNSAnswer* answer) :
+         DNSLookup* lookup, DNSAnswer* answer) :
         io_(io_service), bytes_(0), done_(false),
-        checkin_callback_(checkin),lookup_callback_(lookup),
+        lookup_callback_(lookup),
         answer_callback_(answer)
     {
         // We must use different instantiations for v4 and v6;
@@ -75,10 +72,10 @@ struct UDPServer::Data {
         }
         socket_->bind(udp::endpoint(addr, port));
     }
-    Data(io_service& io_service, int fd, int af, SimpleCallback* checkin,
+    Data(io_service& io_service, int fd, int af,
          DNSLookup* lookup, DNSAnswer* answer) :
          io_(io_service), bytes_(0), done_(false),
-         checkin_callback_(checkin),lookup_callback_(lookup),
+         lookup_callback_(lookup),
          answer_callback_(answer)
     {
         if (af != AF_INET && af != AF_INET6) {
@@ -105,7 +102,6 @@ struct UDPServer::Data {
      */
     Data(const Data& other) :
         io_(other.io_), socket_(other.socket_), bytes_(0), done_(false),
-        checkin_callback_(other.checkin_callback_),
         lookup_callback_(other.lookup_callback_),
         answer_callback_(other.answer_callback_)
     {
@@ -169,7 +165,6 @@ struct UDPServer::Data {
     bool done_;
 
     // Callback functions provided by the caller
-    const SimpleCallback* checkin_callback_;
     const DNSLookup* lookup_callback_;
     const DNSAnswer* answer_callback_;
 
@@ -182,9 +177,9 @@ struct UDPServer::Data {
 /// The constructor. It just creates new internal state object
 /// and lets it handle the initialization.
 UDPServer::UDPServer(io_service& io_service, int fd, int af,
-                     SimpleCallback* checkin, DNSLookup* lookup,
+                     DNSLookup* lookup,
                      DNSAnswer* answer) :
-    data_(new Data(io_service, fd, af, checkin, lookup, answer))
+    data_(new Data(io_service, fd, af, lookup, answer))
 { }
 
 /// The function operator is implemented with the "stackless coroutine"
@@ -263,15 +258,6 @@ UDPServer::operator()(asio::error_code ec, size_t length) {
         data_->io_message_.reset(new IOMessage(data_->data_.get(),
             data_->bytes_, *data_->iosock_, *data_->peer_));
 
-        // Perform any necessary operations prior to processing an incoming
-        // query (e.g., checking for queued configuration messages).
-        //
-        // (XXX: it may be a performance issue to check in for every single
-        // incoming query; we may wish to throttle this in the future.)
-        if (data_->checkin_callback_ != NULL) {
-            (*data_->checkin_callback_)(*data_->io_message_);
-        }
-
         // If we don't have a DNS Lookup provider, there's no point in
         // continuing; we exit the coroutine permanently.
         if (data_->lookup_callback_ == NULL) {
diff --git a/src/lib/asiodns/udp_server.h b/src/lib/asiodns/udp_server.h
index c2b1b96..1c1dd9f 100644
--- a/src/lib/asiodns/udp_server.h
+++ b/src/lib/asiodns/udp_server.h
@@ -43,14 +43,12 @@ public:
     /// \param io_service the asio::io_service to work with
     /// \param fd the file descriptor of opened UDP socket
     /// \param af address family, either AF_INET or AF_INET6
-    /// \param checkin the callbackprovider for non-DNS events
     /// \param lookup the callbackprovider for DNS lookup events
     /// \param answer the callbackprovider for DNS answer events
     /// \throw isc::InvalidParameter if af is neither AF_INET nor AF_INET6
     /// \throw isc::asiolink::IOError when a low-level error happens, like the
     ///     fd is not a valid descriptor.
     UDPServer(asio::io_service& io_service, int fd, int af,
-              isc::asiolink::SimpleCallback* checkin = NULL,
               DNSLookup* lookup = NULL, DNSAnswer* answer = NULL);
 
     /// \brief The function operator
diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am
index b2f88f8..9a76871 100644
--- a/src/lib/asiolink/Makefile.am
+++ b/src/lib/asiolink/Makefile.am
@@ -42,4 +42,4 @@ if USE_CLANGPP
 libb10_asiolink_la_CXXFLAGS += -Wno-error
 endif
 libb10_asiolink_la_CPPFLAGS = $(AM_CPPFLAGS)
-libb10_asiolink_la_LIBADD = $(top_builddir)/src/lib/log/libb10-log.la
+libb10_asiolink_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h
index 6b0a43c..7df4a80 100644
--- a/src/lib/asiolink/tcp_socket.h
+++ b/src/lib/asiolink/tcp_socket.h
@@ -19,7 +19,6 @@
 #error "asio.hpp must be included before including this, see asiolink.h as to why"
 #endif
 
-#include <log/dummylog.h>
 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <unistd.h>             // for some IPC/network system calls
diff --git a/src/lib/asiolink/udp_socket.h b/src/lib/asiolink/udp_socket.h
index 5712957..4ac49c9 100644
--- a/src/lib/asiolink/udp_socket.h
+++ b/src/lib/asiolink/udp_socket.h
@@ -19,7 +19,6 @@
 #error "asio.hpp must be included before including this, see asiolink.h as to why"
 #endif
 
-#include <log/dummylog.h>
 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <unistd.h>             // for some IPC/network system calls
diff --git a/src/lib/config/ccsession.cc b/src/lib/config/ccsession.cc
index 0d43b27..780fe95 100644
--- a/src/lib/config/ccsession.cc
+++ b/src/lib/config/ccsession.cc
@@ -595,6 +595,8 @@ ModuleCCSession::checkModuleCommand(const std::string& cmd_str,
                                  "Command given but no "
                                  "command handler for module"));
         }
+    } else if (unhandled_callback_) {
+        unhandled_callback_(cmd_str, target_module, arg);
     }
     return (ElementPtr());
 }
diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h
index 04e3046..c536861 100644
--- a/src/lib/config/ccsession.h
+++ b/src/lib/config/ccsession.h
@@ -575,6 +575,50 @@ public:
     /// \param id The id of request as returned by groupRecvMsgAsync.
     void cancelAsyncRecv(const AsyncRecvRequestID& id);
 
+    /// \brief Subscribe to a group
+    ///
+    /// Wrapper around the CCSession::subscribe.
+    void subscribe(const std::string& group) {
+        session_.subscribe(group, isc::cc::CC_INSTANCE_WILDCARD);
+    }
+
+    /// \brief Unsubscribe from a group.
+    ///
+    /// Wrapper around the CCSession::unsubscribe.
+    void unsubscribe(const std::string& group) {
+        session_.unsubscribe(group, isc::cc::CC_INSTANCE_WILDCARD);
+    }
+
+    /// \brief Callback type for unhandled commands
+    ///
+    /// The type of functions that are not handled by the ModuleCCSession
+    /// because they are not aimed at the module.
+    ///
+    /// The parameters are:
+    /// - Name of the command.
+    /// - The module it was aimed for (may be empty).
+    /// - The parameters of the command.
+    typedef boost::function<void (const std::string&, const std::string&,
+                                  const isc::data::ConstElementPtr&)>
+        UnhandledCallback;
+
+    /// \brief Register a callback for messages sent to foreign modules.
+    ///
+    /// Usually, a command aimed at foreign module (or sent directly)
+    /// is discarded. By registering a callback here, these can be
+    /// examined.
+    ///
+    /// \note A callback overwrites the previous one set.
+    /// \todo This is a temporary, unclean, solution. A more generic
+    ///     one needs to be designed. Also, a solution that is able
+    ///     to send an answer would be great.
+    ///
+    /// \param callback The new callback to use. It may be an empty
+    ///     function.
+    void setUnhandledCallback(const UnhandledCallback& callback) {
+        unhandled_callback_ = callback;
+    }
+
 private:
     ModuleSpec readModuleSpecification(const std::string& filename);
     void startCheck();
@@ -624,6 +668,8 @@ private:
                             isc::data::ConstElementPtr new_config);
 
     ModuleSpec fetchRemoteSpec(const std::string& module, bool is_filename);
+
+    UnhandledCallback unhandled_callback_;
 };
 
 /// \brief Default handler for logging config updates
diff --git a/src/lib/config/tests/ccsession_unittests.cc b/src/lib/config/tests/ccsession_unittests.cc
index 700ffe4..4a04412 100644
--- a/src/lib/config/tests/ccsession_unittests.cc
+++ b/src/lib/config/tests/ccsession_unittests.cc
@@ -157,6 +157,17 @@ TEST_F(CCSessionTest, notifyNoParams) {
             session.getMsgQueue()->get(1)->toWire();
 }
 
+// Try to subscribe and unsubscribe once again
+TEST_F(CCSessionTest, subscribe) {
+    ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL, false,
+                         false);
+    EXPECT_FALSE(session.haveSubscription("A group", "*"));
+    mccs.subscribe("A group");
+    EXPECT_TRUE(session.haveSubscription("A group", "*"));
+    mccs.unsubscribe("A group");
+    EXPECT_FALSE(session.haveSubscription("A group", "*"));
+}
+
 TEST_F(CCSessionTest, createAnswer) {
     ConstElementPtr answer;
     answer = createAnswer();
@@ -686,6 +697,16 @@ TEST_F(CCSessionTest, remoteConfig) {
     }
 }
 
+void
+callback(std::string* command, std::string* target, ConstElementPtr *params,
+         const std::string& command_real, const std::string& target_real,
+         const ConstElementPtr& params_real)
+{
+    *command = command_real;
+    *target = target_real;
+    *params = params_real;
+}
+
 TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
     // client will ask for config
     session.getMessages()->add(createAnswer(0, el("{  }")));
@@ -721,6 +742,22 @@ TEST_F(CCSessionTest, ignoreRemoteConfigCommands) {
     result = mccs.checkCommand();
     EXPECT_EQ(0, session.getMsgQueue()->size());
     EXPECT_EQ(0, result);
+
+    // Check that we can get the ignored commands by registering a callback
+    std::string command, target;
+    ConstElementPtr params;
+    mccs.setUnhandledCallback(boost::bind(&callback, &command, &target,
+                                          &params, _1, _2, _3));
+    session.addMessage(el("{ \"command\": [ \"good_command\","
+                          "{\"param\": true} ] }"), "Spec1", "*");
+    EXPECT_EQ(1, session.getMsgQueue()->size());
+    result = mccs.checkCommand();
+    EXPECT_EQ(0, session.getMsgQueue()->size());
+    EXPECT_EQ(0, result);
+
+    EXPECT_EQ("good_command", command);
+    EXPECT_EQ("Spec1", target);
+    EXPECT_TRUE(params && el("{\"param\": true}")->equals(*params));
 }
 
 TEST_F(CCSessionTest, initializationFail) {
diff --git a/src/lib/datasrc/client_list.cc b/src/lib/datasrc/client_list.cc
index 4fe1eb3..531821c 100644
--- a/src/lib/datasrc/client_list.cc
+++ b/src/lib/datasrc/client_list.cc
@@ -330,7 +330,7 @@ ConfigurableClientList::findInternal(MutableResult& candidate,
     // and the need_updater parameter is true, get the zone there.
 }
 
-void
+bool
 ConfigurableClientList::resetMemorySegment
     (const std::string& datasrc_name,
      ZoneTableSegment::MemorySegmentOpenMode mode,
@@ -340,9 +340,10 @@ ConfigurableClientList::resetMemorySegment
         if (info.name_ == datasrc_name) {
             ZoneTableSegment& segment = *info.ztable_segment_;
             segment.reset(mode, config_params);
-            break;
+            return true;
         }
     }
+    return false;
 }
 
 ConfigurableClientList::ZoneWriterPair
diff --git a/src/lib/datasrc/client_list.h b/src/lib/datasrc/client_list.h
index e4df415..77c2fd5 100644
--- a/src/lib/datasrc/client_list.h
+++ b/src/lib/datasrc/client_list.h
@@ -385,7 +385,8 @@ public:
     /// \param datasrc_name The name of the data source whose segment to reset
     /// \param mode The open mode for the new memory segment
     /// \param config_params The configuration for the new memory segment.
-    void resetMemorySegment
+    /// \return If the data source was found and reset.
+    bool resetMemorySegment
         (const std::string& datasrc_name,
          memory::ZoneTableSegment::MemorySegmentOpenMode mode,
          isc::data::ConstElementPtr config_params);
@@ -453,8 +454,6 @@ public:
                             bool want_finder = true) const;
 
     /// \brief This holds one data source client and corresponding information.
-    ///
-    /// \todo The content yet to be defined.
     struct DataSourceInfo {
         DataSourceInfo(DataSourceClient* data_src_client,
                        const DataSourceClientContainerPtr& container,
@@ -526,7 +525,7 @@ public:
     /// This may throw standard exceptions, such as std::bad_alloc. Otherwise,
     /// it is exception free.
     std::vector<DataSourceStatus> getStatus() const;
-public:
+
     /// \brief Access to the data source clients.
     ///
     /// It can be used to examine the loaded list of data sources clients
diff --git a/src/lib/datasrc/memory/domaintree.h b/src/lib/datasrc/memory/domaintree.h
index 36e1bc9..5f41371 100644
--- a/src/lib/datasrc/memory/domaintree.h
+++ b/src/lib/datasrc/memory/domaintree.h
@@ -356,6 +356,16 @@ private:
         }
     }
 
+    /// \brief Returns if the node color is black
+    bool isBlack() const {
+        return (getColor() == BLACK);
+    }
+
+    /// \brief Returns if the node color is red
+    bool isRed() const {
+        return (!isBlack());
+    }
+
     /// \brief Sets the color of this node
     void setColor(const DomainTreeNodeColor color) {
         if (color == RED) {
@@ -435,6 +445,21 @@ public:
     /// This method never throws an exception.
     const DomainTreeNode<T>* predecessor() const;
 
+    /// \brief returns the node distance from the root of its subtree
+    size_t getDistance() const {
+        size_t nodes = 1;
+        for (const DomainTreeNode<T>* node = this;
+             node != NULL;
+             node = node->getParent()) {
+            if (node->isSubTreeRoot()) {
+                break;
+            }
+            ++nodes;
+        }
+
+        return (nodes);
+    }
+
 private:
     /// \brief private shared implementation of successor and predecessor
     ///
@@ -489,6 +514,41 @@ private:
     const DomainTreeNode<T>* getRight() const {
         return (right_.get());
     }
+
+    /// \brief Access grandparent node as bare pointer.
+    ///
+    /// The grandparent node is the parent's parent.
+    ///
+    /// \return the grandparent node if one exists, NULL otherwise.
+    DomainTreeNode<T>* getGrandParent() {
+        DomainTreeNode<T>* parent = getParent();
+        if (parent != NULL) {
+            return (parent->getParent());
+        } else {
+            // If there's no parent, there's no grandparent.
+            return (NULL);
+        }
+    }
+
+    /// \brief Access uncle node as bare pointer.
+    ///
+    /// An uncle node is defined as the parent node's sibling. It exists
+    /// at the same level as the parent.
+    ///
+    /// \return the uncle node if one exists, NULL otherwise.
+    DomainTreeNode<T>* getUncle() {
+        DomainTreeNode<T>* grandparent = getGrandParent();
+        if (grandparent == NULL) {
+            // If there's no grandparent, there's no uncle.
+            return (NULL);
+        }
+        if (getParent() == grandparent->getLeft()) {
+            return (grandparent->getRight());
+        } else {
+            return (grandparent->getLeft());
+        }
+    }
+
     //@}
 
     /// \brief The subdomain tree.
@@ -1814,11 +1874,13 @@ DomainTree<T>::insert(util::MemorySegment& mem_sgmt,
     } else if (order < 0) {
         node->setSubTreeRoot(false);
         parent->left_ = node;
+        insertRebalance(current_root, node);
     } else {
         node->setSubTreeRoot(false);
         parent->right_ = node;
+        insertRebalance(current_root, node);
     }
-    insertRebalance(current_root, node);
+
     if (new_node != NULL) {
         *new_node = node;
     }
@@ -1894,61 +1956,143 @@ DomainTree<T>::nodeFission(util::MemorySegment& mem_sgmt,
 }
 
 
+/// \brief Fix Red-Black tree properties after an ordinary BST
+/// insertion.
+///
+/// After a normal binary search tree insertion, the Red-Black tree
+/// properties may be violated. This method fixes these properties by
+/// doing tree rotations and recoloring nodes in the tree appropriately.
+///
+/// \param subtree_root The root of the current sub-tree where the node
+/// is being inserted.
+/// \param node The node which was inserted by ordinary BST insertion.
 template <typename T>
 void
 DomainTree<T>::insertRebalance
-    (typename DomainTreeNode<T>::DomainTreeNodePtr* root,
+    (typename DomainTreeNode<T>::DomainTreeNodePtr* subtree_root,
      DomainTreeNode<T>* node)
 {
-    DomainTreeNode<T>* uncle;
-    DomainTreeNode<T>* parent;
-    while (node != (*root).get() &&
-           ((parent = node->getParent())->getColor()) ==
-           DomainTreeNode<T>::RED) {
-        // Here, node->parent_ is not NULL and it is also red, so
-        // node->parent_->parent_ is also not NULL.
-        if (parent == parent->getParent()->getLeft()) {
-            uncle = parent->getParent()->getRight();
-
-            if (uncle != NULL && uncle->getColor() ==
-                DomainTreeNode<T>::RED) {
-                parent->setColor(DomainTreeNode<T>::BLACK);
-                uncle->setColor(DomainTreeNode<T>::BLACK);
-                parent->getParent()->setColor(DomainTreeNode<T>::RED);
-                node = parent->getParent();
-            } else {
-                if (node == parent->getRight()) {
-                    node = parent;
-                    leftRotate(root, node);
-                    parent = node->getParent();
-                }
-                parent->setColor(DomainTreeNode<T>::BLACK);
-                parent->getParent()->setColor(DomainTreeNode<T>::RED);
-                rightRotate(root, parent->getParent());
-            }
+    // The node enters this method colored RED. We assume in our
+    // red-black implementation that NULL values in left and right
+    // children are BLACK.
+    //
+    // Case 1. If node is at the subtree root, we don't need to change
+    // its position in the tree. We re-color it BLACK further below
+    // (right before we return).
+    while (node != (*subtree_root).get()) {
+        // Case 2. If the node is not subtree root, but its parent is
+        // colored BLACK, then we're done. This is because the new node
+        // introduces a RED node in the path through it (from its
+        // subtree root to its children colored BLACK) but doesn't
+        // change the red-black properties.
+        DomainTreeNode<T>* parent = node->getParent();
+        if (parent->isBlack()) {
+            break;
+        }
+
+        DomainTreeNode<T>* uncle = node->getUncle();
+        DomainTreeNode<T>* grandparent = node->getGrandParent();
+
+        if ((uncle != NULL) && uncle->isRed()) {
+            // Case 3. Here, the node's parent is colored RED and the
+            // uncle node is also RED. In this case, the grandparent
+            // must be BLACK (due to existing red-black state).  We set
+            // both the parent and uncle nodes to BLACK then, change the
+            // grandparent to RED, and iterate the while loop with
+            // node = grandparent. This is the only case that causes
+            // insertion to have a max insertion time of log(n).
+            parent->setColor(DomainTreeNode<T>::BLACK);
+            uncle->setColor(DomainTreeNode<T>::BLACK);
+            grandparent->setColor(DomainTreeNode<T>::RED);
+            node = grandparent;
         } else {
-            uncle = parent->getParent()->getLeft();
-
-            if (uncle != NULL && uncle->getColor() ==
-                DomainTreeNode<T>::RED) {
-                parent->setColor(DomainTreeNode<T>::BLACK);
-                uncle->setColor(DomainTreeNode<T>::BLACK);
-                parent->getParent()->setColor(DomainTreeNode<T>::RED);
-                node = parent->getParent();
+            // Case 4. Here, the node and node's parent are colored RED,
+            // and the uncle node is BLACK. Only in this case, tree
+            // rotations are necessary.
+
+            /* First we check if we need to convert to a canonical form:
+             *
+             * (a) If the node is the right-child of its parent, and the
+             * node's parent is the left-child of the node's
+             * grandparent, rotate left about the parent so that the old
+             * 'node' becomes the new parent, and the old parent becomes
+             * the new 'node'.
+             *
+             *       G(B)                   G(B)
+             *      /   \                  /   \
+             *    P(R)  U(B)     =>     P*(R)  U(B)
+             *       \                  /
+             *       N(R)             N*(R)
+             *
+             *                    (P* is old N, N* is old P)
+             *
+             * (b) If the node is the left-child of its parent, and the
+             * node's parent is the right-child of the node's
+             * grandparent, rotate right about the parent so that the
+             * old 'node' becomes the new parent, and the old parent
+             * becomes the new 'node'.
+             *
+             *      G(B)                   G(B)
+             *     /   \                  /   \
+             *   U(B)  P(R)     =>     U(B)  P*(R)
+             *         /                        \
+             *       N(R)                      N*(R)
+             *
+             *                    (P* is old N, N* is old P)
+             */
+            if ((node == parent->getRight()) &&
+                (parent == grandparent->getLeft())) {
+                node = parent;
+                leftRotate(subtree_root, parent);
+            } else if ((node == parent->getLeft()) &&
+                       (parent == grandparent->getRight())) {
+                node = parent;
+                rightRotate(subtree_root, parent);
+            }
+
+            // Also adjust the parent variable (node is already adjusted
+            // above). The grandparent variable need not be adjusted.
+            parent = node->getParent();
+
+            /* Here, we're in a canonical form where the uncle node is
+             * BLACK and both the node and its parent are together
+             * either left-children or right-children of their
+             * corresponding parents.
+             *
+             *       G(B)        or       G(B)
+             *      /   \                /   \
+             *    P(R)  U(B)           U(B)  P(R)
+             *   /                              \
+             * N(R)                             N(R)
+             *
+             * We rotate around the grandparent, right or left,
+             * depending on the orientation above, color the old
+             * grandparent RED (it used to be BLACK) and color the
+             * parent BLACK (it used to be RED). This restores the
+             * red-black property that the number of BLACK nodes from
+             * subtree root to the leaves (the NULL children which are
+             * assumed BLACK) are equal, and that every RED node has a
+             * BLACK parent.
+             */
+            parent->setColor(DomainTreeNode<T>::BLACK);
+            grandparent->setColor(DomainTreeNode<T>::RED);
+
+            if (node == parent->getLeft()) {
+                rightRotate(subtree_root, grandparent);
             } else {
-                if (node == parent->getLeft()) {
-                    node = parent;
-                    rightRotate(root, node);
-                    parent = node->getParent();
-                }
-                parent->setColor(DomainTreeNode<T>::BLACK);
-                parent->getParent()->setColor(DomainTreeNode<T>::RED);
-                leftRotate(root, parent->getParent());
+                leftRotate(subtree_root, grandparent);
             }
+
+            // In this case, the tree is ready now and we explicitly
+            // break out of the loop here. Even if we continue the loop,
+            // it will exit the loop in case 2 above, but that's not so
+            // obvious.
+            break;
         }
     }
 
-    (*root)->setColor(DomainTreeNode<T>::BLACK);
+    // Color sub-tree roots black.
+    (*subtree_root)->setColor(DomainTreeNode<T>::BLACK);
 }
 
 
@@ -2043,7 +2187,7 @@ DomainTree<T>::dumpTreeHelper(std::ostream& os,
 
     indent(os, depth);
     os << node->getLabels() << " ("
-       << ((node->getColor() == DomainTreeNode<T>::BLACK) ? "black" : "red")
+       << (node->isBlack() ? "black" : "red")
        << ")";
     if (node->isEmpty()) {
         os << " [invisible]";
@@ -2107,7 +2251,7 @@ DomainTree<T>::dumpDotHelper(std::ostream& os,
     }
     os << "\"] [";
 
-    if (node->getColor() == DomainTreeNode<T>::RED) {
+    if (node->isRed()) {
         os << "color=red";
     } else {
         os << "color=black";
diff --git a/src/lib/datasrc/static.zone.pre b/src/lib/datasrc/static.zone.pre
index 13c0c9d..8046410 100644
--- a/src/lib/datasrc/static.zone.pre
+++ b/src/lib/datasrc/static.zone.pre
@@ -1,10 +1,10 @@
-;; This is the content of the BIND./CH zone. It contains the version and
-;; authors (called VERSION.BIND. and AUTHORS.BIND.). You can add more or
-;; modify the zone. Then you can reload the zone by issuing the command
+;; This file contains records for the BIND./CH zone. It contains version
+;; (VERSION.BIND.) and authors (AUTHORS.BIND.) information. You can add
+;; more records or modify this zone file like any other zone file. If
+;; you modify this file, you can reload the zone by issuing the
+;; following command in the bindctl program:
 ;;
-;;   loadzone CH BIND
-;;
-;; in the bindctl.
+;;     Auth loadzone CH BIND
 
 ;; This is here mostly for technical reasons.
 BIND.           0   CH  SOA bind. authors.bind. 0 28800 7200 604800 86400
diff --git a/src/lib/datasrc/tests/client_list_unittest.cc b/src/lib/datasrc/tests/client_list_unittest.cc
index eb69556..256e2ed 100644
--- a/src/lib/datasrc/tests/client_list_unittest.cc
+++ b/src/lib/datasrc/tests/client_list_unittest.cc
@@ -368,7 +368,8 @@ public:
                        const std::string& datasrc_name,
                        ZoneTableSegment::MemorySegmentOpenMode mode,
                        ConstElementPtr config_params) {
-        list.resetMemorySegment(datasrc_name, mode, config_params);
+        EXPECT_TRUE(list.resetMemorySegment(datasrc_name, mode,
+                                            config_params));
     }
     virtual std::string getType() {
         return ("mapped");
@@ -383,6 +384,13 @@ INSTANTIATE_TEST_CASE_P(ListTestMapped, ListTest,
 
 #endif
 
+// Calling reset on empty list finds no data and returns false.
+TEST_P(ListTest, emptyReset) {
+    EXPECT_FALSE(list_->resetMemorySegment("Something",
+                                           memory::ZoneTableSegment::CREATE,
+                                           Element::create()));
+}
+
 // Test the test itself
 TEST_P(ListTest, selfTest) {
     EXPECT_EQ(result::SUCCESS, ds_[0]->findZone(Name("example.org")).code);
diff --git a/src/lib/datasrc/tests/memory/domaintree_unittest.cc b/src/lib/datasrc/tests/memory/domaintree_unittest.cc
index 45e256a..3e6bceb 100644
--- a/src/lib/datasrc/tests/memory/domaintree_unittest.cc
+++ b/src/lib/datasrc/tests/memory/domaintree_unittest.cc
@@ -17,6 +17,7 @@
 #include <exceptions/exceptions.h>
 
 #include <util/memory_segment_local.h>
+#include <util/random/random_number_generator.h>
 
 #include <dns/name.h>
 #include <dns/rrclass.h>
@@ -28,6 +29,8 @@
 
 #include <dns/tests/unittest_util.h>
 
+#include <boost/format.hpp>
+
 using namespace std;
 using namespace isc;
 using namespace isc::dns;
@@ -94,6 +97,10 @@ protected:
         const char* const domain_names[] = {
             "c", "b", "a", "x.d.e.f", "z.d.e.f", "g.h", "i.g.h", "o.w.y.d.e.f",
             "j.z.d.e.f", "p.w.y.d.e.f", "q.w.y.d.e.f", "k.g.h"};
+        const int node_distances[] = {
+            3, 1, 2, 2, 2, 3, 1, 2, 1, 1, 2, 2
+        };
+
         int name_count = sizeof(domain_names) / sizeof(domain_names[0]);
         for (int i = 0; i < name_count; ++i) {
             dtree.insert(mem_sgmt_, Name(domain_names[i]), &dtnode);
@@ -103,7 +110,8 @@ protected:
 
             dtree_expose_empty_node.insert(mem_sgmt_, Name(domain_names[i]),
                                             &dtnode);
-            EXPECT_EQ(static_cast<int*>(NULL), dtnode->setData(new int(i + 1)));
+            EXPECT_EQ(static_cast<int*>(NULL), dtnode->setData(
+                          new int(node_distances[i])));
         }
     }
 
@@ -126,6 +134,110 @@ TEST_F(DomainTreeTest, nodeCount) {
     EXPECT_EQ(0, dtree.getNodeCount());
 }
 
+TEST_F(DomainTreeTest, getDistance) {
+    TestDomainTreeNodeChain node_path;
+    const TestDomainTreeNode* node = NULL;
+    EXPECT_EQ(TestDomainTree::EXACTMATCH,
+              dtree_expose_empty_node.find(Name("a"),
+                                           &node,
+                                           node_path));
+    while (node != NULL) {
+        const int* distance = node->getData();
+        if (distance != NULL) {
+            EXPECT_EQ(*distance, node->getDistance());
+        }
+        node = dtree_expose_empty_node.nextNode(node_path);
+    }
+}
+
+void
+checkDistances(const TestDomainTree& tree, int distance) {
+    TestDomainTreeNodeChain node_path;
+    const TestDomainTreeNode* node = NULL;
+
+    // Try to find a node left of the left-most node, and start from its
+    // next node (which is the left-most node in its subtree).
+    EXPECT_EQ(TestDomainTree::NOTFOUND,
+              tree.find<void*>(Name("0"), &node, node_path, NULL, NULL));
+    while ((node = tree.nextNode(node_path)) != NULL) {
+        // The distance from each node to its sub-tree root must be less
+        // than 2 * log(n).
+        EXPECT_GE(2 * distance, node->getDistance());
+    }
+}
+
+TEST_F(DomainTreeTest, checkDistanceRandom) {
+    // This test checks an important performance-related property of the
+    // DomainTree (a red-black tree), which is important for us: the
+    // longest path from a sub-tree's root to a node is no more than
+    // 2log(n). This tests that the tree is balanced.
+
+    // Names are inserted in random order.
+
+    TreeHolder mytree_holder(mem_sgmt_, TestDomainTree::create(mem_sgmt_));
+    TestDomainTree& mytree = *mytree_holder.get();
+    isc::util::random::UniformRandomIntegerGenerator gen('a', 'z');
+    const int log_num_nodes = 20;
+
+    // Make a large million+ node top-level domain tree, i.e., the
+    // following code inserts names such as:
+    //
+    //   savoucnsrkrqzpkqypbygwoiliawpbmz.
+    //   wkadamcbbpjtundbxcmuayuycposvngx.
+    //   wzbpznemtooxdpjecdxynsfztvnuyfao.
+    //   yueojmhyffslpvfmgyfwioxegfhepnqq.
+    //
+    for (int i = 0; i < (1 << log_num_nodes); i++) {
+        string namestr;
+        while (true) {
+            for (int j = 0; j < 32; j++) {
+                namestr += gen();
+            }
+            namestr += '.';
+
+            if (mytree.insert(mem_sgmt_, Name(namestr), &dtnode) ==
+                TestDomainTree::SUCCESS) {
+                break;
+            }
+
+            namestr.clear();
+        }
+
+        EXPECT_EQ(static_cast<int*>(NULL), dtnode->setData(new int(i + 1)));
+    }
+
+    checkDistances(mytree, log_num_nodes);
+}
+
+TEST_F(DomainTreeTest, checkDistanceSorted) {
+    // This test checks an important performance-related property of the
+    // DomainTree (a red-black tree), which is important for us: the
+    // longest path from a sub-tree's root to a node is no more than
+    // 2log(n). This tests that the tree is balanced.
+
+    // Names are inserted in sorted order.
+
+    TreeHolder mytree_holder(mem_sgmt_, TestDomainTree::create(mem_sgmt_));
+    TestDomainTree& mytree = *mytree_holder.get();
+    const int log_num_nodes = 20;
+
+    // Make a large million+ node top-level domain tree, i.e., the
+    // following code inserts names such as:
+    //
+    //   name00000000.
+    //   name00000001.
+    //   name00000002.
+    //   name00000003.
+    //
+    for (int i = 0; i < (1 << log_num_nodes); i++) {
+        const string namestr(boost::str(boost::format("name%08x.") % i));
+        mytree.insert(mem_sgmt_, Name(namestr), &dtnode);
+        EXPECT_EQ(static_cast<int*>(NULL), dtnode->setData(new int(i + 1)));
+    }
+
+    checkDistances(mytree, log_num_nodes);
+}
+
 TEST_F(DomainTreeTest, setGetData) {
     // set new data to an existing node.  It should have some data.
     int* newdata = new int(11);
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
index 1e97205..128aafe 100644
--- a/src/lib/dhcp/iface_mgr.cc
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -51,7 +51,8 @@ IfaceMgr::instance() {
 Iface::Iface(const std::string& name, int ifindex)
     :name_(name), ifindex_(ifindex), mac_len_(0), hardware_type_(0),
      flag_loopback_(false), flag_up_(false), flag_running_(false),
-     flag_multicast_(false), flag_broadcast_(false), flags_(0)
+     flag_multicast_(false), flag_broadcast_(false), flags_(0),
+     inactive4_(false), inactive6_(false)
 {
     memset(mac_, 0, sizeof(mac_));
 }
@@ -295,7 +296,8 @@ bool IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast) {
 
         if (iface->flag_loopback_ ||
             !iface->flag_up_ ||
-            !iface->flag_running_) {
+            !iface->flag_running_ ||
+            iface->inactive4_) {
             continue;
         }
 
@@ -361,7 +363,8 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
 
         if (iface->flag_loopback_ ||
             !iface->flag_up_ ||
-            !iface->flag_running_) {
+            !iface->flag_running_ ||
+            iface->inactive6_) {
             continue;
         }
 
diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h
index 35b4dc0..217524d 100644
--- a/src/lib/dhcp/iface_mgr.h
+++ b/src/lib/dhcp/iface_mgr.h
@@ -309,6 +309,14 @@ public:
     /// Interface flags (this value is as is returned by OS,
     /// it may mean different things on different OSes).
     uint32_t flags_;
+
+    /// Indicates that IPv4 sockets should (true) or should not (false)
+    /// be opened on this interface.
+    bool inactive4_;
+
+    /// Indicates that IPv6 sockets should (true) or should not (false)
+    /// be opened on this interface.
+    bool inactive6_;
 };
 
 /// @brief Handles network interfaces, transmission and reception.
diff --git a/src/lib/dhcp/option6_client_fqdn.h b/src/lib/dhcp/option6_client_fqdn.h
index 8815273..d0fa563 100644
--- a/src/lib/dhcp/option6_client_fqdn.h
+++ b/src/lib/dhcp/option6_client_fqdn.h
@@ -118,8 +118,8 @@ public:
     ///
     /// @param flag a combination of flags to be stored in flags field.
     /// @param domain_name a name to be stored in the domain-name field.
-    /// @param partial_domain_name indicates if the domain name is partial
-    /// (if true) or full (false).
+    /// @param domain_name_type indicates if the domain name is partial
+    /// or full.
     explicit Option6ClientFqdn(const uint8_t flag,
                                const std::string& domain_name,
                                const DomainNameType domain_name_type = FULL);
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc
index 919235b..07ff7c7 100644
--- a/src/lib/dhcp/pkt4.cc
+++ b/src/lib/dhcp/pkt4.cc
@@ -102,55 +102,60 @@ Pkt4::len() {
     return (length);
 }
 
-bool
+void
 Pkt4::pack() {
     if (!hwaddr_) {
         isc_throw(InvalidOperation, "Can't build Pkt4 packet. HWAddr not set.");
     }
 
-    size_t hw_len = hwaddr_->hwaddr_.size();
-
-    bufferOut_.writeUint8(op_);
-    bufferOut_.writeUint8(hwaddr_->htype_);
-    bufferOut_.writeUint8(hw_len < MAX_CHADDR_LEN ? hw_len : MAX_CHADDR_LEN);
-    bufferOut_.writeUint8(hops_);
-    bufferOut_.writeUint32(transid_);
-    bufferOut_.writeUint16(secs_);
-    bufferOut_.writeUint16(flags_);
-    bufferOut_.writeUint32(ciaddr_);
-    bufferOut_.writeUint32(yiaddr_);
-    bufferOut_.writeUint32(siaddr_);
-    bufferOut_.writeUint32(giaddr_);
-
-
-    if (hw_len <= MAX_CHADDR_LEN) {
-        // write up to 16 bytes of the hardware address (CHADDR field is 16
-        // bytes long in DHCPv4 message).
-        bufferOut_.writeData(&hwaddr_->hwaddr_[0],
-                             (hw_len < MAX_CHADDR_LEN ? hw_len : MAX_CHADDR_LEN) );
-        hw_len = MAX_CHADDR_LEN - hw_len;
-    } else {
-        hw_len = MAX_CHADDR_LEN;
+    try {
+        size_t hw_len = hwaddr_->hwaddr_.size();
+
+        bufferOut_.writeUint8(op_);
+        bufferOut_.writeUint8(hwaddr_->htype_);
+        bufferOut_.writeUint8(hw_len < MAX_CHADDR_LEN ? 
+                              hw_len : MAX_CHADDR_LEN);
+        bufferOut_.writeUint8(hops_);
+        bufferOut_.writeUint32(transid_);
+        bufferOut_.writeUint16(secs_);
+        bufferOut_.writeUint16(flags_);
+        bufferOut_.writeUint32(ciaddr_);
+        bufferOut_.writeUint32(yiaddr_);
+        bufferOut_.writeUint32(siaddr_);
+        bufferOut_.writeUint32(giaddr_);
+
+
+        if (hw_len <= MAX_CHADDR_LEN) {
+            // write up to 16 bytes of the hardware address (CHADDR field is 16
+            // bytes long in DHCPv4 message).
+            bufferOut_.writeData(&hwaddr_->hwaddr_[0], 
+                                 (hw_len < MAX_CHADDR_LEN ? 
+                                  hw_len : MAX_CHADDR_LEN) );
+            hw_len = MAX_CHADDR_LEN - hw_len;
+        } else {
+            hw_len = MAX_CHADDR_LEN;
+        }
+
+        // write (len) bytes of padding
+        vector<uint8_t> zeros(hw_len, 0);
+        bufferOut_.writeData(&zeros[0], hw_len);
+        // bufferOut_.writeData(chaddr_, MAX_CHADDR_LEN);
+
+        bufferOut_.writeData(sname_, MAX_SNAME_LEN);
+        bufferOut_.writeData(file_, MAX_FILE_LEN);
+
+        // write DHCP magic cookie
+        bufferOut_.writeUint32(DHCP_OPTIONS_COOKIE);
+
+        LibDHCP::packOptions(bufferOut_, options_);
+
+        // add END option that indicates end of options
+        // (End option is very simple, just a 255 octet)
+         bufferOut_.writeUint8(DHO_END);
+     } catch(const Exception& e) {
+        // An exception is thrown and message will be written to Logger
+         isc_throw(InvalidOperation, e.what());
     }
-
-    // write (len) bytes of padding
-    vector<uint8_t> zeros(hw_len, 0);
-    bufferOut_.writeData(&zeros[0], hw_len);
-    // bufferOut_.writeData(chaddr_, MAX_CHADDR_LEN);
-
-    bufferOut_.writeData(sname_, MAX_SNAME_LEN);
-    bufferOut_.writeData(file_, MAX_FILE_LEN);
-
-    // write DHCP magic cookie
-    bufferOut_.writeUint32(DHCP_OPTIONS_COOKIE);
-
-    LibDHCP::packOptions(bufferOut_, options_);
-
-    // add END option that indicates end of options
-    // (End option is very simple, just a 255 octet)
-    bufferOut_.writeUint8(DHO_END);
-
-    return (true);
 }
 
 void
diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h
index d232745..3b945f1 100644
--- a/src/lib/dhcp/pkt4.h
+++ b/src/lib/dhcp/pkt4.h
@@ -72,8 +72,8 @@ public:
     /// Options must be stored in options_ field.
     /// Output buffer will be stored in bufferOut_.
     ///
-    /// @return true if packing procedure was successful
-    bool
+    /// @throw InvalidOperation if packing fails
+    void
     pack();
 
     /// @brief Parses on-wire form of DHCPv4 packet.
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
index 27c8ca6..9ace94a 100644
--- a/src/lib/dhcp/pkt6.cc
+++ b/src/lib/dhcp/pkt6.cc
@@ -181,20 +181,21 @@ uint16_t Pkt6::directLen() const {
 }
 
 
-bool
+void
 Pkt6::pack() {
     switch (proto_) {
     case UDP:
-        return packUDP();
+        packUDP();
+        break;
     case TCP:
-        return packTCP();
+        packTCP();
+        break;
     default:
         isc_throw(BadValue, "Invalid protocol specified (non-TCP, non-UDP)");
     }
-    return (false); // never happens
 }
 
-bool
+void
 Pkt6::packUDP() {
     try {
 
@@ -252,16 +253,15 @@ Pkt6::packUDP() {
         LibDHCP::packOptions(bufferOut_, options_);
     }
     catch (const Exception& e) {
-        /// @todo: throw exception here once we turn this function to void.
-        return (false);
+       // An exception is thrown and message will be written to Logger
+       isc_throw(InvalidOperation, e.what());
     }
-    return (true);
 }
 
-bool
+void
 Pkt6::packTCP() {
     /// TODO Implement this function.
-    isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover)"
+    isc_throw(NotImplemented, "DHCPv6 over TCP (bulk leasequery and failover)"
               "not implemented yet.");
 }
 
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index c65142e..a7f95c6 100644
--- a/src/lib/dhcp/pkt6.h
+++ b/src/lib/dhcp/pkt6.h
@@ -116,8 +116,10 @@ public:
     /// Output buffer will be stored in data_. Length
     /// will be set in data_len_.
     ///
-    /// @return true if packing procedure was successful
-    bool pack();
+    /// @throw BadValue if packet protocol is invalid, InvalidOperation
+    /// if packing fails, or NotImplemented if protocol is TCP (IPv6 over TCP is
+    /// not yet supported).
+    void pack();
 
     /// @brief Dispatch method that handles binary packet parsing.
     ///
@@ -139,11 +141,6 @@ public:
     /// @return reference to output buffer
     const isc::util::OutputBuffer& getBuffer() const { return (bufferOut_); };
 
-    /// @brief Returns reference to input buffer.
-    ///
-    /// @return reference to input buffer
-    const OptionBuffer& getData() const { return(data_); }
-
     /// @brief Returns protocol of this packet (UDP or TCP).
     ///
     /// @return protocol type
@@ -347,9 +344,7 @@ public:
 
     /// collection of options present in this message
     ///
-    /// @todo: Text mentions protected, but this is really public
-    ///
-    /// @warning This protected member is accessed by derived
+    /// @warning This public member is accessed by derived
     /// classes directly. One of such derived classes is
     /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
     /// behavior must be taken into consideration before making
@@ -410,18 +405,30 @@ public:
     /// to be impossible). Therefore public field is considered the best
     /// (or least bad) solution.
     std::vector<RelayInfo> relay_info_;
+
+
+    /// unparsed data (in received packets)
+    ///
+    /// @warning This public member is accessed by derived
+    /// classes directly. One of such derived classes is
+    /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
+    /// behavior must be taken into consideration before making
+    /// changes to this member such as access scope restriction or
+    /// data format change etc.
+    OptionBuffer data_;
+
 protected:
     /// Builds on wire packet for TCP transmission.
     ///
     /// TODO This function is not implemented yet.
     ///
-    /// @return true, if build was successful
-    bool packTCP();
+    /// @throw NotImplemented, IPv6 over TCP is not yet supported.
+    void packTCP();
 
     /// Builds on wire packet for UDP transmission.
     ///
-    /// @return true, if build was successful
-    bool packUDP();
+    /// @throw InvalidOperation if packing fails
+    void packUDP();
 
     /// @brief Parses on-wire form of TCP DHCPv6 packet.
     ///
@@ -492,16 +499,6 @@ protected:
     /// DHCPv6 transaction-id
     uint32_t transid_;
 
-    /// unparsed data (in received packets)
-    ///
-    /// @warning This protected member is accessed by derived
-    /// classes directly. One of such derived classes is
-    /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
-    /// behavior must be taken into consideration before making
-    /// changes to this member such as access scope restriction or
-    /// data format change etc.
-    OptionBuffer data_;
-
     /// name of the network interface the packet was received/to be sent over
     std::string iface_;
 
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index cdb0159..488ecb3 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -766,9 +766,9 @@ TEST_F(IfaceMgrTest, sendReceive6) {
     ASSERT_TRUE(rcvPkt); // received our own packet
 
     // let's check that we received what was sent
-    ASSERT_EQ(sendPkt->getData().size(), rcvPkt->getData().size());
-    EXPECT_EQ(0, memcmp(&sendPkt->getData()[0], &rcvPkt->getData()[0],
-                        rcvPkt->getData().size()));
+    ASSERT_EQ(sendPkt->data_.size(), rcvPkt->data_.size());
+    EXPECT_EQ(0, memcmp(&sendPkt->data_[0], &rcvPkt->data_[0],
+                        rcvPkt->data_.size()));
 
     EXPECT_EQ(sendPkt->getRemoteAddr().toText(), rcvPkt->getRemoteAddr().toText());
 
diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc
index d4907d6..138148a 100644
--- a/src/lib/dhcp/tests/pkt6_unittest.cc
+++ b/src/lib/dhcp/tests/pkt6_unittest.cc
@@ -64,8 +64,8 @@ TEST_F(Pkt6Test, constructor) {
     uint8_t data[] = { 0, 1, 2, 3, 4, 5 };
     scoped_ptr<Pkt6> pkt1(new Pkt6(data, sizeof(data)));
 
-    EXPECT_EQ(6, pkt1->getData().size());
-    EXPECT_EQ(0, memcmp( &pkt1->getData()[0], data, sizeof(data)));
+    EXPECT_EQ(6, pkt1->data_.size());
+    EXPECT_EQ(0, memcmp( &pkt1->data_[0], data, sizeof(data)));
 }
 
 /// @brief returns captured actual SOLICIT packet
@@ -221,7 +221,7 @@ TEST_F(Pkt6Test, packUnpack) {
     EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
               parent->len());
 
-    EXPECT_TRUE(parent->pack());
+    EXPECT_NO_THROW(parent->pack());
 
     EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
               parent->len());
@@ -515,7 +515,7 @@ TEST_F(Pkt6Test, relayPack) {
 
     EXPECT_EQ(DHCPV6_ADVERTISE, parent->getType());
 
-    EXPECT_TRUE(parent->pack());
+    EXPECT_NO_THROW(parent->pack());
 
     EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN // ADVERTISE
               + Pkt6::DHCPV6_RELAY_HDR_LEN // Relay header
diff --git a/src/lib/dhcp_ddns/.gitignore b/src/lib/dhcp_ddns/.gitignore
new file mode 100644
index 0000000..6388b8c
--- /dev/null
+++ b/src/lib/dhcp_ddns/.gitignore
@@ -0,0 +1,2 @@
+/dhcp_ddns_messages.cc
+/dhcp_ddns_messages.h
diff --git a/src/lib/dhcp_ddns/Makefile.am b/src/lib/dhcp_ddns/Makefile.am
new file mode 100644
index 0000000..260288b
--- /dev/null
+++ b/src/lib/dhcp_ddns/Makefile.am
@@ -0,0 +1,54 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS  = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(BOTAN_INCLUDE)
+AM_CXXFLAGS  = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+
+# Define rule to build logging source files from message file
+dhcp_ddns_messages.h dhcp_ddns_messages.cc: dhcp_ddns_messages.mes
+	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
+
+# Tell automake that the message files are built as part of the build process
+# (so that they are built before the main library is built).
+BUILT_SOURCES = dhcp_ddns_messages.h dhcp_ddns_messages.cc
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST = dhcp_ddns_messages.mes libdhcp_ddns.dox
+
+# Get rid of generated message files on a clean
+CLEANFILES = *.gcno *.gcda dhcp_ddns_messages.h dhcp_ddns_messages.cc
+
+lib_LTLIBRARIES = libb10-dhcp_ddns.la
+libb10_dhcp_ddns_la_SOURCES  =
+libb10_dhcp_ddns_la_SOURCES += dhcp_ddns_log.cc dhcp_ddns_log.h
+libb10_dhcp_ddns_la_SOURCES += ncr_io.cc ncr_io.h
+libb10_dhcp_ddns_la_SOURCES += ncr_msg.cc ncr_msg.h
+libb10_dhcp_ddns_la_SOURCES += ncr_udp.cc ncr_udp.h
+
+nodist_libb10_dhcp_ddns_la_SOURCES = dhcp_ddns_messages.cc dhcp_ddns_messages.h
+
+libb10_dhcp_ddns_la_CXXFLAGS = $(AM_CXXFLAGS)
+libb10_dhcp_ddns_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+libb10_dhcp_ddns_la_LDFLAGS  = $(AM_LDFLAGS)
+libb10_dhcp_ddns_la_LDFLAGS += ${BOTAN_LDFLAGS}
+libb10_dhcp_ddns_la_LIBADD  =
+libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
+libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
+libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+libb10_dhcp_ddns_la_LIBADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+libb10_dhcp_ddns_la_LIBADD += ${BOTAN_LIBS} ${BOTAN_RPATH}
+
+if USE_CLANGPP
+# Disable unused parameter warning caused by some of the
+# Boost headers when compiling with clang.
+libb10_dhcp_ddns_la_CXXFLAGS += -Wno-unused-parameter
+endif
+
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_log.cc b/src/lib/dhcp_ddns/dhcp_ddns_log.cc
new file mode 100644
index 0000000..4411b41
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_log.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// Defines the logger used by the top-level component of b10-dhcp_ddns.
+
+#include <dhcp_ddns/dhcp_ddns_log.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Defines the logger used within lib dhcp_ddns.
+isc::log::Logger dhcp_ddns_logger("libdhcp-ddns");
+
+} // namespace dhcp_ddns
+} // namespace isc
+
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_log.h b/src/lib/dhcp_ddns/dhcp_ddns_log.h
new file mode 100644
index 0000000..951f61e
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_log.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCP_DDNS_LOG_H
+#define DHCP_DDNS_LOG_H
+
+#include <log/logger_support.h>
+#include <log/macros.h>
+#include <dhcp_ddns/dhcp_ddns_messages.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// Define the logger for the "dhcp_ddns" logging.
+extern isc::log::Logger dhcp_ddns_logger;
+
+} // namespace dhcp_ddns
+} // namespace isc
+
+#endif // DHCP_DDNS_LOG_H
diff --git a/src/lib/dhcp_ddns/dhcp_ddns_messages.mes b/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
new file mode 100644
index 0000000..1934859
--- /dev/null
+++ b/src/lib/dhcp_ddns/dhcp_ddns_messages.mes
@@ -0,0 +1,63 @@
+# Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::dhcp_ddns
+
+% DHCP_DDNS_INVALID_NCR application received an invalid DNS update request: %1
+This is an error message that indicates that an invalid request to update
+a DNS entry was received by the application.  Either the format or the content
+of the request is incorrect. The request will be ignored.
+
+% DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR application encountered an error while closing the listener used to receive NameChangeRequests : %1
+This is an error message that indicates the application was unable to close the
+listener connection used to receive NameChangeRequests.  Closure may occur
+during the course of error recovery or during normal shutdown procedure.  In
+either case the error is unlikely to impair the application's ability to
+process requests but it should be reported for analysis.
+
+% DHCP_DDNS_NCR_RECV_NEXT_ERROR application could not initiate the next read following a request receive.
+This is a error message indicating that NameChangeRequest listener could not
+start another read after receiving a request.  While possible, this is highly
+unlikely and is probably a programmatic error.  The application should recover
+on its own.
+
+% DHCP_DDNS_NCR_SEND_CLOSE_ERROR DHCP-DDNS client encountered an error while closing the sender connection used to send NameChangeRequests : %1
+This is an error message that indicates the DHCP-DDNS client was unable to
+close the connection used to send NameChangeRequests.  Closure may occur during
+the course of error recovery or during normal shutdown procedure.  In either
+case the error is unlikely to impair the client's ability to send requests but
+it should be reported for analysis.
+
+% DHCP_DDNS_NCR_SEND_NEXT_ERROR DHCP-DDNS client could not initiate the next request send following send completion.
+This is a error message indicating that NameChangeRequest sender could not
+start another send after completing the send of the previous request.  While
+possible, this is highly unlikely and is probably a programmatic error.  The
+application should recover on its own.
+
+% DHCP_DDNS_NCR_UDP_RECV_ERROR UDP socket receive error while listening for DNS Update requests: %1
+This is an error message indicating that an IO error occurred while listening
+over a UDP socket for DNS update requests. This could indicate a network
+connectivity or system resource issue.
+
+% DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR unexpected exception thrown from the application receive completion handler: %1
+This is an error message that indicates that an exception was thrown but not
+caught in the application's request receive completion handler.  This is a
+programmatic error that needs to be reported.  Dependent upon the nature of
+the error the application may or may not continue operating normally.
+
+% DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR unexpected exception thrown from the DHCP-DDNS client send completion handler: %1
+This is an error message that indicates that an exception was thrown but not
+caught in the application's send completion handler.  This is a programmatic
+error that needs to be reported.  Dependent upon the nature of the error the
+client may or may not continue operating normally.
diff --git a/src/lib/dhcp_ddns/libdhcp_ddns.dox b/src/lib/dhcp_ddns/libdhcp_ddns.dox
new file mode 100644
index 0000000..af7c943
--- /dev/null
+++ b/src/lib/dhcp_ddns/libdhcp_ddns.dox
@@ -0,0 +1,47 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/**
+ at page libdhcp_ddns DHCP_DDNS Request IO Library
+
+ at section libdhcp_ddnsIntro Libdhcp_ddns Library Introduction
+
+libdhcp_ddns is a library of classes for sending and receiving requests used
+by ISC's  DHCP-DDNS service for carrying out DHCP-driven DDNS.  These requests
+are implemented in this library by the class, NameChangeRequest.
+
+This class provides services for constructing the requests as well as
+marshalling them to and from various transport formats.  Currently, the only
+format supported is JSON, however the design of the classes in this library is such that supporting additional formats will be easy to add.
+
+For sending and receiving NameChangeRequests, this library supplies an abstract
+pair of classes, NameChangeSender and NameChangeListener.  NameChangeSender
+defines the public interface that DHCP_DDNS clients, such as DHCP servers use
+for sending requests to DHCP_DDNS.   NameChangeListener is used by request
+consumers, primarily the DHCP_DDNS service, for receiving the requests.
+
+By providing abstract interfaces, the implementation isolates the senders and
+listeners from any underlying details of request transportation.  This was done
+to allow support for a variety of transportation mechanisms.  Currently, the
+only transport supported is via UDP Sockets, though support for TCP/IP sockets
+is forthcoming.  There may eventually be an implementation which is driven
+through an RDBMS.
+
+The UDP implementation is provided by NameChangeUDPSender and NameChangeUPDListener.
+The implementation is strictly unidirectional.  This means that there is no explicit
+acknowledgement of receipt of a request, and as it is UDP there is no guarantee of
+delivery.  Once provided, transport via TCP/IP sockets will provide a more reliable
+mechanism.
+
+*/
diff --git a/src/lib/dhcp_ddns/ncr_io.cc b/src/lib/dhcp_ddns/ncr_io.cc
new file mode 100644
index 0000000..38c8de0
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_io.cc
@@ -0,0 +1,283 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp_ddns/dhcp_ddns_log.h>
+#include <dhcp_ddns/ncr_io.h>
+
+namespace isc {
+namespace dhcp_ddns {
+
+//************************** NameChangeListener ***************************
+
+NameChangeListener::NameChangeListener(RequestReceiveHandler&
+                                       recv_handler)
+    : listening_(false), recv_handler_(recv_handler) {
+};
+
+void
+NameChangeListener::startListening(isc::asiolink::IOService& io_service) {
+    if (amListening()) {
+        // This amounts to a programmatic error.
+        isc_throw(NcrListenerError, "NameChangeListener is already listening");
+    }
+
+    // Call implementation dependent open.
+    try {
+        open(io_service);
+    } catch (const isc::Exception& ex) {
+        stopListening();
+        isc_throw(NcrListenerOpenError, "Open failed:" << ex.what());
+    }
+
+    // Set our status to listening.
+    setListening(true);
+
+    // Start the first asynchronous receive.
+    try {
+        doReceive();
+    } catch (const isc::Exception& ex) {
+        stopListening();
+        isc_throw(NcrListenerReceiveError, "doReceive failed:" << ex.what());
+    }
+}
+
+void
+NameChangeListener::stopListening() {
+    try {
+        // Call implementation dependent close.
+        close();
+    } catch (const isc::Exception &ex) {
+        // Swallow exceptions. If we have some sort of error we'll log
+        // it but we won't propagate the throw.
+        LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR)
+                  .arg(ex.what());
+    }
+
+    // Set it false, no matter what.  This allows us to at least try to
+    // re-open via startListening().
+    setListening(false);
+}
+
+void
+NameChangeListener::invokeRecvHandler(const Result result,
+                                      NameChangeRequestPtr& ncr) {
+    // Call the registered application layer handler.
+    // Surround the invocation with a try-catch. The invoked handler is
+    // not supposed to throw, but in the event it does we will at least
+    // report it.
+    try {
+        recv_handler_(result, ncr);
+    } catch (const std::exception& ex) {
+        LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
+                  .arg(ex.what());
+    }
+
+    // Start the next IO layer asynchronous receive.
+    // In the event the handler above intervened and decided to stop listening
+    // we need to check that first.
+    if (amListening()) {
+        try {
+            doReceive();
+        } catch (const isc::Exception& ex) {
+            // It is possible though unlikely, for doReceive to fail without
+            // scheduling the read. While, unlikely, it does mean the callback
+            // will not get called with a failure. A throw here would surface
+            // at the IOService::run (or run variant) invocation.  So we will
+            // close the window by invoking the application handler with
+            // a failed result, and let the application layer sort it out.
+            LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_RECV_NEXT_ERROR)
+                      .arg(ex.what());
+
+            // Call the registered application layer handler.
+            // Surround the invocation with a try-catch. The invoked handler is
+            // not supposed to throw, but in the event it does we will at least
+            // report it.
+            NameChangeRequestPtr empty;
+            try {
+                recv_handler_(ERROR, empty);
+            } catch (const std::exception& ex) {
+                LOG_ERROR(dhcp_ddns_logger,
+                          DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
+                          .arg(ex.what());
+            }
+        }
+    }
+}
+
+//************************* NameChangeSender ******************************
+
+NameChangeSender::NameChangeSender(RequestSendHandler& send_handler,
+                                   size_t send_queue_max)
+    : sending_(false), send_handler_(send_handler),
+      send_queue_max_(send_queue_max) {
+
+    // Queue size must be big enough to hold at least 1 entry.
+    if (send_queue_max == 0) {
+        isc_throw(NcrSenderError, "NameChangeSender constructor"
+                  " queue size must be greater than zero");
+    }
+}
+
+void
+NameChangeSender::startSending(isc::asiolink::IOService& io_service) {
+    if (amSending()) {
+        // This amounts to a programmatic error.
+        isc_throw(NcrSenderError, "NameChangeSender is already sending");
+    }
+
+    // Clear send marker.
+    ncr_to_send_.reset();
+
+    // Call implementation dependent open.
+    try {
+        open(io_service);
+    } catch (const isc::Exception& ex) {
+        stopSending();
+        isc_throw(NcrSenderOpenError, "Open failed: " << ex.what());
+    }
+
+    // Set our status to sending.
+    setSending(true);
+}
+
+void
+NameChangeSender::stopSending() {
+    try {
+        // Call implementation dependent close.
+        close();
+    } catch (const isc::Exception &ex) {
+        // Swallow exceptions. If we have some sort of error we'll log
+        // it but we won't propagate the throw.
+        LOG_ERROR(dhcp_ddns_logger, 
+                  DHCP_DDNS_NCR_SEND_CLOSE_ERROR).arg(ex.what());
+    }
+
+    // Set it false, no matter what.  This allows us to at least try to
+    // re-open via startSending().
+    setSending(false);
+}
+
+void
+NameChangeSender::sendRequest(NameChangeRequestPtr& ncr) {
+    if (!amSending()) {
+        isc_throw(NcrSenderError, "sender is not ready to send");
+    }
+
+    if (!ncr) {
+        isc_throw(NcrSenderError, "request to send is empty");
+    }
+
+    if (send_queue_.size() >= send_queue_max_) {
+        isc_throw(NcrSenderQueueFull, "send queue has reached maximum capacity:"
+                  << send_queue_max_ );
+    }
+
+    // Put it on the queue.
+    send_queue_.push_back(ncr);
+
+    // Call sendNext to schedule the next one to go.
+    sendNext();
+}
+
+void
+NameChangeSender::sendNext() {
+    if (ncr_to_send_) {
+        // @todo Not sure if there is any risk of getting stuck here but
+        // an interval timer to defend would be good.
+        // In reality, the derivation should ensure they timeout themselves
+        return;
+    }
+
+    // If queue isn't empty, then get one from the front. Note we leave
+    // it on the front of the queue until we successfully send it.
+    if (!send_queue_.empty()) {
+        ncr_to_send_ = send_queue_.front();
+
+       // @todo start defense timer
+       // If a send were to hang and we timed it out, then timeout
+       // handler need to cycle thru open/close ?
+
+       // Call implementation dependent send.
+       doSend(ncr_to_send_);
+    }
+}
+
+void
+NameChangeSender::invokeSendHandler(const NameChangeSender::Result result) {
+    // @todo reset defense timer
+    if (result == SUCCESS) {
+        // It shipped so pull it off the queue.
+        send_queue_.pop_front();
+    }
+
+    // Invoke the completion handler passing in the result and a pointer
+    // the request involved.
+    // Surround the invocation with a try-catch. The invoked handler is
+    // not supposed to throw, but in the event it does we will at least
+    // report it.
+    try {
+        send_handler_(result, ncr_to_send_);
+    } catch (const std::exception& ex) {
+        LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR)
+                  .arg(ex.what());
+    }
+
+    // Clear the pending ncr pointer.
+    ncr_to_send_.reset();
+
+    // Set up the next send
+    try {
+        sendNext();
+    } catch (const isc::Exception& ex) {
+        // It is possible though unlikely, for sendNext to fail without
+        // scheduling the send. While, unlikely, it does mean the callback
+        // will not get called with a failure. A throw here would surface
+        // at the IOService::run (or run variant) invocation.  So we will
+        // close the window by invoking the application handler with
+        // a failed result, and let the application layer sort it out.
+        LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_SEND_NEXT_ERROR)
+                  .arg(ex.what());
+
+        // Invoke the completion handler passing in failed result.
+        // Surround the invocation with a try-catch. The invoked handler is
+        // not supposed to throw, but in the event it does we will at least
+        // report it.
+        try {
+            send_handler_(ERROR, ncr_to_send_);
+        } catch (const std::exception& ex) {
+            LOG_ERROR(dhcp_ddns_logger, 
+                      DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR).arg(ex.what());
+        }
+    }
+}
+
+void
+NameChangeSender::skipNext() {
+    if (!send_queue_.empty()) {
+        // Discards the request at the front of the queue.
+        send_queue_.pop_front();
+    }
+}
+
+void
+NameChangeSender::clearSendQueue() {
+    if (amSending()) {
+        isc_throw(NcrSenderError, "Cannot clear queue while sending");
+    }
+
+    send_queue_.clear();
+}
+
+} // namespace isc::dhcp_ddns
+} // namespace isc
diff --git a/src/lib/dhcp_ddns/ncr_io.h b/src/lib/dhcp_ddns/ncr_io.h
new file mode 100644
index 0000000..b239425
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_io.h
@@ -0,0 +1,638 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef NCR_IO_H
+#define NCR_IO_H
+
+/// @file ncr_io.h
+/// @brief This file defines abstract classes for exchanging NameChangeRequests.
+///
+/// These classes are used for sending and receiving requests to update DNS
+/// information for FQDNs embodied as NameChangeRequests (aka NCRs). Ultimately,
+/// NCRs must move through the following three layers in order to implement
+/// DHCP-DDNS:
+///
+///    * Application layer - the business layer which needs to
+///    transport NameChangeRequests, and is unaware of the means by which
+///    they are transported.
+///
+///    * NameChangeRequest layer - This is the layer which acts as the
+///    intermediary between the Application layer and the IO layer.  It must
+///    be able to move NameChangeRequests to the IO layer as raw data and move
+///    raw data from the IO layer in the Application layer as
+///    NameChangeRequests.
+///
+///    * IO layer - the low-level layer that is directly responsible for
+///    sending and receiving data asynchronously and is supplied through
+///    other libraries.  This layer is largely unaware of the nature of the
+///    data being transmitted.  In other words, it doesn't know beans about
+///    NCRs.
+///
+/// The abstract classes defined here implement the latter, middle layer,
+/// the NameChangeRequest layer.  There are two types of participants in this
+/// middle ground:
+///
+///    * listeners - Receive NCRs from one or more sources. The DHCP-DDNS
+///   application, (aka D2), is a listener. Listeners are embodied by the
+///   class, NameChangeListener.
+///
+///    * senders - sends NCRs to a given target.  DHCP servers are senders.
+///   Senders are embodied by the class, NameChangeListener.
+///
+/// These two classes present a public interface for asynchronous
+/// communications that is independent of the IO layer mechanisms.  While the
+/// type and details of the IO mechanism are not relevant to either class, it
+/// is presumed to use isc::asiolink library for asynchronous event processing.
+///
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <exceptions/exceptions.h>
+
+#include <deque>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Exception thrown if an NcrListenerError encounters a general error.
+class NcrListenerError : public isc::Exception {
+public:
+    NcrListenerError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class NcrListenerOpenError : public isc::Exception {
+public:
+    NcrListenerOpenError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO receive.
+class NcrListenerReceiveError : public isc::Exception {
+public:
+    NcrListenerReceiveError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Abstract interface for receiving NameChangeRequests.
+///
+/// NameChangeListener provides the means to:
+/// -  Supply a callback to invoke upon receipt of an NCR or a listening
+/// error
+/// -  Start listening using a given IOService instance to process events
+/// -  Stop listening
+///
+/// It implements the high level logic flow to listen until a request arrives,
+/// invoke the implementation's handler and return to listening for the next
+/// request.
+///
+/// It provides virtual methods that allow derivations supply implementations
+/// to open the appropriate IO source, perform a listen, and close the IO
+/// source.
+///
+/// The overall design is based on a callback chain. The listener's caller (the
+/// application) supplies an "application" layer callback through which it will
+/// receive inbound NameChangeRequests.  The listener derivation will supply
+/// its own callback to the IO layer to process receive events from its IO
+/// source.  This is referred to as the NameChangeRequest completion handler.
+/// It is through this handler that the NameChangeRequest layer gains access
+/// to the low level IO read service results.  It is expected to assemble
+/// NameChangeRequests from the inbound data and forward them to the
+/// application layer by invoking the application layer callback registered
+/// with the listener.
+///
+/// The application layer callback is structured around a nested class,
+/// RequestReceiveHandler.  It consists of single, abstract operator() which
+/// accepts a result code and a pointer to a NameChangeRequest as parameters.
+/// In order to receive inbound NCRs, a caller implements a derivation of the
+/// RequestReceiveHandler and supplies an instance of this derivation to the
+/// NameChangeListener constructor. This "registers" the handler with the
+/// listener.
+///
+/// To begin listening, the caller invokes the listener's startListener()
+/// method, passing in an IOService instance.  This in turn will pass the
+/// IOService into the virtual method, open().  The open method is where the
+/// listener derivation performs the steps necessary to prepare its IO source
+/// for reception (e.g. opening a socket, connecting to a database).
+///
+/// Assuming the open is successful, startListener will call the virtual
+/// doReceive() method.  The listener derivation uses this method to
+/// instigate an IO layer asynchronous passing in its IO layer callback to
+/// handle receive events from its IO source.
+///
+/// As stated earlier, the derivation's NameChangeRequest completion handler
+/// MUST invoke the application layer handler registered with the listener.
+/// This is done by passing in either a success status and a populated
+/// NameChangeRequest or an error status and an empty request into the
+/// listener's invokeRecvHandler method. This is the mechanism by which the
+/// listener's caller is handed inbound NCRs.
+class NameChangeListener {
+public:
+
+    /// @brief Defines the outcome of an asynchronous NCR receive
+    enum Result {
+      SUCCESS,
+      TIME_OUT,
+      STOPPED,
+      ERROR
+    };
+
+    /// @brief Abstract class for defining application layer receive callbacks.
+    ///
+    /// Applications which will receive NameChangeRequests must provide a
+    /// derivation of this class to the listener constructor in order to
+    /// receive NameChangeRequests.
+    class RequestReceiveHandler {
+      public:
+        /// @brief Function operator implementing a NCR receive callback.
+        ///
+        /// This method allows the application to receive the inbound
+        /// NameChangeRequests. It is intended to function as a hand off of
+        /// information and should probably not be time-consuming.
+        ///
+        /// @param result contains that receive outcome status.
+        /// @param ncr is a pointer to the newly received NameChangeRequest if
+        /// result is NameChangeListener::SUCCESS.  It is indeterminate other
+        /// wise.
+        /// @throw This method MUST NOT throw.
+        virtual void operator ()(const Result result,
+                                 NameChangeRequestPtr& ncr) = 0;
+
+        virtual ~RequestReceiveHandler() {
+        }
+    };
+
+    /// @brief Constructor
+    ///
+    /// @param recv_handler is a pointer the application layer handler to be
+    /// invoked each time a NCR is received or a receive error occurs.
+    NameChangeListener(RequestReceiveHandler& recv_handler);
+
+    /// @brief Destructor
+    virtual ~NameChangeListener() {
+    };
+
+    /// @brief Prepares the IO for reception and initiates the first receive.
+    ///
+    /// Calls the derivation's open implementation to initialize the IO layer
+    /// source for receiving inbound requests.  If successful, it issues first
+    /// asynchronous read by calling the derivation's doReceive implementation.
+    ///
+    /// @param io_service is the IOService that will handle IO event processing.
+    ///
+    /// @throw NcrListenError if the listener is already "listening" or
+    /// in the event the open or doReceive methods fail.
+    void startListening(isc::asiolink::IOService& io_service);
+
+    /// @brief Closes the IO source and stops listen logic.
+    ///
+    /// Calls the derivation's implementation of close and marks the state
+    /// as not listening.
+    void stopListening();
+
+    /// @brief Calls the NCR receive handler registered with the listener.
+    ///
+    /// This is the hook by which the listener's caller's NCR receive handler
+    /// is called.  This method MUST be invoked by the derivation's
+    /// implementation of doReceive.
+    ///
+    /// NOTE:
+    /// The handler invoked by this method MUST NOT THROW. The handler is
+    /// at application level and should trap and handle any errors at
+    /// that level, rather than throw exceptions.  If an error has occurred
+    /// prior to invoking the handler, it will be expressed in terms a failed
+    /// result being passed to the handler, not a throw.  Therefore any
+    /// exceptions at the handler level are application issues and should be
+    /// dealt with at that level.
+    ///
+    /// This method does wrap the handler invocation within a try-catch
+    /// block as a fail-safe.  The exception will be logged but the
+    /// receive logic will continue.  What this implies is that continued
+    /// operation may or may not succeed as the application has violated
+    /// the interface contract.
+    ///
+    /// @param result contains that receive outcome status.
+    /// @param ncr is a pointer to the newly received NameChangeRequest if
+    /// result is NameChangeListener::SUCCESS.  It is indeterminate other
+    /// wise.
+    void invokeRecvHandler(const Result result, NameChangeRequestPtr& ncr);
+
+    /// @brief Abstract method which opens the IO source for reception.
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// prepare the IO source to receive requests.
+    ///
+    /// @param io_service is the IOService that process IO events.
+    ///
+    /// @throw If the implementation encounters an error it MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void open(isc::asiolink::IOService& io_service) = 0;
+
+    /// @brief Abstract method which closes the IO source.
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// "close" the IO source.
+    ///
+    /// @throw If the implementation encounters an error it  MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void close() = 0;
+
+    /// @brief Initiates an IO layer asynchronous read.
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// initiate an asynchronous read of the IO source with the
+    /// derivation's IO layer handler as the IO completion callback.
+    ///
+    /// @throw If the implementation encounters an error it  MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void doReceive() = 0;
+
+    /// @brief Returns true if the listener is listening, false otherwise.
+    ///
+    /// A true value indicates that the IO source has been opened successfully,
+    /// and that receive loop logic is active.
+    bool amListening() const {
+        return (listening_);
+    }
+
+private:
+    /// @brief Sets the listening indicator to the given value.
+    ///
+    /// Note, this method is private as it is used the base class is solely
+    /// responsible for managing the state.
+    ///
+    /// @param value is the new value to assign to the indicator.
+    void setListening(bool value) {
+        listening_ = value;
+    }
+
+    /// @brief Indicates if the listener is listening.
+    bool listening_;
+
+    /// @brief Application level NCR receive completion handler.
+    RequestReceiveHandler& recv_handler_;
+};
+
+/// @brief Defines a smart pointer to an instance of a listener.
+typedef boost::shared_ptr<NameChangeListener> NameChangeListenerPtr;
+
+
+/// @brief Thrown when a NameChangeSender encounters an error.
+class NcrSenderError : public isc::Exception {
+public:
+    NcrSenderError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class NcrSenderOpenError : public isc::Exception {
+public:
+    NcrSenderOpenError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO send.
+class NcrSenderQueueFull : public isc::Exception {
+public:
+    NcrSenderQueueFull(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO send.
+class NcrSenderSendError : public isc::Exception {
+public:
+    NcrSenderSendError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Abstract interface for sending NameChangeRequests.
+///
+/// NameChangeSender provides the means to:
+/// - Supply a callback to invoke upon completing the delivery of an NCR or a
+/// send error
+/// - Start sending using a given IOService instance to process events
+/// - Queue NCRs for delivery
+/// - Stop sending
+///
+/// It implements the high level logic flow to queue requests for delivery,
+/// and ship them one at a time, waiting for the send to complete prior to
+/// sending the next request in the queue.  If a send fails, the request
+/// will remain at the front of queue and will be the send will be retried
+/// endlessly unless the caller dequeues the request.  Note, it is presumed that
+/// a send failure is some form of IO error such as loss of connectivity and
+/// not a message content error.  It should not be possible to queue an invalid
+/// message.
+///
+/// It should be noted that once a request is placed onto the send queue it
+/// will remain there until one of three things occur:
+///     * It is successfully delivered
+///     * @c NameChangeSender::skipNext() is called
+///     * @c NameChangeSender::clearSendQueue() is called
+///
+/// The queue contents are preserved across start and stop listening
+/// transitions. This is to provide for error recovery without losing
+/// undelivered requests.
+
+/// It provides virtual methods so derivations may supply implementations to
+/// open the appropriate IO sink, perform a send, and close the IO sink.
+///
+/// The overall design is based on a callback chain.  The sender's caller (the
+/// application) supplies an "application" layer callback through which it will
+/// be given send completion notifications. The sender derivation will employ
+/// its own callback at the IO layer to process send events from its IO sink.
+/// This callback is expected to forward the outcome of each asynchronous send
+/// to the application layer by invoking the application layer callback
+/// registered with the sender.
+///
+/// The application layer callback is structured around a nested class,
+/// RequestSendHandler.  It consists of single, abstract operator() which
+/// accepts a result code and a pointer to a NameChangeRequest as parameters.
+/// In order to receive send completion notifications, a caller implements a
+/// derivation of the RequestSendHandler and supplies an instance of this
+/// derivation to the NameChangeSender constructor. This "registers" the
+/// handler with the sender.
+///
+/// To begin sending, the caller invokes the listener's startSending()
+/// method, passing in an IOService instance.  This in turn will pass the
+/// IOService into the virtual method, open().  The open method is where the
+/// sender derivation performs the steps necessary to prepare its IO sink for
+/// output (e.g. opening a socket, connecting to a database).  At this point,
+/// the sender is ready to send messages.
+///
+/// In order to send a request, the application layer invokes the sender
+/// method, sendRequest(), passing in the NameChangeRequest to send.  This
+/// method places the request onto the back of the send queue, and then invokes
+/// the sender method, sendNext().
+///
+/// If there is already a send in progress when sendNext() is called, the method
+/// will return immediately rather than initiate the next send.  This is to
+/// ensure that sends are processed sequentially.
+///
+/// If there is not a send in progress and the send queue is not empty,
+/// the sendNext method will pass the NCR at the front of the send queue into
+/// the virtual doSend() method.
+///
+/// The sender derivation uses this doSend() method to instigate an IO layer
+/// asynchronous send with its IO layer callback to handle send events from its
+/// IO sink.
+///
+/// As stated earlier, the derivation's IO layer callback MUST invoke the
+/// application layer handler registered with the sender.  This is done by
+/// passing in  a status indicating the outcome of the send into the sender's
+/// invokeSendHandler method. This is the mechanism by which the sender's
+/// caller is handed outbound notifications.
+
+/// After invoking the application layer handler, the invokeSendHandler method
+/// will call the sendNext() method to initiate the next send.  This ensures
+/// that requests continue to dequeue and ship.
+///
+class NameChangeSender {
+public:
+
+    /// @brief Defines the type used for the request send queue.
+    typedef std::deque<NameChangeRequestPtr> SendQueue;
+
+    /// @brief Defines a default maximum number of entries in the send queue.
+    static const size_t MAX_QUEUE_DEFAULT = 1024;
+
+    /// @brief Defines the outcome of an asynchronous NCR send.
+    enum Result {
+        SUCCESS,
+        TIME_OUT,
+        STOPPED,
+        ERROR
+    };
+
+    /// @brief Abstract class for defining application layer send callbacks.
+    ///
+    /// Applications which will send NameChangeRequests must provide a
+    /// derivation of this class to the sender constructor in order to
+    /// receive send outcome notifications.
+    class RequestSendHandler {
+      public:
+        /// @brief Function operator implementing a NCR send callback.
+        ///
+        /// This method allows the application to receive the outcome of
+        /// each send.  It is intended to function as a hand off of information
+        /// and should probably not be time-consuming.
+        ///
+        /// @param result contains that send outcome status.
+        /// @param ncr is a pointer to the NameChangeRequest that was
+        /// delivered (or attempted).
+        ///
+        /// @throw This method MUST NOT throw.
+        virtual void operator ()(const Result result,
+                                 NameChangeRequestPtr& ncr) = 0;
+
+        virtual ~RequestSendHandler() {
+        }
+    };
+
+    /// @brief Constructor
+    ///
+    /// @param send_handler is a pointer the application layer handler to be
+    /// invoked each time a NCR send attempt completes.
+    /// @param send_queue_max is the maximum number of entries allowed in the
+    /// send queue.  Once the maximum number is reached, all calls to
+    /// sendRequest will fail with an exception.
+    NameChangeSender(RequestSendHandler& send_handler,
+            size_t send_queue_max = MAX_QUEUE_DEFAULT);
+
+    /// @brief Destructor
+    virtual ~NameChangeSender() {
+    }
+
+    /// @brief Prepares the IO for transmission.
+    ///
+    /// Calls the derivation's open implementation to initialize the IO layer
+    /// sink for sending outbound requests.
+    ///
+    /// @param io_service is the IOService that will handle IO event processing.
+    ///
+    /// @throw NcrSenderError if the sender is already "sending" or
+    /// NcrSenderOpenError if the open fails.
+    void startSending(isc::asiolink::IOService & io_service);
+
+    /// @brief Closes the IO sink and stops send logic.
+    ///
+    /// Calls the derivation's implementation of close and marks the state
+    /// as not sending.
+    void stopSending();
+
+    /// @brief Queues the given request to be sent.
+    ///
+    /// The given request is placed at the back of the send queue and then
+    /// sendNext is invoked.
+
+    ///
+    /// @param ncr is the NameChangeRequest to send.
+    ///
+    /// @throw NcrSenderError if the sender is not in sending state or
+    /// the request is empty; NcrSenderQueueFull if the send queue has reached
+    /// capacity.
+    void sendRequest(NameChangeRequestPtr& ncr);
+
+    /// @brief Dequeues and sends the next request on the send queue.
+    ///
+    /// If there is already a send in progress just return. If there is not
+    /// a send in progress and the send queue is not empty the grab the next
+    /// message on the front of the queue and call doSend().
+    ///
+    void sendNext();
+
+    /// @brief Calls the NCR send completion handler registered with the
+    /// sender.
+    ///
+    /// This is the hook by which the sender's caller's NCR send completion
+    /// handler is called.  This method MUST be invoked by the derivation's
+    /// implementation of doSend.   Note that if the send was a success,
+    /// the entry at the front of the queue is removed from the queue.
+    /// If not we leave it there so we can retry it.  After we invoke the
+    /// handler we clear the pending ncr value and queue up the next send.
+    ///
+    /// NOTE:
+    /// The handler invoked by this method MUST NOT THROW. The handler is
+    /// application level logic and should trap and handle any errors at
+    /// that level, rather than throw exceptions.  If IO errors have occurred
+    /// prior to invoking the handler, they are expressed in terms a failed
+    /// result being passed to the handler.  Therefore any exceptions at the
+    /// handler level are application issues and should be dealt with at that
+    /// level.
+    ///
+    /// This method does wrap the handler invocation within a try-catch
+    /// block as a fail-safe.  The exception will be logged but the
+    /// send logic will continue.  What this implies is that continued
+    /// operation may or may not succeed as the application has violated
+    /// the interface contract.
+    ///
+    /// @param result contains that send outcome status.
+    void invokeSendHandler(const NameChangeSender::Result result);
+
+    /// @brief Removes the request at the front of the send queue
+    ///
+    /// This method can be used to avoid further retries of a failed
+    /// send. It is provided primarily as a just-in-case measure. Since
+    /// a failed send results in the same request being retried continuously
+    /// this method makes it possible to remove that entry, causing the
+    /// subsequent entry in the queue to be attempted on the next send.
+    /// It is presumed that sends will only fail due to some sort of
+    /// communications issue. In the unlikely event that a request is
+    /// somehow tainted and causes an send failure based on its content,
+    /// this method provides a means to remove th message.
+    void skipNext();
+
+    /// @brief Flushes all entries in the send queue
+    ///
+    /// This method can be used to discard all of the NCRs currently in the
+    /// the send queue.  Note it may not be called while the sender is in
+    /// the sending state.
+    /// @throw NcrSenderError if called and sender is in sending state.
+    void clearSendQueue();
+
+    /// @brief Abstract method which opens the IO sink for transmission.
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// prepare the IO sink to send requests.
+    ///
+    /// @param io_service is the IOService that process IO events.
+    ///
+    /// @throw If the implementation encounters an error it MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void open(isc::asiolink::IOService& io_service) = 0;
+
+    /// @brief Abstract method which closes the IO sink.
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// "close" the IO sink.
+    ///
+    /// @throw If the implementation encounters an error it MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void close() = 0;
+
+    /// @brief Initiates an IO layer asynchronous send
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// initiate an asynchronous send through the IO sink of the given NCR.
+    ///
+    /// @param ncr is a pointer to the NameChangeRequest to send.
+    /// derivation's IO layer handler as the IO completion callback.
+    ///
+    /// @throw If the implementation encounters an error it MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void doSend(NameChangeRequestPtr& ncr) = 0;
+
+    /// @brief Returns true if the sender is in send mode, false otherwise.
+    ///
+    /// A true value indicates that the IO sink has been opened successfully,
+    /// and that send loop logic is active.
+    bool amSending() const {
+        return (sending_);
+    }
+
+    /// @brief Returns true when a send is in progress.
+    ///
+    /// A true value indicates that a request is actively in the process of
+    /// being delivered.
+    bool isSendInProgress() const {
+        return ((ncr_to_send_) ? true : false);
+    }
+
+    /// @brief Returns the maximum number of entries allowed in the send queue.
+    size_t getQueueMaxSize() const  {
+        return (send_queue_max_);
+    }
+
+    /// @brief Returns the number of entries currently in the send queue.
+    size_t getQueueSize() const {
+        return (send_queue_.size());
+    }
+
+private:
+    /// @brief Sets the sending indicator to the given value.
+    ///
+    /// Note, this method is private as it is used the base class is solely
+    /// responsible for managing the state.
+    ///
+    /// @param value is the new value to assign to the indicator.
+    void setSending(bool value) {
+            sending_ = value;
+    }
+
+    /// @brief Boolean indicator which tracks sending status.
+    bool sending_;
+
+    /// @brief A pointer to regisetered send completion handler.
+    RequestSendHandler& send_handler_;
+
+    /// @brief Maximum number of entries permitted in the send queue.
+    size_t send_queue_max_;
+
+    /// @brief Queue of the requests waiting to be sent.
+    SendQueue send_queue_;
+
+    /// @brief Pointer to the request which is in the process of being sent.
+    NameChangeRequestPtr ncr_to_send_;
+};
+
+/// @brief Defines a smart pointer to an instance of a sender.
+typedef boost::shared_ptr<NameChangeSender> NameChangeSenderPtr;
+
+} // namespace isc::dhcp_ddns
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcp_ddns/ncr_msg.cc b/src/lib/dhcp_ddns/ncr_msg.cc
new file mode 100644
index 0000000..8832579
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_msg.cc
@@ -0,0 +1,534 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp_ddns/ncr_msg.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_error.h>
+
+#include <botan/sha2_32.h>
+
+#include <sstream>
+#include <limits>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/********************************* D2Dhcid ************************************/
+
+D2Dhcid::D2Dhcid() {
+}
+
+D2Dhcid::D2Dhcid(const std::string& data) {
+    fromStr(data);
+}
+
+D2Dhcid::D2Dhcid(const isc::dhcp::DUID& duid,
+                 const std::vector<uint8_t>& wire_fqdn) {
+    fromDUID(duid, wire_fqdn);
+}
+
+
+void
+D2Dhcid::fromStr(const std::string& data) {
+    bytes_.clear();
+    try {
+        isc::util::encode::decodeHex(data, bytes_);
+    } catch (const isc::Exception& ex) {
+        isc_throw(NcrMessageError, "Invalid data in Dhcid:" << ex.what());
+    }
+}
+
+std::string
+D2Dhcid::toStr() const {
+    return (isc::util::encode::encodeHex(bytes_));
+}
+
+void
+D2Dhcid::fromDUID(const isc::dhcp::DUID& duid,
+                  const std::vector<uint8_t>& wire_fqdn) {
+    // DHCID created from DUID starts with two bytes representing
+    // a type of the identifier. The value of 0x0002 indicates that
+    // DHCID has been created from DUID. The 3rd byte is equal to 1
+    // which indicates that the SHA-256 algorithm is used to create
+    // a DHCID digest. This value is called digest-type.
+    static uint8_t dhcid_header[] = { 0x00, 0x02, 0x01 };
+
+    // We get FQDN in the wire format, so we don't know if it is
+    // valid. It is caller's responsibility to make sure it is in
+    // the valid format. Here we just make sure it is not empty.
+    if (wire_fqdn.empty()) {
+        isc_throw(isc::dhcp_ddns::NcrMessageError,
+                  "empty FQDN used to create DHCID");
+    }
+
+    // Get the wire representation of the DUID.
+    std::vector<uint8_t> data = duid.getDuid();
+    // It should be DUID class responsibility to validate the DUID
+    // but let's be on the safe side here and make sure that empty
+    // DUID is not returned.
+    if (data.empty()) {
+        isc_throw(isc::dhcp_ddns::NcrMessageError,
+                  "empty DUID used to create DHCID");
+    }
+
+    // Append FQDN in the wire format.
+    data.insert(data.end(), wire_fqdn.begin(), wire_fqdn.end());
+
+    // Use the DUID and FQDN to compute the digest (see RFC4701, section 3).
+    Botan::SecureVector<Botan::byte> secure;
+    try {
+        Botan::SHA_256 sha;
+        // We have checked already that the DUID and FQDN aren't empty
+        // so it is safe to assume that the data buffer is not empty.
+        secure = sha.process(static_cast<const Botan::byte*>(&data[0]),
+                             data.size());
+    } catch (const std::exception& ex) {
+        isc_throw(isc::dhcp_ddns::NcrMessageError,
+                  "error while generating DHCID from DUID: "
+                  << ex.what());
+    }
+
+    // The exception unsafe part is finished, so we can finally replace
+    // the contents of bytes_.
+    bytes_.assign(dhcid_header, dhcid_header + sizeof(dhcid_header));
+    bytes_.insert(bytes_.end(), secure.begin(), secure.end());
+}
+
+
+/**************************** NameChangeRequest ******************************/
+
+NameChangeRequest::NameChangeRequest()
+    : change_type_(CHG_ADD), forward_change_(false),
+    reverse_change_(false), fqdn_(""), ip_address_(""),
+    dhcid_(), lease_expires_on_(), lease_length_(0), status_(ST_NEW) {
+}
+
+NameChangeRequest::NameChangeRequest(const NameChangeType change_type,
+            const bool forward_change, const bool reverse_change,
+            const std::string& fqdn, const std::string& ip_address,
+            const D2Dhcid& dhcid,
+            const uint64_t lease_expires_on,
+            const uint32_t lease_length)
+    : change_type_(change_type), forward_change_(forward_change),
+    reverse_change_(reverse_change), fqdn_(fqdn), ip_address_(ip_address),
+    dhcid_(dhcid), lease_expires_on_(lease_expires_on),
+    lease_length_(lease_length), status_(ST_NEW) {
+
+    // Validate the contents. This will throw a NcrMessageError if anything
+    // is invalid.
+    validateContent();
+}
+
+NameChangeRequestPtr
+NameChangeRequest::fromFormat(const NameChangeFormat format,
+                              isc::util::InputBuffer& buffer) {
+    // Based on the format requested, pull the marshalled request from
+    // InputBuffer and pass it into the appropriate format-specific factory.
+    NameChangeRequestPtr ncr;
+    switch (format) {
+    case FMT_JSON: {
+        try {
+            // Get the length of the JSON text.
+            size_t len = buffer.readUint16();
+
+            // Read the text from the buffer into a vector.
+            std::vector<uint8_t> vec;
+            buffer.readVector(vec, len);
+
+            // Turn the vector into a string.
+            std::string string_data(vec.begin(), vec.end());
+
+            // Pass the string of JSON text into JSON factory to create the
+            // NameChangeRequest instance.  Note the factory may throw
+            // NcrMessageError.
+            ncr = NameChangeRequest::fromJSON(string_data);
+        } catch (isc::util::InvalidBufferPosition& ex) {
+            // Read error accessing data in InputBuffer.
+            isc_throw(NcrMessageError, "fromFormat: buffer read error: "
+                      << ex.what());
+        }
+
+        break;
+        }
+    default:
+        // Programmatic error, shouldn't happen.
+        isc_throw(NcrMessageError, "fromFormat - invalid format");
+        break;
+    }
+
+    return (ncr);
+}
+
+void
+NameChangeRequest::toFormat(const NameChangeFormat format,
+                            isc::util::OutputBuffer& buffer) const {
+    // Based on the format requested, invoke the appropriate format handler
+    // which will marshal this request's contents into the OutputBuffer.
+    switch (format) {
+    case FMT_JSON: {
+        // Invoke toJSON to create a JSON text of this request's contents.
+        std::string json = toJSON();
+        uint16_t length = json.size();
+
+        // Write the length of the JSON text to the OutputBuffer first, then
+        // write the JSON text itself.
+        buffer.writeUint16(length);
+        buffer.writeData(json.c_str(), length);
+        break;
+        }
+    default:
+        // Programmatic error, shouldn't happen.
+        isc_throw(NcrMessageError, "toFormat - invalid format");
+        break;
+    }
+}
+
+NameChangeRequestPtr
+NameChangeRequest::fromJSON(const std::string& json) {
+    // This method leverages the existing JSON parsing provided by isc::data
+    // library.  Should this prove to be a performance issue, it may be that
+    // lighter weight solution would be appropriate.
+
+    // Turn the string of JSON text into an Element set.
+    isc::data::ElementPtr elements;
+    try {
+        elements = isc::data::Element::fromJSON(json);
+    } catch (isc::data::JSONError& ex) {
+        isc_throw(NcrMessageError,
+                  "Malformed NameChangeRequest JSON: " << ex.what());
+    }
+
+    // Get a map of the Elements, keyed by element name.
+    ElementMap element_map = elements->mapValue();
+    isc::data::ConstElementPtr element;
+
+    // Use default constructor to create a "blank" NameChangeRequest.
+    NameChangeRequestPtr ncr(new NameChangeRequest());
+
+    // For each member of NameChangeRequest, find its element in the map and
+    // call the appropriate Element-based setter.  These setters may throw
+    // NcrMessageError if the given Element is the wrong type or its data
+    // content is lexically invalid.   If the element is NOT found in the
+    // map, getElement will throw NcrMessageError indicating the missing
+    // member. Currently there are no optional values.
+    element = ncr->getElement("change_type", element_map);
+    ncr->setChangeType(element);
+
+    element = ncr->getElement("forward_change", element_map);
+    ncr->setForwardChange(element);
+
+    element = ncr->getElement("reverse_change", element_map);
+    ncr->setReverseChange(element);
+
+    element = ncr->getElement("fqdn", element_map);
+    ncr->setFqdn(element);
+
+    element = ncr->getElement("ip_address", element_map);
+    ncr->setIpAddress(element);
+
+    element = ncr->getElement("dhcid", element_map);
+    ncr->setDhcid(element);
+
+    element = ncr->getElement("lease_expires_on", element_map);
+    ncr->setLeaseExpiresOn(element);
+
+    element = ncr->getElement("lease_length", element_map);
+    ncr->setLeaseLength(element);
+
+    // All members were in the Element set and were correct lexically. Now
+    // validate the overall content semantically.  This will throw an
+    // NcrMessageError if anything is amiss.
+    ncr->validateContent();
+
+    // Everything is valid, return the new instance.
+    return (ncr);
+}
+
+std::string
+NameChangeRequest::toJSON() const  {
+    // Create a JSON string of this request's contents.  Note that this method
+    // does NOT use the isc::data library as generating the output is straight
+    // forward.
+    std::ostringstream stream;
+
+    stream << "{\"change_type\":" << getChangeType() << ","
+        << "\"forward_change\":"
+        << (isForwardChange() ? "true" : "false") << ","
+        << "\"reverse_change\":"
+        << (isReverseChange() ? "true" : "false") << ","
+        << "\"fqdn\":\"" << getFqdn() << "\","
+        << "\"ip_address\":\"" << getIpAddress() << "\","
+        << "\"dhcid\":\"" << getDhcid().toStr() << "\","
+        << "\"lease_expires_on\":\""  << getLeaseExpiresOnStr() << "\","
+        << "\"lease_length\":" << getLeaseLength() << "}";
+
+    return (stream.str());
+}
+
+
+void
+NameChangeRequest::validateContent() {
+    //@todo This is an initial implementation which provides a minimal amount
+    // of validation.  FQDN, DHCID, and IP Address members are all currently
+    // strings, these may be replaced with richer classes.
+    if (fqdn_ == "") {
+        isc_throw(NcrMessageError, "FQDN cannot be blank");
+    }
+
+    // Validate IP Address.
+    try {
+        isc::asiolink::IOAddress io_addr(ip_address_);
+    } catch (const isc::asiolink::IOError& ex) {
+        isc_throw(NcrMessageError,
+                  "Invalid ip address string for ip_address: " << ip_address_);
+    }
+
+    // Validate the DHCID.
+    if (dhcid_.getBytes().size()  == 0) {
+        isc_throw(NcrMessageError, "DHCID cannot be blank");
+    }
+
+    // Ensure the request specifies at least one direction to update.
+    if (!forward_change_ && !reverse_change_) {
+        isc_throw(NcrMessageError,
+                  "Invalid Request, forward and reverse flags are both false");
+    }
+}
+
+isc::data::ConstElementPtr
+NameChangeRequest::getElement(const std::string& name,
+                              const ElementMap& element_map) const {
+    // Look for "name" in the element map.
+    ElementMap::const_iterator it = element_map.find(name);
+    if (it == element_map.end()) {
+        // Didn't find the element, so throw.
+        isc_throw(NcrMessageError,
+                  "NameChangeRequest value missing for: " << name );
+    }
+
+    // Found the element, return it.
+    return (it->second);
+}
+
+void
+NameChangeRequest::setChangeType(const NameChangeType value) {
+    change_type_ = value;
+}
+
+
+void
+NameChangeRequest::setChangeType(isc::data::ConstElementPtr element) {
+    long raw_value = -1;
+    try {
+        // Get the element's integer value.
+        raw_value = element->intValue();
+    } catch (isc::data::TypeError& ex) {
+        // We expect a integer Element type, don't have one.
+        isc_throw(NcrMessageError,
+                  "Wrong data type for change_type: " << ex.what());
+    }
+
+    if ((raw_value != CHG_ADD) && (raw_value != CHG_REMOVE)) {
+        // Value is not a valid change type.
+        isc_throw(NcrMessageError,
+                  "Invalid data value for change_type: " << raw_value);
+    }
+
+    // Good to go, make the assignment.
+    setChangeType(static_cast<NameChangeType>(raw_value));
+}
+
+void
+NameChangeRequest::setForwardChange(const bool value) {
+    forward_change_ = value;
+}
+
+void
+NameChangeRequest::setForwardChange(isc::data::ConstElementPtr element) {
+    bool value;
+    try {
+        // Get the element's boolean value.
+        value = element->boolValue();
+    } catch (isc::data::TypeError& ex) {
+        // We expect a boolean Element type, don't have one.
+        isc_throw(NcrMessageError,
+                  "Wrong data type for forward_change :" << ex.what());
+    }
+
+    // Good to go, make the assignment.
+    setForwardChange(value);
+}
+
+void
+NameChangeRequest::setReverseChange(const bool value) {
+    reverse_change_ = value;
+}
+
+void
+NameChangeRequest::setReverseChange(isc::data::ConstElementPtr element) {
+    bool value;
+    try {
+        // Get the element's boolean value.
+        value = element->boolValue();
+    } catch (isc::data::TypeError& ex) {
+        // We expect a boolean Element type, don't have one.
+        isc_throw(NcrMessageError,
+                  "Wrong data type for reverse_change :" << ex.what());
+    }
+
+    // Good to go, make the assignment.
+    setReverseChange(value);
+}
+
+
+void
+NameChangeRequest::setFqdn(isc::data::ConstElementPtr element) {
+    setFqdn(element->stringValue());
+}
+
+void
+NameChangeRequest::setFqdn(const std::string& value) {
+    fqdn_ = value;
+}
+
+void
+NameChangeRequest::setIpAddress(const std::string& value) {
+    ip_address_ = value;
+}
+
+
+void
+NameChangeRequest::setIpAddress(isc::data::ConstElementPtr element) {
+    setIpAddress(element->stringValue());
+}
+
+
+void
+NameChangeRequest::setDhcid(const std::string& value) {
+    dhcid_.fromStr(value);
+}
+
+void
+NameChangeRequest::setDhcid(isc::data::ConstElementPtr element) {
+    setDhcid(element->stringValue());
+}
+
+std::string
+NameChangeRequest::getLeaseExpiresOnStr() const {
+    return (isc::util::timeToText64(lease_expires_on_));
+}
+
+void
+NameChangeRequest::setLeaseExpiresOn(const std::string&  value) {
+    try {
+        lease_expires_on_ = isc::util::timeFromText64(value);
+    } catch(...) {
+        // We were given an invalid string, so throw.
+        isc_throw(NcrMessageError,
+            "Invalid date-time string: [" << value << "]");
+    }
+
+}
+
+void NameChangeRequest::setLeaseExpiresOn(isc::data::ConstElementPtr element) {
+    // Pull out the string value and pass it into the string setter.
+    setLeaseExpiresOn(element->stringValue());
+}
+
+void
+NameChangeRequest::setLeaseLength(const uint32_t value) {
+    lease_length_ = value;
+}
+
+void
+NameChangeRequest::setLeaseLength(isc::data::ConstElementPtr element) {
+    long value = -1;
+    try {
+        // Get the element's integer value.
+        value = element->intValue();
+    } catch (isc::data::TypeError& ex) {
+        // We expect a integer Element type, don't have one.
+        isc_throw(NcrMessageError,
+                  "Wrong data type for lease_length: " << ex.what());
+    }
+
+    // Make sure we the range is correct and value is positive.
+    if (value > std::numeric_limits<uint32_t>::max()) {
+        isc_throw(NcrMessageError, "lease_length value " << value <<
+                "is too large for unsigned 32-bit integer.");
+    }
+    if (value < 0) {
+        isc_throw(NcrMessageError, "lease_length value " << value <<
+             "is negative.  It must greater than or equal to zero ");
+    }
+
+    // Good to go, make the assignment.
+    setLeaseLength(static_cast<uint32_t>(value));
+}
+
+void
+NameChangeRequest::setStatus(const NameChangeStatus value) {
+    status_ = value;
+}
+
+std::string
+NameChangeRequest::toText() const {
+    std::ostringstream stream;
+
+    stream << "Type: " << static_cast<int>(change_type_) << " (";
+    switch (change_type_) {
+    case CHG_ADD:
+        stream << "CHG_ADD)\n";
+        break;
+    case CHG_REMOVE:
+        stream << "CHG_REMOVE)\n";
+        break;
+    default:
+        // Shouldn't be possible.
+        stream << "Invalid Value\n";
+    }
+
+    stream << "Forward Change: " << (forward_change_ ? "yes" : "no")
+           << std::endl
+           << "Reverse Change: " << (reverse_change_ ? "yes" : "no")
+           << std::endl
+           << "FQDN: [" << fqdn_ << "]" << std::endl
+           << "IP Address: [" << ip_address_  << "]" << std::endl
+           << "DHCID: [" << dhcid_.toStr() << "]" << std::endl
+           << "Lease Expires On: " << getLeaseExpiresOnStr() << std::endl
+           << "Lease Length: " << lease_length_ << std::endl;
+
+    return (stream.str());
+}
+
+bool
+NameChangeRequest::operator == (const NameChangeRequest& other) {
+    return ((change_type_ == other.change_type_) &&
+            (forward_change_ == other.forward_change_) &&
+            (reverse_change_ == other.reverse_change_) &&
+            (fqdn_ == other.fqdn_) &&
+            (ip_address_ == other.ip_address_) &&
+            (dhcid_ == other.dhcid_) &&
+            (lease_expires_on_ == other.lease_expires_on_) &&
+            (lease_length_ == other.lease_length_));
+}
+
+bool
+NameChangeRequest::operator != (const NameChangeRequest& other) {
+    return (!(*this == other));
+}
+
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/dhcp_ddns/ncr_msg.h b/src/lib/dhcp_ddns/ncr_msg.h
new file mode 100644
index 0000000..00a464a
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_msg.h
@@ -0,0 +1,521 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef NCR_MSG_H
+#define NCR_MSG_H
+
+/// @file ncr_msg.h
+/// @brief This file provides the classes needed to embody, compose, and
+/// decompose DNS update requests that are sent by DHCP-DDNS clients to
+/// DHCP-DDNS. These requests are referred to as NameChangeRequests.
+
+#include <cc/data.h>
+#include <dhcp/duid.h>
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+#include <util/time_utilities.h>
+
+#include <time.h>
+#include <string>
+
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Exception thrown when NameChangeRequest marshalling error occurs.
+class NcrMessageError : public isc::Exception {
+public:
+    NcrMessageError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Defines the types of DNS updates that can be requested.
+enum NameChangeType {
+  CHG_ADD,
+  CHG_REMOVE
+};
+
+/// @brief Defines the runtime processing status values for requests.
+enum NameChangeStatus  {
+  ST_NEW,
+  ST_PENDING,
+  ST_COMPLETED,
+  ST_FAILED
+};
+
+/// @brief Defines the list of data wire formats supported.
+enum NameChangeFormat {
+  FMT_JSON
+};
+
+/// @brief Container class for handling the DHCID value within a
+/// NameChangeRequest. It provides conversion to and from string for JSON
+/// formatting, but stores the data internally as unsigned bytes.
+class D2Dhcid {
+public:
+    /// @brief Default constructor
+    D2Dhcid();
+
+    /// @brief Constructor - Creates a new instance, populated by converting
+    /// a given string of digits into an array of unsigned bytes.
+    ///
+    /// @param data is a string of hexadecimal digits. The format is simply
+    /// a contiguous stream of digits, with no delimiters. For example a string
+    /// containing "14A3" converts to a byte array containing:  0x14, 0xA3.
+    ///
+    /// @throw NcrMessageError if the input data contains non-digits
+    /// or there is an odd number of digits.
+    D2Dhcid(const std::string& data);
+
+    /// @brief Constructor, creates an instance of the @c D2Dhcid from the
+    /// @c isc::dhcp::DUID.
+    ///
+    /// @param duid An object representing DUID.
+    /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+    D2Dhcid(const isc::dhcp::DUID& duid,
+            const std::vector<uint8_t>& wire_fqdn);
+
+    /// @brief Returns the DHCID value as a string of hexadecimal digits.
+    ///
+    /// @return a string containing a contiguous stream of digits.
+    std::string toStr() const;
+
+    /// @brief Sets the DHCID value based on the given string.
+    ///
+    /// @param data is a string of hexadecimal digits. The format is simply
+    /// a contiguous stream of digits, with no delimiters. For example a string
+    /// containing "14A3" converts to a byte array containing:  0x14, 0xA3.
+    ///
+    /// @throw NcrMessageError if the input data contains non-digits
+    /// or there is an odd number of digits.
+    void fromStr(const std::string& data);
+
+    /// @brief Sets the DHCID value based on the DUID and FQDN.
+    ///
+    /// @param duid A @c isc::dhcp::DUID object encapsulating DUID.
+    /// @param wire_fqdn A on-wire canonical representation of the FQDN.
+    void fromDUID(const isc::dhcp::DUID& duid,
+                  const std::vector<uint8_t>& wire_fqdn);
+
+    /// @brief Returns a reference to the DHCID byte vector.
+    ///
+    /// @return a reference to the vector.
+    const std::vector<uint8_t>& getBytes() {
+        return (bytes_);
+    }
+
+    /// @brief Compares two D2Dhcids for equality
+    bool operator==(const D2Dhcid& other) const {
+        return (this->bytes_ == other.bytes_);
+    }
+
+    /// @brief Compares two D2Dhcids for inequality
+    bool operator!=(const D2Dhcid& other) const {
+        return (this->bytes_ != other.bytes_);
+    }
+
+    /// @brief Compares two D2Dhcids lexcially
+    bool operator<(const D2Dhcid& other) const {
+        return (this->bytes_ < other.bytes_);
+    }
+
+private:
+    /// @brief Storage for the DHCID value in unsigned bytes.
+    std::vector<uint8_t> bytes_;
+};
+
+class NameChangeRequest;
+/// @brief Defines a pointer to a NameChangeRequest.
+typedef boost::shared_ptr<NameChangeRequest> NameChangeRequestPtr;
+
+/// @brief Defines a map of Elements, keyed by their string name.
+typedef std::map<std::string, isc::data::ConstElementPtr> ElementMap;
+
+/// @brief  Represents a DHCP-DDNS client request.
+/// This class is used by DHCP-DDNS clients (e.g. DHCP4, DHCP6) to
+/// request DNS updates.  Each message contains a single DNS change (either an
+/// add/update or a remove) for a single FQDN.  It provides marshalling services
+/// for moving instances to and from the wire.  Currently, the only format
+/// supported is JSON, however the class provides an interface such that other
+/// formats can be readily supported.
+class NameChangeRequest {
+public:
+    /// @brief Default Constructor.
+    NameChangeRequest();
+
+    /// @brief Constructor.  Full constructor, which provides parameters for
+    /// all of the class members, except status.
+    ///
+    /// @param change_type the type of change (Add or Update)
+    /// @param forward_change indicates if this change should be sent to forward
+    /// DNS servers.
+    /// @param reverse_change indicates if this change should be sent to reverse
+    /// DNS servers.
+    /// @param fqdn the domain name whose pointer record(s) should be
+    /// updated.
+    /// @param ip_address the ip address leased to the given FQDN.
+    /// @param dhcid the lease client's unique DHCID.
+    /// @param lease_expires_on a timestamp containing the date/time the lease
+    /// expires.
+    /// @param lease_length the amount of time in seconds for which the
+    /// lease is valid (TTL).
+    NameChangeRequest(const NameChangeType change_type,
+                      const bool forward_change, const bool reverse_change,
+                      const std::string& fqdn, const std::string& ip_address,
+                      const D2Dhcid& dhcid,
+                      const uint64_t lease_expires_on,
+                      const uint32_t lease_length);
+
+    /// @brief Static method for creating a NameChangeRequest from a
+    /// buffer containing a marshalled request in a given format.
+    ///
+    /// When the format is:
+    ///
+    /// JSON: The buffer is expected to contain a two byte unsigned integer
+    /// which specified the length of the JSON text; followed by the JSON
+    /// text itself.  This method attempts to extract "length" characters
+    /// from the buffer. This data is used to create a character string that
+    /// is than treated as JSON which is then parsed into the data needed
+    /// to create a request instance.
+    ///
+    /// (NOTE currently only JSON is supported.)
+    ///
+    /// @param format indicates the data format to use
+    /// @param buffer is the input buffer containing the marshalled request
+    ///
+    /// @return a pointer to the new NameChangeRequest
+    ///
+    /// @throw NcrMessageError if an error occurs creating new
+    /// request.
+    static NameChangeRequestPtr fromFormat(const NameChangeFormat format,
+                                           isc::util::InputBuffer& buffer);
+
+    /// @brief Instance method for marshalling the contents of the request
+    /// into the given buffer in the given format.
+    ///
+    /// When the format is:
+    ///
+    /// JSON: Upon completion, the buffer will contain a two byte unsigned
+    /// integer which specifies the length of the JSON text; followed by the
+    /// JSON text itself. The JSON text contains the names and values for all
+    /// the request data needed to reassemble the request on the receiving
+    /// end. The JSON text in the buffer is NOT null-terminated.
+    ///
+    /// (NOTE currently only JSON is supported.)
+    ///
+    /// @param format indicates the data format to use
+    /// @param buffer is the output buffer to which the request should be
+    /// marshalled.
+    void toFormat(const NameChangeFormat format,
+                  isc::util::OutputBuffer& buffer) const;
+
+    /// @brief Static method for creating a NameChangeRequest from a
+    /// string containing a JSON rendition of a request.
+    ///
+    /// @param json is a string containing the JSON text
+    ///
+    /// @return a pointer to the new NameChangeRequest
+    ///
+    /// @throw NcrMessageError if an error occurs creating new request.
+    static NameChangeRequestPtr fromJSON(const std::string& json);
+
+    /// @brief Instance method for marshalling the contents of the request
+    /// into a string of JSON text.
+    ///
+    /// @return a string containing the JSON rendition of the request
+    std::string toJSON() const;
+
+    /// @brief Validates the content of a populated request.  This method is
+    /// used by both the full constructor and from-wire marshalling to ensure
+    /// that the request is content valid.  Currently it enforces the
+    /// following rules:
+    ///
+    ///  - FQDN must not be blank.
+    ///  - The IP address must be a valid address.
+    ///  - The DHCID must not be blank.
+    ///  - The lease expiration date must be a valid date/time.
+    ///  - That at least one of the two direction flags, forward change and
+    ///    reverse change is true.
+    ///
+    /// @todo This is an initial implementation which provides a minimal amount
+    /// of validation.  FQDN, DHCID, and IP Address members are all currently
+    /// strings, these may be replaced with richer classes.
+    ///
+    /// @throw NcrMessageError if the request content violates any
+    /// of the validation rules.
+    void validateContent();
+
+    /// @brief Fetches the request change type.
+    ///
+    /// @return the change type
+    NameChangeType getChangeType() const {
+        return (change_type_);
+    }
+
+    /// @brief Sets the change type to the given value.
+    ///
+    /// @param value is the NameChangeType value to assign to the request.
+    void setChangeType(const NameChangeType value);
+
+    /// @brief Sets the change type to the value of the given Element.
+    ///
+    /// @param element is an integer Element containing the change type value.
+    ///
+    /// @throw NcrMessageError if the element is not an integer
+    /// Element or contains an invalid value.
+    void setChangeType(isc::data::ConstElementPtr element);
+
+    /// @brief Checks forward change flag.
+    ///
+    /// @return a true if the forward change flag is true.
+    bool isForwardChange() const {
+        return (forward_change_);
+    }
+
+    /// @brief Sets the forward change flag to the given value.
+    ///
+    /// @param value contains the new value to assign to the forward change
+    /// flag
+    void setForwardChange(const bool value);
+
+    /// @brief Sets the forward change flag to the value of the given Element.
+    ///
+    /// @param element is a boolean Element containing the forward change flag
+    /// value.
+    ///
+    /// @throw NcrMessageError if the element is not a boolean
+    /// Element
+    void setForwardChange(isc::data::ConstElementPtr element);
+
+    /// @brief Checks reverse change flag.
+    ///
+    /// @return a true if the reverse change flag is true.
+    bool isReverseChange() const {
+        return (reverse_change_);
+    }
+
+    /// @brief Sets the reverse change flag to the given value.
+    ///
+    /// @param value contains the new value to assign to the reverse change
+    /// flag
+    void setReverseChange(const bool value);
+
+    /// @brief Sets the reverse change flag to the value of the given Element.
+    ///
+    /// @param element is a boolean Element containing the reverse change flag
+    /// value.
+    ///
+    /// @throw NcrMessageError if the element is not a boolean
+    /// Element
+    void setReverseChange(isc::data::ConstElementPtr element);
+
+    /// @brief Fetches the request FQDN
+    ///
+    /// @return a string containing the FQDN
+    const std::string getFqdn() const {
+        return (fqdn_);
+    }
+
+    /// @brief Sets the FQDN to the given value.
+    ///
+    /// @param value contains the new value to assign to the FQDN
+    void setFqdn(const std::string& value);
+
+    /// @brief Sets the FQDN to the value of the given Element.
+    ///
+    /// @param element is a string Element containing the FQDN
+    ///
+    /// @throw NcrMessageError if the element is not a string
+    /// Element
+    void setFqdn(isc::data::ConstElementPtr element);
+
+    /// @brief Fetches the request IP address.
+    ///
+    /// @return a string containing the IP address
+    const std::string& getIpAddress() const {
+        return (ip_address_);
+    }
+
+    /// @brief Sets the IP address to the given value.
+    ///
+    /// @param value contains the new value to assign to the IP address
+    void setIpAddress(const std::string& value);
+
+    /// @brief Sets the IP address to the value of the given Element.
+    ///
+    /// @param element is a string Element containing the IP address
+    ///
+    /// @throw NcrMessageError if the element is not a string
+    /// Element
+    void setIpAddress(isc::data::ConstElementPtr element);
+
+    /// @brief Fetches the request DHCID
+    ///
+    /// @return a reference to the request's D2Dhcid
+    const D2Dhcid& getDhcid() const {
+        return (dhcid_);
+    }
+
+    /// @brief Sets the DHCID based on the given string value.
+    ///
+    /// @param value is a string of hexadecimal digits. The format is simply
+    /// a contiguous stream of digits, with no delimiters. For example a string
+    /// containing "14A3" converts to a byte array containing:  0x14, 0xA3.
+    ///
+    /// @throw NcrMessageError if the input data contains non-digits
+    /// or there is an odd number of digits.
+    void setDhcid(const std::string& value);
+
+    /// @brief Sets the DHCID based on the value of the given Element.
+    ///
+    /// @param element is a string Element containing the string of hexadecimal
+    /// digits. (See setDhcid(std::string&) above.)
+    ///
+    /// @throw NcrMessageError if the input data contains non-digits
+    /// or there is an odd number of digits.
+    void setDhcid(isc::data::ConstElementPtr element);
+
+    /// @brief Fetches the request lease expiration
+    ///
+    /// @return the lease expiration as the number of seconds since
+    /// the (00:00:00 January 1, 1970)
+    uint64_t getLeaseExpiresOn() const {
+        return (lease_expires_on_);
+    }
+
+    /// @brief Fetches the request lease expiration as string.
+    ///
+    /// The format of the string returned is:
+    ///
+    ///    YYYYMMDDHHmmSS
+    ///
+    /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
+    /// NOTE This is always UTC time.
+    ///
+    /// @return a ISO date-time string of the lease expiration.
+    std::string getLeaseExpiresOnStr() const;
+
+    /// @brief Sets the lease expiration based on the given string.
+    ///
+    /// @param value is an date-time string from which to set the
+    /// lease expiration. The format of the input is:
+    ///
+    ///    YYYYMMDDHHmmSS
+    ///
+    /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
+    /// NOTE This is always UTC time.
+    ///
+    /// @throw NcrMessageError if the ISO string is invalid.
+    void setLeaseExpiresOn(const std::string& value);
+
+    /// @brief Sets the lease expiration based on the given Element.
+    ///
+    /// @param element is string Element containing a date-time string.
+    ///
+    /// @throw NcrMessageError if the element is not a string
+    /// Element, or if the element value is an invalid date-time string.
+    void setLeaseExpiresOn(isc::data::ConstElementPtr element);
+
+    /// @brief Fetches the request lease length.
+    ///
+    /// @return an integer containing the lease length
+    uint32_t getLeaseLength() const {
+        return (lease_length_);
+    }
+
+    /// @brief Sets the lease length to the given value.
+    ///
+    /// @param value contains the new value to assign to the lease length
+    void setLeaseLength(const uint32_t value);
+
+    /// @brief Sets the lease length to the value of the given Element.
+    ///
+    /// @param element is a integer Element containing the lease length
+    ///
+    /// @throw NcrMessageError if the element is not a string
+    /// Element
+    void setLeaseLength(isc::data::ConstElementPtr element);
+
+    /// @brief Fetches the request status.
+    ///
+    /// @return the request status as a NameChangeStatus
+    NameChangeStatus getStatus() const {
+        return (status_);
+    }
+
+    /// @brief Sets the request status to the given value.
+    ///
+    /// @param value contains the new value to assign to request status
+    void setStatus(const NameChangeStatus value);
+
+    /// @brief Given a name, finds and returns an element from a map of
+    /// elements.
+    ///
+    /// @param name is the name of the desired element
+    /// @param element_map is the map of elements to search
+    ///
+    /// @return a pointer to the element if located
+    /// @throw NcrMessageError if the element cannot be found within
+    /// the map
+    isc::data::ConstElementPtr getElement(const std::string& name,
+                                          const ElementMap& element_map) const;
+
+    /// @brief Returns a text rendition of the contents of the request.
+    /// This method is primarily for logging purposes.
+    ///
+    /// @return a string containing the text.
+    std::string toText() const;
+
+    bool operator == (const NameChangeRequest& b);
+    bool operator != (const NameChangeRequest& b);
+
+private:
+    /// @brief Denotes the type of this change as either an Add or a Remove.
+    NameChangeType change_type_;
+
+    /// @brief Indicates if this change should sent to forward DNS servers.
+    bool forward_change_;
+
+    /// @brief Indicates if this change should sent to reverse DNS servers.
+    bool reverse_change_;
+
+    /// @brief The domain name whose DNS entry(ies) are to be updated.
+    /// @todo Currently, this is a std::string but may be replaced with
+    /// dns::Name which provides additional validation and domain name
+    /// manipulation.
+    std::string fqdn_;
+
+    /// @brief The ip address leased to the FQDN.
+    std::string ip_address_;
+
+    /// @brief The lease client's unique DHCID.
+    /// @todo Currently, this is uses D2Dhcid it but may be replaced with
+    /// dns::DHCID which provides additional validation.
+    D2Dhcid dhcid_;
+
+    /// @brief The date-time the lease expires.
+    uint64_t lease_expires_on_;
+
+    /// @brief The amount of time in seconds for which the lease is valid (TTL).
+    uint32_t lease_length_;
+
+    /// @brief The processing status of the request.  Used internally.
+    NameChangeStatus status_;
+};
+
+
+}; // end of isc::dhcp_ddns namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/lib/dhcp_ddns/ncr_udp.cc b/src/lib/dhcp_ddns/ncr_udp.cc
new file mode 100644
index 0000000..55444b2
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_udp.cc
@@ -0,0 +1,299 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp_ddns/dhcp_ddns_log.h>
+#include <dhcp_ddns/ncr_udp.h>
+
+#include <asio/ip/udp.hpp>
+#include <asio/error_code.hpp>
+#include <boost/bind.hpp>
+
+namespace isc {
+namespace dhcp_ddns {
+
+//*************************** UDPCallback ***********************
+UDPCallback::UDPCallback (RawBufferPtr& buffer, const size_t buf_size,
+                          UDPEndpointPtr& data_source,
+                          const UDPCompletionHandler& handler)
+    : handler_(handler), data_(new Data(buffer, buf_size, data_source)) {
+    if (handler.empty()) {
+        isc_throw(NcrUDPError, "UDPCallback - handler can't be null");
+    }
+
+    if (!buffer) {
+        isc_throw(NcrUDPError, "UDPCallback - buffer can't be null");
+    }
+}
+
+void
+UDPCallback::operator ()(const asio::error_code error_code,
+                         const size_t bytes_transferred) {
+
+    // Save the result state and number of bytes transferred.
+    setErrorCode(error_code);
+    setBytesTransferred(bytes_transferred);
+
+    // Invoke the NameChangeRequest layer completion handler.
+    // First argument is a boolean indicating success or failure.
+    // The second is a pointer to "this" callback object. By passing
+    // ourself in, we make all of the service related data available
+    // to the completion handler.
+    handler_(!error_code, this);
+}
+
+void
+UDPCallback::putData(const uint8_t* src, size_t len) {
+    if (!src) {
+        isc_throw(NcrUDPError, "UDPCallback putData, data source is NULL");
+    }
+
+    if (len > data_->buf_size_) {
+        isc_throw(NcrUDPError, "UDPCallback putData, data length too large");
+    }
+
+    memcpy (data_->buffer_.get(), src, len);
+    data_->put_len_ = len;
+}
+
+
+//*************************** NameChangeUDPListener ***********************
+NameChangeUDPListener::
+NameChangeUDPListener(const isc::asiolink::IOAddress& ip_address,
+                      const uint32_t port, const NameChangeFormat format,
+                      RequestReceiveHandler& ncr_recv_handler,
+                      const bool reuse_address)
+    : NameChangeListener(ncr_recv_handler), ip_address_(ip_address),
+      port_(port), format_(format), reuse_address_(reuse_address) {
+    // Instantiate the receive callback.  This gets passed into each receive.
+    // Note that the callback constructor is passed an instance method
+    // pointer to our completion handler method, receiveCompletionHandler.
+    RawBufferPtr buffer(new uint8_t[RECV_BUF_MAX]);
+    UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
+    recv_callback_.reset(new
+                         UDPCallback(buffer, RECV_BUF_MAX, data_source,
+                                     boost::bind(&NameChangeUDPListener::
+                                     receiveCompletionHandler, this, _1, _2)));
+}
+
+NameChangeUDPListener::~NameChangeUDPListener() {
+    // Clean up.
+    stopListening();
+}
+
+void
+NameChangeUDPListener::open(isc::asiolink::IOService& io_service) {
+    // create our endpoint and bind the the low level socket to it.
+    isc::asiolink::UDPEndpoint endpoint(ip_address_.getAddress(), port_);
+
+    // Create the low level socket.
+    try {
+        asio_socket_.reset(new asio::ip::udp::
+                           socket(io_service.get_io_service(),
+                                  (ip_address_.isV4() ? asio::ip::udp::v4() :
+                                   asio::ip::udp::v6())));
+
+        // Set the socket option to reuse addresses if it is enabled.
+        if (reuse_address_) {
+            asio_socket_->set_option(asio::socket_base::reuse_address(true));
+        }
+
+        // Bind the low level socket to our endpoint.
+        asio_socket_->bind(endpoint.getASIOEndpoint());
+    } catch (asio::system_error& ex) {
+        isc_throw (NcrUDPError, ex.code().message());
+    }
+
+    // Create the asiolink socket from the low level socket.
+    socket_.reset(new NameChangeUDPSocket(*asio_socket_));
+}
+
+
+void
+NameChangeUDPListener::doReceive() {
+    // Call the socket's asychronous receiving, passing ourself in as callback.
+    RawBufferPtr recv_buffer = recv_callback_->getBuffer();
+    socket_->asyncReceive(recv_buffer.get(), recv_callback_->getBufferSize(),
+                          0, recv_callback_->getDataSource().get(),
+                          *recv_callback_);
+}
+
+void
+NameChangeUDPListener::close() {
+    // Whether we think we are listening or not, make sure we aren't.
+    // Since we are managing our own socket, we need to cancel and close
+    // it ourselves.
+    if (asio_socket_) {
+        try {
+            asio_socket_->cancel();
+            asio_socket_->close();
+        } catch (asio::system_error& ex) {
+            // It is really unlikely that this will occur.
+            // If we do reopen later it will be with a new socket instance.
+            // Repackage exception as one that is conformant with the interface.
+            isc_throw (NcrUDPError, ex.code().message());
+        }
+    }
+}
+
+void
+NameChangeUDPListener::receiveCompletionHandler(const bool successful,
+                                                const UDPCallback *callback) {
+    NameChangeRequestPtr ncr;
+    Result result = SUCCESS;
+
+    if (successful) {
+        // Make an InputBuffer from our internal array
+        isc::util::InputBuffer input_buffer(callback->getData(),
+                                            callback->getBytesTransferred());
+
+        try {
+            ncr = NameChangeRequest::fromFormat(format_, input_buffer);
+        } catch (const NcrMessageError& ex) {
+            // log it and go back to listening
+            LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_INVALID_NCR).arg(ex.what());
+
+            // Queue up the next recieve.
+            doReceive();
+            return;
+        }
+    } else {
+        asio::error_code error_code = callback->getErrorCode();
+        LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_RECV_ERROR)
+                  .arg(error_code.message());
+        result = ERROR;
+    }
+
+    // Call the application's registered request receive handler.
+    invokeRecvHandler(result, ncr);
+}
+
+
+//*************************** NameChangeUDPSender ***********************
+
+NameChangeUDPSender::
+NameChangeUDPSender(const isc::asiolink::IOAddress& ip_address,
+                    const uint32_t port,
+                    const isc::asiolink::IOAddress& server_address,
+                    const uint32_t server_port, const NameChangeFormat format,
+                    RequestSendHandler& ncr_send_handler,
+                    const size_t send_que_max, const bool reuse_address)
+    : NameChangeSender(ncr_send_handler, send_que_max),
+      ip_address_(ip_address), port_(port), server_address_(server_address),
+      server_port_(server_port), format_(format),
+      reuse_address_(reuse_address) {
+    // Instantiate the send callback.  This gets passed into each send.
+    // Note that the callback constructor is passed the an instance method
+    // pointer to our completion handler, sendCompletionHandler.
+    RawBufferPtr buffer(new uint8_t[SEND_BUF_MAX]);
+    UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
+    send_callback_.reset(new UDPCallback(buffer, SEND_BUF_MAX, data_source,
+                                         boost::bind(&NameChangeUDPSender::
+                                         sendCompletionHandler, this,
+                                         _1, _2)));
+}
+
+NameChangeUDPSender::~NameChangeUDPSender() {
+    // Clean up.
+    stopSending();
+}
+
+void
+NameChangeUDPSender::open(isc::asiolink::IOService& io_service) {
+    // create our endpoint and bind the the low level socket to it.
+    isc::asiolink::UDPEndpoint endpoint(ip_address_.getAddress(), port_);
+
+    // Create the low level socket.
+    try {
+        asio_socket_.reset(new asio::ip::udp::
+                           socket(io_service.get_io_service(),
+                                  (ip_address_.isV4() ? asio::ip::udp::v4() :
+                                   asio::ip::udp::v6())));
+
+        // Set the socket option to reuse addresses if it is enabled.
+        if (reuse_address_) {
+            asio_socket_->set_option(asio::socket_base::reuse_address(true));
+        }
+
+        // Bind the low leve socket to our endpoint.
+        asio_socket_->bind(endpoint.getASIOEndpoint());
+    } catch (asio::system_error& ex) {
+        isc_throw (NcrUDPError, ex.code().message());
+    }
+
+    // Create the asiolink socket from the low level socket.
+    socket_.reset(new NameChangeUDPSocket(*asio_socket_));
+
+    // Create the server endpoint
+    server_endpoint_.reset(new isc::asiolink::
+                           UDPEndpoint(server_address_.getAddress(),
+                                       server_port_));
+
+    send_callback_->setDataSource(server_endpoint_);
+}
+
+void
+NameChangeUDPSender::close() {
+    // Whether we think we are sending or not, make sure we aren't.
+    // Since we are managing our own socket, we need to cancel and close
+    // it ourselves.
+    if (asio_socket_) {
+        try {
+            asio_socket_->cancel();
+            asio_socket_->close();
+        } catch (asio::system_error& ex) {
+            // It is really unlikely that this will occur.
+            // If we do reopen later it will be with a new socket instance.
+            // Repackage exception as one that is conformant with the interface.
+            isc_throw (NcrUDPError, ex.code().message());
+        }
+    }
+}
+
+void
+NameChangeUDPSender::doSend(NameChangeRequestPtr& ncr) {
+    // Now use the NCR to write JSON to an output buffer.
+    isc::util::OutputBuffer ncr_buffer(SEND_BUF_MAX);
+    ncr->toFormat(format_, ncr_buffer);
+
+    // Copy the wire-ized request to callback.  This way we know after
+    // send completes what we sent (or attempted to send).
+    send_callback_->putData(static_cast<const uint8_t*>(ncr_buffer.getData()),
+                            ncr_buffer.getLength());
+
+    // Call the socket's asychronous send, passing our callback
+    socket_->asyncSend(send_callback_->getData(), send_callback_->getPutLen(),
+                       send_callback_->getDataSource().get(), *send_callback_);
+}
+
+void
+NameChangeUDPSender::sendCompletionHandler(const bool successful,
+                                           const UDPCallback *send_callback) {
+    Result result;
+    if (successful) {
+        result = SUCCESS;
+    }
+    else {
+        // On a failure, log the error and set the result to ERROR.
+        asio::error_code error_code = send_callback->getErrorCode();
+        LOG_ERROR(dhcp_ddns_logger, DHCP_DDNS_NCR_UDP_RECV_ERROR)
+                  .arg(error_code.message());
+
+        result = ERROR;
+    }
+
+    // Call the application's registered request send handler.
+    invokeSendHandler(result);
+}
+}; // end of isc::dhcp_ddns namespace
+}; // end of isc namespace
diff --git a/src/lib/dhcp_ddns/ncr_udp.h b/src/lib/dhcp_ddns/ncr_udp.h
new file mode 100644
index 0000000..7648a61
--- /dev/null
+++ b/src/lib/dhcp_ddns/ncr_udp.h
@@ -0,0 +1,562 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef NCR_UDP_LISTENER_H
+#define NCR_UDP_LISTENER_H
+
+/// @file ncr_udp.h
+/// @brief This file provides UDP socket based implementation for sending and
+/// receiving NameChangeRequests
+///
+/// These classes are derived from the abstract classes, NameChangeListener
+/// and NameChangeSender (see ncr_io.h).
+///
+/// The following discussion will refer to three layers of communications:
+///
+///    * Application layer - This is the business layer which needs to
+///    transport NameChangeRequests, and is unaware of the means by which
+///    they are transported.
+///
+///    * IO layer - This is the low-level layer that is directly responsible
+///    for sending and receiving data asynchronously and is supplied through
+///    other libraries.  This layer is largely unaware of the nature of the
+///    data being transmitted.  In other words, it doesn't know beans about
+///    NCRs.
+///
+///    * NameChangeRequest layer - This is the layer which acts as the
+///    intermediary between the Application layer and the IO layer.  It must
+///    be able to move NameChangeRequests to the IO layer as raw data and move
+///    raw data from the IO layer in the Application layer as
+///    NameChangeRequests.
+///
+/// This file defines NameChangeUDPListener class for receiving NCRs, and
+/// NameChangeUDPSender for sending NCRs.
+///
+/// Both the listener and sender implementations utilize the same underlying
+/// construct to move NCRs to and from a UDP socket. This construct consists
+/// of a set of classes centered around isc::asiolink::UDPSocket.  UDPSocket
+/// is a templated class that supports asio asynchronous event processing; and
+/// which accepts as its parameter, the name of a callback class.
+///
+/// The asynchronous services provided by UDPSocket typically accept a buffer
+/// for transferring data (either in or out depending on the service direction)
+/// and an object which supplies a callback to invoke upon completion of the
+/// service.
+///
+/// The callback class must provide an operator() with the following signature:
+/// @code
+///    void operator ()(const asio::error_code error_code,
+///                     const size_t bytes_transferred);
+/// @endcode
+///
+/// Upon completion of the service, the callback instance's operator() is
+/// invoked by the asio layer.  It is given both a outcome result and the
+/// number of bytes either read or written, to or from the buffer supplied
+/// to the service.
+///
+/// Typically, an asiolink based implementation would simply implement the
+/// callback operator directly.  However, the nature of the asiolink library
+/// is such that the callback object may be copied several times during course
+/// of a service invocation.  This implies that any class being used as a
+/// callback class must be copyable.  This is not always desirable.  In order
+/// to separate the callback class from the NameChangeRequest, the construct
+/// defines the UDPCallback class for use as a copyable, callback object.
+///
+/// The UDPCallback class provides the asiolink layer callback operator(),
+/// which is invoked by the asiolink layer upon service completion.  It
+/// contains:
+///    * a pointer to the transfer buffer
+///    * the capacity of the transfer buffer
+///    * a IO layer outcome result
+///    * the number of bytes transferred
+///    * a method pointer to a NameChangeRequest layer completion handler
+///
+/// This last item, is critical. It points to an instance method that
+/// will be invoked by the UDPCallback operator.  This provides access to
+/// the outcome of the service call to the NameChangeRequest layer without
+/// that layer being used as the actual callback object.
+///
+/// The completion handler method signature is codified in the typedef,
+/// UDPCompletionHandler, and must be as follows:
+///
+/// @code
+///     void(const bool, const UDPCallback*)
+/// @endcode
+///
+/// Note that is accepts two parameters.  The first is a boolean indicator
+/// which indicates if the service call completed successfully or not.  The
+/// second is a pointer to the callback object invoked by the IOService upon
+/// completion of the service.  The callback instance will contain all of the
+/// pertinent information about the invocation and outcome of the service.
+///
+/// Using the contents of the callback, it is the responsibility of the
+/// UDPCompletionHandler to interpret the results of the service invocation and
+/// pass the interpretation to the application layer via either
+/// NameChangeListener::invokeRecvHandler in the case of the UDP listener, or
+/// NameChangeSender::invokeSendHandler in the case of UDP sender.
+///
+#include <asio.hpp>
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <util/buffer.h>
+
+#include <boost/shared_array.hpp>
+
+/// responsibility of the completion handler to perform the steps necessary
+/// to interpret the raw data provided by the service outcome.   The
+/// UDPCallback operator implementation is mostly a pass through.
+///
+namespace isc {
+namespace dhcp_ddns {
+
+/// @brief Thrown when a UDP level exception occurs.
+class NcrUDPError : public isc::Exception {
+public:
+    NcrUDPError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+class UDPCallback;
+/// @brief Defines a function pointer for NameChangeRequest completion handlers.
+typedef boost::function<void(const bool, const UDPCallback*)>
+          UDPCompletionHandler;
+
+/// @brief Defines a dynamically allocated shared array.
+typedef boost::shared_array<uint8_t> RawBufferPtr;
+
+typedef boost::shared_ptr<asiolink::UDPEndpoint> UDPEndpointPtr;
+
+/// @brief Implements the callback class passed into UDPSocket calls.
+///
+/// It serves as the link between the asiolink::UDPSocket asynchronous services
+/// and the NameChangeRequest layer. The class provides the asiolink layer
+/// callback operator(), which is invoked by the asiolink layer upon service
+/// completion.  It contains all of the data pertinent to both the invocation
+/// and completion of a service, as well as a pointer to NameChangeRequest
+/// layer completion handler to invoke.
+///
+class UDPCallback {
+
+public:
+    /// @brief Container class which stores service invocation related data.
+    ///
+    /// Because the callback object may be copied numerous times during the
+    /// course of service invocation, it does not directly contain data values.
+    /// Rather it will retain a shared pointer to an instance of this structure
+    /// thus ensuring that all copies of the callback object, ultimately refer
+    /// to the same data values.
+    struct Data {
+
+        /// @brief Constructor
+        ///
+        /// @param buffer is a pointer to the data transfer buffer. This is
+        /// the buffer data will be written to on a read, or read from on a
+        /// send.
+        /// @param buf_size is the capacity of the buffer
+        /// @param data_source storage for UDP endpoint which supplied the data
+        Data(RawBufferPtr& buffer, const size_t buf_size,
+             UDPEndpointPtr& data_source)
+            : buffer_(buffer), buf_size_(buf_size), data_source_(data_source),
+              put_len_(0), error_code_(), bytes_transferred_(0) {
+        };
+
+        /// @brief A pointer to the data transfer buffer.
+        RawBufferPtr buffer_;
+
+        /// @brief Storage capacity of the buffer.
+        size_t buf_size_;
+
+        /// @brief The UDP endpoint that is the origin of the data transferred.
+        UDPEndpointPtr data_source_;
+
+        /// @brief Stores this size of the data within the buffer when written
+        /// there manually. (See UDPCallback::putData()) .
+        size_t put_len_;
+
+        /// @brief Stores the IO layer result code of the completed IO service.
+        asio::error_code error_code_;
+
+        /// @brief Stores the number of bytes transferred by completed IO
+        /// service.
+        /// For a read it is the number of bytes written into the
+        /// buffer.  For a write it is the number of bytes read from the
+        /// buffer.
+        size_t bytes_transferred_;
+
+    };
+
+    /// @brief Used as the callback object for UDPSocket services.
+    ///
+    /// @param buffer is a pointer to the data transfer buffer. This is
+    /// the buffer data will be written to on a read, or read from on a
+    /// send.
+    /// @param buf_size is the capacity of the buffer
+    /// @param data_source storage for UDP endpoint which supplied the data
+    /// @param handler is a method pointer to the completion handler that
+    /// is to be called by the operator() implementation.
+    ///
+    /// @throw NcrUDPError if either the handler or buffer pointers
+    /// are invalid.
+    UDPCallback (RawBufferPtr& buffer, const size_t buf_size,
+                 UDPEndpointPtr& data_source,
+                 const UDPCompletionHandler& handler);
+
+    /// @brief Operator that will be invoked by the asiolink layer.
+    ///
+    /// @param error_code is the IO layer result code of the
+    /// completed IO service.
+    /// @param bytes_transferred is the number of bytes transferred by
+    /// completed IO.
+    /// For a read it is the number of bytes written into the
+    /// buffer.  For a write it is the number of bytes read from the
+    /// buffer.
+    void operator ()(const asio::error_code error_code,
+                     const size_t bytes_transferred);
+
+    /// @brief Returns the number of bytes transferred by the completed IO
+    /// service.
+    ///
+    /// For a read it is the number of bytes written into the
+    /// buffer.  For a write it is the number of bytes read from the
+    /// buffer.
+    size_t getBytesTransferred() const {
+        return (data_->bytes_transferred_);
+    }
+
+    /// @brief Sets the number of bytes transferred.
+    ///
+    /// @param value is the new value to assign to bytes transferred.
+    void setBytesTransferred(const size_t value) {
+        data_->bytes_transferred_ = value;
+    }
+
+    /// @brief Returns the completed IO layer service outcome status.
+    asio::error_code getErrorCode() const {
+        return (data_->error_code_);
+    }
+
+    /// @brief Sets the completed IO layer service outcome status.
+    ///
+    /// @param value is the new value to assign to outcome status.
+    void setErrorCode(const asio::error_code value) {
+        data_->error_code_  = value;
+    }
+
+    /// @brief Returns the data transfer buffer.
+    RawBufferPtr getBuffer() const {
+        return (data_->buffer_);
+    }
+
+    /// @brief Returns the data transfer buffer capacity.
+    size_t getBufferSize() const {
+        return (data_->buf_size_);
+    }
+
+    /// @brief Returns a pointer the data transfer buffer content.
+    const uint8_t* getData() const {
+        return (data_->buffer_.get());
+    }
+
+    /// @brief Copies data into the data transfer buffer.
+    ///
+    /// Copies the given number of bytes from the given source buffer
+    /// into the data transfer buffer, and updates the value of put length.
+    /// This method may be used when performing sends to make a copy of
+    /// the "raw data" that was shipped (or attempted) accessible to the
+    /// upstream callback.
+    ///
+    /// @param src is a pointer to the data source from which to copy
+    /// @param len is the number of bytes to copy
+    ///
+    /// @throw NcrUDPError if the number of bytes to copy exceeds
+    /// the buffer capacity or if the source pointer is invalid.
+    void putData(const uint8_t* src, size_t len);
+
+    /// @brief Returns the number of bytes manually written into the
+    /// transfer buffer.
+    size_t getPutLen() const {
+        return (data_->put_len_);
+    }
+
+    /// @brief Sets the data source to the given endpoint.
+    ///
+    /// @param endpoint is the new value to assign to data source.
+    void setDataSource(UDPEndpointPtr& endpoint) {
+        data_->data_source_ = endpoint;
+    }
+
+    /// @brief Returns the UDP endpoint that provided the transferred data.
+    const UDPEndpointPtr& getDataSource() {
+        return (data_->data_source_);
+    }
+
+  private:
+    /// @brief NameChangeRequest layer completion handler to invoke.
+    UDPCompletionHandler handler_;
+
+    /// @brief Shared pointer to the service data container.
+    boost::shared_ptr<Data> data_;
+};
+
+/// @brief Convenience type for UDP socket based listener
+typedef isc::asiolink::UDPSocket<UDPCallback> NameChangeUDPSocket;
+
+/// @brief Provides the ability to receive NameChangeRequests via  UDP socket
+///
+/// This class is a derivation of the NameChangeListener which is capable of
+/// receiving NameChangeRequests through a UDP socket.  The caller need only
+/// supply network addressing and a RequestReceiveHandler instance to receive
+/// NameChangeRequests asynchronously.
+class NameChangeUDPListener : public NameChangeListener {
+public:
+    /// @brief Defines the maximum size packet that can be received.
+    static const size_t RECV_BUF_MAX = isc::asiolink::
+                                       UDPSocket<UDPCallback>::MIN_SIZE;
+
+    /// @brief Constructor
+    ///
+    /// @param ip_address is the network address on which to listen
+    /// @param port is the UDP port on which to listen
+    /// @param format is the wire format of the inbound requests. Currently
+    /// only JSON is supported
+    /// @param ncr_recv_handler the receive handler object to notify when
+    /// a receive completes.
+    /// @param reuse_address enables IP address sharing when true
+    /// It defaults to false.
+    ///
+    /// @throw base class throws NcrListenerError if handler is invalid.
+    NameChangeUDPListener(const isc::asiolink::IOAddress& ip_address,
+                          const uint32_t port,
+                          const NameChangeFormat format,
+                          RequestReceiveHandler& ncr_recv_handler,
+                          const bool reuse_address = false);
+
+    /// @brief Destructor.
+    virtual ~NameChangeUDPListener();
+
+    /// @brief Opens a UDP socket using the given IOService.
+    ///
+    /// Creates a NameChangeUDPSocket bound to the listener's ip address
+    /// and port, that is monitored by the given IOService instance.
+    ///
+    /// @param io_service the IOService which will monitor the socket.
+    ///
+    /// @throw NcrUDPError if the open fails.
+    virtual void open(isc::asiolink::IOService& io_service);
+
+    /// @brief Closes the UDPSocket.
+    ///
+    /// It first invokes the socket's cancel method which should stop any
+    /// pending read and remove the socket callback from the IOService. It
+    /// then calls the socket's close method to actually close the socket.
+    ///
+    /// @throw NcrUDPError if the open fails.
+    virtual void close();
+
+    /// @brief Initiates an asynchronous read on the socket.
+    ///
+    /// Invokes the asyncReceive() method on the socket passing in the
+    /// recv_callback_ member's transfer buffer as the receive buffer, and
+    /// recv_callback_ itself as the callback object.
+    ///
+    /// @throw NcrUDPError if the open fails.
+    void doReceive();
+
+    /// @brief Implements the NameChangeRequest level receive completion
+    /// handler.
+    ///
+    /// This method is invoked by the UPDCallback operator() implementation,
+    /// passing in the boolean success indicator and pointer to itself.
+    ///
+    /// If the indicator denotes success, then the method will attempt to
+    /// to construct a NameChangeRequest from the received data.  If the
+    /// construction was successful, it will send the new NCR to the
+    /// application layer by calling invokeRecvHandler() with a success
+    /// status and a pointer to the new NCR.
+    ///
+    /// If the buffer contains invalid data such that construction fails,
+    /// the method will log the failure and then call doReceive() to start a
+    /// initiate the next receive.
+    ///
+    /// If the indicator denotes failure the method will log the failure and
+    /// notify the application layer by calling invokeRecvHandler() with
+    /// an error status and an empty pointer.
+    ///
+    /// @param successful boolean indicator that should be true if the
+    /// socket receive completed without error, false otherwise.
+    /// @param recv_callback pointer to the callback instance which handled
+    /// the socket receive completion.
+    void receiveCompletionHandler(const bool successful,
+                                  const UDPCallback* recv_callback);
+private:
+    /// @brief IP address on which to listen for requests.
+    isc::asiolink::IOAddress ip_address_;
+
+    /// @brief Port number on which to listen for requests.
+    uint32_t port_;
+
+    /// @brief Wire format of the inbound requests.
+    NameChangeFormat format_;
+
+    /// @brief Low level socket underneath the listening socket
+    boost::shared_ptr<asio::ip::udp::socket> asio_socket_;
+
+    /// @brief NameChangeUDPSocket listening socket
+    boost::shared_ptr<NameChangeUDPSocket> socket_;
+
+    /// @brief Pointer to the receive callback
+    boost::shared_ptr<UDPCallback> recv_callback_;
+
+    /// @brief Flag which enables the reuse address socket option if true.
+    bool reuse_address_;
+
+    ///
+    /// @name Copy and constructor assignment operator
+    ///
+    /// The copy constructor and assignment operator are private to avoid
+    /// potential issues with multiple listeners attempting to share sockets
+    /// and callbacks.
+private:
+    NameChangeUDPListener(const NameChangeUDPListener& source);
+    NameChangeUDPListener& operator=(const NameChangeUDPListener& source);
+    //@}
+};
+
+
+/// @brief Provides the ability to send NameChangeRequests via  UDP socket
+///
+/// This class is a derivation of the NameChangeSender which is capable of
+/// sending NameChangeRequests through a UDP socket.  The caller need only
+/// supply network addressing and a RequestSendHandler instance to send
+/// NameChangeRequests asynchronously.
+class NameChangeUDPSender : public NameChangeSender {
+public:
+
+    /// @brief Defines the maximum size packet that can be sent.
+    static const size_t SEND_BUF_MAX =  NameChangeUDPListener::RECV_BUF_MAX;
+
+    /// @brief Constructor
+    ///
+    /// @param ip_address the IP address from which to send
+    /// @param port the port from which to send
+    /// @param server_address the IP address of the target listener
+    /// @param server_port is the IP port  of the target listener
+    /// @param format is the wire format of the outbound requests.
+    /// @param ncr_send_handler the send handler object to notify when
+    /// when a send completes.
+    /// @param send_que_max sets the maximum number of entries allowed in
+    /// the send queue.
+    /// It defaults to NameChangeSender::MAX_QUEUE_DEFAULT
+    /// @param reuse_address enables IP address sharing when true
+    /// It defaults to false.
+    ///
+    NameChangeUDPSender(const isc::asiolink::IOAddress& ip_address,
+        const uint32_t port, const isc::asiolink::IOAddress& server_address,
+        const uint32_t server_port, const NameChangeFormat format,
+        RequestSendHandler& ncr_send_handler,
+        const size_t send_que_max = NameChangeSender::MAX_QUEUE_DEFAULT,
+        const bool reuse_address = false);
+
+    /// @brief Destructor
+    virtual ~NameChangeUDPSender();
+
+
+    /// @brief Opens a UDP socket using the given IOService.
+    ///
+    /// Creates a NameChangeUDPSocket bound to the sender's IP address
+    /// and port, that is monitored by the given IOService instance.
+    ///
+    /// @param io_service the IOService which will monitor the socket.
+    ///
+    /// @throw NcrUDPError if the open fails.
+    virtual void open(isc::asiolink::IOService& io_service);
+
+
+    /// @brief Closes the UDPSocket.
+    ///
+    /// It first invokes the socket's cancel method which should stop any
+    /// pending send and remove the socket callback from the IOService. It
+    /// then calls the socket's close method to actually close the socket.
+    ///
+    /// @throw NcrUDPError if the open fails.
+    virtual void close();
+
+    /// @brief Sends a given request asynchronously over the socket
+    ///
+    /// The given NameChangeRequest is converted to wire format and copied
+    /// into the send callback's transfer buffer.  Then the socket's
+    /// asyncSend() method is called, passing in send_callback_ member's
+    /// transfer buffer as the send buffer and the send_callback_ itself
+    /// as the callback object.
+    virtual void doSend(NameChangeRequestPtr& ncr);
+
+    /// @brief Implements the NameChangeRequest level send completion handler.
+    ///
+    /// This method is invoked by the UDPCallback operator() implementation,
+    /// passing in the boolean success indicator and pointer to itself.
+    ///
+    /// If the indicator denotes success, then the method will notify the
+    /// application layer by calling invokeSendHandler() with a success
+    /// status.
+    ///
+    /// If the indicator denotes failure the method will log the failure and
+    /// notify the application layer by calling invokeRecvHandler() with
+    /// an error status.
+    ///
+    /// @param successful boolean indicator that should be true if the
+    /// socket send completed without error, false otherwise.
+    /// @param send_callback pointer to the callback instance which handled
+    /// the socket receive completion.
+    void sendCompletionHandler(const bool successful,
+                               const UDPCallback* send_callback);
+
+private:
+    /// @brief IP address from which to send.
+    isc::asiolink::IOAddress ip_address_;
+
+    /// @brief Port from which to send.
+    uint32_t port_;
+
+    /// @brief IP address of the target listener.
+    isc::asiolink::IOAddress server_address_;
+
+    /// @brief Port of the target listener.
+    uint32_t server_port_;
+
+    /// @brief Wire format of the outbound requests.
+    NameChangeFormat format_;
+
+    /// @brief Low level socket underneath the sending socket.
+    boost::shared_ptr<asio::ip::udp::socket> asio_socket_;
+
+    /// @brief NameChangeUDPSocket sending socket.
+    boost::shared_ptr<NameChangeUDPSocket> socket_;
+
+    /// @brief Endpoint of the target listener.
+    boost::shared_ptr<isc::asiolink::UDPEndpoint> server_endpoint_;
+
+    /// @brief Pointer to the send callback
+    boost::shared_ptr<UDPCallback> send_callback_;
+
+    /// @brief Flag which enables the reuse address socket option if true.
+    bool reuse_address_;
+};
+
+} // namespace isc::dhcp_ddns
+} // namespace isc
+
+#endif
diff --git a/src/lib/dhcp_ddns/tests/.gitignore b/src/lib/dhcp_ddns/tests/.gitignore
new file mode 100644
index 0000000..f022996
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/.gitignore
@@ -0,0 +1 @@
+/libdhcp_ddns_unittests
diff --git a/src/lib/dhcp_ddns/tests/Makefile.am b/src/lib/dhcp_ddns/tests/Makefile.am
new file mode 100644
index 0000000..ca39064
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/Makefile.am
@@ -0,0 +1,61 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(BOTAN_INCLUDE)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dhcp_ddns/tests\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS_ENVIRONMENT = \
+	$(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+if HAVE_GTEST
+TESTS += libdhcp_ddns_unittests
+
+libdhcp_ddns_unittests_SOURCES  = run_unittests.cc
+libdhcp_ddns_unittests_SOURCES += ../dhcp_ddns_log.cc ../dhcp_ddns_log.h
+libdhcp_ddns_unittests_SOURCES += ../ncr_io.cc ../ncr_io.h
+libdhcp_ddns_unittests_SOURCES += ../ncr_msg.cc ../ncr_msg.h
+libdhcp_ddns_unittests_SOURCES += ../ncr_udp.cc ../ncr_udp.h
+libdhcp_ddns_unittests_SOURCES += ncr_unittests.cc
+libdhcp_ddns_unittests_SOURCES += ncr_udp_unittests.cc
+nodist_libdhcp_ddns_unittests_SOURCES = ../dhcp_ddns_messages.h ../dhcp_ddns_messages.cc
+
+libdhcp_ddns_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
+
+libdhcp_ddns_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
+
+libdhcp_ddns_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_CLANGPP
+# This is to workaround unused variables tcout and tcerr in
+# log4cplus's streams.h and unused parameters from some of the
+# Boost headers.
+libdhcp_ddns_unittests_CXXFLAGS += -Wno-unused-parameter
+endif
+
+libdhcp_ddns_unittests_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+libdhcp_ddns_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+libdhcp_ddns_unittests_LDADD += ${BOTAN_LIBS} ${BOTAN_RPATH}
+libdhcp_ddns_unittests_LDADD += $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
new file mode 100644
index 0000000..9571ebc
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/ncr_udp_unittests.cc
@@ -0,0 +1,498 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <asiolink/interval_timer.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <dhcp_ddns/ncr_udp.h>
+#include <util/time_utilities.h>
+
+#include <asio/ip/udp.hpp>
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <gtest/gtest.h>
+#include <algorithm>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest test messages.
+const char *valid_msgs[] =
+{
+    // Valid Add.
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Valid Remove.
+     "{"
+     " \"change_type\" : 1 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+     // Valid Add with IPv6 address
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}"
+};
+
+const char* TEST_ADDRESS = "127.0.0.1";
+const uint32_t LISTENER_PORT = 5301;
+const uint32_t SENDER_PORT = LISTENER_PORT+1;
+const long TEST_TIMEOUT = 5 * 1000;
+
+/// @brief A NOP derivation for constructor test purposes.
+class SimpleListenHandler : public NameChangeListener::RequestReceiveHandler {
+public:
+    virtual void operator ()(const NameChangeListener::Result,
+                             NameChangeRequestPtr&) {
+    }
+};
+
+/// @brief Tests the NameChangeUDPListener constructors.
+/// This test verifies that:
+/// 1. Given valid parameters, the listener constructor works
+TEST(NameChangeUDPListenerBasicTest, constructionTests) {
+    // Verify the default constructor works.
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    uint32_t port = LISTENER_PORT;
+    isc::asiolink::IOService io_service;
+    SimpleListenHandler ncr_handler;
+    // Verify that valid constructor works.
+    EXPECT_NO_THROW(NameChangeUDPListener(ip_address, port, FMT_JSON,
+                                          ncr_handler));
+}
+
+/// @brief Tests NameChangeUDPListener starting and stopping listening .
+/// This test verifies that the listener will:
+/// 1. Enter listening state
+/// 2. If in the listening state, does not allow calls to start listening
+/// 3. Exist the listening state
+/// 4. Return to the listening state after stopping
+TEST(NameChangeUDPListenerBasicTest, basicListenTests) {
+    // Verify the default constructor works.
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    uint32_t port = LISTENER_PORT;
+    isc::asiolink::IOService io_service;
+    SimpleListenHandler ncr_handler;
+
+    NameChangeListenerPtr listener;
+    ASSERT_NO_THROW(listener.reset(
+        new NameChangeUDPListener(ip_address, port, FMT_JSON, ncr_handler)));
+
+    // Verify that we can start listening.
+    EXPECT_NO_THROW(listener->startListening(io_service));
+    EXPECT_TRUE(listener->amListening());
+
+    // Verify that attempting to listen when we already are is an error.
+    EXPECT_THROW(listener->startListening(io_service), NcrListenerError);
+
+    // Verify that we can stop listening.
+    EXPECT_NO_THROW(listener->stopListening());
+    EXPECT_FALSE(listener->amListening());
+
+    // Verify that attempting to stop listening when we are not is ok.
+    EXPECT_NO_THROW(listener->stopListening());
+
+    // Verify that we can re-enter listening.
+    EXPECT_NO_THROW(listener->startListening(io_service));
+    EXPECT_TRUE(listener->amListening());
+}
+
+/// @brief Compares two NameChangeRequests for equality.
+bool checkSendVsReceived(NameChangeRequestPtr sent_ncr,
+                         NameChangeRequestPtr received_ncr) {
+    return ((sent_ncr && received_ncr) &&
+        (*sent_ncr == *received_ncr));
+}
+
+/// @brief Text fixture for testing NameChangeUDPListener
+class NameChangeUDPListenerTest : public virtual ::testing::Test,
+                                  NameChangeListener::RequestReceiveHandler {
+public:
+    isc::asiolink::IOService io_service_;
+    NameChangeListener::Result result_;
+    NameChangeRequestPtr sent_ncr_;
+    NameChangeRequestPtr received_ncr_;
+    NameChangeListenerPtr listener_;
+    isc::asiolink::IntervalTimer test_timer_;
+
+    /// @brief Constructor
+    //
+    // Instantiates the listener member and the test timer. The timer is used
+    // to ensure a test doesn't go awry and hang forever.
+    NameChangeUDPListenerTest()
+        : io_service_(), result_(NameChangeListener::SUCCESS),
+          test_timer_(io_service_) {
+        isc::asiolink::IOAddress addr(TEST_ADDRESS);
+        listener_.reset(new NameChangeUDPListener(addr, LISTENER_PORT,
+                                              FMT_JSON, *this, true));
+
+        // Set the test timeout to break any running tasks if they hang.
+        test_timer_.setup(boost::bind(&NameChangeUDPListenerTest::
+                                      testTimeoutHandler, this),
+                          TEST_TIMEOUT);
+    }
+
+    virtual ~NameChangeUDPListenerTest(){
+    }
+    
+
+    /// @brief Converts JSON string into an NCR and sends it to the listener.
+    ///
+    void sendNcr(const std::string& msg) {
+        // Build an NCR  from json string. This verifies that the
+        // test string is valid.
+        ASSERT_NO_THROW(sent_ncr_ = NameChangeRequest::fromJSON(msg));
+
+        // Now use the NCR to write JSON to an output buffer.
+        isc::util::OutputBuffer ncr_buffer(1024);
+        ASSERT_NO_THROW(sent_ncr_->toFormat(FMT_JSON, ncr_buffer));
+
+        // Create a UDP socket through which our "sender" will send the NCR.
+        asio::ip::udp::socket
+            udp_socket(io_service_.get_io_service(), asio::ip::udp::v4());
+
+        // Create an endpoint pointed at the listener.
+        asio::ip::udp::endpoint
+            listener_endpoint(asio::ip::address::from_string(TEST_ADDRESS),
+                              LISTENER_PORT);
+
+        // A response message is now ready to send. Send it!
+        // Note this uses a synchronous send so it ships immediately.
+        // If listener isn't in listening mode, it will get missed.
+        udp_socket.send_to(asio::buffer(ncr_buffer.getData(),
+                                     ncr_buffer.getLength()),
+                            listener_endpoint);
+    }
+
+    /// @brief RequestReceiveHandler operator implementation for receiving NCRs.
+    ///
+    /// The fixture acts as the "application" layer.  It derives from
+    /// RequestReceiveHandler and as such implements operator() in order to
+    /// receive NCRs.
+    virtual void operator ()(const NameChangeListener::Result result,
+                             NameChangeRequestPtr& ncr) {
+        // save the result and the NCR we received
+        result_ = result;
+        received_ncr_ = ncr;
+    }
+    // @brief Handler invoked when test timeout is hit.
+    //
+    // This callback stops all running (hanging) tasks on IO service.
+    void testTimeoutHandler() {
+        io_service_.stop();
+        FAIL() << "Test timeout hit.";
+    }
+};
+
+/// @brief  Tests NameChangeUDPListener ability to receive NCRs.
+/// This test verifies that a listener can enter listening mode and
+/// receive NCRs in wire format on its UDP socket; reconstruct the
+/// NCRs and delivery them to the "application" layer.
+TEST_F(NameChangeUDPListenerTest, basicReceivetest) {
+    // Verify we can enter listening mode.
+    ASSERT_FALSE(listener_->amListening());
+    ASSERT_NO_THROW(listener_->startListening(io_service_));
+    ASSERT_TRUE(listener_->amListening());
+
+    // Iterate over a series of requests, sending and receiving one
+    /// at time.
+    int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+    for (int i = 0; i < num_msgs; i++) {
+        // We are not verifying ability to send, so if we can't test is over.
+        ASSERT_NO_THROW(sendNcr(valid_msgs[i]));
+
+        // Execute no more then one event, which should be receive complete.
+        EXPECT_NO_THROW(io_service_.run_one());
+
+        // Verify the "application" status value for a successful complete.
+        EXPECT_EQ(NameChangeListener::SUCCESS, result_);
+
+        // Verify the received request matches the sent request.
+        EXPECT_TRUE(checkSendVsReceived(sent_ncr_, received_ncr_));
+    }
+
+    // Verify we can gracefully stop listening.
+    EXPECT_NO_THROW(listener_->stopListening());
+    EXPECT_FALSE(listener_->amListening());
+}
+
+/// @brief A NOP derivation for constructor test purposes.
+class SimpleSendHandler : public NameChangeSender::RequestSendHandler {
+public:
+    virtual void operator ()(const NameChangeSender::Result,
+                             NameChangeRequestPtr&) {
+    }
+};
+
+/// @brief Tests the NameChangeUDPSender constructors.
+/// This test verifies that:
+/// 1. Constructing with a max queue size of 0 is not allowed
+/// 2. Given valid parameters, the sender constructor works
+/// 3. Default construction provides default max queue size
+/// 4. Construction with a custom max queue size works
+TEST(NameChangeUDPSenderBasicTest, constructionTests) {
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    uint32_t port = SENDER_PORT;
+    isc::asiolink::IOService io_service;
+    SimpleSendHandler ncr_handler;
+
+    // Verify that constructing with an queue size of zero is not allowed.
+    EXPECT_THROW(NameChangeUDPSender(ip_address, port,
+        ip_address, port, FMT_JSON, ncr_handler, 0), NcrSenderError);
+
+    NameChangeSenderPtr sender;
+    // Verify that valid constructor works.
+    EXPECT_NO_THROW(sender.reset(
+                    new NameChangeUDPSender(ip_address, port, ip_address, port,
+                                            FMT_JSON, ncr_handler)));
+
+    // Verify that send queue default max is correct.
+    size_t expected = NameChangeSender::MAX_QUEUE_DEFAULT;
+    EXPECT_EQ(expected, sender->getQueueMaxSize());
+
+    // Verify that constructor with a valid custom queue size works.
+    EXPECT_NO_THROW(sender.reset(
+                    new NameChangeUDPSender(ip_address, port, ip_address, port,
+                                            FMT_JSON, ncr_handler, 100)));
+
+    EXPECT_EQ(100, sender->getQueueMaxSize());
+}
+
+/// @brief Tests NameChangeUDPSender basic send functionality
+/// This test verifies that:
+TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    uint32_t port = SENDER_PORT;
+    isc::asiolink::IOService io_service;
+    SimpleSendHandler ncr_handler;
+
+    // Tests are based on a list of messages, get the count now.
+    int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+    // Create the sender, setting the queue max equal to the number of
+    // messages we will have in the list.
+    NameChangeUDPSender sender(ip_address, port, ip_address, port,
+                               FMT_JSON, ncr_handler, num_msgs);
+
+    // Verify that we can start sending.
+    EXPECT_NO_THROW(sender.startSending(io_service));
+    EXPECT_TRUE(sender.amSending());
+
+    // Verify that attempting to send when we already are is an error.
+    EXPECT_THROW(sender.startSending(io_service), NcrSenderError);
+
+    // Verify that we can stop sending.
+    EXPECT_NO_THROW(sender.stopSending());
+    EXPECT_FALSE(sender.amSending());
+
+    // Verify that attempting to stop sending when we are not is ok.
+    EXPECT_NO_THROW(sender.stopSending());
+
+    // Verify that we can re-enter sending after stopping.
+    EXPECT_NO_THROW(sender.startSending(io_service));
+    EXPECT_TRUE(sender.amSending());
+
+    // Iterate over a series of messages, sending each one. Since we
+    // do not invoke IOService::run, then the messages should accumulate
+    // in the queue.
+    NameChangeRequestPtr ncr;
+    for (int i = 0; i < num_msgs; i++) {
+        ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+        EXPECT_NO_THROW(sender.sendRequest(ncr));
+        // Verify that the queue count increments in step with each send.
+        EXPECT_EQ(i+1, sender.getQueueSize());
+    }
+
+    // Verify that attempting to send an additional message results in a
+    // queue full exception.
+    EXPECT_THROW(sender.sendRequest(ncr), NcrSenderQueueFull);
+
+    // Loop for the number of valid messages and invoke IOService::run_one.
+    // This should send exactly one message and the queue count should
+    // decrement accordingly.
+    for (int i = num_msgs; i > 0; i--) {
+        io_service.run_one();
+        // Verify that the queue count decrements in step with each run.
+        EXPECT_EQ(i-1, sender.getQueueSize());
+    }
+
+    // Verify that the queue is empty.
+    EXPECT_EQ(0, sender.getQueueSize());
+
+    // Verify that we can add back to the queue
+    EXPECT_NO_THROW(sender.sendRequest(ncr));
+    EXPECT_EQ(1, sender.getQueueSize());
+
+    // Verify that we can remove the current entry at the front of the queue.
+    EXPECT_NO_THROW(sender.skipNext());
+    EXPECT_EQ(0, sender.getQueueSize());
+
+    // Verify that flushing the queue is not allowed in sending state.
+    EXPECT_THROW(sender.clearSendQueue(), NcrSenderError);
+
+    // Put a message on the queue.
+    EXPECT_NO_THROW(sender.sendRequest(ncr));
+    EXPECT_EQ(1, sender.getQueueSize());
+
+    // Verify that we can gracefully stop sending.
+    EXPECT_NO_THROW(sender.stopSending());
+    EXPECT_FALSE(sender.amSending());
+
+    // Verify that the queue is preserved after leaving sending state.
+    EXPECT_EQ(1, sender.getQueueSize());
+
+    // Verify that flushing the queue works when not sending.
+    EXPECT_NO_THROW(sender.clearSendQueue());
+    EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Text fixture that allows testing a listener and sender together
+/// It derives from both the receive and send handler classes and contains
+/// and instance of UDP listener and UDP sender.
+class NameChangeUDPTest : public virtual ::testing::Test,
+                          NameChangeListener::RequestReceiveHandler,
+                          NameChangeSender::RequestSendHandler {
+public:
+    isc::asiolink::IOService io_service_;
+    NameChangeListener::Result recv_result_;
+    NameChangeSender::Result send_result_;
+    NameChangeListenerPtr listener_;
+    NameChangeSenderPtr   sender_;
+    isc::asiolink::IntervalTimer test_timer_;
+
+    std::vector<NameChangeRequestPtr> sent_ncrs_;
+    std::vector<NameChangeRequestPtr> received_ncrs_;
+
+    NameChangeUDPTest()
+        : io_service_(), recv_result_(NameChangeListener::SUCCESS),
+          send_result_(NameChangeSender::SUCCESS), test_timer_(io_service_) {
+        isc::asiolink::IOAddress addr(TEST_ADDRESS);
+        // Create our listener instance. Note that reuse_address is true.
+        listener_.reset(
+            new NameChangeUDPListener(addr, LISTENER_PORT, FMT_JSON,
+                                      *this, true));
+
+        // Create our sender instance. Note that reuse_address is true.
+        sender_.reset(
+            new NameChangeUDPSender(addr, SENDER_PORT, addr, LISTENER_PORT,
+                                    FMT_JSON, *this, 100, true));
+
+        // Set the test timeout to break any running tasks if they hang.
+        test_timer_.setup(boost::bind(&NameChangeUDPTest::testTimeoutHandler,
+                                      this),
+                          TEST_TIMEOUT);
+    }
+
+    void reset_results() {
+        sent_ncrs_.clear();
+        received_ncrs_.clear();
+    }
+
+    /// @brief Implements the receive completion handler.
+    virtual void operator ()(const NameChangeListener::Result result,
+                             NameChangeRequestPtr& ncr) {
+        // save the result and the NCR received.
+        recv_result_ = result;
+        received_ncrs_.push_back(ncr);
+    }
+
+    /// @brief Implements the send completion handler.
+    virtual void operator ()(const NameChangeSender::Result result,
+                             NameChangeRequestPtr& ncr) {
+        // save the result and the NCR sent.
+        send_result_ = result;
+        sent_ncrs_.push_back(ncr);
+    }
+
+    // @brief Handler invoked when test timeout is hit.
+    //
+    // This callback stops all running (hanging) tasks on IO service.
+    void testTimeoutHandler() {
+        io_service_.stop();
+        FAIL() << "Test timeout hit.";
+    }
+};
+
+/// @brief Uses a sender and listener to test UDP-based NCR delivery
+/// Conducts a "round-trip" test using a sender to transmit a set of valid
+/// NCRs to a listener.  The test verifies that what was sent matches what
+/// was received both in quantity and in content.
+TEST_F (NameChangeUDPTest, roundTripTest) {
+    // Place the listener into listening state.
+    ASSERT_NO_THROW(listener_->startListening(io_service_));
+    EXPECT_TRUE(listener_->amListening());
+
+    // Get the number of messages in the list of test messages.
+    int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+    // Place the sender into sending state.
+    ASSERT_NO_THROW(sender_->startSending(io_service_));
+    EXPECT_TRUE(sender_->amSending());
+
+    for (int i = 0; i < num_msgs; i++) {
+        NameChangeRequestPtr ncr;
+        ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+        sender_->sendRequest(ncr);
+        EXPECT_EQ(i+1, sender_->getQueueSize());
+    }
+
+    // Execute callbacks until we have sent and received all of messages.
+    while (sender_->getQueueSize() > 0 || (received_ncrs_.size() < num_msgs)) {
+        EXPECT_NO_THROW(io_service_.run_one());
+    }
+
+    // Send queue should be empty.
+    EXPECT_EQ(0, sender_->getQueueSize());
+
+    // We should have the same number of sends and receives as we do messages.
+    ASSERT_EQ(num_msgs, sent_ncrs_.size());
+    ASSERT_EQ(num_msgs, received_ncrs_.size());
+
+    // Verify that what we sent matches what we received.
+    for (int i = 0; i < num_msgs; i++) {
+        EXPECT_TRUE (checkSendVsReceived(sent_ncrs_[i], received_ncrs_[i]));
+    }
+
+    // Verify that we can gracefully stop listening.
+    EXPECT_NO_THROW(listener_->stopListening());
+    EXPECT_FALSE(listener_->amListening());
+
+    // Verify that we can gracefully stop sending.
+    EXPECT_NO_THROW(sender_->stopSending());
+    EXPECT_FALSE(sender_->amSending());
+}
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp_ddns/tests/ncr_unittests.cc b/src/lib/dhcp_ddns/tests/ncr_unittests.cc
new file mode 100644
index 0000000..8148797
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/ncr_unittests.cc
@@ -0,0 +1,455 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp_ddns/ncr_msg.h>
+#include <dhcp/duid.h>
+#include <util/time_utilities.h>
+
+#include <gtest/gtest.h>
+#include <algorithm>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest renditions.
+/// They are used as input to test conversion from JSON.
+const char *valid_msgs[] =
+{
+    // Valid Add.
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Valid Remove.
+     "{"
+     " \"change_type\" : 1 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+     // Valid Add with IPv6 address
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}"
+};
+
+/// @brief Defines a list of invalid JSON NameChangeRequest renditions.
+/// They are used as input to test conversion from JSON.
+const char *invalid_msgs[] =
+{
+    // Invalid change type.
+     "{"
+     " \"change_type\" : 7 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Invalid forward change.
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : \"bogus\" , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Invalid reverse change.
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : 500 , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Forward and reverse change both false.
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : false , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Blank FQDN
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Bad IP address
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"xxxxxx\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Blank DHCID
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Odd number of digits in DHCID
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Text in DHCID
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"THIS IS BOGUS!!!\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Invalid lease expiration string
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"Wed Jun 26 13:46:46 EDT 2013\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Non-integer for lease length.
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : \"BOGUS\" "
+     "}"
+
+};
+
+/// @brief Tests the NameChangeRequest constructors.
+/// This test verifies that:
+/// 1. Default constructor works.
+/// 2. "Full" constructor, when given valid parameter values, works.
+/// 3. "Full" constructor, given a blank FQDN fails
+/// 4. "Full" constructor, given an invalid IP Address FQDN fails
+/// 5. "Full" constructor, given a blank DHCID fails
+/// 6. "Full" constructor, given false for both forward and reverse fails
+TEST(NameChangeRequestTest, constructionTests) {
+    // Verify the default constructor works.
+    NameChangeRequestPtr ncr;
+    EXPECT_NO_THROW(ncr.reset(new NameChangeRequest()));
+    EXPECT_TRUE(ncr);
+
+    // Verify that full constructor works.
+    uint64_t expiry = isc::util::detail::gettimeWrapper();
+    D2Dhcid dhcid("010203040A7F8E3D");
+
+    EXPECT_NO_THROW(ncr.reset(new NameChangeRequest(
+                    CHG_ADD, true, true, "walah.walah.com",
+                    "192.168.1.101", dhcid, expiry, 1300)));
+    EXPECT_TRUE(ncr);
+    ncr.reset();
+
+    // Verify blank FQDN is detected.
+    EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "",
+                 "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+    // Verify that an invalid IP address is detected.
+    EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "valid.fqdn",
+                 "xxx.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+    // Verify that a blank DHCID is detected.
+    D2Dhcid blank_dhcid;
+    EXPECT_THROW(NameChangeRequest(CHG_ADD, true, true, "walah.walah.com",
+                 "192.168.1.101", blank_dhcid, expiry, 1300), NcrMessageError);
+
+    // Verify that one or both of direction flags must be true.
+    EXPECT_THROW(NameChangeRequest(CHG_ADD, false, false, "valid.fqdn",
+                "192.168.1.101", dhcid, expiry, 1300), NcrMessageError);
+
+}
+
+/// @brief Tests the basic workings of D2Dhcid to and from string conversions.
+/// It verifies that:
+/// 1. DHCID input strings must contain an even number of characters
+/// 2. DHCID input strings must contain only hexadecimal character digits
+/// 3. A valid DHCID string converts correctly.
+/// 4. Converting a D2Dhcid to a string works correctly.
+/// 5. Equality, inequality, and less-than-equal operators work.
+TEST(NameChangeRequestTest, dhcidTest) {
+    D2Dhcid dhcid;
+
+    // Odd number of digits should be rejected.
+    std::string test_str = "010203040A7F8E3";
+    EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
+
+    // Non digit content should be rejected.
+    test_str = "0102BOGUSA7F8E3D";
+    EXPECT_THROW(dhcid.fromStr(test_str), NcrMessageError);
+
+    // Verify that valid input converts into a proper byte array.
+    test_str = "010203040A7F8E3D";
+    ASSERT_NO_THROW(dhcid.fromStr(test_str));
+
+    // Create a test vector of expected byte contents.
+    const uint8_t bytes[] = { 0x1, 0x2, 0x3, 0x4, 0xA, 0x7F, 0x8E, 0x3D };
+    std::vector<uint8_t> expected_bytes(bytes, bytes + sizeof(bytes));
+
+    // Fetch the byte vector from the dhcid and verify if equals the expected
+    // content.
+    const std::vector<uint8_t>& converted_bytes = dhcid.getBytes();
+    EXPECT_EQ(expected_bytes.size(), converted_bytes.size());
+    EXPECT_TRUE (std::equal(expected_bytes.begin(),
+                            expected_bytes.begin()+expected_bytes.size(),
+                            converted_bytes.begin()));
+
+    // Convert the new dhcid back to string and verify it matches the original
+    // DHCID input string.
+    std::string next_str = dhcid.toStr();
+    EXPECT_EQ(test_str, next_str);
+
+    // Test equality, inequality, and less-than-equal operators
+    test_str="AABBCCDD";
+    EXPECT_NO_THROW(dhcid.fromStr(test_str));
+
+    D2Dhcid other_dhcid;
+    EXPECT_NO_THROW(other_dhcid.fromStr(test_str));
+
+    EXPECT_TRUE(dhcid == other_dhcid);
+    EXPECT_FALSE(dhcid != other_dhcid);
+
+    EXPECT_NO_THROW(other_dhcid.fromStr("BBCCDDEE"));
+    EXPECT_TRUE(dhcid < other_dhcid);
+
+}
+
+/// Tests that DHCID is correctly created from a DUID and FQDN. The final format
+/// of the DHCID is as follows:
+/// <identifier-type> <digest-type-code> <digest>
+/// where:
+/// - identifier-type (2 octets) is 0x0002.
+/// - digest-type-code (1 octet) indicates SHA-256 hashing and is equal 0x1.
+/// - digest = SHA-256(<DUID> <FQDN>)
+/// Note: FQDN is given in the on-wire canonical format.
+TEST(NameChangeRequestTest, dhcidFromDUID) {
+    D2Dhcid dhcid;
+
+    // Create DUID.
+    uint8_t duid_data[] = { 0, 1, 2, 3, 4, 5, 6 };
+    DUID duid(duid_data, sizeof(duid_data));
+
+    // Create FQDN in on-wire format: myhost.example.com. It is encoded
+    // as a set of labels, each preceded by its length. The whole FQDN
+    // is zero-terminated.
+    const uint8_t fqdn_data[] = {
+        6, 109, 121, 104, 111, 115, 116,     // myhost.
+        7, 101, 120, 97, 109, 112, 108, 101, // example.
+        3, 99, 111, 109, 0                   // com.
+    };
+    std::vector<uint8_t> wire_fqdn(fqdn_data, fqdn_data + sizeof(fqdn_data));
+
+    // Create DHCID.
+    ASSERT_NO_THROW(dhcid.fromDUID(duid, wire_fqdn));
+
+    // The reference DHCID (represented as string of hexadecimal digits)
+    // has been calculated using one of the online calculators.
+    std::string dhcid_ref = "0002012191B7B21AF97E0E656DF887C5E2D"
+        "EF30E7758A207EDF4CCB2DE8CA37066021C";
+
+    // Make sure that the DHCID is valid.
+    EXPECT_EQ(dhcid_ref, dhcid.toStr());
+}
+
+/// @brief Verifies the fundamentals of converting from and to JSON.
+/// It verifies that:
+/// 1. A NameChangeRequest can be created from a valid JSON string.
+/// 2. A valid JSON string can be created from a NameChangeRequest
+TEST(NameChangeRequestTest, basicJsonTest) {
+    // Define valid JSON rendition of a request.
+    std::string msg_str = "{"
+                            "\"change_type\":1,"
+                            "\"forward_change\":true,"
+                            "\"reverse_change\":false,"
+                            "\"fqdn\":\"walah.walah.com\","
+                            "\"ip_address\":\"192.168.2.1\","
+                            "\"dhcid\":\"010203040A7F8E3D\","
+                            "\"lease_expires_on\":\"20130121132405\","
+                            "\"lease_length\":1300"
+                          "}";
+
+    // Verify that a NameChangeRequests can be instantiated from the
+    // a valid JSON rendition.
+    NameChangeRequestPtr ncr;
+    ASSERT_NO_THROW(ncr  = NameChangeRequest::fromJSON(msg_str));
+    ASSERT_TRUE(ncr);
+
+    // Verify that the JSON string created by the new request equals the
+    // original input string.
+    std::string json_str = ncr->toJSON();
+    EXPECT_EQ(msg_str, json_str);
+}
+
+/// @brief Tests a variety of invalid JSON message strings.
+/// This test iterates over a list of JSON messages, each containing a single
+/// content error. The list of messages is defined by the global array,
+/// invalid_messages. Currently that list contains the following invalid
+/// conditions:
+///  1. Invalid change type
+///  2. Invalid forward change
+///  3. Invalid reverse change
+///  4. Forward and reverse change both false
+///  5. Invalid forward change
+///  6. Blank FQDN
+///  7. Bad IP address
+///  8. Blank DHCID
+///  9. Odd number of digits in DHCID
+/// 10. Text in DHCID
+/// 11. Invalid lease expiration string
+/// 12. Non-integer for lease length.
+/// If more permutations arise they can easily be added to the list.
+TEST(NameChangeRequestTest, invalidMsgChecks) {
+    // Iterate over the list of JSON strings, attempting to create a
+    // NameChangeRequest. The attempt should throw a NcrMessageError.
+    int num_msgs = sizeof(invalid_msgs)/sizeof(char*);
+    for (int i = 0; i < num_msgs; i++) {
+        EXPECT_THROW(NameChangeRequest::fromJSON(invalid_msgs[i]),
+                     NcrMessageError) << "Invalid message not caught idx: "
+                     << i << std::endl << " text:[" << invalid_msgs[i] << "]"
+                     << std::endl;
+    }
+}
+
+/// @brief Tests a variety of valid JSON message strings.
+/// This test iterates over a list of JSON messages, each containing a single
+/// valid request rendition. The list of messages is defined by the global
+/// array, valid_messages. Currently that list contains the following valid
+/// messages:
+///  1. Valid, IPv4 Add
+///  2. Valid, IPv4 Remove
+///  3. Valid, IPv6 Add
+/// If more permutations arise they can easily be added to the list.
+TEST(NameChangeRequestTest, validMsgChecks) {
+    // Iterate over the list of JSON strings, attempting to create a
+    // NameChangeRequest. The attempt should succeed.
+    int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+    for (int i = 0; i < num_msgs; i++) {
+        EXPECT_NO_THROW(NameChangeRequest::fromJSON(valid_msgs[i]))
+                        << "Valid message failed,  message idx: " << i
+                        << std::endl << " text:[" << valid_msgs[i] << "]"
+                        << std::endl;
+    }
+}
+
+/// @brief Tests converting to and from JSON via isc::util buffer classes.
+/// This test verifies that:
+/// 1. A NameChangeRequest can be rendered in JSON written to an OutputBuffer
+/// 2. A InputBuffer containing a valid JSON request rendition can be used
+/// to create a NameChangeRequest.
+TEST(NameChangeRequestTest, toFromBufferTest) {
+    // Define a string containing a valid JSON NameChangeRequest rendition.
+    std::string msg_str = "{"
+                            "\"change_type\":1,"
+                            "\"forward_change\":true,"
+                            "\"reverse_change\":false,"
+                            "\"fqdn\":\"walah.walah.com\","
+                            "\"ip_address\":\"192.168.2.1\","
+                            "\"dhcid\":\"010203040A7F8E3D\","
+                            "\"lease_expires_on\":\"20130121132405\","
+                            "\"lease_length\":1300"
+                          "}";
+
+    // Create a request from JSON directly.
+    NameChangeRequestPtr ncr;
+    ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(msg_str));
+
+    // Verify that we output the request as JSON text to a buffer
+    // without error.
+    isc::util::OutputBuffer output_buffer(1024);
+    ASSERT_NO_THROW(ncr->toFormat(FMT_JSON, output_buffer));
+
+    // Make an InputBuffer from the OutputBuffer.
+    isc::util::InputBuffer input_buffer(output_buffer.getData(),
+                                        output_buffer.getLength());
+
+    // Verify that we can create a new request from the InputBuffer.
+    NameChangeRequestPtr ncr2;
+    ASSERT_NO_THROW(ncr2 =
+                    NameChangeRequest::fromFormat(FMT_JSON, input_buffer));
+
+    // Convert the new request to JSON directly.
+    std::string final_str = ncr2->toJSON();
+
+    // Verify that the final string matches the original.
+    ASSERT_EQ(final_str, msg_str);
+}
+
+
+} // end of anonymous namespace
+
diff --git a/src/lib/dhcp_ddns/tests/run_unittests.cc b/src/lib/dhcp_ddns/tests/run_unittests.cc
new file mode 100644
index 0000000..bd32c4d
--- /dev/null
+++ b/src/lib/dhcp_ddns/tests/run_unittests.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log/logger_support.h>
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    isc::log::initLogger();
+
+    int result = RUN_ALL_TESTS();
+
+    return (result);
+}
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index 29e8c2f..0925182 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -61,6 +61,8 @@ libb10_dhcpsrv_la_LIBADD   = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/util/libb10-util.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/cc/libb10-cc.la
+libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/hooks/libb10-hooks.la
+
 libb10_dhcpsrv_la_LDFLAGS  = -no-undefined -version-info 3:0:0
 if HAVE_MYSQL
 libb10_dhcpsrv_la_LDFLAGS += $(MYSQL_LIBS)
diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc
index d3fdd0b..9c9017c 100644
--- a/src/lib/dhcpsrv/alloc_engine.cc
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -16,11 +16,37 @@
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 
+#include <hooks/server_hooks.h>
+#include <hooks/hooks_manager.h>
+
 #include <cstring>
 #include <vector>
 #include <string.h>
 
 using namespace isc::asiolink;
+using namespace isc::hooks;
+
+namespace {
+
+/// Structure that holds registered hook indexes
+struct AllocEngineHooks {
+    int hook_index_lease4_select_; ///< index for "lease4_receive" hook point
+    int hook_index_lease6_select_; ///< index for "lease6_receive" hook point
+
+    /// Constructor that registers hook points for AllocationEngine
+    AllocEngineHooks() {
+        hook_index_lease4_select_ = HooksManager::registerHook("lease4_select");
+        hook_index_lease6_select_ = HooksManager::registerHook("lease6_select");
+    }
+};
+
+// Declare a Hooks object. As this is outside any function or method, it
+// will be instantiated (and the constructor run) when the module is loaded.
+// As a result, the hook indexes will be defined before any method in this
+// module is called.
+AllocEngineHooks Hooks;
+
+}; // anonymous namespace
 
 namespace isc {
 namespace dhcp {
@@ -161,6 +187,10 @@ AllocEngine::AllocEngine(AllocType engine_type, unsigned int attempts)
     default:
         isc_throw(BadValue, "Invalid/unsupported allocation algorithm");
     }
+
+    // Register hook points
+    hook_index_lease4_select_ = Hooks.hook_index_lease4_select_;
+    hook_index_lease6_select_ = Hooks.hook_index_lease6_select_;
 }
 
 Lease6Ptr
@@ -171,7 +201,8 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
                               const bool fwd_dns_update,
                               const bool rev_dns_update,
                               const std::string& hostname,
-                              bool fake_allocation /* = false */ ) {
+                              bool fake_allocation,
+                              const isc::hooks::CalloutHandlePtr& callout_handle) {
 
     try {
         // That check is not necessary. We create allocator in AllocEngine
@@ -205,8 +236,10 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
 
                 // the hint is valid and not currently used, let's create a lease for it
                 Lease6Ptr lease = createLease6(subnet, duid, iaid,
-                                               hint, fwd_dns_update,
+                                               hint,
+                                               fwd_dns_update,
                                                rev_dns_update, hostname,
+                                               callout_handle,
                                                fake_allocation);
 
                 // It can happen that the lease allocation failed (we could have lost
@@ -219,7 +252,8 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
                 if (existing->expired()) {
                     return (reuseExpiredLease(existing, subnet, duid, iaid,
                                               fwd_dns_update, rev_dns_update,
-                                              hostname, fake_allocation));
+                                              hostname, callout_handle,
+                                              fake_allocation));
                 }
 
             }
@@ -254,7 +288,8 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
                 // free. Let's allocate it.
                 Lease6Ptr lease = createLease6(subnet, duid, iaid, candidate,
                                                fwd_dns_update, rev_dns_update,
-                                               hostname, fake_allocation);
+                                               hostname,
+                                               callout_handle, fake_allocation);
                 if (lease) {
                     return (lease);
                 }
@@ -266,7 +301,8 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
                 if (existing->expired()) {
                     return (reuseExpiredLease(existing, subnet, duid, iaid,
                                               fwd_dns_update, rev_dns_update,
-                                              hostname, fake_allocation));
+                                              hostname, callout_handle,
+                                              fake_allocation));
                 }
             }
 
@@ -292,7 +328,8 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
                               const ClientIdPtr& clientid,
                               const HWAddrPtr& hwaddr,
                               const IOAddress& hint,
-                              bool fake_allocation /* = false */ ) {
+                              bool fake_allocation,
+                              const isc::hooks::CalloutHandlePtr& callout_handle) {
 
     try {
         // Allocator is always created in AllocEngine constructor and there is
@@ -345,7 +382,8 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
                 /// implemented
 
                 // The hint is valid and not currently used, let's create a lease for it
-                Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, hint, fake_allocation);
+                Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, hint,
+                                               callout_handle, fake_allocation);
 
                 // It can happen that the lease allocation failed (we could have lost
                 // the race condition. That means that the hint is lo longer usable and
@@ -356,7 +394,7 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
             } else {
                 if (existing->expired()) {
                     return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
-                                              fake_allocation));
+                                              callout_handle, fake_allocation));
                 }
 
             }
@@ -390,7 +428,7 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
                 // there's no existing lease for selected candidate, so it is
                 // free. Let's allocate it.
                 Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, candidate,
-                                              fake_allocation);
+                                               callout_handle, fake_allocation);
                 if (lease) {
                     return (lease);
                 }
@@ -401,7 +439,7 @@ AllocEngine::allocateAddress4(const SubnetPtr& subnet,
             } else {
                 if (existing->expired()) {
                     return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
-                                              fake_allocation));
+                                              callout_handle, fake_allocation));
                 }
             }
 
@@ -450,6 +488,7 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
                                          const bool fwd_dns_update,
                                          const bool rev_dns_update,
                                          const std::string& hostname,
+                                         const isc::hooks::CalloutHandlePtr& callout_handle,
                                          bool fake_allocation /*= false */ ) {
 
     if (!expired->expired()) {
@@ -473,6 +512,39 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
     /// @todo: log here that the lease was reused (there's ticket #2524 for
     /// logging in libdhcpsrv)
 
+    // Let's execute all callouts registered for lease6_select
+    if (callout_handle &&
+        HooksManager::getHooksManager().calloutsPresent(hook_index_lease6_select_)) {
+
+        // Delete all previous arguments
+        callout_handle->deleteAllArguments();
+
+        // Pass necessary arguments
+        // Subnet from which we do the allocation
+        callout_handle->setArgument("subnet6", subnet);
+
+        // Is this solicit (fake = true) or request (fake = false)
+        callout_handle->setArgument("fake_allocation", fake_allocation);
+
+        // The lease that will be assigned to a client
+        callout_handle->setArgument("lease6", expired);
+
+        // Call the callouts
+        HooksManager::callCallouts(hook_index_lease6_select_, *callout_handle);
+
+        // Callouts decided to skip the action. This means that the lease is not
+        // assigned, so the client will get NoAddrAvail as a result. The lease
+        // won't be inserted into the database.
+        if (callout_handle->getSkip()) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP);
+            return (Lease6Ptr());
+        }
+
+        // Let's use whatever callout returned. Hopefully it is the same lease
+        // we handled to it.
+        callout_handle->getArgument("lease6", expired);
+    }
+
     if (!fake_allocation) {
         // for REQUEST we do update the lease
         LeaseMgrFactory::instance().updateLease6(expired);
@@ -490,6 +562,7 @@ Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
                                          const SubnetPtr& subnet,
                                          const ClientIdPtr& clientid,
                                          const HWAddrPtr& hwaddr,
+                                         const isc::hooks::CalloutHandlePtr& callout_handle,
                                          bool fake_allocation /*= false */ ) {
 
     if (!expired->expired()) {
@@ -512,6 +585,44 @@ Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
     /// @todo: log here that the lease was reused (there's ticket #2524 for
     /// logging in libdhcpsrv)
 
+    // Let's execute all callouts registered for lease4_select
+    if (callout_handle &&
+        HooksManager::getHooksManager().calloutsPresent(hook_index_lease4_select_)) {
+
+        // Delete all previous arguments
+        callout_handle->deleteAllArguments();
+
+        // Pass necessary arguments
+
+        // Subnet from which we do the allocation. Convert the general subnet
+        // pointer to a pointer to a Subnet4.  Note that because we are using
+        // boost smart pointers here, we need to do the cast using the boost
+        // version of dynamic_pointer_cast.
+        Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(subnet);
+        callout_handle->setArgument("subnet4", subnet4);
+
+        // Is this solicit (fake = true) or request (fake = false)
+        callout_handle->setArgument("fake_allocation", fake_allocation);
+
+        // The lease that will be assigned to a client
+        callout_handle->setArgument("lease4", expired);
+
+        // Call the callouts
+        HooksManager::callCallouts(hook_index_lease6_select_, *callout_handle);
+
+        // Callouts decided to skip the action. This means that the lease is not
+        // assigned, so the client will get NoAddrAvail as a result. The lease
+        // won't be inserted into the database.
+        if (callout_handle->getSkip()) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
+            return (Lease4Ptr());
+        }
+
+        // Let's use whatever callout returned. Hopefully it is the same lease
+        // we handled to it.
+        callout_handle->getArgument("lease4", expired);
+    }
+
     if (!fake_allocation) {
         // for REQUEST we do update the lease
         LeaseMgrFactory::instance().updateLease4(expired);
@@ -532,6 +643,7 @@ Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
                                     const bool fwd_dns_update,
                                     const bool rev_dns_update,
                                     const std::string& hostname,
+                                    const isc::hooks::CalloutHandlePtr& callout_handle,
                                     bool fake_allocation /*= false */ ) {
 
     Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid, iaid,
@@ -542,6 +654,38 @@ Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
     lease->fqdn_rev_ = rev_dns_update;
     lease->hostname_ = hostname;
 
+    // Let's execute all callouts registered for lease6_select
+    if (callout_handle &&
+        HooksManager::getHooksManager().calloutsPresent(hook_index_lease6_select_)) {
+
+        // Delete all previous arguments
+        callout_handle->deleteAllArguments();
+
+        // Pass necessary arguments
+
+        // Subnet from which we do the allocation
+        callout_handle->setArgument("subnet6", subnet);
+
+        // Is this solicit (fake = true) or request (fake = false)
+        callout_handle->setArgument("fake_allocation", fake_allocation);
+        callout_handle->setArgument("lease6", lease);
+
+        // This is the first callout, so no need to clear any arguments
+        HooksManager::callCallouts(hook_index_lease6_select_, *callout_handle);
+
+        // Callouts decided to skip the action. This means that the lease is not
+        // assigned, so the client will get NoAddrAvail as a result. The lease
+        // won't be inserted into the database.
+        if (callout_handle->getSkip()) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE6_SELECT_SKIP);
+            return (Lease6Ptr());
+        }
+
+        // Let's use whatever callout returned. Hopefully it is the same lease
+        // we handled to it.
+        callout_handle->getArgument("lease6", lease);
+    }
+
     if (!fake_allocation) {
         // That is a real (REQUEST) allocation
         bool status = LeaseMgrFactory::instance().addLease(lease);
@@ -574,6 +718,7 @@ Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
                                     const DuidPtr& clientid,
                                     const HWAddrPtr& hwaddr,
                                     const IOAddress& addr,
+                                    const isc::hooks::CalloutHandlePtr& callout_handle,
                                     bool fake_allocation /*= false */ ) {
     if (!hwaddr) {
         isc_throw(BadValue, "Can't create a lease with NULL HW address");
@@ -591,6 +736,46 @@ Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
                                subnet->getT1(), subnet->getT2(), now,
                                subnet->getID()));
 
+    // Let's execute all callouts registered for lease4_select
+    if (callout_handle &&
+        HooksManager::getHooksManager().calloutsPresent(hook_index_lease4_select_)) {
+
+        // Delete all previous arguments
+        callout_handle->deleteAllArguments();
+
+        // Pass necessary arguments
+
+        // Subnet from which we do the allocation (That's as far as we can go
+        // with using SubnetPtr to point to Subnet4 object. Users should not
+        // be confused with dynamic_pointer_casts. They should get a concrete
+        // pointer (Subnet4Ptr) pointing to a Subnet4 object.
+        Subnet4Ptr subnet4 = boost::dynamic_pointer_cast<Subnet4>(subnet);
+        callout_handle->setArgument("subnet4", subnet4);
+
+        // Is this solicit (fake = true) or request (fake = false)
+        callout_handle->setArgument("fake_allocation", fake_allocation);
+
+        // Pass the intended lease as well
+        callout_handle->setArgument("lease4", lease);
+
+        // This is the first callout, so no need to clear any arguments
+        HooksManager::callCallouts(hook_index_lease4_select_, *callout_handle);
+
+        // Callouts decided to skip the action. This means that the lease is not
+        // assigned, so the client will get NoAddrAvail as a result. The lease
+        // won't be inserted into the database.
+        if (callout_handle->getSkip()) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_HOOKS, DHCPSRV_HOOK_LEASE4_SELECT_SKIP);
+            return (Lease4Ptr());
+        }
+
+        // Let's use whatever callout returned. Hopefully it is the same lease
+        // we handled to it.
+        callout_handle->getArgument("lease4", lease);
+    }
+
+
+
     if (!fake_allocation) {
         // That is a real (REQUEST) allocation
         bool status = LeaseMgrFactory::instance().addLease(lease);
diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h
index fb10f23..52a0f81 100644
--- a/src/lib/dhcpsrv/alloc_engine.h
+++ b/src/lib/dhcpsrv/alloc_engine.h
@@ -20,6 +20,7 @@
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/lease_mgr.h>
+#include <hooks/callout_handle.h>
 
 #include <boost/shared_ptr.hpp>
 #include <boost/noncopyable.hpp>
@@ -193,13 +194,16 @@ protected:
     /// @param hint a hint that the client provided
     /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
     ///        an address for DISCOVER that is not really allocated (true)
+    /// @param callout_handle a callout handle (used in hooks). A lease callouts
+    ///        will be executed if this parameter is passed.
     /// @return Allocated IPv4 lease (or NULL if allocation failed)
     Lease4Ptr
     allocateAddress4(const SubnetPtr& subnet,
                      const ClientIdPtr& clientid,
                      const HWAddrPtr& hwaddr,
                      const isc::asiolink::IOAddress& hint,
-                     bool fake_allocation);
+                     bool fake_allocation,
+                     const isc::hooks::CalloutHandlePtr& callout_handle);
 
     /// @brief Renews a IPv4 lease
     ///
@@ -240,6 +244,9 @@ protected:
     /// @param hostname A fully qualified domain-name of the client.
     /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
     ///        an address for SOLICIT that is not really allocated (true)
+    /// @param callout_handle a callout handle (used in hooks). A lease callouts
+    ///        will be executed if this parameter is passed.
+    ///
     /// @return Allocated IPv6 lease (or NULL if allocation failed)
     Lease6Ptr
     allocateAddress6(const Subnet6Ptr& subnet,
@@ -249,7 +256,8 @@ protected:
                      const bool fwd_dns_update,
                      const bool rev_dns_update,
                      const std::string& hostname,
-                     bool fake_allocation);
+                     bool fake_allocation,
+                     const isc::hooks::CalloutHandlePtr& callout_handle);
 
     /// @brief Destructor. Used during DHCPv6 service shutdown.
     virtual ~AllocEngine();
@@ -265,6 +273,9 @@ private:
     /// @param clientid client identifier
     /// @param hwaddr client's hardware address
     /// @param addr an address that was selected and is confirmed to be available
+    /// @param callout_handle a callout handle (used in hooks). A lease callouts
+    ///        will be executed if this parameter is passed (and there are callouts
+    ///        registered)
     /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
     ///        an address for DISCOVER that is not really allocated (true)
     /// @return allocated lease (or NULL in the unlikely case of the lease just
@@ -272,6 +283,7 @@ private:
     Lease4Ptr createLease4(const SubnetPtr& subnet, const DuidPtr& clientid,
                            const HWAddrPtr& hwaddr,
                            const isc::asiolink::IOAddress& addr,
+                           const isc::hooks::CalloutHandlePtr& callout_handle,
                            bool fake_allocation = false);
 
     /// @brief creates a lease and inserts it in LeaseMgr if necessary
@@ -290,6 +302,9 @@ private:
     /// @param rev_dns_update A boolean value which indicates that server takes
     /// responibility for the reverse DNS Update for this lease (if true).
     /// @param hostname A fully qualified domain-name of the client.
+    /// @param callout_handle a callout handle (used in hooks). A lease callouts
+    ///        will be executed if this parameter is passed (and there are callouts
+    ///        registered)
     /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
     ///        an address for SOLICIT that is not really allocated (true)
     /// @return allocated lease (or NULL in the unlikely case of the lease just
@@ -298,6 +313,7 @@ private:
                            uint32_t iaid, const isc::asiolink::IOAddress& addr,
                            const bool fwd_dns_update, const bool rev_dns_update,
                            const std::string& hostname,
+                           const isc::hooks::CalloutHandlePtr& callout_handle,
                            bool fake_allocation = false);
 
     /// @brief Reuses expired IPv4 lease
@@ -310,6 +326,8 @@ private:
     /// @param subnet subnet the lease is allocated from
     /// @param clientid client identifier
     /// @param hwaddr client's hardware address
+    /// @param callout_handle a callout handle (used in hooks). A lease callouts
+    ///        will be executed if this parameter is passed.
     /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
     ///        an address for DISCOVER that is not really allocated (true)
     /// @return refreshed lease
@@ -317,6 +335,7 @@ private:
     Lease4Ptr reuseExpiredLease(Lease4Ptr& expired, const SubnetPtr& subnet,
                                 const ClientIdPtr& clientid,
                                 const HWAddrPtr& hwaddr,
+                                const isc::hooks::CalloutHandlePtr& callout_handle,
                                 bool fake_allocation = false);
 
     /// @brief Reuses expired IPv6 lease
@@ -334,6 +353,8 @@ private:
     /// @param rev_dns_update A boolean value which indicates that server takes
     /// responibility for the reverse DNS Update for this lease (if true).
     /// @param hostname A fully qualified domain-name of the client.
+    /// @param callout_handle a callout handle (used in hooks). A lease callouts
+    ///        will be executed if this parameter is passed.
     /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
     ///        an address for SOLICIT that is not really allocated (true)
     /// @return refreshed lease
@@ -343,6 +364,7 @@ private:
                                 const bool fwd_dns_update,
                                 const bool rev_dns_update,
                                 const std::string& hostname,
+                                const isc::hooks::CalloutHandlePtr& callout_handle,
                                 bool fake_allocation = false);
 
     /// @brief a pointer to currently used allocator
@@ -350,6 +372,10 @@ private:
 
     /// @brief number of attempts before we give up lease allocation (0=unlimited)
     unsigned int attempts_;
+
+    // hook name indexes (used in hooks callouts)
+    int hook_index_lease4_select_; ///< index for lease4_select hook
+    int hook_index_lease6_select_; ///< index for lease6_select hook
 };
 
 }; // namespace isc::dhcp
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 592efb7..56e4c8e 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -262,13 +262,66 @@ void CfgMgr::deleteSubnets6() {
     subnets6_.clear();
 }
 
+
 std::string CfgMgr::getDataDir() {
     return (datadir_);
 }
 
+void
+CfgMgr::addActiveIface(const std::string& iface) {
+    if (isIfaceListedActive(iface)) {
+        isc_throw(DuplicateListeningIface,
+                  "attempt to add duplicate interface '" << iface << "'"
+                  " to the set of interfaces on which server listens");
+    }
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_IFACE)
+        .arg(iface);
+    active_ifaces_.push_back(iface);
+}
+
+void
+CfgMgr::activateAllIfaces() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+              DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE);
+    all_ifaces_active_ = true;
+}
+
+void
+CfgMgr::deleteActiveIfaces() {
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+              DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES);
+    active_ifaces_.clear();
+    all_ifaces_active_ = false;
+}
+
+bool
+CfgMgr::isActiveIface(const std::string& iface) const {
+
+    // @todo Verify that the interface with the specified name is
+    // present in the system.
+
+    // If all interfaces are marked active, there is no need to check that
+    // the name of this interface has been explicitly listed.
+    if (all_ifaces_active_) {
+        return (true);
+    }
+    return (isIfaceListedActive(iface));
+}
+
+bool
+CfgMgr::isIfaceListedActive(const std::string& iface) const {
+    for (ActiveIfacesCollection::const_iterator it = active_ifaces_.begin();
+         it != active_ifaces_.end(); ++it) {
+        if (iface == *it) {
+            return (true);
+        }
+    }
+    return (false);
+}
 
 CfgMgr::CfgMgr()
-    :datadir_(DHCP_DATA_DIR) {
+    : datadir_(DHCP_DATA_DIR),
+      all_ifaces_active_(false) {
     // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
     // Note: the definition of DHCP_DATA_DIR needs to include quotation marks
     // See AM_CPPFLAGS definition in Makefile.am
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index 05c1752..0ec51d0 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -30,10 +30,21 @@
 #include <map>
 #include <string>
 #include <vector>
+#include <list>
 
 namespace isc {
 namespace dhcp {
 
+/// @brief Exception thrown when the same interface has been specified twice.
+///
+/// In particular, this exception is thrown when adding interface to the set
+/// of interfaces on which server is supposed to listen.
+class DuplicateListeningIface : public Exception {
+public:
+    DuplicateListeningIface(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
 
 /// @brief Configuration Manager
 ///
@@ -201,6 +212,26 @@ public:
     /// completely new?
     void deleteSubnets6();
 
+    /// @brief returns const reference to all subnets6
+    ///
+    /// This is used in a hook (subnet4_select), where the hook is able
+    /// to choose a different subnet. Server code has to offer a list
+    /// of possible choices (i.e. all subnets).
+    /// @return a pointer to const Subnet6 collection
+    const Subnet4Collection* getSubnets4() {
+        return (&subnets4_);
+    }
+
+    /// @brief returns const reference to all subnets6
+    ///
+    /// This is used in a hook (subnet6_select), where the hook is able
+    /// to choose a different subnet. Server code has to offer a list
+    /// of possible choices (i.e. all subnets).
+    /// @return a pointer to const Subnet6 collection
+    const Subnet6Collection* getSubnets6() {
+        return (&subnets6_);
+    }
+
     /// @brief get IPv4 subnet by address
     ///
     /// Finds a matching subnet, based on an address. This can be used
@@ -237,6 +268,43 @@ public:
     /// @return data directory
     std::string getDataDir();
 
+    /// @brief Adds the name of the interface to the set of interfaces on which
+    /// server should listen.
+    ///
+    /// @param iface A name of the interface being added to the listening set.
+    void addActiveIface(const std::string& iface);
+
+    /// @brief Sets the flag which indicates that server is supposed to listen
+    /// on all available interfaces.
+    ///
+    /// This function does not close or open sockets. It simply marks that
+    /// server should start to listen on all interfaces the next time sockets
+    /// are reopened. Server should examine this flag when it gets reconfigured
+    /// and configuration changes the interfaces that server should listen on.
+    void activateAllIfaces();
+
+    /// @brief Clear the collection of the interfaces that server should listen
+    /// on.
+    ///
+    /// Apart from clearing the list of interfaces specified with
+    /// @c CfgMgr::addListeningInterface, it also disables listening on all
+    /// interfaces if it has been enabled using
+    /// @c CfgMgr::activateAllInterfaces.
+    /// Likewise @c CfgMgr::activateAllIfaces, this function does not close or
+    /// open sockets. It marks all interfaces inactive for DHCP traffic.
+    /// Server should examine this new setting when it attempts to
+    /// reopen sockets (as a result of reconfiguration).
+    void deleteActiveIfaces();
+
+    /// @brief Check if specified interface should be used to listen to DHCP
+    /// traffic.
+    ///
+    /// @param iface A name of the interface to be checked.
+    ///
+    /// @return true if the specified interface belongs to the set of the
+    /// interfaces on which server is configured to listen.
+    bool isActiveIface(const std::string& iface) const;
+
 protected:
 
     /// @brief Protected constructor.
@@ -268,6 +336,20 @@ protected:
 
 private:
 
+    /// @brief Checks if the specified interface is listed as active.
+    ///
+    /// This function searches for the specified interface name on the list of
+    /// active interfaces: @c CfgMgr::active_ifaces_. It does not take into
+    /// account @c CfgMgr::all_ifaces_active_ flag. If this flag is set to true
+    /// but the specified interface does not belong to
+    /// @c CfgMgr::active_ifaces_, it will return false.
+    ///
+    /// @param iface interface name.
+    ///
+    /// @return true if specified interface belongs to
+    /// @c CfgMgr::active_ifaces_.
+    bool isIfaceListedActive(const std::string& iface) const;
+
     /// @brief A collection of option definitions.
     ///
     /// A collection of option definitions that can be accessed
@@ -283,6 +365,16 @@ private:
 
     /// @brief directory where data files (e.g. server-id) are stored
     std::string datadir_;
+
+    /// @name A collection of interface names on which server listens.
+    //@{
+    typedef std::list<std::string> ActiveIfacesCollection;
+    std::list<std::string> active_ifaces_;
+    //@}
+
+    /// A flag which indicates that server should listen on all available
+    /// interfaces.
+    bool all_ifaces_active_;
 };
 
 } // namespace isc::dhcp
diff --git a/src/lib/dhcpsrv/dhcp_parsers.cc b/src/lib/dhcpsrv/dhcp_parsers.cc
index 4d1dc73..52c226b 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.cc
+++ b/src/lib/dhcpsrv/dhcp_parsers.cc
@@ -33,6 +33,10 @@ using namespace isc::data;
 namespace isc {
 namespace dhcp {
 
+namespace {
+const char* ALL_IFACES_KEYWORD = "*";
+}
+
 // *********************** ParserContext  *************************
 
 ParserContext::ParserContext(Option::Universe universe):
@@ -53,17 +57,17 @@ ParserContext::ParserContext(const ParserContext& rhs):
         universe_(rhs.universe_) {
     }
 
-ParserContext& 
+ParserContext&
 ParserContext::operator=(const ParserContext& rhs) {
         if (this != &rhs) {
-            boolean_values_ = 
+            boolean_values_ =
                 BooleanStoragePtr(new BooleanStorage(*(rhs.boolean_values_)));
-            uint32_values_ = 
+            uint32_values_ =
                 Uint32StoragePtr(new Uint32Storage(*(rhs.uint32_values_)));
-            string_values_ = 
+            string_values_ =
                 StringStoragePtr(new StringStorage(*(rhs.string_values_)));
             options_ = OptionStoragePtr(new OptionStorage(*(rhs.options_)));
-            option_defs_ = 
+            option_defs_ =
                 OptionDefStoragePtr(new OptionDefStorage(*(rhs.option_defs_)));
             universe_ = rhs.universe_;
         }
@@ -77,14 +81,14 @@ DebugParser::DebugParser(const std::string& param_name)
     :param_name_(param_name) {
 }
 
-void 
+void
 DebugParser::build(ConstElementPtr new_config) {
     value_ = new_config;
     std::cout << "Build for token: [" << param_name_ << "] = ["
-        << value_->str() << "]" << std::endl; 
+        << value_->str() << "]" << std::endl;
 }
 
-void 
+void
 DebugParser::commit() {
     // Debug message. The whole DebugParser class is used only for parser
     // debugging, and is not used in production code. It is very convenient
@@ -102,7 +106,7 @@ template<> void ValueParser<bool>::build(isc::data::ConstElementPtr value) {
     try {
         value_ = value->boolValue();
     } catch (const isc::data::TypeError &) {
-        isc_throw(BadValue, " Wrong value type for " << param_name_ 
+        isc_throw(BadValue, " Wrong value type for " << param_name_
                   << " : build called with a non-boolean element.");
     }
 }
@@ -140,33 +144,83 @@ template <> void ValueParser<std::string>::build(ConstElementPtr value) {
 
 // ******************** InterfaceListConfigParser *************************
 
-InterfaceListConfigParser::InterfaceListConfigParser(const std::string& 
-                                                     param_name) {
-    if (param_name != "interface") {
+InterfaceListConfigParser::
+InterfaceListConfigParser(const std::string& param_name)
+    : activate_all_(false),
+      param_name_(param_name) {
+    if (param_name_ != "interfaces") {
         isc_throw(BadValue, "Internal error. Interface configuration "
             "parser called for the wrong parameter: " << param_name);
     }
 }
 
-void 
+void
 InterfaceListConfigParser::build(ConstElementPtr value) {
+    // First, we iterate over all specified entries and add it to the
+    // local container so as we can do some basic validation, e.g. eliminate
+    // duplicates.
     BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
-        interfaces_.push_back(iface->str());
+        std::string iface_name = iface->stringValue();
+        if (iface_name != ALL_IFACES_KEYWORD) {
+            // Let's eliminate duplicates. We could possibly allow duplicates,
+            // but if someone specified duplicated interface name it is likely
+            // that he mistyped the configuration. Failing here should draw his
+            // attention.
+            if (isIfaceAdded(iface_name)) {
+                isc_throw(isc::dhcp::DhcpConfigError, "duplicate interface"
+                          << " name '" << iface_name << "' specified in '"
+                          << param_name_ << "' configuration parameter");
+            }
+            // @todo check that this interface exists in the system!
+            // The IfaceMgr exposes mechanisms to check this.
+
+            // Add the interface name if ok.
+            interfaces_.push_back(iface_name);
+
+        } else {
+            activate_all_ = true;
+
+        }
     }
 }
 
-void 
+void
 InterfaceListConfigParser::commit() {
-    /// @todo: Implement per interface listening. Currently always listening
-    /// on all interfaces.
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+    // Remove active interfaces and clear a flag which marks all interfaces
+    // active
+    cfg_mgr.deleteActiveIfaces();
+
+    if (activate_all_) {
+        // Activate all interfaces. There is not need to add their names
+        // explicitly.
+        cfg_mgr.activateAllIfaces();
+
+    } else {
+        // Explicitly add names of the interfaces which server should listen on.
+        BOOST_FOREACH(std::string iface, interfaces_) {
+            cfg_mgr.addActiveIface(iface);
+        }
+    }
+}
+
+bool
+InterfaceListConfigParser::isIfaceAdded(const std::string& iface) const {
+    for (IfaceListStorage::const_iterator it = interfaces_.begin();
+         it != interfaces_.end(); ++it) {
+        if (iface == *it) {
+            return (true);
+        }
+    }
+    return (false);
 }
 
 // **************************** OptionDataParser *************************
 OptionDataParser::OptionDataParser(const std::string&, OptionStoragePtr options,
                                   ParserContextPtr global_context)
-    : boolean_values_(new BooleanStorage()), 
-    string_values_(new StringStorage()), uint32_values_(new Uint32Storage()), 
-    options_(options), option_descriptor_(false), 
+    : boolean_values_(new BooleanStorage()),
+    string_values_(new StringStorage()), uint32_values_(new Uint32Storage()),
+    options_(options), option_descriptor_(false),
     global_context_(global_context) {
     if (!options_) {
         isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
@@ -179,22 +233,22 @@ OptionDataParser::OptionDataParser(const std::string&, OptionStoragePtr options,
     }
 }
 
-void 
+void
 OptionDataParser::build(ConstElementPtr option_data_entries) {
     BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
         ParserPtr parser;
         if (param.first == "name" || param.first == "data" ||
             param.first == "space") {
-            StringParserPtr name_parser(new StringParser(param.first, 
-                                        string_values_)); 
+            StringParserPtr name_parser(new StringParser(param.first,
+                                        string_values_));
             parser = name_parser;
         } else if (param.first == "code") {
-            Uint32ParserPtr code_parser(new Uint32Parser(param.first, 
-                                       uint32_values_)); 
+            Uint32ParserPtr code_parser(new Uint32Parser(param.first,
+                                       uint32_values_));
             parser = code_parser;
         } else if (param.first == "csv-format") {
-            BooleanParserPtr value_parser(new BooleanParser(param.first, 
-                                         boolean_values_)); 
+            BooleanParserPtr value_parser(new BooleanParser(param.first,
+                                         boolean_values_));
             parser = value_parser;
         } else {
             isc_throw(DhcpConfigError,
@@ -216,12 +270,12 @@ OptionDataParser::build(ConstElementPtr option_data_entries) {
     createOption();
 }
 
-void 
+void
 OptionDataParser::commit() {
     if (!option_descriptor_.option) {
-        // Before we can commit the new option should be configured. If it is 
+        // Before we can commit the new option should be configured. If it is
         // not than somebody must have called commit() before build().
-        isc_throw(isc::InvalidOperation, 
+        isc_throw(isc::InvalidOperation,
             "parser logic error: no option has been configured and"
             " thus there is nothing to commit. Has build() been called?");
     }
@@ -245,7 +299,7 @@ OptionDataParser::commit() {
     options_->addItem(option_descriptor_, option_space_);
 }
 
-void 
+void
 OptionDataParser::createOption() {
     // Option code is held in the uint32_t storage but is supposed to
     // be uint16_t value. We need to check that value in the configuration
@@ -262,7 +316,7 @@ OptionDataParser::createOption() {
 
     // Check that the option name has been specified, is non-empty and does not
     // contain spaces
-    std::string option_name = string_values_->getParam("name"); 
+    std::string option_name = string_values_->getParam("name");
     if (option_name.empty()) {
         isc_throw(DhcpConfigError, "name of the option with code '"
                 << option_code << "' is empty");
@@ -271,7 +325,7 @@ OptionDataParser::createOption() {
                 << "', space character is not allowed");
     }
 
-    std::string option_space = string_values_->getParam("space"); 
+    std::string option_space = string_values_->getParam("space");
     if (!OptionSpace::validateName(option_space)) {
         isc_throw(DhcpConfigError, "invalid option space name '"
                 << option_space << "' specified for option '"
@@ -287,7 +341,7 @@ OptionDataParser::createOption() {
         // need to search for its definition among user-configured
         // options. They are expected to be in the global storage
         // already.
-        OptionDefContainerPtr defs = 
+        OptionDefContainerPtr defs =
             global_context_->option_defs_->getItems(option_space);
 
         // The getItems() should never return the NULL pointer. If there are
@@ -341,16 +395,16 @@ OptionDataParser::createOption() {
                       << " does not have a definition.");
         }
 
-        // @todo We have a limited set of option definitions intiialized at 
-        // the moment.  In the future we want to initialize option definitions 
-        // for all options.  Consequently an error will be issued if an option 
+        // @todo We have a limited set of option definitions intiialized at
+        // the moment.  In the future we want to initialize option definitions
+        // for all options.  Consequently an error will be issued if an option
         // definition does not exist for a particular option code. For now it is
         // ok to create generic option if definition does not exist.
-        OptionPtr option(new Option(global_context_->universe_, 
+        OptionPtr option(new Option(global_context_->universe_,
                         static_cast<uint16_t>(option_code), binary));
-        // The created option is stored in option_descriptor_ class member 
-        // until the commit stage when it is inserted into the main storage. 
-        // If an option with the same code exists in main storage already the 
+        // The created option is stored in option_descriptor_ class member
+        // until the commit stage when it is inserted into the main storage.
+        // If an option with the same code exists in main storage already the
         // old option is replaced.
         option_descriptor_.option = option;
         option_descriptor_.persistent = false;
@@ -372,9 +426,9 @@ OptionDataParser::createOption() {
         // an instance of our option.
         try {
             OptionPtr option = csv_format ?
-                def->optionFactory(global_context_->universe_, 
+                def->optionFactory(global_context_->universe_,
                                   option_code, data_tokens) :
-                def->optionFactory(global_context_->universe_, 
+                def->optionFactory(global_context_->universe_,
                                   option_code, binary);
             Subnet::OptionDescriptor desc(option, false);
             option_descriptor_.option = option;
@@ -392,10 +446,10 @@ OptionDataParser::createOption() {
 }
 
 // **************************** OptionDataListParser *************************
-OptionDataListParser::OptionDataListParser(const std::string&, 
+OptionDataListParser::OptionDataListParser(const std::string&,
     OptionStoragePtr options, ParserContextPtr global_context,
     OptionDataParserFactory* optionDataParserFactory)
-    : options_(options), local_options_(new OptionStorage()), 
+    : options_(options), local_options_(new OptionStorage()),
     global_context_(global_context),
     optionDataParserFactory_(optionDataParserFactory) {
     if (!options_) {
@@ -414,11 +468,11 @@ OptionDataListParser::OptionDataListParser(const std::string&,
     }
 }
 
-void 
+void
 OptionDataListParser::build(ConstElementPtr option_data_list) {
     BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
-        boost::shared_ptr<OptionDataParser> 
-            parser((*optionDataParserFactory_)("option-data", 
+        boost::shared_ptr<OptionDataParser>
+            parser((*optionDataParserFactory_)("option-data",
                     local_options_, global_context_));
 
         // options_ member will hold instances of all options thus
@@ -430,7 +484,7 @@ OptionDataListParser::build(ConstElementPtr option_data_list) {
     }
 }
 
-void 
+void
 OptionDataListParser::commit() {
     BOOST_FOREACH(ParserPtr parser, parsers_) {
         parser->commit();
@@ -443,7 +497,7 @@ OptionDataListParser::commit() {
 }
 
 // ******************************** OptionDefParser ****************************
-OptionDefParser::OptionDefParser(const std::string&, 
+OptionDefParser::OptionDefParser(const std::string&,
                                 OptionDefStoragePtr storage)
     : storage_(storage), boolean_values_(new BooleanStorage()),
     string_values_(new StringStorage()), uint32_values_(new Uint32Storage()) {
@@ -453,23 +507,23 @@ OptionDefParser::OptionDefParser(const std::string&,
     }
 }
 
-void 
+void
 OptionDefParser::build(ConstElementPtr option_def) {
     // Parse the elements that make up the option definition.
      BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
         std::string entry(param.first);
         ParserPtr parser;
-        if (entry == "name" || entry == "type" || entry == "record-types" 
+        if (entry == "name" || entry == "type" || entry == "record-types"
             || entry == "space" || entry == "encapsulate") {
-            StringParserPtr str_parser(new StringParser(entry, 
+            StringParserPtr str_parser(new StringParser(entry,
                                        string_values_));
             parser = str_parser;
         } else if (entry == "code") {
-            Uint32ParserPtr code_parser(new Uint32Parser(entry, 
+            Uint32ParserPtr code_parser(new Uint32Parser(entry,
                                         uint32_values_));
             parser = code_parser;
         } else if (entry == "array") {
-            BooleanParserPtr array_parser(new BooleanParser(entry, 
+            BooleanParserPtr array_parser(new BooleanParser(entry,
                                          boolean_values_));
             parser = array_parser;
         } else {
@@ -501,7 +555,7 @@ OptionDefParser::build(ConstElementPtr option_def) {
     }
 }
 
-void 
+void
 OptionDefParser::commit() {
     if (storage_ && option_definition_ &&
         OptionSpace::validateName(option_space_name_)) {
@@ -509,7 +563,7 @@ OptionDefParser::commit() {
     }
 }
 
-void 
+void
 OptionDefParser::createOptionDef() {
     // Get the option space name and validate it.
     std::string space = string_values_->getParam("space");
@@ -589,7 +643,7 @@ OptionDefParser::createOptionDef() {
 }
 
 // ******************************** OptionDefListParser ************************
-OptionDefListParser::OptionDefListParser(const std::string&, 
+OptionDefListParser::OptionDefListParser(const std::string&,
     OptionDefStoragePtr storage) :storage_(storage) {
     if (!storage_) {
         isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
@@ -597,7 +651,7 @@ OptionDefListParser::OptionDefListParser(const std::string&,
     }
 }
 
-void 
+void
 OptionDefListParser::build(ConstElementPtr option_def_list) {
     // Clear existing items in the storage.
     // We are going to replace all of them.
@@ -616,7 +670,7 @@ OptionDefListParser::build(ConstElementPtr option_def_list) {
     }
 }
 
-void 
+void
 OptionDefListParser::commit() {
     CfgMgr& cfg_mgr = CfgMgr::instance();
     cfg_mgr.deleteOptionDefs();
@@ -648,7 +702,7 @@ PoolParser::PoolParser(const std::string&,  PoolStoragePtr pools)
     }
 }
 
-void 
+void
 PoolParser::build(ConstElementPtr pools_list) {
     BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) {
         // That should be a single pool representation. It should contain
@@ -676,7 +730,7 @@ PoolParser::build(ConstElementPtr pools_list) {
                 // will result in interpreting the first digit as output
                 // value and throwing exception if length is written on two
                 // digits (because there are extra characters left over).
-    
+
                 // No checks for values over 128. Range correctness will
                 // be checked in Pool4 constructor.
                 len = boost::lexical_cast<int>(prefix_len);
@@ -708,7 +762,7 @@ PoolParser::build(ConstElementPtr pools_list) {
         }
 }
 
-void 
+void
 PoolParser::commit() {
     if (pools_) {
         // local_pools_ holds the values produced by the build function.
@@ -720,9 +774,9 @@ PoolParser::commit() {
 
 //****************************** SubnetConfigParser *************************
 
-SubnetConfigParser::SubnetConfigParser(const std::string&, 
-                                       ParserContextPtr global_context) 
-    : uint32_values_(new Uint32Storage()), string_values_(new StringStorage()), 
+SubnetConfigParser::SubnetConfigParser(const std::string&,
+                                       ParserContextPtr global_context)
+    : uint32_values_(new Uint32Storage()), string_values_(new StringStorage()),
     pools_(new PoolStorage()), options_(new OptionStorage()),
     global_context_(global_context) {
     // The first parameter should always be "subnet", but we don't check
@@ -733,7 +787,7 @@ SubnetConfigParser::SubnetConfigParser(const std::string&,
     }
 }
 
-void 
+void
 SubnetConfigParser::build(ConstElementPtr subnet) {
     BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
         ParserPtr parser(createSubnetConfigParser(param.first));
@@ -757,8 +811,8 @@ SubnetConfigParser::build(ConstElementPtr subnet) {
     createSubnet();
 }
 
-void 
-SubnetConfigParser::appendSubOptions(const std::string& option_space, 
+void
+SubnetConfigParser::appendSubOptions(const std::string& option_space,
                                      OptionPtr& option) {
     // Only non-NULL options are stored in option container.
     // If this option pointer is NULL this is a serious error.
@@ -812,7 +866,7 @@ SubnetConfigParser::appendSubOptions(const std::string& option_space,
     }
 }
 
-void 
+void
 SubnetConfigParser::createSubnet() {
     std::string subnet_txt;
     try {
@@ -843,11 +897,11 @@ SubnetConfigParser::createSubnet() {
     isc::asiolink::IOAddress addr(subnet_txt.substr(0, pos));
     uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
 
-    // Call the subclass's method to instantiate the subnet 
+    // Call the subclass's method to instantiate the subnet
     initSubnet(addr, len);
 
     // Add pools to it.
-    for (PoolStorage::iterator it = pools_->begin(); it != pools_->end(); 
+    for (PoolStorage::iterator it = pools_->begin(); it != pools_->end();
          ++it) {
         subnet_->addPool(*it);
     }
@@ -922,7 +976,7 @@ SubnetConfigParser::createSubnet() {
             // values we don't add option from the global storage
             // if there is one already.
             Subnet::OptionDescriptor existing_desc =
-                    subnet_->getOptionDescriptor(option_space, 
+                    subnet_->getOptionDescriptor(option_space,
                                                 desc.option->getType());
             if (!existing_desc.option) {
                 // Add sub-options (if any).
@@ -933,15 +987,15 @@ SubnetConfigParser::createSubnet() {
     }
 }
 
-isc::dhcp::Triplet<uint32_t> 
+isc::dhcp::Triplet<uint32_t>
 SubnetConfigParser::getParam(const std::string& name) {
     uint32_t value = 0;
     try {
-        // look for local value 
+        // look for local value
         value = uint32_values_->getParam(name);
     } catch (const DhcpConfigError &) {
         try {
-            // no local, use global value 
+            // no local, use global value
             value = global_context_->uint32_values_->getParam(name);
         } catch (const DhcpConfigError &) {
             isc_throw(DhcpConfigError, "Mandatory parameter " << name
diff --git a/src/lib/dhcpsrv/dhcp_parsers.h b/src/lib/dhcpsrv/dhcp_parsers.h
index e453204..37dc950 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.h
+++ b/src/lib/dhcpsrv/dhcp_parsers.h
@@ -47,8 +47,8 @@ typedef boost::shared_ptr<OptionStorage> OptionStoragePtr;
 /// @brief A template class that stores named elements of a given data type.
 ///
 /// This template class is provides data value storage for configuration parameters
-/// of a given data type.  The values are stored by parameter name and as instances 
-/// of type "ValueType". 
+/// of a given data type.  The values are stored by parameter name and as instances
+/// of type "ValueType".
 ///
 /// @param ValueType is the data type of the elements to store.
 template<typename ValueType>
@@ -57,7 +57,7 @@ class ValueStorage {
         /// @brief  Stores the the parameter and its value in the store.
         ///
         /// If the parameter does not exist in the store, then it will be added,
-        /// otherwise its data value will be updated with the given value. 
+        /// otherwise its data value will be updated with the given value.
         ///
         /// @param name is the name of the paramater to store.
         /// @param value is the data value to store.
@@ -71,10 +71,10 @@ class ValueStorage {
         /// @param name is the name of the parameter for which the data
         /// value is desired.
         ///
-        /// @return The paramater's data value of type <ValueType>.
+        /// @return The paramater's data value of type @c ValueType.
         /// @throw DhcpConfigError if the parameter is not found.
         ValueType getParam(const std::string& name) const {
-            typename std::map<std::string, ValueType>::const_iterator param 
+            typename std::map<std::string, ValueType>::const_iterator param
                 = values_.find(name);
 
             if (param == values_.end()) {
@@ -87,8 +87,8 @@ class ValueStorage {
 
         /// @brief  Remove the parameter from the store.
         ///
-        /// Deletes the entry for the given parameter from the store if it 
-        /// exists. 
+        /// Deletes the entry for the given parameter from the store if it
+        /// exists.
         ///
         /// @param name is the name of the paramater to delete.
         void delParam(const std::string& name) {
@@ -108,7 +108,7 @@ class ValueStorage {
 };
 
 
-/// @brief a collection of elements that store uint32 values 
+/// @brief a collection of elements that store uint32 values
 typedef ValueStorage<uint32_t> Uint32Storage;
 typedef boost::shared_ptr<Uint32Storage> Uint32StoragePtr;
 
@@ -128,9 +128,9 @@ typedef boost::shared_ptr<BooleanStorage> BooleanStoragePtr;
 class ParserContext {
 public:
     /// @brief Constructor
-    /// 
+    ///
     /// @param universe is the Option::Universe value of this
-    /// context. 
+    /// context.
     ParserContext(Option::Universe universe);
 
     /// @brief Copy constructor
@@ -161,12 +161,12 @@ public:
 /// @brief Pointer to various parser context.
 typedef boost::shared_ptr<ParserContext> ParserContextPtr;
 
-/// @brief Simple data-type parser template class 
+/// @brief Simple data-type parser template class
 ///
 /// This is the template class for simple data-type parsers. It supports
-/// parsing a configuration parameter with specific data-type for its 
-/// possible values. It provides a common constructor, commit, and templated 
-/// data storage.  The "build" method implementation must be provided by a 
+/// parsing a configuration parameter with specific data-type for its
+/// possible values. It provides a common constructor, commit, and templated
+/// data storage.  The "build" method implementation must be provided by a
 /// declaring type.
 /// @param ValueType is the data type of the configuration paramater value
 /// the parser should handle.
@@ -182,7 +182,7 @@ public:
     /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
     /// name is empty.
     /// @throw isc::dhcp::DhcpConfigError if storage is null.
-    ValueParser(const std::string& param_name, 
+    ValueParser(const std::string& param_name,
         boost::shared_ptr<ValueStorage<ValueType> > storage)
         : storage_(storage), param_name_(param_name), value_() {
         // Empty parameter name is invalid.
@@ -199,12 +199,12 @@ public:
     }
 
 
-    /// @brief Parse a given element into a value of type <ValueType>
+    /// @brief Parse a given element into a value of type @c ValueType
     ///
     /// @param value a value to be parsed.
     ///
     /// @throw isc::BadValue Typically the implementing type will throw
-    /// a BadValue exception when given an invalid Element to parse. 
+    /// a BadValue exception when given an invalid Element to parse.
     void build(isc::data::ConstElementPtr value);
 
     /// @brief Put a parsed value to the storage.
@@ -213,7 +213,7 @@ public:
         // its value. If it doesn't we insert a new element.
         storage_->setParam(param_name_, value_);
     }
-    
+ 
 private:
     /// Pointer to the storage where committed value is stored.
     boost::shared_ptr<ValueStorage<ValueType> > storage_;
@@ -302,8 +302,23 @@ public:
     virtual void commit();
 
 private:
+    /// @brief Check that specified interface exists in
+    /// @c InterfaceListConfigParser::interfaces_.
+    ///
+    /// @param iface A name of the interface.
+    ///
+    /// @return true if specified interface name was found.
+    bool isIfaceAdded(const std::string& iface) const;
+
     /// contains list of network interfaces
-    std::vector<std::string> interfaces_;
+    typedef std::list<std::string> IfaceListStorage;
+    IfaceListStorage interfaces_;
+
+    // Should server listen on all interfaces.
+    bool activate_all_;
+
+    // Parsed parameter name
+    std::string param_name_;
 };
 
 
@@ -332,11 +347,11 @@ public:
     /// @param dummy first argument is ignored, all Parser constructors
     /// accept string as first argument.
     /// @param options is the option storage in which to store the parsed option
-    /// upon "commit". 
-    /// @param global_context is a pointer to the global context which 
-    /// stores global scope parameters, options, option defintions. 
+    /// upon "commit".
+    /// @param global_context is a pointer to the global context which
+    /// stores global scope parameters, options, option defintions.
     /// @throw isc::dhcp::DhcpConfigError if options or global_context are null.
-    OptionDataParser(const std::string&, OptionStoragePtr options, 
+    OptionDataParser(const std::string& dummy, OptionStoragePtr options,
                     ParserContextPtr global_context);
 
     /// @brief Parses the single option data.
@@ -356,31 +371,31 @@ public:
 
     /// @brief Commits option value.
     ///
-    /// This function adds a new option to the storage or replaces an existing 
+    /// This function adds a new option to the storage or replaces an existing
     /// option with the same code.
     ///
-    /// @throw isc::InvalidOperation if failed to set pointer to storage or 
+    /// @throw isc::InvalidOperation if failed to set pointer to storage or
     /// failed
     /// to call build() prior to commit. If that happens data in the storage
     /// remain un-modified.
     virtual void commit();
 
-    /// @brief virtual destructor to ensure orderly destruction of derivations. 
+    /// @brief virtual destructor to ensure orderly destruction of derivations.
     virtual ~OptionDataParser(){};
 
 protected:
     /// @brief Finds an option definition within the server's option space
-    /// 
-    /// Given an option space and an option code, find the correpsonding 
+    ///
+    /// Given an option space and an option code, find the correpsonding
     /// option defintion within the server's option defintion storage. This
     /// method is pure virtual requiring derivations to manage which option
     /// space(s) is valid for search.
     ///
-    /// @param option_space name of the parameter option space 
-    /// @param option_code numeric value of the parameter to find 
-    /// @return OptionDefintionPtr of the option defintion or an 
+    /// @param option_space name of the parameter option space
+    /// @param option_code numeric value of the parameter to find
+    /// @return OptionDefintionPtr of the option defintion or an
     /// empty OptionDefinitionPtr if not found.
-    /// @throw DhcpConfigError if the option space requested is not valid 
+    /// @throw DhcpConfigError if the option space requested is not valid
     /// for this server.
     virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
             std::string& option_space, uint32_t option_code) = 0;
@@ -420,13 +435,13 @@ private:
     /// Option space name where the option belongs to.
     std::string option_space_;
 
-    /// Parsing context which contains global values, options and option 
+    /// Parsing context which contains global values, options and option
     /// definitions.
     ParserContextPtr global_context_;
 };
 
 ///@brief Function pointer for OptionDataParser factory methods
-typedef OptionDataParser *OptionDataParserFactory(const std::string&, 
+typedef OptionDataParser *OptionDataParserFactory(const std::string&,
                      OptionStoragePtr options, ParserContextPtr global_context);
 
 /// @brief Parser for option data values within a subnet.
@@ -439,15 +454,15 @@ class OptionDataListParser : public DhcpConfigParser {
 public:
     /// @brief Constructor.
     ///
-    /// @param string& nominally would be param name, this is always ignored.
+    /// @param dummy nominally would be param name, this is always ignored.
     /// @param options parsed option storage for options in this list
-    /// @param global_context is a pointer to the global context which 
-    /// stores global scope parameters, options, option defintions. 
-    /// @param optionDataParserFactory factory method for creating individual 
-    /// option parsers 
+    /// @param global_context is a pointer to the global context which
+    /// stores global scope parameters, options, option defintions.
+    /// @param optionDataParserFactory factory method for creating individual
+    /// option parsers
     /// @throw isc::dhcp::DhcpConfigError if options or global_context are null.
-    OptionDataListParser(const std::string&, OptionStoragePtr options, 
-                        ParserContextPtr global_context, 
+    OptionDataListParser(const std::string& dummy, OptionStoragePtr options,
+                        ParserContextPtr global_context,
                         OptionDataParserFactory *optionDataParserFactory);
 
     /// @brief Parses entries that define options' data for a subnet.
@@ -477,7 +492,7 @@ private:
     /// Collection of parsers;
     ParserCollection parsers_;
 
-    /// Parsing context which contains global values, options and option 
+    /// Parsing context which contains global values, options and option
     /// definitions.
     ParserContextPtr global_context_;
 
@@ -495,10 +510,10 @@ public:
     ///
     /// @param dummy first argument is ignored, all Parser constructors
     /// accept string as first argument.
-    /// @param storage is the definition storage in which to store the parsed 
-    /// definition upon "commit". 
+    /// @param storage is the definition storage in which to store the parsed
+    /// definition upon "commit".
     /// @throw isc::dhcp::DhcpConfigError if storage is null.
-    OptionDefParser(const std::string&, OptionDefStoragePtr storage);
+    OptionDefParser(const std::string& dummy, OptionDefStoragePtr storage);
 
     /// @brief Parses an entry that describes single option definition.
     ///
@@ -546,10 +561,10 @@ public:
     ///
     /// @param dummy first argument is ignored, all Parser constructors
     /// accept string as first argument.
-    /// @param storage is the definition storage in which to store the parsed 
-    /// definitions in this list 
+    /// @param storage is the definition storage in which to store the parsed
+    /// definitions in this list
     /// @throw isc::dhcp::DhcpConfigError if storage is null.
-    OptionDefListParser(const std::string&, OptionDefStoragePtr storage);
+    OptionDefListParser(const std::string& dummy, OptionDefStoragePtr storage);
 
     /// @brief Parse configuration entries.
     ///
@@ -566,7 +581,7 @@ public:
 
 private:
     /// @brief storage for option definitions.
-    OptionDefStoragePtr storage_; 
+    OptionDefStoragePtr storage_;
 };
 
 /// @brief a collection of pools
@@ -587,14 +602,13 @@ class PoolParser : public DhcpConfigParser {
 public:
 
     /// @brief constructor.
-   
-
+    ///
     /// @param dummy first argument is ignored, all Parser constructors
     /// accept string as first argument.
-    /// @param pools is the storage in which to store the parsed pool 
-    /// upon "commit". 
+    /// @param pools is the storage in which to store the parsed pool
+    /// upon "commit".
     /// @throw isc::dhcp::DhcpConfigError if storage is null.
-    PoolParser(const std::string&,  PoolStoragePtr pools);
+    PoolParser(const std::string& dummy, PoolStoragePtr pools);
 
     /// @brief parses the actual list
     ///
@@ -614,9 +628,9 @@ protected:
     ///
     /// @param addr is the IP  prefix of the pool.
     /// @param len is the prefix length.
-    /// @param ignored dummy parameter to provide symmetry between 
-    /// @return returns a PoolPtr to the new Pool object. 
-    virtual PoolPtr poolMaker(isc::asiolink::IOAddress &addr, uint32_t len, 
+    /// @param ptype is the type of pool to create.
+    /// @return returns a PoolPtr to the new Pool object.
+    virtual PoolPtr poolMaker(isc::asiolink::IOAddress &addr, uint32_t len,
                            int32_t ptype=0) = 0;
 
     /// @brief Creates a Pool object given starting and ending IP addresses.
@@ -625,7 +639,7 @@ protected:
     /// @param max is the last IP address in the pool.
     /// @param ptype is the type of pool to create (not used by all derivations)
     /// @return returns a PoolPtr to the new Pool object.
-    virtual PoolPtr poolMaker(isc::asiolink::IOAddress &min, 
+    virtual PoolPtr poolMaker(isc::asiolink::IOAddress &min,
                            isc::asiolink::IOAddress &max, int32_t ptype=0) = 0;
 
     /// @brief pointer to the actual Pools storage
@@ -654,7 +668,7 @@ public:
     /// @param subnet pointer to the content of subnet definition
     ///
     /// @throw isc::DhcpConfigError if subnet configuration parsing failed.
-    virtual void build(isc::data::ConstElementPtr subnet); 
+    virtual void build(isc::data::ConstElementPtr subnet);
 
     /// @brief Adds the created subnet to a server's configuration.
     virtual void commit() = 0;
@@ -671,7 +685,7 @@ protected:
                                             const std::string& config_id) = 0;
 
     /// @brief Determines if the given option space name and code describe
-    /// a standard option for the  server. 
+    /// a standard option for the  server.
     ///
     /// @param option_space is the name of the option space to consider
     /// @param code is the numeric option code to consider
@@ -687,20 +701,20 @@ protected:
                                                              uint32_t code) = 0;
 
     /// @brief Issues a server specific warning regarding duplicate subnet
-    /// options. 
-    /// 
+    /// options.
+    ///
     /// @param code is the numeric option code of the duplicate option
-    /// @param addr is the subnet address 
+    /// @param addr is the subnet address
     /// @todo a means to know the correct logger and perhaps a common
     /// message would allow this method to be emitted by the base class.
-    virtual void duplicate_option_warning(uint32_t code, 
+    virtual void duplicate_option_warning(uint32_t code,
         isc::asiolink::IOAddress& addr) = 0;
 
-    /// @brief Instantiates the subnet based on a given IP prefix and prefix 
-    /// length.  
-    /// 
+    /// @brief Instantiates the subnet based on a given IP prefix and prefix
+    /// length.
+    ///
     /// @param addr is the IP prefix of the subnet.
-    /// @param len is the prefix length 
+    /// @param len is the prefix length
     virtual void initSubnet(isc::asiolink::IOAddress addr, uint8_t len) = 0;
 
     /// @brief Returns value for a given parameter (after using inheritance)
@@ -724,7 +738,7 @@ private:
 
     /// @brief Create a new subnet using a data from child parsers.
     ///
-    /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing 
+    /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing
     /// failed.
     void createSubnet();
 
@@ -748,7 +762,7 @@ protected:
     /// Pointer to the created subnet object.
     isc::dhcp::SubnetPtr subnet_;
 
-    /// Parsing context which contains global values, options and option 
+    /// Parsing context which contains global values, options and option
     /// definitions.
     ParserContextPtr global_context_;
 };
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.h b/src/lib/dhcpsrv/dhcpsrv_log.h
index 9b6350a..fe997ff 100644
--- a/src/lib/dhcpsrv/dhcpsrv_log.h
+++ b/src/lib/dhcpsrv/dhcpsrv_log.h
@@ -50,6 +50,9 @@ const int DHCPSRV_DBG_TRACE_DETAIL = DBGLVL_TRACE_DETAIL;
 /// Record detailed (and verbose) data on the server.
 const int DHCPSRV_DBG_TRACE_DETAIL_DATA = DBGLVL_TRACE_DETAIL_DATA;
 
+// Trace hook related operations
+const int DHCPSRV_DBG_HOOKS = DBGLVL_TRACE_BASIC;
+
 ///@}
 
 
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
index b2a7807..3e47a35 100644
--- a/src/lib/dhcpsrv/dhcpsrv_messages.mes
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -54,6 +54,10 @@ consider reducing the lease lifetime.  In this way, addresses allocated
 to clients that are no longer active on the network will become available
 available sooner.
 
+% DHCPSRV_CFGMGR_ADD_IFACE adding listening interface %1
+A debug message issued when new interface is being added to the collection of
+interfaces on which server listens to DHCP messages.
+
 % DHCPSRV_CFGMGR_ADD_SUBNET4 adding subnet %1
 A debug message reported when the DHCP configuration manager is adding the
 specified IPv4 subnet to its database.
@@ -62,6 +66,16 @@ specified IPv4 subnet to its database.
 A debug message reported when the DHCP configuration manager is adding the
 specified IPv6 subnet to its database.
 
+% DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE enabling listening on all interfaces
+A debug message issued when server is being configured to listen on all
+interfaces.
+
+% DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES stop listening on all interfaces
+A debug message issued when configuration manager clears the internal list
+of active interfaces. This doesn't prevent the server from listening to
+the DHCP traffic through open sockets, but will rather be used by Interface
+Manager to select active interfaces when sockets are re-opened.
+
 % DHCPSRV_CFGMGR_DELETE_SUBNET4 deleting all IPv4 subnets
 A debug message noting that the DHCP configuration manager has deleted all IPv4
 subnets in its database.
@@ -121,6 +135,18 @@ the database access parameters are changed: in the latter case, the
 server closes the currently open database, and opens a database using
 the new parameters.
 
+% DHCPSRV_HOOK_LEASE4_SELECT_SKIP Lease4 creation was skipped, because of callout skip flag.
+This debug message is printed when a callout installed on lease4_select
+hook point sets the skip flag. It means that the server was told that
+no lease4 should be assigned. The server will not put that lease in its
+database and the client will get a NAK packet.
+
+% DHCPSRV_HOOK_LEASE6_SELECT_SKIP Lease6 (non-temporary) creation was skipped, because of callout skip flag.
+This debug message is printed when a callout installed on lease6_select
+hook point sets the skip flag. It means that the server was told that
+no lease6 should be assigned. The server will not put that lease in its
+database and the client will get a NoAddrsAvail for that IA_NA option.
+
 % DHCPSRV_INVALID_ACCESS invalid database access string: %1
 This is logged when an attempt has been made to parse a database access string
 and the attempt ended in error.  The access string in question - which
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index 9a81ef6..238cef6 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -69,6 +69,7 @@ libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
 libdhcpsrv_unittests_LDADD += $(GTEST_LDADD)
 endif
 
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
index 9462439..65997d2 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
@@ -25,18 +25,24 @@
 
 #include <dhcpsrv/tests/test_utils.h>
 
+#include <hooks/server_hooks.h>
+#include <hooks/callout_manager.h>
+#include <hooks/hooks_manager.h>
+
 #include <boost/shared_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 
 #include <iostream>
 #include <sstream>
+#include <algorithm>
 #include <set>
 #include <time.h>
 
 using namespace std;
 using namespace isc;
 using namespace isc::asiolink;
+using namespace isc::hooks;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
 
@@ -105,7 +111,7 @@ public:
         // @todo: check cltt
      }
 
-    ~AllocEngine6Test() {
+    virtual ~AllocEngine6Test() {
         factory_.destroy();
     }
 
@@ -173,7 +179,7 @@ public:
         // @todo: check cltt
      }
 
-    ~AllocEngine4Test() {
+    virtual ~AllocEngine4Test() {
         factory_.destroy();
     }
 
@@ -205,7 +211,7 @@ TEST_F(AllocEngine6Test, simpleAlloc6) {
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
                                                IOAddress("::"), false,
                                                false, "",
-                                               false);
+                                               false, CalloutHandlePtr());
 
     // Check that we got a lease
     ASSERT_TRUE(lease);
@@ -229,7 +235,8 @@ TEST_F(AllocEngine6Test, fakeAlloc6) {
 
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
                                                IOAddress("::"), false,
-                                               false, "", true);
+                                               false, "", true,
+                                               CalloutHandlePtr());
 
     // Check that we got a lease
     ASSERT_TRUE(lease);
@@ -252,7 +259,7 @@ TEST_F(AllocEngine6Test, allocWithValidHint6) {
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
                                                IOAddress("2001:db8:1::15"),
                                                false, false, "",
-                                               false);
+                                               false, CalloutHandlePtr());
 
     // Check that we got a lease
     ASSERT_TRUE(lease);
@@ -291,7 +298,7 @@ TEST_F(AllocEngine6Test, allocWithUsedHint6) {
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
                                                IOAddress("2001:db8:1::1f"),
                                                false, false, "",
-                                               false);
+                                               false, CalloutHandlePtr());
     // Check that we got a lease
     ASSERT_TRUE(lease);
 
@@ -325,7 +332,7 @@ TEST_F(AllocEngine6Test, allocBogusHint6) {
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
                                                IOAddress("3000::abc"),
                                                false, false, "",
-                                               false);
+                                               false, CalloutHandlePtr());
     // Check that we got a lease
     ASSERT_TRUE(lease);
 
@@ -352,13 +359,15 @@ TEST_F(AllocEngine6Test, allocateAddress6Nulls) {
     // Allocations without subnet are not allowed
     Lease6Ptr lease = engine->allocateAddress6(Subnet6Ptr(), duid_, iaid_,
                                                IOAddress("::"),
-                                               false, false, "", false);
+                                               false, false, "", false,
+                                               CalloutHandlePtr());
     ASSERT_FALSE(lease);
 
     // Allocations without DUID are not allowed either
     lease = engine->allocateAddress6(subnet_, DuidPtr(), iaid_,
                                      IOAddress("::"),
-                                     false, false, "", false);
+                                     false, false, "", false,
+                                     CalloutHandlePtr());
     ASSERT_FALSE(lease);
 }
 
@@ -449,7 +458,7 @@ TEST_F(AllocEngine6Test, smallPool6) {
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
                                                IOAddress("::"),
                                                false, false, "",
-                                               false);
+                                               false, CalloutHandlePtr());
 
     // Check that we got that single lease
     ASSERT_TRUE(lease);
@@ -496,7 +505,8 @@ TEST_F(AllocEngine6Test, outOfAddresses6) {
     // else, so the allocation should fail
     Lease6Ptr lease2 = engine->allocateAddress6(subnet_, duid_, iaid_,
                                                 IOAddress("::"),
-                                                false, false, "", false);
+                                                false, false, "", false,
+                                                CalloutHandlePtr());
     EXPECT_FALSE(lease2);
 }
 
@@ -531,7 +541,7 @@ TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
     // CASE 1: Asking for any address
     lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
                                      false, false, "",
-                                     true);
+                                     true, CalloutHandlePtr());
     // Check that we got that single lease
     ASSERT_TRUE(lease);
     EXPECT_EQ(addr.toText(), lease->addr_.toText());
@@ -543,7 +553,7 @@ TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
     lease = engine->allocateAddress6(subnet_, duid_, iaid_,
                                      IOAddress(addr.toText()),
                                      false, false, "",
-                                     true);
+                                     true, CalloutHandlePtr());
     // Check that we got that single lease
     ASSERT_TRUE(lease);
     EXPECT_EQ(addr.toText(), lease->addr_.toText());
@@ -578,7 +588,8 @@ TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
     // A client comes along, asking specifically for this address
     lease = engine->allocateAddress6(subnet_, duid_, iaid_,
                                      IOAddress(addr.toText()),
-                                     false, false, "", false);
+                                     false, false, "", false,
+                                     CalloutHandlePtr());
 
     // Check that he got that single lease
     ASSERT_TRUE(lease);
@@ -601,7 +612,8 @@ TEST_F(AllocEngine4Test, simpleAlloc4) {
     ASSERT_TRUE(engine);
 
     Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
-                                               IOAddress("0.0.0.0"), false);
+                                               IOAddress("0.0.0.0"), false,
+                                               CalloutHandlePtr());
 
     // Check that we got a lease
     ASSERT_TRUE(lease);
@@ -624,7 +636,8 @@ TEST_F(AllocEngine4Test, fakeAlloc4) {
     ASSERT_TRUE(engine);
 
     Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
-                                               IOAddress("0.0.0.0"), true);
+                                               IOAddress("0.0.0.0"), true,
+                                               CalloutHandlePtr());
 
     // Check that we got a lease
     ASSERT_TRUE(lease);
@@ -647,7 +660,7 @@ TEST_F(AllocEngine4Test, allocWithValidHint4) {
 
     Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
                                                IOAddress("192.0.2.105"),
-                                               false);
+                                               false, CalloutHandlePtr());
 
     // Check that we got a lease
     ASSERT_TRUE(lease);
@@ -687,7 +700,7 @@ TEST_F(AllocEngine4Test, allocWithUsedHint4) {
     // twice.
     Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
                                                IOAddress("192.0.2.106"),
-                                               false);
+                                               false, CalloutHandlePtr());
     // Check that we got a lease
     ASSERT_TRUE(lease);
 
@@ -721,7 +734,7 @@ TEST_F(AllocEngine4Test, allocBogusHint4) {
     // with the normal allocation
     Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
                                                IOAddress("10.1.1.1"),
-                                               false);
+                                               false, CalloutHandlePtr());
     // Check that we got a lease
     ASSERT_TRUE(lease);
 
@@ -748,18 +761,19 @@ TEST_F(AllocEngine4Test, allocateAddress4Nulls) {
 
     // Allocations without subnet are not allowed
     Lease4Ptr lease = engine->allocateAddress4(SubnetPtr(), clientid_, hwaddr_,
-                                               IOAddress("0.0.0.0"), false);
+                                               IOAddress("0.0.0.0"), false,
+                                               CalloutHandlePtr());
     EXPECT_FALSE(lease);
 
     // Allocations without HW address are not allowed
     lease = engine->allocateAddress4(subnet_, clientid_, HWAddrPtr(),
-                                     IOAddress("0.0.0.0"), false);
+                                     IOAddress("0.0.0.0"), false, CalloutHandlePtr());
     EXPECT_FALSE(lease);
 
     // Allocations without client-id are allowed
     clientid_ = ClientIdPtr();
     lease = engine->allocateAddress4(subnet_, ClientIdPtr(), hwaddr_,
-                                     IOAddress("0.0.0.0"), false);
+                                     IOAddress("0.0.0.0"), false, CalloutHandlePtr());
     // Check that we got a lease
     ASSERT_TRUE(lease);
 
@@ -863,7 +877,7 @@ TEST_F(AllocEngine4Test, smallPool4) {
     cfg_mgr.addSubnet4(subnet_);
 
     Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
-                                               false);
+                                               false, CalloutHandlePtr());
 
     // Check that we got that single lease
     ASSERT_TRUE(lease);
@@ -911,7 +925,8 @@ TEST_F(AllocEngine4Test, outOfAddresses4) {
     // else, so the allocation should fail
 
     Lease4Ptr lease2 = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
-                                                IOAddress("0.0.0.0"), false);
+                                                IOAddress("0.0.0.0"), false,
+                                                CalloutHandlePtr());
     EXPECT_FALSE(lease2);
 }
 
@@ -944,7 +959,7 @@ TEST_F(AllocEngine4Test, discoverReuseExpiredLease4) {
 
     // CASE 1: Asking for any address
     lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
-                                     true);
+                                     true, CalloutHandlePtr());
     // Check that we got that single lease
     ASSERT_TRUE(lease);
     EXPECT_EQ(addr.toText(), lease->addr_.toText());
@@ -954,7 +969,7 @@ TEST_F(AllocEngine4Test, discoverReuseExpiredLease4) {
 
     // CASE 2: Asking specifically for this address
     lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress(addr.toText()),
-                                     true);
+                                     true, CalloutHandlePtr());
     // Check that we got that single lease
     ASSERT_TRUE(lease);
     EXPECT_EQ(addr.toText(), lease->addr_.toText());
@@ -981,7 +996,8 @@ TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
 
     // A client comes along, asking specifically for this address
     lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
-                                     IOAddress(addr.toText()), false);
+                                     IOAddress(addr.toText()), false,
+                                     CalloutHandlePtr());
 
     // Check that he got that single lease
     ASSERT_TRUE(lease);
@@ -995,6 +1011,8 @@ TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
     detailCompareLease(lease, from_mgr);
 }
 
+/// @todo write renewLease6
+
 // This test checks if a lease is really renewed when renewLease4 method is
 // called
 TEST_F(AllocEngine4Test, renewLease4) {
@@ -1035,4 +1053,436 @@ TEST_F(AllocEngine4Test, renewLease4) {
     detailCompareLease(lease, from_mgr);
 }
 
+/// @brief helper class used in Hooks testing in AllocEngine6
+///
+/// It features a couple of callout functions and buffers to store
+/// the data that is accessible via callouts.
+class HookAllocEngine6Test : public AllocEngine6Test {
+public:
+    HookAllocEngine6Test() {
+        resetCalloutBuffers();
+    }
+
+    virtual ~HookAllocEngine6Test() {
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+            "lease6_select");
+    }
+
+    /// @brief clears out buffers, so callouts can store received arguments
+    void resetCalloutBuffers() {
+        callback_name_ = string("");
+        callback_subnet6_.reset();
+        callback_fake_allocation_ = false;
+        callback_lease6_.reset();
+        callback_argument_names_.clear();
+        callback_addr_original_ = IOAddress("::");
+        callback_addr_updated_ = IOAddress("::");
+    }
+
+    /// callback that stores received callout name and received values
+    static int
+    lease6_select_callout(CalloutHandle& callout_handle) {
+
+        callback_name_ = string("lease6_select");
+
+        callout_handle.getArgument("subnet6", callback_subnet6_);
+        callout_handle.getArgument("fake_allocation", callback_fake_allocation_);
+        callout_handle.getArgument("lease6", callback_lease6_);
+
+        callback_addr_original_ = callback_lease6_->addr_;
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    /// callback that overrides the lease with different values
+    static int
+    lease6_select_different_callout(CalloutHandle& callout_handle) {
+
+        // Let's call the basic callout, so it can record all parameters
+        lease6_select_callout(callout_handle);
+
+        // Now we need to tweak the least a bit
+        Lease6Ptr lease;
+        callout_handle.getArgument("lease6", lease);
+        callback_addr_updated_ = addr_override_;
+        lease->addr_ = callback_addr_updated_;
+        lease->t1_ = t1_override_;
+        lease->t2_ = t2_override_;
+        lease->preferred_lft_ = pref_override_;
+        lease->valid_lft_ = valid_override_;
+
+        return (0);
+    }
+
+    // Values to be used in callout to override lease6 content
+    static const IOAddress addr_override_;
+    static const uint32_t t1_override_;
+    static const uint32_t t2_override_;
+    static const uint32_t pref_override_;
+    static const uint32_t valid_override_;
+
+    // Callback will store original and overridden values here
+    static IOAddress callback_addr_original_;
+    static IOAddress callback_addr_updated_;
+
+    // Buffers (callback will store received values here)
+    static string callback_name_;
+    static Subnet6Ptr callback_subnet6_;
+    static Lease6Ptr callback_lease6_;
+    static bool callback_fake_allocation_;
+    static vector<string> callback_argument_names_;
+};
+
+// For some reason intialization within a class makes the linker confused.
+// linker complains about undefined references if they are defined within
+// the class declaration.
+const IOAddress HookAllocEngine6Test::addr_override_("2001:db8::abcd");
+const uint32_t HookAllocEngine6Test::t1_override_ = 6000;
+const uint32_t HookAllocEngine6Test::t2_override_ = 7000;
+const uint32_t HookAllocEngine6Test::pref_override_ = 8000;
+const uint32_t HookAllocEngine6Test::valid_override_ = 9000;
+
+IOAddress HookAllocEngine6Test::callback_addr_original_("::");
+IOAddress HookAllocEngine6Test::callback_addr_updated_("::");
+
+string HookAllocEngine6Test::callback_name_;
+Subnet6Ptr HookAllocEngine6Test::callback_subnet6_;
+Lease6Ptr HookAllocEngine6Test::callback_lease6_;
+bool HookAllocEngine6Test::callback_fake_allocation_;
+vector<string> HookAllocEngine6Test::callback_argument_names_;
+
+// This test checks if the lease6_select callout is executed and expected
+// parameters as passed.
+TEST_F(HookAllocEngine6Test, lease6_select) {
+
+    // Note: The following order is working as expected:
+    // 1. create AllocEngine (that register hook points)
+    // 2. call loadLibraries()
+    //
+    // This order, however, causes segfault in HooksManager
+    // 1. call loadLibraries()
+    // 2. create AllocEngine (that register hook points)
+
+    // Create allocation engine (hook names are registered in its ctor)
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    // Initialize Hooks Manager
+    vector<string> libraries; // no libraries at this time
+    HooksManager::loadLibraries(libraries);
+
+    // Install pkt6_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease6_select", lease6_select_callout));
+
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+    Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
+                                               false, false, "",
+                                               false, callout_handle);
+    // Check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // Do all checks on the lease
+    checkLease6(lease);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Check that callouts were indeed called
+    EXPECT_EQ("lease6_select", callback_name_);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    ASSERT_TRUE(callback_lease6_);
+    detailCompareLease(callback_lease6_, from_mgr);
+
+    ASSERT_TRUE(callback_subnet6_);
+    EXPECT_EQ(subnet_->toText(), callback_subnet6_->toText());
+
+    EXPECT_FALSE(callback_fake_allocation_);
+
+    // Check if all expected parameters are reported. It's a bit tricky, because
+    // order may be different. If the test starts failing, because someone tweaked
+    // hooks engine, we'll have to implement proper vector matching (ignoring order)
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back("fake_allocation");
+    expected_argument_names.push_back("lease6");
+    expected_argument_names.push_back("subnet6");
+
+    sort(callback_argument_names_.begin(), callback_argument_names_.end());
+    sort(expected_argument_names.begin(), expected_argument_names.end());
+
+    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+}
+
+// This test checks if lease6_select callout is able to override the values
+// in a lease6.
+TEST_F(HookAllocEngine6Test, change_lease6_select) {
+
+    // Make sure that the overridden values are different than the ones from
+    // subnet originally used to create the lease
+    ASSERT_NE(t1_override_, subnet_->getT1());
+    ASSERT_NE(t2_override_, subnet_->getT2());
+    ASSERT_NE(pref_override_, subnet_->getPreferred());
+    ASSERT_NE(valid_override_, subnet_->getValid());
+    ASSERT_FALSE(subnet_->inRange(addr_override_));
+
+    // Create allocation engine (hook names are registered in its ctor)
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    // Initialize Hooks Manager
+    vector<string> libraries; // no libraries at this time
+    HooksManager::loadLibraries(libraries);
+
+    // Install a callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease6_select", lease6_select_different_callout));
+
+    // Normally, dhcpv6_srv would passed the handle when calling allocateAddress6,
+    // but in tests we need to create it on our own.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+    // Call allocateAddress6. Callouts should be triggered here.
+    Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
+                                               false, false, "",
+                                               false, callout_handle);
+    // Check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // See if the values overridden by callout are there
+    EXPECT_TRUE(lease->addr_.equals(addr_override_));
+    EXPECT_EQ(t1_override_, lease->t1_);
+    EXPECT_EQ(t2_override_, lease->t2_);
+    EXPECT_EQ(pref_override_, lease->preferred_lft_);
+    EXPECT_EQ(valid_override_, lease->valid_lft_);
+
+    // Now check if the lease is in the database
+    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Check if values in the database are overridden
+    EXPECT_TRUE(from_mgr->addr_.equals(addr_override_));
+    EXPECT_EQ(t1_override_, from_mgr->t1_);
+    EXPECT_EQ(t2_override_, from_mgr->t2_);
+    EXPECT_EQ(pref_override_, from_mgr->preferred_lft_);
+    EXPECT_EQ(valid_override_, from_mgr->valid_lft_);
+}
+
+
+/// @brief helper class used in Hooks testing in AllocEngine4
+///
+/// It features a couple of callout functions and buffers to store
+/// the data that is accessible via callouts.
+class HookAllocEngine4Test : public AllocEngine4Test {
+public:
+    HookAllocEngine4Test() {
+        resetCalloutBuffers();
+    }
+
+    virtual ~HookAllocEngine4Test() {
+        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
+            "lease4_select");
+    }
+
+    /// @brief clears out buffers, so callouts can store received arguments
+    void resetCalloutBuffers() {
+        callback_name_ = string("");
+        callback_subnet4_.reset();
+        callback_fake_allocation_ = false;
+        callback_lease4_.reset();
+        callback_argument_names_.clear();
+        callback_addr_original_ = IOAddress("::");
+        callback_addr_updated_ = IOAddress("::");
+    }
+
+    /// callback that stores received callout name and received values
+    static int
+    lease4_select_callout(CalloutHandle& callout_handle) {
+
+        callback_name_ = string("lease4_select");
+
+        callout_handle.getArgument("subnet4", callback_subnet4_);
+        callout_handle.getArgument("fake_allocation", callback_fake_allocation_);
+        callout_handle.getArgument("lease4", callback_lease4_);
+
+        callback_addr_original_ = callback_lease4_->addr_;
+
+        callback_argument_names_ = callout_handle.getArgumentNames();
+        return (0);
+    }
+
+    /// callback that overrides the lease with different values
+    static int
+    lease4_select_different_callout(CalloutHandle& callout_handle) {
+
+        // Let's call the basic callout, so it can record all parameters
+        lease4_select_callout(callout_handle);
+
+        // Now we need to tweak the least a bit
+        Lease4Ptr lease;
+        callout_handle.getArgument("lease4", lease);
+        callback_addr_updated_ = addr_override_;
+        lease->addr_ = callback_addr_updated_;
+        lease->t1_ = t1_override_;
+        lease->t2_ = t2_override_;
+        lease->valid_lft_ = valid_override_;
+
+        return (0);
+    }
+
+    // Values to be used in callout to override lease4 content
+    static const IOAddress addr_override_;
+    static const uint32_t t1_override_;
+    static const uint32_t t2_override_;
+    static const uint32_t valid_override_;
+
+    // Callback will store original and overridden values here
+    static IOAddress callback_addr_original_;
+    static IOAddress callback_addr_updated_;
+
+    // Buffers (callback will store received values here)
+    static string callback_name_;
+    static Subnet4Ptr callback_subnet4_;
+    static Lease4Ptr callback_lease4_;
+    static bool callback_fake_allocation_;
+    static vector<string> callback_argument_names_;
+};
+
+// For some reason intialization within a class makes the linker confused.
+// linker complains about undefined references if they are defined within
+// the class declaration.
+const IOAddress HookAllocEngine4Test::addr_override_("192.0.3.1");
+const uint32_t HookAllocEngine4Test::t1_override_ = 4000;
+const uint32_t HookAllocEngine4Test::t2_override_ = 7000;
+const uint32_t HookAllocEngine4Test::valid_override_ = 9000;
+
+IOAddress HookAllocEngine4Test::callback_addr_original_("::");
+IOAddress HookAllocEngine4Test::callback_addr_updated_("::");
+
+string HookAllocEngine4Test::callback_name_;
+Subnet4Ptr HookAllocEngine4Test::callback_subnet4_;
+Lease4Ptr HookAllocEngine4Test::callback_lease4_;
+bool HookAllocEngine4Test::callback_fake_allocation_;
+vector<string> HookAllocEngine4Test::callback_argument_names_;
+
+// This test checks if the lease4_select callout is executed and expected
+// parameters as passed.
+TEST_F(HookAllocEngine4Test, lease4_select) {
+
+    // Note: The following order is working as expected:
+    // 1. create AllocEngine (that register hook points)
+    // 2. call loadLibraries()
+    //
+    // This order, however, causes segfault in HooksManager
+    // 1. call loadLibraries()
+    // 2. create AllocEngine (that register hook points)
+
+    // Create allocation engine (hook names are registered in its ctor)
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    // Initialize Hooks Manager
+    vector<string> libraries; // no libraries at this time
+    HooksManager::loadLibraries(libraries);
+
+    // Install pkt4_receive_callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease4_select", lease4_select_callout));
+
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+    Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+                                               IOAddress("0.0.0.0"),
+                                               false, callout_handle);
+    // Check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // Do all checks on the lease
+    checkLease4(lease);
+
+    // Check that the lease is indeed in LeaseMgr
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Check that callouts were indeed called
+    EXPECT_EQ("lease4_select", callback_name_);
+
+    // Now check that the lease in LeaseMgr has the same parameters
+    ASSERT_TRUE(callback_lease4_);
+    detailCompareLease(callback_lease4_, from_mgr);
+
+    ASSERT_TRUE(callback_subnet4_);
+    EXPECT_EQ(subnet_->toText(), callback_subnet4_->toText());
+
+    EXPECT_EQ(callback_fake_allocation_, false);
+
+    // Check if all expected parameters are reported. It's a bit tricky, because
+    // order may be different. If the test starts failing, because someone tweaked
+    // hooks engine, we'll have to implement proper vector matching (ignoring order)
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back("fake_allocation");
+    expected_argument_names.push_back("lease4");
+    expected_argument_names.push_back("subnet4");
+    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+}
+
+// This test checks if lease4_select callout is able to override the values
+// in a lease4.
+TEST_F(HookAllocEngine4Test, change_lease4_select) {
+
+    // Make sure that the overridden values are different than the ones from
+    // subnet originally used to create the lease
+    ASSERT_NE(t1_override_, subnet_->getT1());
+    ASSERT_NE(t2_override_, subnet_->getT2());
+    ASSERT_NE(valid_override_, subnet_->getValid());
+    ASSERT_FALSE(subnet_->inRange(addr_override_));
+
+    // Create allocation engine (hook names are registered in its ctor)
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    // Initialize Hooks Manager
+    vector<string> libraries; // no libraries at this time
+    HooksManager::loadLibraries(libraries);
+
+    // Install a callout
+    EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                        "lease4_select", lease4_select_different_callout));
+
+    // Normally, dhcpv4_srv would passed the handle when calling allocateAddress4,
+    // but in tests we need to create it on our own.
+    CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+    // Call allocateAddress4. Callouts should be triggered here.
+    Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+                                               false, callout_handle);
+    // Check that we got a lease
+    ASSERT_TRUE(lease);
+
+    // See if the values overridden by callout are there
+    EXPECT_TRUE(lease->addr_.equals(addr_override_));
+    EXPECT_EQ(t1_override_, lease->t1_);
+    EXPECT_EQ(t2_override_, lease->t2_);
+    EXPECT_EQ(valid_override_, lease->valid_lft_);
+
+    // Now check if the lease is in the database
+    Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    // Check if values in the database are overridden
+    EXPECT_TRUE(from_mgr->addr_.equals(addr_override_));
+    EXPECT_EQ(t1_override_, from_mgr->t1_);
+    EXPECT_EQ(t2_override_, from_mgr->t2_);
+    EXPECT_EQ(valid_override_, from_mgr->valid_lft_);
+}
+
+
+
 }; // End of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index 77c3e36..38d2f0a 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -165,6 +165,7 @@ public:
         CfgMgr::instance().deleteSubnets4();
         CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().deleteOptionDefs();
+        CfgMgr::instance().deleteActiveIfaces();
     }
 
     /// @brief generates interface-id option based on provided text
@@ -573,6 +574,50 @@ TEST_F(CfgMgrTest, optionSpace6) {
     // @todo decide if a duplicate vendor space is allowed.
 }
 
+// This test verifies that it is possible to specify interfaces that server
+// should listen on.
+TEST_F(CfgMgrTest, addActiveIface) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    cfg_mgr.addActiveIface("eth0");
+    cfg_mgr.addActiveIface("eth1");
+
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
+
+    cfg_mgr.deleteActiveIfaces();
+
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth0"));
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth1"));
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
+}
+
+// This test verifies that it is possible to set the flag which configures the
+// server to listen on all interfaces.
+TEST_F(CfgMgrTest, activateAllIfaces) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    cfg_mgr.addActiveIface("eth0");
+    cfg_mgr.addActiveIface("eth1");
+
+    ASSERT_TRUE(cfg_mgr.isActiveIface("eth0"));
+    ASSERT_TRUE(cfg_mgr.isActiveIface("eth1"));
+    ASSERT_FALSE(cfg_mgr.isActiveIface("eth2"));
+
+    cfg_mgr.activateAllIfaces();
+
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth2"));
+
+    cfg_mgr.deleteActiveIfaces();
+
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth0"));
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth1"));
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
+}
+
 // No specific tests for getSubnet6. That method (2 overloaded versions) is tested
 // in Dhcpv6SrvTest.selectSubnetAddr and Dhcpv6SrvTest.selectSubnetIface
 // (see src/bin/dhcp6/tests/dhcp6_srv_unittest.cc)
diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
index 687ef92..6704c57 100644
--- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
+++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
@@ -42,12 +42,13 @@ public:
     /// @brief Constructor
     ///
     DhcpParserTest() {
+        CfgMgr::instance().deleteActiveIfaces();
     }
 };
 
 
 /// @brief Check BooleanParser basic functionality.
-/// 
+///
 /// Verifies that the parser:
 /// 1. Does not allow empty for storage.
 /// 2. Rejects a non-boolean element.
@@ -94,7 +95,7 @@ TEST_F(DhcpParserTest, booleanParserTest) {
 }
 
 /// @brief Check StringParser basic functionality
-/// 
+///
 /// Verifies that the parser:
 /// 1. Does not allow empty for storage.
 /// 2. Builds with a nont string value.
@@ -134,7 +135,7 @@ TEST_F(DhcpParserTest, stringParserTest) {
 }
 
 /// @brief Check Uint32Parser basic functionality
-/// 
+///
 /// Verifies that the parser:
 /// 1. Does not allow empty for storage.
 /// 2. Rejects a non-integer element.
@@ -163,8 +164,8 @@ TEST_F(DhcpParserTest, uint32ParserTest) {
     ElementPtr int_element = Element::create(-1);
     EXPECT_THROW(parser.build(int_element), isc::BadValue);
 
-    // Verify that parser with rejects too large a value provided we are on 
-    // 64-bit platform. 
+    // Verify that parser with rejects too large a value provided we are on
+    // 64-bit platform.
     if (sizeof(long) > sizeof(uint32_t)) {
         long max = (long)(std::numeric_limits<uint32_t>::max()) + 1;
         int_element->setValue(max);
@@ -197,30 +198,61 @@ TEST_F(DhcpParserTest, uint32ParserTest) {
 ///
 /// Verifies that the parser:
 /// 1. Does not allow empty for storage.
-/// 2. Does not allow name other than "interface"
-///
-/// InterfaceListParser doesn't do very much, this test will need to 
-/// expand once it does.
+/// 2. Does not allow name other than "interfaces"
+/// 3. Parses list of interfaces and adds them to CfgMgr
+/// 4. Parses wildcard interface name and sets a CfgMgr flag which indicates
+/// that server will listen on all interfaces.
 TEST_F(DhcpParserTest, interfaceListParserTest) {
 
-    const std::string name = "interface";
+    const std::string name = "interfaces";
 
     // Verify that parser constructor fails if parameter name isn't "interface"
     EXPECT_THROW(InterfaceListConfigParser("bogus_name"), isc::BadValue);
 
-    InterfaceListConfigParser parser(name);
+    boost::scoped_ptr<InterfaceListConfigParser>
+        parser(new InterfaceListConfigParser(name));
     ElementPtr list_element = Element::createList();
     list_element->add(Element::create("eth0"));
     list_element->add(Element::create("eth1"));
+
+    // Make sure there are no interfaces added yet.
+    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
+    ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
+
+    // This should parse the configuration and add eth0 and eth1 to the list
+    // of interfaces that server should listen on.
+    parser->build(list_element);
+    parser->commit();
+
+    // Use CfgMgr instance to check if eth0 and eth1 was added, and that
+    // eth2 was not added.
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
+
+    // Add keyword all to the configuration. This should activate all
+    // interfaces, including eth2, even though it has not been explicitly
+    // added.
+    list_element->add(Element::create("*"));
+
+    // Reset parser's state.
+    parser.reset(new InterfaceListConfigParser(name));
+    parser->build(list_element);
+    parser->commit();
+
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth2"));
 }
 
-/// @brief Test Implementation of abstract OptionDataParser class. Allows 
-/// testing basic option parsing.   
+/// @brief Test Implementation of abstract OptionDataParser class. Allows
+/// testing basic option parsing.
 class UtestOptionDataParser : public OptionDataParser {
 public:
 
-    UtestOptionDataParser(const std::string&, 
-        OptionStoragePtr options, ParserContextPtr global_context) 
+    UtestOptionDataParser(const std::string&,
+        OptionStoragePtr options, ParserContextPtr global_context)
         :OptionDataParser("", options, global_context) {
     }
 
@@ -234,12 +266,12 @@ protected:
     virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
                 std::string&, uint32_t) {
         OptionDefinitionPtr def;
-        // always return empty 
+        // always return empty
         return (def);
     }
 };
 
-/// @brief Test Fixture class which provides basic structure for testing 
+/// @brief Test Fixture class which provides basic structure for testing
 /// configuration parsing.  This is essentially the same structure provided
 /// by dhcp servers.
 class ParseConfigTest : public ::testing::Test {
@@ -253,15 +285,15 @@ public:
         reset_context();
     }
 
-    /// @brief Parses a configuration.   
+    /// @brief Parses a configuration.
     ///
     /// Parse the given configuration, populating the context storage with
-    /// the parsed elements.  
-    /// 
+    /// the parsed elements.
+    ///
     /// @param config_set is the set of elements to parse.
     /// @return returns an ConstElementPtr containing the numeric result
     /// code and outcome comment.
-    isc::data::ConstElementPtr parseElementSet(isc::data::ConstElementPtr 
+    isc::data::ConstElementPtr parseElementSet(isc::data::ConstElementPtr
                                            config_set) {
         // Answer will hold the result.
         ConstElementPtr answer;
@@ -293,7 +325,7 @@ public:
             }
 
             // The option values parser is the next one to be run.
-            std::map<std::string, ConstElementPtr>::const_iterator 
+            std::map<std::string, ConstElementPtr>::const_iterator
                                 option_config = values_map.find("option-data");
             if (option_config != values_map.end()) {
                 option_parser->build(option_config->second);
@@ -316,21 +348,21 @@ public:
 
     /// @brief Create an element parser based on the element name.
     ///
-    /// Note that currently it only supports option-defs and option-data, 
-    /// 
-    /// @param config_id is the name of the configuration element. 
+    /// Note that currently it only supports option-defs and option-data,
+    ///
+    /// @param config_id is the name of the configuration element.
     /// @return returns a raw pointer to DhcpConfigParser. Note caller is
     /// responsible for deleting it once no longer needed.
     /// @throw throws NotImplemented if element name isn't supported.
     DhcpConfigParser* createConfigParser(const std::string& config_id) {
         DhcpConfigParser* parser = NULL;
         if (config_id.compare("option-data") == 0) {
-            parser = new OptionDataListParser(config_id, 
-                                          parser_context_->options_, 
+            parser = new OptionDataListParser(config_id,
+                                          parser_context_->options_,
                                           parser_context_,
                                           UtestOptionDataParser::factory);
         } else if (config_id.compare("option-def") == 0) {
-            parser  = new OptionDefListParser(config_id, 
+            parser  = new OptionDefListParser(config_id,
                                           parser_context_->option_defs_);
         } else {
             isc_throw(NotImplemented,
@@ -341,15 +373,15 @@ public:
         return (parser);
     }
 
-    /// @brief Convenicee method for parsing a configuration 
-    /// 
+    /// @brief Convenicee method for parsing a configuration
+    ///
     /// Given a configuration string, convert it into Elements
-    /// and parse them. 
+    /// and parse them.
     /// @param config is the configuration string to parse
     ///
-    /// @return retuns 0 if the configuration parsed successfully, 
+    /// @return retuns 0 if the configuration parsed successfully,
     /// non-zero otherwise failure.
-    int parseConfiguration (std::string &config) {    
+    int parseConfiguration (std::string &config) {
         int rcode_ = 1;
         // Turn config into elements.
         // Test json just to make sure its valid.
@@ -363,17 +395,17 @@ public:
         return (rcode_);
     }
 
-    /// @brief Find an option definition for a given space and code within 
+    /// @brief Find an option definition for a given space and code within
     /// the parser context.
     /// @param space is the space name of the desired option.
     /// @param code is the numeric "type" of the desired option.
     /// @return returns an OptionDefinitionPtr which points to the found
     /// definition or is empty.
-    /// ASSERT_ tests don't work inside functions that return values 
+    /// ASSERT_ tests don't work inside functions that return values
     OptionDefinitionPtr getOptionDef(std::string space, uint32_t code)
     {
         OptionDefinitionPtr def;
-        OptionDefContainerPtr defs = 
+        OptionDefContainerPtr defs =
                             parser_context_->option_defs_->getItems(space);
         // Should always be able to get definitions list even if it is empty.
         EXPECT_TRUE(defs);
@@ -387,41 +419,41 @@ public:
                 def = *(idx.begin());
             }
         }
-        return (def); 
+        return (def);
     }
 
-    /// @brief Find an option for a given space and code within the parser 
+    /// @brief Find an option for a given space and code within the parser
     /// context.
     /// @param space is the space name of the desired option.
     /// @param code is the numeric "type" of the desired option.
     /// @return returns an OptionPtr which points to the found
     /// option or is empty.
-    /// ASSERT_ tests don't work inside functions that return values 
+    /// ASSERT_ tests don't work inside functions that return values
     OptionPtr getOptionPtr(std::string space, uint32_t code)
     {
         OptionPtr option_ptr;
-        Subnet::OptionContainerPtr options = 
+        Subnet::OptionContainerPtr options =
                             parser_context_->options_->getItems(space);
         // Should always be able to get options list even if it is empty.
         EXPECT_TRUE(options);
         if (options) {
             // Attempt to find desired option.
             const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
-            const Subnet::OptionContainerTypeRange& range = 
+            const Subnet::OptionContainerTypeRange& range =
                                                         idx.equal_range(code);
             int cnt = std::distance(range.first, range.second);
             EXPECT_EQ(1, cnt);
             if (cnt == 1) {
-                Subnet::OptionDescriptor desc = *(idx.begin()); 
-                option_ptr = desc.option; 
+                Subnet::OptionDescriptor desc = *(idx.begin());
+                option_ptr = desc.option;
                 EXPECT_TRUE(option_ptr);
             }
         }
 
-        return (option_ptr); 
+        return (option_ptr);
     }
 
-    /// @brief Wipes the contents of the context to allowing another parsing 
+    /// @brief Wipes the contents of the context to allowing another parsing
     /// during a given test if needed.
     void reset_context(){
         // Note set context universe to V6 as it has to be something.
@@ -436,7 +468,7 @@ public:
 };
 
 /// @brief Check Basic parsing of option definitions.
-/// 
+///
 /// Note that this tests basic operation of the OptionDefinitionListParser and
 /// OptionDefinitionParser.  It uses a simple configuration consisting of one
 /// one definition and verifies that it is parsed and committed to storage
@@ -461,7 +493,7 @@ TEST_F(ParseConfigTest, basicOptionDefTest) {
     ASSERT_TRUE(rcode == 0);
 
     // Verify that the option definition can be retrieved.
-    OptionDefinitionPtr def = getOptionDef("isc", 100); 
+    OptionDefinitionPtr def = getOptionDef("isc", 100);
     ASSERT_TRUE(def);
 
     // Verify that the option definition is correct.
@@ -473,7 +505,7 @@ TEST_F(ParseConfigTest, basicOptionDefTest) {
 }
 
 /// @brief Check Basic parsing of options.
-/// 
+///
 /// Note that this tests basic operation of the OptionDataListParser and
 /// OptionDataParser.  It uses a simple configuration consisting of one
 /// one definition and matching option data.  It verifies that the option
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
index 124301a..d5535ae 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
@@ -487,6 +487,10 @@ TEST(Lease6, OperatorEquals) {
                                subnet_id);
     Lease6 lease2(Lease6::LEASE_IA_NA, addr, duid, iaid, 100, 200, 50, 80,
                                subnet_id);
+
+    // cltt_ constructs with time(NULL), make sure they are always equal
+    lease1.cltt_ = lease2.cltt_;
+
     EXPECT_TRUE(lease1 == lease2);
     EXPECT_FALSE(lease1 != lease2);
 
diff --git a/src/lib/dhcpsrv/tests/test_libraries.h b/src/lib/dhcpsrv/tests/test_libraries.h
new file mode 100644
index 0000000..925215d
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/test_libraries.h
@@ -0,0 +1,51 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef TEST_LIBRARIES_H
+#define TEST_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+
+// Take care of differences in DLL naming between operating systems.
+
+#ifdef OS_OSX
+#define DLL_SUFFIX ".dylib"
+
+#else
+#define DLL_SUFFIX ".so"
+
+#endif
+
+
+// Names of the libraries used in these tests.  These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// shared library.
+
+// Library with load/unload functions creating marker files to check their
+// operation.
+static const char* CALLOUT_LIBRARY_1 = "/home/marcin/devel/bind10/src/lib/dhcpsrv/tests/.libs/libco1"
+                                           DLL_SUFFIX;
+static const char* CALLOUT_LIBRARY_2 = "/home/marcin/devel/bind10/src/lib/dhcpsrv/tests/.libs/libco2"
+                                           DLL_SUFFIX;
+
+// Name of a library which is not present.
+static const char* NOT_PRESENT_LIBRARY = "/home/marcin/devel/bind10/src/lib/dhcpsrv/tests/.libs/libnothere"
+                                         DLL_SUFFIX;
+} // anonymous namespace
+
+
+#endif // TEST_LIBRARIES_H
diff --git a/src/lib/hooks/.gitignore b/src/lib/hooks/.gitignore
new file mode 100644
index 0000000..5a9364c
--- /dev/null
+++ b/src/lib/hooks/.gitignore
@@ -0,0 +1,2 @@
+/hooks_messages.cc
+/hooks_messages.h
diff --git a/src/lib/hooks/callout_manager.cc b/src/lib/hooks/callout_manager.cc
index 4ba3640..df27f45 100644
--- a/src/lib/hooks/callout_manager.cc
+++ b/src/lib/hooks/callout_manager.cc
@@ -29,6 +29,19 @@ using namespace std;
 namespace isc {
 namespace hooks {
 
+// Constructor
+CalloutManager::CalloutManager(int num_libraries)
+    : current_hook_(-1), current_library_(-1),
+      hook_vector_(ServerHooks::getServerHooks().getCount()),
+      library_handle_(this), pre_library_handle_(this, 0),
+      post_library_handle_(this, INT_MAX), num_libraries_(num_libraries)
+{
+    if (num_libraries < 0) {
+        isc_throw(isc::BadValue, "number of libraries passed to the "
+                  "CalloutManager must be >= 0");
+    }
+}
+
 // Check that the index of a library is valid.  It can range from 1 - n
 // (n is the number of libraries), 0 (pre-user library callouts), or INT_MAX
 // (post-user library callouts).  It can also be -1 to indicate an invalid
@@ -46,18 +59,6 @@ CalloutManager::checkLibraryIndex(int library_index) const {
               num_libraries_ << ")");
 }
 
-// Set the number of libraries handled by the CalloutManager.
-
-void
-CalloutManager::setNumLibraries(int num_libraries) {
-    if (num_libraries < 0) {
-        isc_throw(isc::BadValue, "number of libraries passed to the "
-                  "CalloutManager must be >= 0");
-    }
-
-    num_libraries_ = num_libraries;
-}
-
 // Register a callout for the current library.
 
 void
@@ -112,13 +113,15 @@ CalloutManager::calloutsPresent(int hook_index) const {
 void
 CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
 
+    // Clear the "skip" flag so we don't carry state from a previous call.
+    // This is done regardless of whether callouts are present to avoid passing
+    // any state from the previous call of callCallouts().
+    callout_handle.setSkip(false);
+
     // Only initialize and iterate if there are callouts present.  This check
     // also catches the case of an invalid index.
     if (calloutsPresent(hook_index)) {
 
-        // Clear the "skip" flag so we don't carry state from a previous call.
-        callout_handle.setSkip(false);
-
         // Set the current hook index.  This is used should a callout wish to
         // determine to what hook it is attached.
         current_hook_ = hook_index;
diff --git a/src/lib/hooks/callout_manager.h b/src/lib/hooks/callout_manager.h
index 4619006..e1b9e57 100644
--- a/src/lib/hooks/callout_manager.h
+++ b/src/lib/hooks/callout_manager.h
@@ -132,19 +132,8 @@ public:
     ///
     /// @param num_libraries Number of loaded libraries.
     ///
-    /// @throw isc::BadValue if the number of libraries is less than or equal
-    ///        to 0, or if the pointer to the server hooks object is empty.
-    CalloutManager(int num_libraries = 0)
-        : current_hook_(-1), current_library_(-1),
-          hook_vector_(ServerHooks::getServerHooks().getCount()),
-          library_handle_(this), pre_library_handle_(this, 0),
-          post_library_handle_(this, INT_MAX), num_libraries_(num_libraries)
-    {
-        // Check that the number of libraries is OK.  (This does a redundant
-        // set of the number of libraries, but it's only a single assignment
-        // and avoids the need for a separate "check" method.
-        setNumLibraries(num_libraries);
-    }
+    /// @throw isc::BadValue if the number of libraries is less than 0,
+    CalloutManager(int num_libraries = 0);
 
     /// @brief Register a callout on a hook for the current library
     ///
@@ -224,22 +213,6 @@ public:
         return (current_hook_);
     }
 
-    /// @brief Set number of libraries
-    ///
-    /// Sets the number of libraries.  Although the value is passed to the
-    /// constructor, in some cases that is only an estimate and the number
-    /// can only be determined after the CalloutManager is created.
-    ///
-    /// @note If the number if libraries is reset, it must be done *before*
-    ///       any callouts are registered.
-    ///
-    /// @param num_libraries Number of libraries served by this CalloutManager.
-    ///
-    /// @throw BadValue Number of libraries must be >= 0.
-    /// @throw LibraryCountChanged Number of libraries has been changed after
-    ///        callouts have been registered.
-    void setNumLibraries(int num_libraries);
-
     /// @brief Get number of libraries
     ///
     /// Returns the number of libraries that this CalloutManager is expected
diff --git a/src/lib/hooks/hook_user.dox b/src/lib/hooks/hook_user.dox
new file mode 100644
index 0000000..a804720
--- /dev/null
+++ b/src/lib/hooks/hook_user.dox
@@ -0,0 +1,1031 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Note: the prefix "hooksdg" to all labels is an abbreviation for "Hooks
+// Developer's Guide" and is used to prevent a clash with symbols in any
+// other Doxygen file.
+
+/**
+ @page hooksdgDevelopersGuide Hook Developer's Guide
+
+ @section hooksdgIntroduction Introduction
+
+Although the BIND 10 framework and its associated DNS and DHCP programs
+provide comprehensive functionality, there will be times when it does
+not quite do what you require: the processing has to be extended in some
+way to solve your problem.
+
+Since the BIND 10 source code is freely available (BIND 10 being an
+open-source project), one option is to modify it to do what
+you want.  Whilst perfectly feasible, there are drawbacks:
+
+- Although well-documented, BIND 10 is a large program.  Just
+understanding how it works will take a significant amount of time. In
+addition, despite the fact that its object-oriented design keeps the
+coupling between modules to a minimum, an inappropriate change to one
+part of the program during the extension could cause another to
+behave oddly or to stop working altogether.
+
+- The change may need to be re-applied or re-written with every new
+version of BIND 10.  As new functionality is added or bugs are fixed,
+the code or algorithms in the core software may change - and may change
+significantly.
+
+To overcome these problems, BIND 10 provides the "Hooks" interface -
+a defined interface for third-party or user-written code. (For ease of
+reference in the rest of this document, all such code will be referred
+to as "user code".)  At specific points in its processing
+("hook points") BIND 10 will make a call to this code.  The call passes
+data that the user code can examine and, if required, modify.
+BIND 10 uses the modified data in the remainder of its processing.
+
+In order to minimise the interaction between BIND 10 and the user
+code, the latter is built independently of BIND 10 in the form of
+a shared library (or libraries).  These are made known to BIND 10
+through its configuration mechanism, and BIND 10 loads the library at
+run time. Libraries can be unloaded and reloaded as needed while BIND
+10 is running.
+
+Use of a defined API and the BIND 10 configuration mechanism means that
+as new versions of BIND 10 are released, there is no need to modify
+the user code.  Unless there is a major change in an interface
+(which will be clearly documented), all that will be required is a rebuild
+of the libraries.
+
+ at note Although the defined interface should not change, the internals
+of some of the classes and structures referenced by the user code may
+change between versions of BIND 10.  These changes have to be reflected
+in the compiled version of the software, hence the need for a rebuild.
+
+ at subsection hooksdgLanguages Languages
+
+The core of BIND 10 is written in C++.  While it is the intention to
+provide interfaces into user code written in other languages, the initial
+versions of the Hooks system requires that user code be written in C++.
+All examples in this guide are in that language.
+
+ at subsection hooksdgTerminology Terminology
+
+In the remainder of this guide, the following terminology is used:
+
+- Hook/Hook Point - used interchageably, this is a point in the code at
+which a call to user functions is made. Each hook has a name and
+each hook can have any number (including 0) of user functions
+attached to it.
+
+- Callout - a user function called by the server at a hook
+point. This is so-named because the server "calls out" to the library
+to execute a user function.
+
+- Framework function - the functions that a user library needs to
+supply in order for the hooks framework to load and unload the library.
+
+- User code/user library - non-BIND 10 code that is compiled into a
+shared library and loaded by BIND 10 into its address space.
+
+
+ at section hooksdgTutorial Tutorial
+
+To illustrate how to write code that integrates with BIND 10, we will
+use the following (rather contrived) example:
+
+The BIND 10 DHCPv4 server is used to allocate IPv4 addresses to clients
+(as well as to pass them other information such as the address of DNS
+servers).  We will suppose that we need to classify clients requesting
+IPv4 addresses according to their hardware address, and want to log both
+the hardware address and allocated IP address for the clients of interest.
+
+The following sections describe how to implement these requirements.
+The code presented here is not efficient and there are better ways of
+doing the task.  The aim however, is to illustrate the main features of
+user hook code not to provide an optimal solution.
+
+
+ at subsection hooksdgFrameworkFunctions Framework Functions
+
+Loading and initializing a library holding user code makes use
+of three (user-supplied) functions:
+
+- version - defines the version of BIND 10 code with which the user-library
+is built
+- load - called when the library is loaded by the server.
+- unload - called when the library is unloaded by the server.
+
+Of these, only "version" is mandatory, although in our example, all three
+are used.
+
+ at subsubsection hooksdgVersionFunction The "version" Function
+
+"version" is used by the hooks framework to check that the libraries
+it is loading are compatible with the version of BIND 10 being run.
+Although the hooks system allows BIND 10 and user code to interface
+through a defined API, the relationship is somewhat tight in that the
+user code will depend on the internal structures of BIND 10.  If these
+change - as they can between BIND 10 releases - and BIND 10 is run with
+a version of user code built against an earlier version of BIND
+10, a program crash could result.
+
+To guard against this, the "version" function must be provided in every
+library.  It returns a constant defined in header files of the version
+of BIND 10 against which it was built.  The hooks framework checks this
+for compatibility with the running version of BIND 10 before loading
+the library.
+
+In this tutorial, we'll put "version" in its own file, version.cc.  The
+contents are:
+
+ at code
+// version.cc
+
+#include <hooks/hooks.h>
+
+extern "C" {
+
+int version() {
+    return (BIND10_HOOKS_VERSION);
+}
+
+};
+ at endcode
+
+The file "hooks/hooks.h" is specified relative to the BIND 10 libraries
+source directory - this is covered later in the section @ref hooksdgBuild.
+It defines the symbol BIND10_HOOKS_VERSION, which has a value that changes
+on every release of BIND 10: this is the value that needs to be returned
+to the hooks framework.
+
+A final point to note is that the definition of "version" is enclosed
+within 'extern "C"' braces.  All functions accessed by the hooks
+framework use C linkage, mainly to avoid the name mangling that
+accompanies use of the C++ compiler, but also to avoid issues related
+to namespaces.
+
+ at subsubsection hooksdgLoadUnloadFunctions The "load" and "unload" Functions
+
+As the names suggest, "load" is called when a library is loaded and
+"unload" called when it is unloaded.  (It is always guaranteed that
+"load" is called: "unload" may not be called in some circumstances,
+e.g. if the system shuts down abnormally.)  These functions are the
+places where any library-wide resources are allocated and deallocated.
+"load" is also the place where any callouts with non-standard names
+(names that are not hook point names) can be registered:
+this is covered further in the section @ref hooksdgCalloutRegistration.
+
+The example does not make any use callouts with non-standard names.  However,
+as our design requires that the log file be open while BIND 10 is active
+and the library loaded, we'll open the file in the "load" function and close
+it in "unload".
+
+We create two files, one for the file handle declaration:
+
+ at code
+// library_common.h
+
+#ifndef LIBRARY_COMMON_H
+#define LIBRARY_COMMON_H
+
+#include <fstream>
+
+// "Interesting clients" log file handle declaration.
+extern std::fstream interesting;
+
+#endif // LIBRARY_COMMON_H
+ at endcode
+
+... and one to hold the "load" and "unload" functions:
+
+ at code
+// load_unload.cc
+
+#include <hooks/hooks.h>
+#include "library_common.h"
+
+// "Interesting clients" log file handle definition.
+std::fstream interesting;
+
+extern "C" {
+
+int load(LibraryHandle&) {
+    interesting.open("/data/clients/interesting.log",
+                     std::fstream::out | std::fstream::app);
+    return (interesting ? 0 : 1);
+}
+
+int unload() {
+    if (interesting) {
+        interesting.close();
+    }
+    return (0);
+}
+
+};
+ at endcode
+
+Notes:
+- The file handle ("interesting") is declared in a header file and defined
+outside of any function.  This means it can be accessed by any function
+within the user library.  For convenience, the definition is in the
+load_unload.cc file.
+- "load" is called with a LibraryHandle argument, this being used in
+the registration of functions.  As no functions are being registered
+in this example, the argument specification omits the variable name
+(whilst retaining the type) to avoid an "unused variable" compiler
+warning. (The LibraryHandle and its use is discussed in the section
+ at ref hooksdgLibraryHandle.)
+- In the current version of the hooks framework, it is not possible to pass
+any configuration information to the "load" function.  The name of the log
+file must therefore be hard-coded as an absolute path name or communicated
+to the user code by some other means.
+- "load" must 0 on success and non-zero on error.  The hooks framework
+will abandon the loading of the library if "load" returns an error status.
+(In this example, "interesting" can be tested as a boolean value,
+returning "true" if the file opened successfully.)
+- "unload" closes the log file if it is open and is a no-op otherwise. As
+with "load", a zero value must be returned on success and a non-zero value
+on an error.  The hooks framework will record a non-zero status return
+as an error in the current BIND 10 log but otherwise ignore it.
+- As before, the function definitions are enclosed in 'extern "C"' braces.
+
+ at subsection hooksdgCallouts Callouts
+
+Having sorted out the framework, we now come to the functions that
+actually do something.  These functions are known as "callouts" because
+the BIND 10 code "calls out" to them.  Each BIND 10 server has a number of
+hooks to which callouts can be attached: server-specific documentation
+describes in detail the points in the server at which the hooks are
+present together with the data passed to callouts attached to them.
+
+Before we continue with the example, we'll discuss how arguments are
+passed to callouts and information is returned to the server.  We will
+also discuss how information can be moved between callouts.
+
+ at subsubsection hooksdgCalloutSignature The Callout Signature
+
+All callouts are declared with the signature:
+ at code
+extern "C" {
+int callout(CalloutHandle& handle);
+};
+ at endcode
+
+(As before, the callout is declared with "C" linkage.)  Information is passed
+between BIND 10 and the callout through name/value pairs in the CalloutHandle
+object. The object is also used to pass information between callouts on a
+per-request basis. (Both of these concepts are explained below.)
+
+A callout returns an "int" as a status return.  A value of 0 indicates
+success, anything else signifies an error.  The status return has no
+effect on server processing; the only difference between a success
+and error code is that if the latter is returned, the server will
+log an error, specifying both the library and hook that generated it.
+Effectively the return status provides a quick way for a callout to log
+error information to the BIND 10 logging system.
+
+ at subsubsection hooksdgArguments Callout Arguments
+
+The CalloutHandle object provides two methods to get and set the
+arguments passed to the callout.  These methods are called (naturally
+enough) getArgument and SetArgument.  Their usage is illustrated by the
+following code snippets.
+
+ at code
+    // Server-side code snippet to show the setting of arguments
+
+    int count = 10;
+    boost::shared_ptr<Pkt4> pktptr = ... // Set to appropriate value
+
+    // Assume that "handle" has been created
+    handle.setArgument("data_count", count);
+    handle.setArgument("inpacket", pktptr);
+
+    // Call the callouts attached to the hook
+    ...
+
+    // Retrieve the modified values
+    handle.getArgument("data_count", count);
+    handle.getArgument("inpacket", pktptr);
+ at endcode
+
+In the callout
+
+ at code
+    int number;
+    boost::shared_ptr<Pkt4> packet;
+
+    // Retrieve data set by the server.
+    handle.getArgument("data_count", number);
+    handle.getArgument("inpacket", packet);
+
+    // Modify "number"
+    number = ...;
+
+    // Update the arguments to send the value back to the server.
+    handle.setArgument("data_count", number);
+ at endcode
+
+As can be seen "getArgument" is used to retrieve data from the
+CalloutHandle, and setArgument used to put data into it.  If a callout
+wishes to alter data and pass it back to the server, it should retrieve
+the data with getArgument, modify it, and call setArgument to send
+it back.
+
+There are several points to be aware of:
+
+- the data type of the variable in the call to getArgument must match
+the data type of the variable passed to the corresponding setArgument
+<B>exactly</B>: using what would normally be considered to be a
+"compatible" type is not enough.  For example, if the server passed
+an argument as an "int" and the callout attempted to retrieve it as a
+"long", an exception would be thrown even though any value that can
+be stored in an "int" will fit into a "long".  This restriction also
+applies the "const" attribute but only as applied to data pointed to by
+pointers, e.g. if an argument is defined as a "char*", an exception will
+be thrown if an attempt is made to retrieve it into a variable of type
+"const char*".  (However, if an argument is set as a "const int", it can
+be retrieved into an "int".)  The documentation of each hook point will
+detail the data type of each argument.
+- Although all arguments can be modified, some altered values may not
+be read by the server. (These would be ones that the server considers
+"read-only".) Consult the documentation of each hook to see whether an
+argument can be used to transfer data back to the server.
+- If a pointer to an object is passed to a callout (either a "raw"
+pointer, or a boost smart pointer (as in the example above), and the
+underlying object is altered through that pointer, the change will be
+reflected in the server even if no call is made to setArgument.
+
+In all cases, consult the documentation for the particular hook to see whether
+parameters can be modified.  As a general rule:
+
+- Do not alter arguments unless you mean the change to be reflected in
+the server.
+- If you alter an argument, call CalloutHandle::setArgument to update the
+value in the CalloutHandle object.
+
+ at subsubsection hooksdgSkipFlag The "Skip" Flag
+
+When a to callouts attached to a hook returns, the server will usually continue
+its processing.  However, a callout might have done something that means that
+the server should follow another path.  Possible actions a server could take
+include:
+
+- Skip the next stage of processing because the callout has already
+done it.  For example, a hook is located just before the DHCP server
+allocates an address to the client.  A callout may decide to allocate
+special addresses for certain clients, in which case it needs to tell
+the server not to allocate an address in this case.
+- Drop the packet and continue with the next request. A possible scenario
+is a DNS server where a callout inspects the source address of an incoming
+packet and compares it against a black list; if the address is on it,
+the callout notifies the server to drop the packet.
+
+To handle these common cases, the CalloutHandle has a "skip" flag.
+This is set by a callout when it wishes the server to skip normal
+processing.  It is set false by the hooks framework before callouts on a
+hook are called.  If the flag is set on return, the server will take the
+"skip" action relevant for the hook.
+
+The methods to get and set the "skip" flag are getSkip and setSkip. Their
+usage is intuitive:
+
+ at code
+    // Get the current setting of the skip flag.
+    bool skip = handle.getSkip();
+
+    // Do some processing...
+        :
+    if (lease_allocated) {
+        // Flag the server to skip the next step of the processing as we
+        // already have an address.
+        handle.setSkip(true);
+    }
+    return;
+    
+ at endcode
+
+Like arguments, the "skip" flag is passed to all callouts on a hook.  Callouts
+later in the list are able to examine (and modify) the settings of earlier ones.
+
+ at subsubsection hooksdgCalloutContext Per-Request Context
+
+Although many of the BIND 10 modules can be characterised as handling
+singles packet - e.g. the DHCPv4 server receives a DISCOVER packet,
+processes it and responds with an OFFER, this is not true in all cases.
+The principal exception is the recursive DNS resolver: this receives a
+packet from a client but that packet may itself generate multiple packets
+being sent to upstream servers. To avoid possible confusion the rest of
+this section uses the term "request" to indicate a request by a client
+for some information or action.
+
+As well as argument information, the CalloutHandle object can be used by
+callouts to attach information to a request being handled by the server.
+This information (known as "context") is not used by the server: its purpose
+is to allow callouts to pass information between one another on a
+per-request basis.
+
+Context only exists only for the duration of the request: when a request
+is completed, the context is destroyed.  A new request starts with no
+context information.  Context is particularly useful in servers that may
+be processing multiple requests simultaneously: callouts can effectively
+attach data to a request that follows the request around the system.
+
+Context information is held as name/value pairs in the same way
+as arguments, being accessed by the pair of methods setContext and
+getContext.  They have the same restrictions as the setArgument and
+getArgument methods - the type of data retrieved from context must
+<B>exactly</B> match the type of the data set.
+
+The example in the next section illustrates their use.
+
+ at subsection hooksdgExampleCallouts Example Callouts
+
+Continuing with the tutorial, the requirements need us to retrieve the
+hardware address of the incoming packet, classify it, and write it,
+together with the assigned IP address, to a log file.  Although we could
+do this in one callout, for this example we'll use two:
+
+- pkt_rcvd - a callout on this hook is invoked when a packet has been
+received and has been parsed.  It is passed a single argument, "query"
+which is an isc::dhcp::Pkt4 object (representing a DHCP v4 packet).
+We will do the classification here.
+
+- v4_lease_write_post - called when the lease (an assignment of an IPv4
+address to a client for a fixed period of time) has been written to the
+database. It is passed two arguments, the query ("query")
+and the response (called "reply").  This is the point at which the
+example code will write the hardware and IP addresses to the log file.
+
+The standard for naming callouts is to give them the same name as
+the hook.  If this is done, the callouts will be automatically found
+by the Hooks system (this is discussed further in section @ref
+hooksdgCalloutRegistration).  For our example, we will assume this is the
+case, so the code for the first callout (used to classify the client's
+hardware address) is:
+
+ at code
+// pkt_rcvd.cc
+
+#include <hooks/hooks.h>
+#include <dhcp/pkt4.h>
+#include "library_common.h"
+
+#include <string>
+
+using namespace isc::dhcp;
+using namespace std;
+
+extern "C" {
+
+// This callout is called at the "pkt_rcvd" hook.
+int pkt_rcvd(CalloutHandle& handle) {
+
+    // A pointer to the packet is passed to the callout via a "boost" smart
+    // pointer. The include file "pkt4.h" typedefs a pointer to the Pkt4
+    // object as Pkt4Ptr.  Retrieve a pointer to the object.
+    Pkt4Ptr query_ptr;
+    handle.getArgument("query", query_ptr);
+
+    // Point to the hardware address.
+    HwAddrPtr hwaddr_ptr = query_ptr->getHWAddr();
+
+    // The hardware address is held in a public member variable. We'll classify
+    // it as interesting if the sum of all the bytes in it is divisible by 4.
+    //  (This is a contrived example after all!)
+    long sum = 0;
+    for (int i = 0; i < hwaddr_ptr->hwaddr_.size(); ++i) {
+        sum += hwaddr_ptr->hwadr_[i];
+    }
+
+    // Classify it.
+    if (sum % 4 == 0) {
+        // Store the text form of the hardware address in the context to pass
+        // to the next callout.
+        handle.setContext("hwaddr", hwaddr_ptr->hwaddr_.toText());
+    }
+
+    return (0);
+};
+ at endcode
+
+The pct_rcvd callout placed the hardware address of an interesting client in
+the "hwaddr" context for the packet.  Turning now to the callout that will
+write this information to the log file:
+
+ at code
+// v4_lease_write.cc
+
+#include <hooks/hooks.h>
+#include <dhcp/pkt4.h>
+#include "library_common.h"
+
+#include <string>
+
+using namespace isc::dhcp;
+using namespace std;
+
+extern "C" {
+
+// This callout is called at the "v4_lease_write_post" hook.
+int v4_lease_write_post(CalloutHandle& handle) {
+
+    // Obtain the hardware address of the "interesting" client.  We have to
+    // use a try...catch block here because if the client was not interesting,
+    // no information would be set and getArgument would thrown an exception.
+    string hwaddr;
+    try (handle.getArgument("hwaddr", hwaddr) {
+
+        // getArgument didn't throw so the client is interesting.  Get a pointer
+        // to the reply.  Note that the argument list for this hook also
+        // contains a pointer to the query: we don't need to access that in this
+        // example.
+        Pkt4Ptr reply;
+        handle.getArgument("reply", reply);
+
+        // Get the string form of the IP address.
+        string ipaddr = reply->getYiaddr().toText();
+
+        // Write the information to the log file.
+        interesting << hwaddr << " " << ipaddr << "\n";
+
+        // ... and to guard against a crash, we'll flush the output stream.
+        flush(interesting);
+
+    } catch (const NoSuchCalloutContext&) {
+
+        // No such element in the per-request context with the name
+        // "hwaddr".  We will do nothing, so just dismiss the exception.
+
+    }
+
+    return (0);
+}
+
+};
+ at endcode
+
+ at subsection hooksdgBuild Building the Library
+
+Building the code requires building a shareable library.  This requires
+the the code be compiled as positition-independent code (using the
+compiler's "-fpic" switch) and linked as a shared library (with the
+linker's "-shared" switch).  The build command also needs to point to
+the BIND 10 include directory and link in the appropriate libraries.
+
+Assuming that BIND 10 has been installed in the default location, the
+command line needed to create the library using the Gnu C++ compiler on a
+Linux system is:
+
+ at code
+g++ -I /usr/include/bind10 -L /usr/lib/bind10 -fpic -shared -o example.so \
+    load_unload.cc pkt_rcvd.cc v4_lease_write.cc version.cc \
+    -lb10-dhcp++ -lb10-util -lb10-exceptions
+ at endcode
+
+Notes:
+- The compilation command and switches required may vary depending on
+your operating system and compiler - consult the relevant documentation
+for details.
+- The values for the "-I" and "-L" switches depend on where you have
+installed BIND 10.
+- The list of libraries that need to be included in the command line
+depends on the functionality used by the hook code and the module to
+which they are attached (e.g. hook code for DNS will need to link against
+the libb10-dns++ library).  Depending on operating system, you may also need
+to explicitly list libraries on which the BIND 10 libraries depend.
+
+ at subsection hooksdgConfiguration Configuring the Hook Library
+
+The final step is to make the library known to BIND 10.  All BIND 10 modules to
+which hooks can be added contain the "hook_library" element, and user
+libraries are added to this. (The BIND 10 hooks system can handle multiple libraries - this is discussed below.).
+
+To add the example library (assumed to be in /usr/local/lib) to the DHCPv4
+module, the following bindctl commands must be executed:
+
+ at code
+> config add Dhcp4/hook_libraries
+> config set Dhcp4/hook_libraries[0] "/usr/local/lib/example.so"
+> config commit
+ at endcode
+
+The DHCPv4 server will load the library and execute the callouts each time a
+request is received.
+
+ at section hooksdgAdvancedTopics Advanced Topics
+
+ at subsection hooksdgContextCreateDestroy Context Creation and Destruction
+
+As well as the hooks defined by the server, the hooks framework defines
+two hooks of its own, "context_create" and "context_destroy".  The first
+is called when a request is created in the server, before any of the
+server-specific hooks gets called.  It's purpose it to allow a library
+to initialize per-request context. The second is called after all
+server-defined hooks have been processed, and is to allow a library to
+tidy up.
+
+As an example, the v4_lease_write example above required that the code
+check for an exception being thrown when accessing the "hwaddr" context
+item in case it was not set.  An alternative strategy would have been to
+provide a callout for the "context_create" hook and set the context item
+"hwaddr" to an empty string. Instead of needing to handle an exception,
+v4_lease_write would be guaranteed to get something when looking for
+the hwaddr item and so could write or not write the output depending on
+the value.
+
+In most cases, "context_destroy" is not needed as the Hooks system
+automatically deletes context. An example where it could be required
+is where memory has been allocated by a callout during the processing
+of a request and a raw pointer to it stored in the context object. On
+destruction of the context, that memory will not be automatically
+released. Freeing in the memory in the "context_destroy callout will solve
+that problem.
+
+Actually, when the context is destroyed, the destructor
+associated with any objects stored in it are run. Rather than point to
+allocated memory with a raw pointer, a better idea would be to point to
+it with a boost "smart" pointer and store that pointer in the context.
+When the context is destroyed, the smart pointer's destructor is run,
+which will automatically delete the pointed-to object.
+
+These approaches are illustrated in the following examples.
+Here it is assumed that the hooks library is performing some form of
+security checking on the packet and needs to maintain information in
+a user-specified "SecurityInformation" object. (The details of this
+fictitious object are of no concern here.) The object is created in
+the context_create callout and used in both the pkt4_rcvd and the
+v4_lease_write_post callouts.
+
+ at code
+// Storing information in a "raw" pointer.  Assume that the
+
+#include <hooks/hooks.h>
+   :
+
+extern "C" {
+
+// context_create callout - called when the request is created.
+int context_create(CalloutHandle& handle) {
+    // Create the security information and store it in the context
+    // for this packet.
+    SecurityInformation* si = new SecurityInformation();
+    handle.setContext("security_information", si);
+}
+
+// Callouts that use the context
+int pktv_rcvd(CalloutHandle& handle) {
+    // Retrieve the pointer to the SecurityInformation object
+    SecurityInformation si;
+    handle.getContext("security_information", si);
+        :
+        :
+    // Set the security information
+    si->setSomething(...);
+
+    // The pointed-to information has been updated but the pointer has not been
+    // altered, so there is no need to call setContext() again.
+}
+
+int v4_lease_write_post(CalloutHandle& handle) {
+    // Retrieve the pointer to the SecurityInformation object
+    SecurityInformation si;
+    handle.getContext("security_information", si);
+        :
+        :
+    // Retrieve security information
+    bool active = si->getSomething(...);
+        :
+}
+
+// Context destruction.  We need to delete the pointed-to SecurityInformation
+// object because we will lose the pointer to it when the CalloutHandle is
+// destroyed.
+int context_destroy(CalloutHandle& handle) {
+    // Retrieve the pointer to the SecurityInformation object
+    SecurityInformation si;
+    handle.getContext("security_information", si);
+
+    // Delete the pointed-to memory.
+    delete si;
+}
+ at endcode
+
+The requirement for the context_destroy callout can be eliminated if
+a Boost shared ptr is used to point to the allocated memory:
+
+ at code
+// Storing information in a "raw" pointer.  Assume that the
+
+#include <hooks/hooks.h>
+#include <boost/shared_ptr.hpp>
+   :
+
+extern "C" {
+
+// context_create callout - called when the request is created.
+
+int context_create(CalloutHandle& handle) {
+    // Create the security information and store it in the context for this
+    // packet.
+    boost::shared_ptr<SecurityInformation> si(new SecurityInformation());
+    handle.setContext("security_information", si);
+}
+
+// Other than the data type, a shared pointer has similar semantics to a "raw"
+// pointer.  Only the code from pkt_rcvd is shown here.
+
+int pktv_rcvd(CalloutHandle& handle) {
+    // Retrieve the pointer to the SecurityInformation object
+    boost::shared_ptr<SecurityInformation> si;
+    handle.setContext("security_information", si);
+        :
+        :
+    // Modify the security information
+    si->setSomething(...);
+
+    // The pointed-to information has been updated but the pointer has not
+    // altered, so theree is no need to reset the context.
+}
+
+// No context_destroy callout is needed to delete the allocated
+// SecurityInformation object.  When the CalloutHandle is destroyed, the shared
+// pointer object will be destroyed.  If that is the last shared pointer to the
+// allocated memory, then it too will be deleted.
+ at endcode
+
+(Note that a Boost shared pointer - rather than any other Boost smart pointer -
+should be used, as the pointer objects are copied within the hooks framework and
+only shared pointers have the correct behavior for the copy operation.)
+
+
+ at subsection hooksdgCalloutRegistration Registering Callouts
+
+As briefly mentioned in @ref hooksdgExampleCallouts, the standard is for
+callouts in the user library to have the same name as the name of the
+hook to which they are being attached.  This convention was followed
+in the tutorial, e.g.  the callout that needed to be attached to the
+"pkt_rcvd" hook was named pkt_rcvd.
+
+The reason for this convention is that when the library is loaded, the
+hook framework automatically searches the library for functions with
+the same names as the server hooks.  When it finds one, it attaches it
+to the appropriate hook point.  This simplifies the loading process and
+bookkeeping required to create a library of callouts.
+
+However, the hooks system is flexible in this area: callouts can have
+non-standard names, and multiple callouts can be registered on a hook.
+
+ at subsubsection hooksdgLibraryHandle The LibraryHandle Object
+
+The way into the part of the hooks framework that allows callout
+registration is through the LibraryHandle object.  This was briefly
+introduced in the discussion of the framework functions, in that
+an object of this type is pass to the "load" function.  A LibraryHandle
+can also be obtained from within a callout by calling the CalloutHandle's
+getLibraryHandle() method.
+
+The LibraryHandle provides three methods to manipulate callouts:
+
+- registerCallout - register a callout on a hook.
+- deregisterCallout - deregister a callout from a hook.
+- deregisterAllCallouts - deregister all callouts on a hook.
+
+The following sections cover some of the ways in which these can be used.
+
+ at subsubsection hooksdgNonstandardCalloutNames Non-Standard Callout Names
+
+The example in the tutorial used standard names for the callouts.  As noted
+above, it is possible to use non-standard names.  Suppose, instead of the
+callout names "pkt_rcvd" and "v4_lease_write", we had named our callouts
+"classify" and "write_data".  The hooks framework would not have registered
+these callouts, so we would have needed to do it ourself.  The place to
+do this is the "load" framework function, and its code would have had to
+been modified to:
+
+ at code
+int load(LibraryHandle& libhandle) {
+    // Register the callouts on the hooks. We assume that a header file
+    // declares the "classify" and "write_data" functions.
+    libhandle.registerCallout("pkt_rcvd", classify);
+    libhandle.registerCallout("v4_lease_write", write_data);
+
+    // Open the log file
+    interesting.open("/data/clients/interesting.log",
+                     std::fstream::out | std::fstream::app);
+    return (interesting ? 0 : 1);
+}
+ at endcode
+
+It is possible for a library to contain callouts with both standard and
+non-standard names: ones with standard names will be registered automatically,
+ones with non-standard names need to be registered manually.
+
+ at subsubsection hooksdgMultipleCallouts Multiple Callouts on a Hook
+
+The BIND 10 hooks framework allows multiple callouts to be attached to 
+a hook point.  Although it is likely to be rare for user code to need to
+do this, there may be instances where it make sense.
+
+To register multiple callouts on a hook, just call
+LibraryHandle::registerCallout multiple times on the same hook, e.g.
+
+ at code
+    libhandle.registerCallout("pkt_rcvd", classify);
+    libhandle.registerCallout("pkt_rcvd", write_data);
+ at endcode
+
+The hooks framework will call the callouts in the order they are
+registered.  The same CalloutHandle is passed between them, so any
+change made to the CalloutHandle's arguments, "skip" flag, or per-request
+context by the first is visible to the second.
+
+ at subsubsection hooksdgDynamicRegistration Dynamic Registration and Reregistration of Callouts
+
+The previous sections have dealt with callouts being registered during
+the call to "load".  The hooks framework is more flexible than that
+in that callouts can be registered and deregistered within a callout.
+In fact, a callout is able to register or deregister itself, and a callout
+is able to be registered on a hook multiple times.
+
+Using our contrived example again, the DHCPv4 server processes one request
+to completion before it starts processing the next.  With this knowledge,
+we could alter the logic of the code so that the callout attached to the
+"pkt_rcvd" hook registers the callout doing the logging when it detects
+an interesting packet, and the callout doing the logging deregisters
+itself in its execution.  The relevant modifications to the code in
+the tutorial are shown below:
+
+ at code
+// pkt_rcvd.cc
+//      :
+
+int pkt_rcvd(CalloutHandle& handle) {
+
+            :
+            :
+
+    // Classify it.
+    if (sum % 4 == 0) {
+        // Store the text form of the hardware address in the context to pass
+        // to the next callout.
+        handle.setContext("hwaddr", hwaddr_ptr->hwaddr_.toText());
+
+        // Register the callback to log the data.
+        handle.getLibraryHandle().registerCallout("v4_lease_write", write_data);
+    }
+
+    return (0);
+};
+ at endcode
+
+ at code
+// v4_lease_write.cc
+        :
+
+int write_data(CalloutHandle& handle) {
+
+    // Obtain the hardware address of the "interesting" client. As the
+    // callback is only registered when interesting data is present, we
+    // know that the context contains the hardware address so an exception
+    // will not be thrown when we call getArgument().
+    string hwaddr;
+    handle.getArgument("hwaddr", hwaddr);
+
+    // The pointer to the reply.
+    ConstPkt4Ptr reply;
+    handle.getArgument("reply", reply);
+
+    // Get the string form of the IP address.
+    string ipaddr = reply->getYiaddr().toText():
+
+    // Write the information to the log file and flush.
+    interesting << hwaddr << " " << ipaddr << "\n";
+    flush(interesting);
+
+    // We've logged the data, so deregister ourself.  This callout will not
+    // be called again until it is registered by pkt_rcvd.
+
+    handle.getLibraryHandle().deregisterCallout("v4_lease_write", write_data);
+
+    return (0);
+}
+ at endcode
+
+Note that the above example used a non-standard name for the callout
+that wrote the data.  Had the name been a standard one, it would have been
+registered when the library was loaded and called for the first request,
+regardless of whether that was defined as "interesting".  (Although as
+callouts with standard names are always registered before "load" gets called,
+we could have got round that problem by deregistering that particular
+callout in the "load" function.)
+
+
+ at note Deregistration of a callout on the hook that is currently
+being called only takes effect when the server next calls the hook.
+To illustrate this, suppose the callouts attached to a hook are A, B and C
+(in that order), and during execution, A deregisters B and C and adds D.
+When callout A returns, B and C will still run.  The next time the server
+calls the hook's callouts, A and D will run (in that order).
+
+ at subsection hooksdgMultipleLibraries Multiple User Libraries
+
+As alluded to in the section @ref hooksdgConfiguration, BIND 10 can load
+multiple libraries.  The libraries are loaded in the order specified in
+the configuration, and the callouts attached to the hooks in the order
+presented by the libraries.
+
+The following picture illustrates this, and also illustrates the scope of
+data passed around the system.
+
+ at image html DataScopeArgument.png "Scope of Arguments"
+
+In this illustration, a server has three hook points, alpha, beta
+and gamma.  Two libraries are configured, library 1 and library 2.
+Library 1 registers the callout "authorize" for hook alpha, "check" for
+hook beta and "add_option" for hook gamma.  Library 2 registers "logpkt",
+"validate" and "putopt"
+
+The horizontal red lines represent arguments to callouts.  When the server
+calls hook alpha, it creates an argument list and calls the
+first callout for the hook, "authorize".  When that callout returns, the
+same (but possibly modified) argument list is passed to the next callout
+in the chain, "logpkt".  Another, separate argument list is created for
+hook beta and passed to the callouts "check" and "validate" in
+that order.  A similar sequence occurs for hook gamma.
+
+The next picture shows the scope of the context associated with a
+request.
+
+ at image html DataScopeContext.png "Illustration of per-library context"
+
+The vertical blue lines represent callout context. Context is
+per-packet but also per-library.  When the server calls "authorize",
+the CalloutHandle's getContext and setContext methods access a context
+created purely for library 1. The next callout on the hook will access
+context created for library 2. These contexts are passed to the callouts
+associated with the next hook.  So when "check" is called, it gets the
+context data that was set by "authorize", when "validate" is called,
+it gets the context data set by "logpkt".
+
+It is stressed that the context for callouts associated with different
+libraries is entirely separate.  For example, suppose "authorize" sets
+the CalloutHandle's context item "foo" to 2 and "logpkt" sets an item of
+the same name to the string "bar".  When "check" accesses the context
+item "foo", it gets a value of 2; when "validate" accesses an item of
+the same name, it gets the value "bar".
+
+It is also stressed that all this context exists only for the life of the
+request being processed.  When that request is complete, all the
+context associated with that request - for all libraries - is destroyed,
+and new context created for the next request.
+
+This structure means that library authors can use per-request context
+without worrying about the presence of other libraries.  Other libraries
+may be present, but will not affect the context values set by a library's
+callouts.
+
+ at subsection hooksdgInterLibraryData Passing Data Between Libraries
+
+In rare cases, it is possible that one library may want to pass
+data to another.  This can be done in a limited way by means of the
+CalloutHandle's setArgument and getArgument calls.  For example, in the
+above diagram, the callout "add_option" can pass a value to "putopt"
+by setting a name.value pair in the hook's argument list.  "putopt"
+would be able to read this, but would not be able to return information
+back to "add_option".
+
+All argument names used by BIND 10 will be a combination of letters
+(both upper- and lower-case), digits, hyphens and underscores: no
+other characters will be used.  As argument names are simple strings,
+it is suggested that if such a mechanism be used, the names of the data
+values passed between the libraries include a special character such as
+the dollar symbol or percent sign.  In this way there is no danger that
+a name will conflict with any existing or future BIND 10 argument names.
+
+
+ at subsection hooksdgRegisterMultipleLibraries Dynamic Callout Registration and Multiple Libraries
+
+On a particular hook, callouts are called in the order the libraries appear
+in the configuration and, within a library, in the order the callouts
+are registered.
+
+This order applies to dynamically-registered callouts as well.  As an
+example, consider the diagram above where for hook "beta", callout "check"
+is followed by callout "validate".  Suppose that when "authorize" is run,
+it registers a new callout ("double_check") on hook "beta".  That
+callout will be inserted at the end of the callouts registered by
+library 1 and before any registered by library 2.  It would therefore
+appear between "check" and "validate".  On the other hand, if it were
+"logpkt" that registered the new callout, "double_check" would appear
+after "validate".
+
+*/
diff --git a/src/lib/hooks/hooks_manager.cc b/src/lib/hooks/hooks_manager.cc
index 39e1fc5..117db61 100644
--- a/src/lib/hooks/hooks_manager.cc
+++ b/src/lib/hooks/hooks_manager.cc
@@ -81,8 +81,15 @@ HooksManager::loadLibrariesInternal(const std::vector<std::string>& libraries) {
     lm_collection_.reset(new LibraryManagerCollection(libraries));
     bool status = lm_collection_->loadLibraries();
 
-    // ... and obtain the callout manager for them.
-    callout_manager_ = lm_collection_->getCalloutManager();
+    if (status) {
+        // ... and obtain the callout manager for them if successful.
+        callout_manager_ = lm_collection_->getCalloutManager();
+    } else {
+        // Unable to load libraries, reset to state before this function was
+        // called.
+        lm_collection_.reset();
+        callout_manager_.reset();
+    }
 
     return (status);
 }
@@ -124,6 +131,19 @@ HooksManager::createCalloutHandle() {
     return (getHooksManager().createCalloutHandleInternal());
 }
 
+// Get the list of the names of loaded libraries.
+
+std::vector<std::string>
+HooksManager::getLibraryNamesInternal() const {
+    return (lm_collection_ ? lm_collection_->getLibraryNames()
+                           : std::vector<std::string>());
+}
+
+std::vector<std::string>
+HooksManager::getLibraryNames() {
+    return (getHooksManager().getLibraryNamesInternal());
+}
+
 // Perform conditional initialization if nothing is loaded.
 
 void
@@ -169,5 +189,12 @@ HooksManager::postCalloutsLibraryHandle() {
     return (getHooksManager().postCalloutsLibraryHandleInternal());
 }
 
+// Validate libraries
+
+std::vector<std::string>
+HooksManager::validateLibraries(const std::vector<std::string>& libraries) {
+    return (LibraryManagerCollection::validateLibraries(libraries));
+}
+
 } // namespace util
 } // namespace isc
diff --git a/src/lib/hooks/hooks_manager.h b/src/lib/hooks/hooks_manager.h
index 03aa521..53a2525 100644
--- a/src/lib/hooks/hooks_manager.h
+++ b/src/lib/hooks/hooks_manager.h
@@ -171,6 +171,30 @@ public:
     ///         registered.
     static int registerHook(const std::string& name);
 
+    /// @brief Return list of loaded libraries
+    ///
+    /// Returns the names of the loaded libraries.
+    ///
+    /// @return List of loaded library names.
+    static std::vector<std::string> getLibraryNames();
+
+    /// @brief Validate library list
+    ///
+    /// For each library passed to it, checks that the library can be opened
+    /// and that the "version" function is present and gives the right answer.
+    /// Each library is closed afterwards.
+    ///
+    /// This is used during the configuration parsing - when the list of hooks
+    /// libraries is changed, each of the new libraries is checked before the
+    /// change is committed.
+    ///
+    /// @param List of libraries to be validated.
+    ///
+    /// @return An empty vector if all libraries validated.  Otherwise it
+    ///         holds the names of the libraries that failed validation.
+    static std::vector<std::string> validateLibraries(
+                       const std::vector<std::string>& libraries);
+
     /// Index numbers for pre-defined hooks.
     static const int CONTEXT_CREATE = ServerHooks::CONTEXT_CREATE;
     static const int CONTEXT_DESTROY = ServerHooks::CONTEXT_DESTROY;
@@ -188,6 +212,17 @@ private:
     /// but actually do the work on the singleton instance of the HooksManager.
     /// See the descriptions of the static methods for more details.
 
+    /// @brief Validate library list
+    ///
+    /// @param List of libraries to be validated.
+    ///
+    /// @return An empty string if all libraries validated.  Otherwise it is
+    ///         the name of the first library that failed validation.  The
+    ///         configuration code can return this to bindctl as an indication
+    ///         of the problem.
+    std::string validateLibrariesInternal(
+                       const std::vector<std::string>& libraries) const;
+
     /// @brief Load and reload libraries
     ///
     /// @param libraries List of libraries to be loaded.  The order is
@@ -234,6 +269,11 @@ private:
     ///         registration.
     LibraryHandle& postCalloutsLibraryHandleInternal();
 
+    /// @brief Return list of loaded libraries
+    ///
+    /// @return List of loaded library names.
+    std::vector<std::string> getLibraryNamesInternal() const;
+
     //@}
 
     /// @brief Initialization to No Libraries
diff --git a/src/lib/hooks/hooks_messages.mes b/src/lib/hooks/hooks_messages.mes
index 33c1282..68b6e3c 100644
--- a/src/lib/hooks/hooks_messages.mes
+++ b/src/lib/hooks/hooks_messages.mes
@@ -14,47 +14,57 @@
 
 $NAMESPACE isc::hooks
 
+% HOOKS_ALL_CALLOUTS_DEREGISTERED hook library at index %1 removed all callouts on hook %2
+A debug message issued when all callouts on the specified hook registered
+by the library with the given index were removed.  This is similar to
+the HOOKS_CALLOUTS_REMOVED message (and the two are likely to be seen
+together), but is issued at a lower-level in the hook framework.
+
+% HOOKS_CALLOUTS_REMOVED callouts removed from hook %1 for library %2
+This is a debug message issued during library unloading.  It notes that
+one of more callouts registered by that library have been removed from
+the specified hook.  This is similar to the HOOKS_DEREGISTER_ALL_CALLOUTS
+message (and the two are likely to be seen together), but is issued at a
+higher-level in the hook framework.
+
 % HOOKS_CALLOUT_CALLED hooks library with index %1 has called a callout on hook %2 that has address %3
 Only output at a high debugging level, this message indicates that
 a callout on the named hook registered by the library with the given
 index (in the list of loaded libraries) has been called and returned a
 success state.  The address of the callout is given in the message
 
+% HOOKS_CALLOUT_DEREGISTERED hook library at index %1 deregistered a callout on hook %2
+A debug message issued when all instances of a particular callouts on
+the hook identified in the message that were registered by the library
+with the given index have been removed.
+
 % HOOKS_CALLOUT_ERROR error returned by callout on hook %1 registered by library with index %2 (callout address %3)
 If a callout returns an error status when called, this error message
 is issued.  It identifies the hook to which the callout is attached, the
 index of the library (in the list of loaded libraries) that registered
 it and the address of the callout.  The error is otherwise ignored.
 
-% HOOKS_CALLOUTS_REMOVED callouts removed from hook %1 for library %2
-This is a debug message issued during library unloading.  It notes that
-one of more callouts registered by that library have been removed from
-the specified hook.  This is similar to the HOOKS_DEREGISTER_ALL_CALLOUTS
-message (and the two are likely to be seen together), but is issued at a
-higher-level in the hook framework.
-
-% HOOKS_CLOSE_ERROR failed to close hook library %1: %2
-BIND 10 has failed to close the named hook library for the stated reason.
-Although this is an error, this should not affect the running system
-other than as a loss of resources.  If this error persists, you should
-restart BIND 10.
-
 % HOOKS_CALLOUT_EXCEPTION exception thrown by callout on hook %1 registered by library with index %2 (callout address %3)
 If a callout throws an exception when called, this error message is
 issued.  It identifies the hook to which the callout is attached, the
 index of the library (in the list of loaded libraries) that registered
 it and the address of the callout.  The error is otherwise ignored.
 
-% HOOKS_ALL_CALLOUTS_DEREGISTERED hook library at index %1 removed all callouts on hook %2
-A debug message issued when all callouts on the specified hook registered
-by the library with the given index were removed.  This is similar to
-the HOOKS_CALLOUTS_REMOVED message (and the two are likely to be seen
-together), but is issued at a lower-level in the hook framework.
+% HOOKS_CALLOUT_REGISTRATION hooks library with index %1 registering callout for hook '%2'
+This is a debug message, output when a library (whose index in the list
+of libraries (being) loaded is given) registers a callout.
 
-% HOOKS_CALLOUT_DEREGISTERED hook library at index %1 deregistered a callout on hook %2
-A debug message issued when all instances of a particular callouts on
-the hook identified in the message that were registered by the library
-with the given index have been removed.
+% HOOKS_CLOSE_ERROR failed to close hook library %1: %2
+BIND 10 has failed to close the named hook library for the stated reason.
+Although this is an error, this should not affect the running system
+other than as a loss of resources.  If this error persists, you should
+restart BIND 10.
+
+% HOOKS_HOOK_LIST_RESET the list of hooks has been reset
+This is a message indicating that the list of hooks has been reset.
+While this is usual when running the BIND 10 test suite, it should not be
+seen when running BIND 10 in a producion environment.  If this appears,
+please report a bug through the usual channels.
 
 % HOOKS_INCORRECT_VERSION hook library %1 is at version %2, require version %3
 BIND 10 has detected that the named hook library has been built against
@@ -69,18 +79,24 @@ library should fix the problem in most cases.
 This information message is issued when a user-supplied hooks library
 has been successfully loaded.
 
+% HOOKS_LIBRARY_LOADING loading hooks library %1
+This is a debug message output just before the specified library is loaded.
+If the action is successfully, it will be followed by the
+HOOKS_LIBRARY_LOADED informational message.
+
 % HOOKS_LIBRARY_UNLOADED hooks library %1 successfully unloaded
 This information message is issued when a user-supplied hooks library
 has been successfully unloaded.
 
+% HOOKS_LIBRARY_UNLOADING unloading library %1
+This is a debug message called when the specified library is
+being unloaded.  If all is successful, it will be followed by the
+HOOKS_LIBRARY_UNLOADED informational message.
+
 % HOOKS_LIBRARY_VERSION hooks library %1 reports its version as %2
 A debug  message issued when the version check on the hooks library
 has succeeded.
 
-% HOOKS_LOAD_SUCCESS 'load' function in hook library %1 returned success
-This is a debug message issued when the "load" function has been found
-in a hook library and has been successfully called.
-
 % HOOKS_LOAD_ERROR 'load' function in hook library %1 returned error %2
 A "load" function was found in the library named in the message and
 was called.  The function returned a non-zero status (also given in
@@ -93,10 +109,9 @@ was called.  The function threw an exception (an error indication)
 during execution, which is an error condition.  The library has been
 unloaded and no callouts from it will be installed.
 
-% HOOKS_LIBRARY_LOADING loading hooks library %1
-This is a debug message output just before the specified library is loaded.
-If the action is successfully, it will be followed by the
-HOOKS_LIBRARY_LOADED informational message.
+% HOOKS_LOAD_SUCCESS 'load' function in hook library %1 returned success
+This is a debug message issued when the "load" function has been found
+in a hook library and has been successfully called.
 
 % HOOKS_NO_LOAD no 'load' function found in hook library %1
 This is a debug message saying that the specified library was loaded
@@ -120,30 +135,11 @@ BIND 10 failed to open the specified hook library for the stated
 reason. The library has not been loaded.  BIND 10 will continue to
 function, but without the services offered by the library.
 
-% HOOKS_CALLOUT_REGISTRATION hooks library with index %1 registering callout for hook '%2'
-This is a debug message, output when a library (whose index in the list
-of libraries (being) loaded is given) registers a callout.
-
-% HOOKS_HOOK_REGISTERED hook %1 was registered
-This is a debug message indicating that a hook of the specified name
-was registered by a BIND 10 server.  The server doing the logging is
-indicated by the full name of the logger used to log this message.
-
 % HOOKS_STD_CALLOUT_REGISTERED hooks library %1 registered standard callout for hook %2 at address %3
 This is a debug message, output when the library loading function has
 located a standard callout (a callout with the same name as a hook point)
 and registered it.  The address of the callout is indicated.
 
-% HOOKS_HOOK_LIST_RESET the list of hooks has been reset
-This is a message indicating that the list of hooks has been reset.
-While this is usual when running the BIND 10 test suite, it should not be
-seen when running BIND 10 in a producion environment.  If this appears,
-please report a bug through the usual channels.
-
-% HOOKS_UNLOAD_SUCCESS 'unload' function in hook library %1 returned success
-This is a debug message issued when an "unload" function has been found
-in a hook library during the unload process, called, and returned success.
-
 % HOOKS_UNLOAD_ERROR 'unload' function in hook library %1 returned error %2
 During the unloading of a library, an "unload" function was found.
 It was called, but returned an error (non-zero) status, resulting in
@@ -156,10 +152,9 @@ called, but in the process generated an exception (an error indication).
 The unload process continued after this message and the library has
 been unloaded.
 
-% HOOKS_LIBRARY_UNLOADING unloading library %1
-This is a debug message called when the specified library is
-being unloaded.  If all is successful, it will be followed by the
-HOOKS_LIBRARY_UNLOADED informational message.
+% HOOKS_UNLOAD_SUCCESS 'unload' function in hook library %1 returned success
+This is a debug message issued when an "unload" function has been found
+in a hook library during the unload process, called, and returned success.
 
 % HOOKS_VERSION_EXCEPTION 'version' function in hook library %1 threw an exception
 This error message is issued if the version() function in the specified
diff --git a/src/lib/hooks/images/DataScopeArgument.dia b/src/lib/hooks/images/DataScopeArgument.dia
new file mode 100644
index 0000000..02a4f17
Binary files /dev/null and b/src/lib/hooks/images/DataScopeArgument.dia differ
diff --git a/src/lib/hooks/images/DataScopeArgument.png b/src/lib/hooks/images/DataScopeArgument.png
new file mode 100644
index 0000000..34a5bd1
Binary files /dev/null and b/src/lib/hooks/images/DataScopeArgument.png differ
diff --git a/src/lib/hooks/images/DataScopeContext.dia b/src/lib/hooks/images/DataScopeContext.dia
new file mode 100644
index 0000000..1e39f5b
Binary files /dev/null and b/src/lib/hooks/images/DataScopeContext.dia differ
diff --git a/src/lib/hooks/images/DataScopeContext.png b/src/lib/hooks/images/DataScopeContext.png
new file mode 100644
index 0000000..ba18875
Binary files /dev/null and b/src/lib/hooks/images/DataScopeContext.png differ
diff --git a/src/lib/hooks/library_manager.cc b/src/lib/hooks/library_manager.cc
index f425508..70c76ba 100644
--- a/src/lib/hooks/library_manager.cc
+++ b/src/lib/hooks/library_manager.cc
@@ -31,6 +31,42 @@ namespace isc {
 namespace hooks {
 
 
+// Constructor (used by external agency)
+LibraryManager::LibraryManager(const std::string& name, int index,
+                               const boost::shared_ptr<CalloutManager>& manager)
+        : dl_handle_(NULL), index_(index), manager_(manager),
+          library_name_(name)
+{
+    if (!manager) {
+        isc_throw(NoCalloutManager, "must specify a CalloutManager when "
+                  "instantiating a LibraryManager object");
+    }
+}
+
+// Constructor (used by "validate" for library validation).  Note that this
+// sets "manager_" to not point to anything, which means that methods such as
+// registerStandardCallout() will fail, probably with a segmentation fault.
+// There are no checks for this condition in those methods: this constructor
+// is declared "private", so can only be executed by a method in this class.
+// The only method to do so is "validateLibrary", which takes care not to call
+// methods requiring a non-NULL manager.
+LibraryManager::LibraryManager(const std::string& name)
+        : dl_handle_(NULL), index_(-1), manager_(), library_name_(name)
+{}
+
+// Destructor.  
+LibraryManager::~LibraryManager() {
+    if (manager_) {
+        // LibraryManager instantiated to load a library, so ensure that
+        // it is unloaded before exiting.
+        static_cast<void>(unloadLibrary());
+    } else {
+        // LibraryManager instantiated to validate a library, so just ensure
+        // that it is closed before exiting.
+        static_cast<void>(closeLibrary());
+    }
+}
+
 // Open the library
 
 bool
@@ -118,8 +154,9 @@ LibraryManager::registerStandardCallouts() {
             // Found a symbol, so register it.
             manager_->getLibraryHandle().registerCallout(hook_names[i],
                                                          pc.calloutPtr());
-            LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_STD_CALLOUT_REGISTERED)
-                .arg(library_name_).arg(hook_names[i]).arg(dlsym_ptr);
+            LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS,
+                      HOOKS_STD_CALLOUT_REGISTERED).arg(library_name_)
+                      .arg(hook_names[i]).arg(dlsym_ptr);
 
         }
     }
@@ -251,41 +288,65 @@ LibraryManager::loadLibrary() {
 }
 
 // The library unloading function.  Call the unload() function (if present),
-// remove callouts from the callout manager, then close the library.
+// remove callouts from the callout manager, then close the library.  This is
+// only run if the library is still loaded and is a no-op if the library is
+// not open.
 
 bool
 LibraryManager::unloadLibrary() {
-    LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LIBRARY_UNLOADING)
-        .arg(library_name_);
+    bool result = true;
+    if (dl_handle_ != NULL) {
+        LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_LIBRARY_UNLOADING)
+            .arg(library_name_);
 
-    // Call the unload() function if present.  Note that this is done first -
-    // operations take place in the reverse order to which they were done when
-    // the library was loaded.
-    bool result = runUnload();
+        // Call the unload() function if present.  Note that this is done first
+        // - operations take place in the reverse order to which they were done
+        // when the library was loaded.
+        result = runUnload();
+
+        // Regardless of status, remove all callouts associated with this
+        // library on all hooks.
+        vector<string> hooks = ServerHooks::getServerHooks().getHookNames();
+        manager_->setLibraryIndex(index_);
+        for (int i = 0; i < hooks.size(); ++i) {
+            bool removed = manager_->deregisterAllCallouts(hooks[i]);
+            if (removed) {
+                LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_REMOVED)
+                    .arg(hooks[i]).arg(library_name_);
+            }
+        }
 
-    // Regardless of status, remove all callouts associated with this library
-    // on all hooks.
-    vector<string> hooks = ServerHooks::getServerHooks().getHookNames();
-    manager_->setLibraryIndex(index_);
-    for (int i = 0; i < hooks.size(); ++i) {
-        bool removed = manager_->deregisterAllCallouts(hooks[i]);
-        if (removed) {
-            LOG_DEBUG(hooks_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_REMOVED)
-                .arg(hooks[i]).arg(library_name_);
+        // ... and close the library.
+        result = closeLibrary() && result;
+        if (result) {
+
+            // Issue the informational message only if the library was unloaded
+            // with no problems.  If there was an issue, an error message would
+            // have been issued.
+            LOG_INFO(hooks_logger, HOOKS_LIBRARY_UNLOADED).arg(library_name_);
         }
     }
+    return (result);
+}
 
-    // ... and close the library.
-    result = closeLibrary() && result;
-    if (result) {
+// Validate the library.  We must be able to open it, and the version function
+// must both exist and return the right number.  Note that this is a static
+// method.
 
-        // Issue the informational message only if the library was unloaded
-        // with no problems.  If there was an issue, an error message would
-        // have been issued.
-        LOG_INFO(hooks_logger, HOOKS_LIBRARY_UNLOADED).arg(library_name_);
-    }
+bool
+LibraryManager::validateLibrary(const std::string& name) {
+    // Instantiate a library manager for the validation.  We use the private
+    // constructor as we don't supply a CalloutManager.
+    LibraryManager manager(name);
 
-    return (result);
+    // Try to open it and, if we succeed, check the version.
+    bool validated = manager.openLibrary() && manager.checkVersion();
+
+    // Regardless of whether the version checked out, close the library. (This
+    // is a no-op if the library failed to open.)
+    static_cast<void>(manager.closeLibrary());
+
+    return (validated);
 }
 
 } // namespace hooks
diff --git a/src/lib/hooks/library_manager.h b/src/lib/hooks/library_manager.h
index 56c770b..dc7c5eb 100644
--- a/src/lib/hooks/library_manager.h
+++ b/src/lib/hooks/library_manager.h
@@ -15,6 +15,8 @@
 #ifndef LIBRARY_MANAGER_H
 #define LIBRARY_MANAGER_H
 
+#include <exceptions/exceptions.h>
+
 #include <boost/shared_ptr.hpp>
 
 #include <string>
@@ -22,13 +24,25 @@
 namespace isc {
 namespace hooks {
 
+/// @brief No Callout Manager
+///
+/// Thrown if a library manager is instantiated by an external agency without
+/// specifying a CalloutManager object.
+class NoCalloutManager : public Exception {
+public:
+    NoCalloutManager(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
 class CalloutManager;
 class LibraryHandle;
 class LibraryManager;
 
 /// @brief Library manager
 ///
-/// This class handles the loading and unloading of a specific library.
+/// This class handles the loading and unloading of a specific library.  It also
+/// provides a static method for checking that a library is valid (this is used
+/// in configuration parsing).
 ///
 /// On loading, it opens the library using dlopen and checks the version (set
 /// with the "version" method.  If all is OK, it iterates through the list of
@@ -58,22 +72,28 @@ class LibraryManager;
 ///       suspends processing of new requests until all existing ones have
 ///       been serviced and all packet/context structures destroyed before
 ///       reloading the libraries.
+///
+/// When validating a library, only the fact that the library can be opened and
+/// version() exists and returns the correct number is checked.  The library
+/// is closed after the validation.
 
 class LibraryManager {
 public:
     /// @brief Constructor
     ///
-    /// Stores the library name.  The actual loading is done in loadLibrary().
+    /// This constructor is used by external agencies (i.e. the
+    /// LibraryManagerCollection) when instantiating a LibraryManager.  It
+    /// stores the library name - the actual actual loading is done in
+    /// loadLibrary().
     ///
     /// @param name Name of the library to load.  This should be an absolute
     ///        path name.
     /// @param index Index of this library
     /// @param manager CalloutManager object
+    ///
+    /// @throw NoCalloutManager Thrown if the manager argument is NULL.
     LibraryManager(const std::string& name, int index,
-                   const boost::shared_ptr<CalloutManager>& manager)
-        : dl_handle_(NULL), index_(index), manager_(manager),
-          library_name_(name)
-    {}
+                   const boost::shared_ptr<CalloutManager>& manager);
 
     /// @brief Destructor
     ///
@@ -81,9 +101,19 @@ public:
     /// feature to ensure closure in the case of an exception destroying this
     /// object.  However, see the caveat in the class header about when it is
     /// safe to unload libraries.
-    ~LibraryManager() {
-        static_cast<void>(unloadLibrary());
-    }
+    ~LibraryManager();
+
+    /// @brief Validate library
+    ///
+    /// A static method that is used to validate a library.  Validation checks
+    /// that the library can be opened, that "version" exists, and that it
+    /// returns the right number.
+    ///
+    /// @param name Name of the library to validate
+    ///
+    /// @return true if the library validated, false if not.  If the library
+    /// fails to validate, the reason for the failure is logged.
+    static bool validateLibrary(const std::string& name);
 
     /// @brief Loads a library
     ///
@@ -176,6 +206,17 @@ protected:
     bool runUnload();
 
 private:
+    /// @brief Validating constructor
+    ///
+    /// Constructor used when the LibraryManager is instantiated to validate
+    /// a library (i.e. by the "validateLibrary" static method).
+    ///
+    /// @param name Name of the library to load.  This should be an absolute
+    ///        path name.
+    LibraryManager(const std::string& name);
+
+    // Member variables
+
     void*       dl_handle_;     ///< Handle returned by dlopen
     int         index_;         ///< Index associated with this library
     boost::shared_ptr<CalloutManager> manager_;
diff --git a/src/lib/hooks/library_manager_collection.cc b/src/lib/hooks/library_manager_collection.cc
index d3f7f34..b9122e2 100644
--- a/src/lib/hooks/library_manager_collection.cc
+++ b/src/lib/hooks/library_manager_collection.cc
@@ -73,24 +73,18 @@ LibraryManagerCollection::loadLibraries() {
                                    callout_manager_));
 
         // Load the library.  On success, add it to the list of loaded
-        // libraries.  On failure, an error will have been logged and the
-        // library closed.
+        // libraries.  On failure, unload all currently loaded libraries,
+        // leaving the object in the state it was in before loadLibraries was
+        // called.
         if (manager->loadLibrary()) {
             lib_managers_.push_back(manager);
+        } else {
+            static_cast<void>(unloadLibraries());
+            return (false);
         }
     }
 
-    // Update the CalloutManager's idea of the number of libraries it is
-    // handling.
-    callout_manager_->setNumLibraries(lib_managers_.size());
-
-    // Get an indication of whether all libraries loaded successfully.
-    bool status = (library_names_.size() == lib_managers_.size());
-
-    // Don't need the library names any more, so free up the space.
-    library_names_.clear();
-
-    return (status);
+    return (true);
 }
 
 // Unload the libraries.
@@ -110,5 +104,26 @@ LibraryManagerCollection::unloadLibraries() {
     callout_manager_.reset();
 }
 
+// Return number of loaded libraries.
+int
+LibraryManagerCollection::getLoadedLibraryCount() const {
+    return (lib_managers_.size());
+}
+
+// Validate the libraries.
+std::vector<std::string>
+LibraryManagerCollection::validateLibraries(
+                          const std::vector<std::string>& libraries) {
+
+    std::vector<std::string> failures;
+    for (int i = 0; i < libraries.size(); ++i) {
+        if (!LibraryManager::validateLibrary(libraries[i])) {
+            failures.push_back(libraries[i]);
+        }
+    }
+
+    return (failures);
+}
+
 } // namespace hooks
 } // namespace isc
diff --git a/src/lib/hooks/library_manager_collection.h b/src/lib/hooks/library_manager_collection.h
index c0f6def..0a255ba 100644
--- a/src/lib/hooks/library_manager_collection.h
+++ b/src/lib/hooks/library_manager_collection.h
@@ -69,13 +69,17 @@ class LibraryManager;
 /// code.  However, the link with the CalloutHandle does at least mean that
 /// authors of server code do not need to be so careful about when they destroy
 /// CalloutHandles.
+///
+/// The collection object also provides a utility function to validate a set
+/// of libraries.  The function checks that each library exists, can be opened,
+/// that the "version" function exists and return the right number.
 
 class LibraryManagerCollection {
 public:
     /// @brief Constructor
     ///
-    /// @param List of libraries that this collection will manage.  The order
-    ///        of the libraries is important.
+    /// @param libraries List of libraries that this collection will manage.
+    ///        The order of the libraries is important.
     LibraryManagerCollection(const std::vector<std::string>& libraries)
         : library_names_(libraries)
     {}
@@ -91,8 +95,11 @@ public:
     ///
     /// Loads the libraries.  This creates the LibraryManager associated with
     /// each library and calls its loadLibrary() method.  If a library fails
-    /// to load, the fact is noted but attempts are made to load the remaining
-    /// libraries.
+    /// to load, the loading is abandoned and all libraries loaded so far
+    /// are unloaded.
+    ///
+    /// @return true if all libraries loaded, false if one or more failed t
+    ////        load.
     bool loadLibraries();
 
     /// @brief Get callout manager
@@ -107,6 +114,36 @@ public:
     ///        construction and the time loadLibraries() is called.
     boost::shared_ptr<CalloutManager> getCalloutManager() const;
 
+    /// @brief Get library names
+    ///
+    /// Returns the list of library names.  If called before loadLibraries(),
+    /// the list is the list of names to be loaded; if called afterwards, it
+    /// is the list of libraries that have been loaded.
+    std::vector<std::string> getLibraryNames() const {
+        return (library_names_);
+    }
+
+    /// @brief Get number of loaded libraries
+    ///
+    /// Mainly for testing, this returns the number of libraries that are
+    /// loaded.
+    ///
+    /// @return Number of libraries that are loaded.
+    int getLoadedLibraryCount() const;
+
+    /// @brief Validate libraries
+    ///
+    /// Utility function to validate libraries.  It checks that the libraries
+    /// exist, can be opened, that a "version" function is present in them, and
+    /// that it returns the right number.  All errors are logged.
+    ///
+    /// @param libraries List of libraries to validate
+    ///
+    /// @return Vector of libraries that faled to validate, or an empty vector
+    ///         if all validated.
+    static std::vector<std::string>
+    validateLibraries(const std::vector<std::string>& libraries);
+
 protected:
     /// @brief Unload libraries
     ///
diff --git a/src/lib/hooks/server_hooks.cc b/src/lib/hooks/server_hooks.cc
index 1a0b157..3057d25 100644
--- a/src/lib/hooks/server_hooks.cc
+++ b/src/lib/hooks/server_hooks.cc
@@ -27,9 +27,18 @@ namespace hooks {
 
 // Constructor - register the pre-defined hooks and check that the indexes
 // assigned to them are as expected.
+//
+// Note that there are no logging messages here or in registerHooks().  The
+// recommended way to initialize hook names is to use static initialization.
+// Here, a static object is declared in a file outside of any function or
+// method.  As a result, it is instantiated and its constructor run before the
+// program starts.  By putting calls to ServerHooks::registerHook() in there,
+// hooks names are already registered when the program runs.  However, at that
+// point, the logging system is not initialized, so messages are unable to
+// be output.
 
 ServerHooks::ServerHooks() {
-    reset();
+    initialize();
 }
 
 // Register a hook.  The index assigned to the hook is the current number
@@ -54,17 +63,14 @@ ServerHooks::registerHook(const string& name) {
     // Element was inserted, so add to the inverse hooks collection.
     inverse_hooks_[index] = name;
 
-    // Log it if debug is enabled
-    LOG_DEBUG(hooks_logger, HOOKS_DBG_TRACE, HOOKS_HOOK_REGISTERED).arg(name);
-
     // ... and return numeric index.
     return (index);
 }
 
-// Reset ServerHooks object to initial state.
+// Set ServerHooks object to initial state.
 
 void
-ServerHooks::reset() {
+ServerHooks::initialize() {
 
     // Clear out the name->index and index->name maps.
     hooks_.clear();
@@ -82,6 +88,15 @@ ServerHooks::reset() {
                   ". context_destroy: expected = " << CONTEXT_DESTROY <<
                   ", actual = " << destroy);
     }
+}
+
+// Reset ServerHooks object to initial state.
+
+void
+ServerHooks::reset() {
+
+    // Clear all hooks then initialize the pre-defined ones.
+    initialize();
 
     // Log a warning - although this is done during testing, it should never be
     // seen in a production system.
diff --git a/src/lib/hooks/server_hooks.h b/src/lib/hooks/server_hooks.h
index f075cb8..c4a7ae8 100644
--- a/src/lib/hooks/server_hooks.h
+++ b/src/lib/hooks/server_hooks.h
@@ -74,8 +74,8 @@ public:
     ///
     /// Resets the collection of hooks to the initial state, with just the
     /// context_create and context_destroy hooks set.  This used during
-    /// construction. It is also used during testing to reset the global
-    /// ServerHooks object.
+    /// testing to reset the global ServerHooks object; it should never be
+    /// used in production.
     ///
     /// @throws isc::Unexpected if the registration of the pre-defined hooks
     ///         fails in some way.
@@ -149,7 +149,7 @@ private:
     /// for the packet has completed.  They allow the server code to allocate
     /// and destroy per-packet context.
     ///
-    /// Constructor is declared private to enforce the singleton nature of
+    /// The constructor is declared private to enforce the singleton nature of
     /// the object.  A reference to the singleton is obtainable through the
     /// getServerHooks() static method.
     ///
@@ -157,6 +157,16 @@ private:
     ///         fails in some way.
     ServerHooks();
 
+    /// @brief Initialize hooks
+    ///
+    /// Sets the collection of hooks to the initial state, with just the
+    /// context_create and context_destroy hooks set.  This is used during
+    /// construction.
+    ///
+    /// @throws isc::Unexpected if the registration of the pre-defined hooks
+    ///         fails in some way.
+    void initialize();
+
     /// Useful typedefs.
     typedef std::map<std::string, int> HookCollection;
     typedef std::map<int, std::string> InverseHookCollection;
diff --git a/src/lib/hooks/tests/.gitignore b/src/lib/hooks/tests/.gitignore
new file mode 100644
index 0000000..6fa0ec3
--- /dev/null
+++ b/src/lib/hooks/tests/.gitignore
@@ -0,0 +1,4 @@
+/marker_file.h
+/test_libraries.h
+
+/run_unittests
diff --git a/src/lib/hooks/tests/callout_manager_unittest.cc b/src/lib/hooks/tests/callout_manager_unittest.cc
index 935987a..c3f3b1d 100644
--- a/src/lib/hooks/tests/callout_manager_unittest.cc
+++ b/src/lib/hooks/tests/callout_manager_unittest.cc
@@ -199,10 +199,6 @@ TEST_F(CalloutManagerTest, NumberOfLibraries) {
 
     EXPECT_NO_THROW(cm.reset(new CalloutManager(42)));
     EXPECT_EQ(42, cm->getNumLibraries());
-
-    // Check that setting the number of libraries alterns the number reported.
-    EXPECT_NO_THROW(cm->setNumLibraries(27));
-    EXPECT_EQ(27, cm->getNumLibraries());
 }
 
 // Check that we can only set the current library index to the correct values.
diff --git a/src/lib/hooks/tests/handles_unittest.cc b/src/lib/hooks/tests/handles_unittest.cc
index e5364bc..c19dff2 100644
--- a/src/lib/hooks/tests/handles_unittest.cc
+++ b/src/lib/hooks/tests/handles_unittest.cc
@@ -841,6 +841,24 @@ TEST_F(HandlesTest, ReturnSkipClear) {
     EXPECT_FALSE(callout_handle.getSkip());
 }
 
+// Check that the skip flag is cleared when callouts are called - even if
+// there are no callouts.
+
+TEST_F(HandlesTest, NoCalloutsSkipTest) {
+    // Note - no callouts are registered on any hook.
+    CalloutHandle callout_handle(getCalloutManager());
+
+    // Clear the skip flag and call a hook with no callouts.
+    callout_handle.setSkip(false);
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+    EXPECT_FALSE(callout_handle.getSkip());
+
+    // Set the skip flag and call a hook with no callouts.
+    callout_handle.setSkip(true);
+    getCalloutManager()->callCallouts(alpha_index_, callout_handle);
+    EXPECT_FALSE(callout_handle.getSkip());
+}
+
 // The next set of callouts do a similar thing to the above "skip" tests,
 // but alter the value of a string argument.  This is for testing that the
 // a callout is able to change an argument and return it to the caller.
diff --git a/src/lib/hooks/tests/hooks_manager_unittest.cc b/src/lib/hooks/tests/hooks_manager_unittest.cc
index c5dba60..136eeae 100644
--- a/src/lib/hooks/tests/hooks_manager_unittest.cc
+++ b/src/lib/hooks/tests/hooks_manager_unittest.cc
@@ -157,33 +157,6 @@ TEST_F(HooksManagerTest, LoadLibrariesWithError) {
     // Load the libraries.  We expect a failure return because one of the
     // libraries fails to load.
     EXPECT_FALSE(HooksManager::loadLibraries(library_names));
-
-    // Execute the callouts.  The first library implements the calculation.
-    //
-    // r3 = (7 * d1 - d2) * d3
-    //
-    // The last-loaded library implements the calculation
-    //
-    // r3 = (10 + d1) * d2 - d3
-    //
-    // Putting the processing for each library together in the appropriate
-    // order, we get:
-    //
-    // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
-    {
-        SCOPED_TRACE("Calculation with libraries loaded");
-        executeCallCallouts(10, 3, 33, 2, 62, 3, 183);
-    }
-
-    // Try unloading the libraries.
-    EXPECT_NO_THROW(HooksManager::unloadLibraries());
-
-    // Re-execute the calculation - callouts can be called but as nothing
-    // happens, the result should always be -1.
-    {
-        SCOPED_TRACE("Calculation with libraries not loaded");
-        executeCallCallouts(-1, 3, -1, 22, -1, 83, -1);
-    }
 }
 
 // Test that we can unload a set of libraries while we have a CalloutHandle
@@ -450,5 +423,102 @@ TEST_F(HooksManagerTest, RegisterHooks) {
     EXPECT_EQ(string("gamma"), names[4]);
 }
 
+// Check that we can get the names of the libraries.
+
+TEST_F(HooksManagerTest, LibraryNames) {
+
+    // Set up the list of libraries to be loaded.
+    std::vector<std::string> library_names;
+    library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+    library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+    // Check the names before the libraries are loaded.
+    std::vector<std::string> loaded_names = HooksManager::getLibraryNames();
+    EXPECT_TRUE(loaded_names.empty());
+
+    // Load the libraries and check the names again.
+    EXPECT_TRUE(HooksManager::loadLibraries(library_names));
+    loaded_names = HooksManager::getLibraryNames();
+    EXPECT_TRUE(library_names == loaded_names);
+
+    // Unload the libraries and check again.
+    EXPECT_NO_THROW(HooksManager::unloadLibraries());
+    loaded_names = HooksManager::getLibraryNames();
+    EXPECT_TRUE(loaded_names.empty());
+}
+
+// Test the library validation function.
+
+TEST_F(HooksManagerTest, validateLibraries) {
+    // Vector of libraries that failed validation
+    std::vector<std::string> failed;
+
+    // Test different vectors of libraries.
+
+    // No libraries should return a success.
+    std::vector<std::string> libraries;
+
+    failed = HooksManager::validateLibraries(libraries);
+    EXPECT_TRUE(failed.empty());
+
+    // Single valid library should validate.
+    libraries.clear();
+    libraries.push_back(BASIC_CALLOUT_LIBRARY);
+
+    failed = HooksManager::validateLibraries(libraries);
+    EXPECT_TRUE(failed.empty());
+
+    // Multiple valid libraries should succeed.
+    libraries.clear();
+    libraries.push_back(BASIC_CALLOUT_LIBRARY);
+    libraries.push_back(FULL_CALLOUT_LIBRARY);
+    libraries.push_back(UNLOAD_CALLOUT_LIBRARY);
+
+    failed = HooksManager::validateLibraries(libraries);
+    EXPECT_TRUE(failed.empty());
+
+    // Single invalid library should fail.
+    libraries.clear();
+    libraries.push_back(NOT_PRESENT_LIBRARY);
+
+    failed = HooksManager::validateLibraries(libraries);
+    EXPECT_TRUE(failed == libraries);
+
+    // Multiple invalid libraries should fail.
+    libraries.clear();
+    libraries.push_back(INCORRECT_VERSION_LIBRARY);
+    libraries.push_back(NO_VERSION_LIBRARY);
+    libraries.push_back(FRAMEWORK_EXCEPTION_LIBRARY);
+
+    failed = HooksManager::validateLibraries(libraries);
+    EXPECT_TRUE(failed == libraries);
+
+    // Combination of valid and invalid (first one valid) should fail.
+    libraries.clear();
+    libraries.push_back(FULL_CALLOUT_LIBRARY);
+    libraries.push_back(INCORRECT_VERSION_LIBRARY);
+    libraries.push_back(NO_VERSION_LIBRARY);
+
+    std::vector<std::string> expected_failures;
+    expected_failures.push_back(INCORRECT_VERSION_LIBRARY);
+    expected_failures.push_back(NO_VERSION_LIBRARY);
+
+    failed = HooksManager::validateLibraries(libraries);
+    EXPECT_TRUE(failed == expected_failures);
+
+    // Combination of valid and invalid (first one invalid) should fail.
+    libraries.clear();
+    libraries.push_back(NO_VERSION_LIBRARY);
+    libraries.push_back(FULL_CALLOUT_LIBRARY);
+    libraries.push_back(INCORRECT_VERSION_LIBRARY);
+
+    expected_failures.clear();
+    expected_failures.push_back(NO_VERSION_LIBRARY);
+    expected_failures.push_back(INCORRECT_VERSION_LIBRARY);
+
+    failed = HooksManager::validateLibraries(libraries);
+    EXPECT_TRUE(failed == expected_failures);
+}
+
 
 } // Anonymous namespace
diff --git a/src/lib/hooks/tests/library_manager_collection_unittest.cc b/src/lib/hooks/tests/library_manager_collection_unittest.cc
index 549142f..7fdbb7d 100644
--- a/src/lib/hooks/tests/library_manager_collection_unittest.cc
+++ b/src/lib/hooks/tests/library_manager_collection_unittest.cc
@@ -76,8 +76,7 @@ TEST_F(LibraryManagerCollectionTest, LoadLibraries) {
 
     // Load the libraries.
     EXPECT_TRUE(lm_collection.loadLibraries());
-    boost::shared_ptr<CalloutManager> manager =
-                                      lm_collection.getCalloutManager();
+    EXPECT_EQ(2, lm_collection.getLoadedLibraryCount());
 
     // Execute the callouts.  The first library implements the calculation.
     //
@@ -91,6 +90,8 @@ TEST_F(LibraryManagerCollectionTest, LoadLibraries) {
     // order, we get:
     //
     // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
+    boost::shared_ptr<CalloutManager> manager =
+                                      lm_collection.getCalloutManager();
     {
         SCOPED_TRACE("Doing calculation with libraries loaded");
         executeCallCallouts(manager, 10, 3, 33, 2, 62, 3, 183);
@@ -98,6 +99,7 @@ TEST_F(LibraryManagerCollectionTest, LoadLibraries) {
 
     // Try unloading the libraries.
     EXPECT_NO_THROW(lm_collection.unloadLibraries());
+    EXPECT_EQ(0, lm_collection.getLoadedLibraryCount());
 
     // Re-execute the calculation - callouts can be called but as nothing
     // happens, the result should always be -1.
@@ -108,8 +110,7 @@ TEST_F(LibraryManagerCollectionTest, LoadLibraries) {
 }
 
 // This is effectively the same test as above, but with a library generating
-// an error when loaded. It is expected that the failing library will not be
-// loaded, but others will be.
+// an error when loaded. It is expected that no libraries will be loaded.
 
 TEST_F(LibraryManagerCollectionTest, LoadLibrariesWithError) {
 
@@ -126,38 +127,9 @@ TEST_F(LibraryManagerCollectionTest, LoadLibrariesWithError) {
     // Load the libraries.  We expect a failure status to be returned as
     // one of the libraries failed to load.
     EXPECT_FALSE(lm_collection.loadLibraries());
-    boost::shared_ptr<CalloutManager> manager =
-                                      lm_collection.getCalloutManager();
-
-    // Expect only two libraries were loaded.
-    EXPECT_EQ(2, manager->getNumLibraries());
-
-    // Execute the callouts.  The first library implements the calculation.
-    //
-    // r3 = (7 * d1 - d2) * d3
-    //
-    // The last-loaded library implements the calculation
-    //
-    // r3 = (10 + d1) * d2 - d3
-    //
-    // Putting the processing for each library together in the appropriate
-    // order, we get:
-    //
-    // r3 = ((10 * d1 + d1) - d2) * d2 * d3 - d3
-    {
-        SCOPED_TRACE("Doing calculation with libraries loaded");
-        executeCallCallouts(manager, 10, 3, 33, 2, 62, 3, 183);
-    }
-
-    // Try unloading the libraries.
-    EXPECT_NO_THROW(lm_collection.unloadLibraries());
 
-    // Re-execute the calculation - callouts can be called but as nothing
-    // happens, the result should always be -1.
-    {
-        SCOPED_TRACE("Doing calculation with libraries not loaded");
-        executeCallCallouts(manager, -1, 3, -1, 22, -1, 83, -1);
-    }
+    // Expect no libraries were loaded.
+    EXPECT_EQ(0, lm_collection.getLoadedLibraryCount());
 }
 
 // Check that everything works even with no libraries loaded.
@@ -170,15 +142,110 @@ TEST_F(LibraryManagerCollectionTest, NoLibrariesLoaded) {
     // be using.
     LibraryManagerCollection lm_collection(library_names);
     EXPECT_TRUE(lm_collection.loadLibraries());
+    EXPECT_EQ(0, lm_collection.getLoadedLibraryCount());
     boost::shared_ptr<CalloutManager> manager =
                                       lm_collection.getCalloutManager();
 
-    // Load the libraries.
-    EXPECT_TRUE(lm_collection.loadLibraries());
-
     // Eecute the calculation - callouts can be called but as nothing
     // happens, the result should always be -1.
     executeCallCallouts(manager, -1, 3, -1, 22, -1, 83, -1);
 }
 
+// Check that we can get the names of the libraries.
+
+TEST_F(LibraryManagerCollectionTest, LibraryNames) {
+
+    // Set up the list of libraries to be loaded.
+    std::vector<std::string> library_names;
+    library_names.push_back(std::string(FULL_CALLOUT_LIBRARY));
+    library_names.push_back(std::string(BASIC_CALLOUT_LIBRARY));
+
+    // Set up the library manager collection and get the callout manager we'll
+    // be using.
+    PublicLibraryManagerCollection lm_collection(library_names);
+
+    // Check the names before the libraries are loaded.
+    std::vector<std::string> collection_names = lm_collection.getLibraryNames();
+    EXPECT_TRUE(library_names == collection_names);
+
+    // Load the libraries and check the names again.
+    EXPECT_TRUE(lm_collection.loadLibraries());
+    EXPECT_EQ(2, lm_collection.getLoadedLibraryCount());
+    collection_names = lm_collection.getLibraryNames();
+    EXPECT_TRUE(library_names == collection_names);
+}
+
+// Test the library validation function.
+
+TEST_F(LibraryManagerCollectionTest, validateLibraries) {
+    // Vector of libraries that failed validation
+    std::vector<std::string> failed;
+
+    // Test different vectors of libraries.
+
+    // No libraries should return a success.
+    std::vector<std::string> libraries;
+
+    failed = LibraryManagerCollection::validateLibraries(libraries);
+    EXPECT_TRUE(failed.empty());
+
+    // Single valid library should validate.
+    libraries.clear();
+    libraries.push_back(BASIC_CALLOUT_LIBRARY);
+
+    failed = LibraryManagerCollection::validateLibraries(libraries);
+    EXPECT_TRUE(failed.empty());
+
+    // Multiple valid libraries should succeed.
+    libraries.clear();
+    libraries.push_back(BASIC_CALLOUT_LIBRARY);
+    libraries.push_back(FULL_CALLOUT_LIBRARY);
+    libraries.push_back(UNLOAD_CALLOUT_LIBRARY);
+
+    failed = LibraryManagerCollection::validateLibraries(libraries);
+    EXPECT_TRUE(failed.empty());
+
+    // Single invalid library should fail.
+    libraries.clear();
+    libraries.push_back(NOT_PRESENT_LIBRARY);
+
+    failed = LibraryManagerCollection::validateLibraries(libraries);
+    EXPECT_TRUE(failed == libraries);
+
+    // Multiple invalid libraries should fail.
+    libraries.clear();
+    libraries.push_back(INCORRECT_VERSION_LIBRARY);
+    libraries.push_back(NO_VERSION_LIBRARY);
+    libraries.push_back(FRAMEWORK_EXCEPTION_LIBRARY);
+
+    failed = LibraryManagerCollection::validateLibraries(libraries);
+    EXPECT_TRUE(failed == libraries);
+
+    // Combination of valid and invalid (first one valid) should fail.
+    libraries.clear();
+    libraries.push_back(FULL_CALLOUT_LIBRARY);
+    libraries.push_back(INCORRECT_VERSION_LIBRARY);
+    libraries.push_back(NO_VERSION_LIBRARY);
+
+    std::vector<std::string> expected_failures;
+    expected_failures.push_back(INCORRECT_VERSION_LIBRARY);
+    expected_failures.push_back(NO_VERSION_LIBRARY);
+
+    failed = LibraryManagerCollection::validateLibraries(libraries);
+    EXPECT_TRUE(failed == expected_failures);
+
+    // Combination of valid and invalid (first one invalid) should fail.
+    libraries.clear();
+    libraries.push_back(NO_VERSION_LIBRARY);
+    libraries.push_back(FULL_CALLOUT_LIBRARY);
+    libraries.push_back(INCORRECT_VERSION_LIBRARY);
+
+    expected_failures.clear();
+    expected_failures.push_back(NO_VERSION_LIBRARY);
+    expected_failures.push_back(INCORRECT_VERSION_LIBRARY);
+
+    failed = LibraryManagerCollection::validateLibraries(libraries);
+    EXPECT_TRUE(failed == expected_failures);
+}
+
 } // Anonymous namespace
diff --git a/src/lib/hooks/tests/library_manager_unittest.cc b/src/lib/hooks/tests/library_manager_unittest.cc
index c2f8cb7..9336946 100644
--- a/src/lib/hooks/tests/library_manager_unittest.cc
+++ b/src/lib/hooks/tests/library_manager_unittest.cc
@@ -552,4 +552,18 @@ TEST_F(LibraryManagerTest, LoadMultipleLibraries) {
     EXPECT_TRUE(lib_manager_4.unloadLibrary());
 }
 
+// Check that libraries can be validated.
+
+TEST_F(LibraryManagerTest, validateLibraries) {
+    EXPECT_TRUE(LibraryManager::validateLibrary(BASIC_CALLOUT_LIBRARY));
+    EXPECT_TRUE(LibraryManager::validateLibrary(FULL_CALLOUT_LIBRARY));
+    EXPECT_FALSE(LibraryManager::validateLibrary(FRAMEWORK_EXCEPTION_LIBRARY));
+    EXPECT_FALSE(LibraryManager::validateLibrary(INCORRECT_VERSION_LIBRARY));
+    EXPECT_TRUE(LibraryManager::validateLibrary(LOAD_CALLOUT_LIBRARY));
+    EXPECT_TRUE(LibraryManager::validateLibrary(LOAD_ERROR_CALLOUT_LIBRARY));
+    EXPECT_FALSE(LibraryManager::validateLibrary(NOT_PRESENT_LIBRARY));
+    EXPECT_FALSE(LibraryManager::validateLibrary(NO_VERSION_LIBRARY));
+    EXPECT_TRUE(LibraryManager::validateLibrary(UNLOAD_CALLOUT_LIBRARY));
+}
+
 } // Anonymous namespace
diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am
index cc00beb..6056fb5 100644
--- a/src/lib/log/Makefile.am
+++ b/src/lib/log/Makefile.am
@@ -8,7 +8,6 @@ CLEANFILES = *.gcno *.gcda
 
 lib_LTLIBRARIES = libb10-log.la
 libb10_log_la_SOURCES  =
-libb10_log_la_SOURCES += dummylog.h dummylog.cc
 libb10_log_la_SOURCES += logimpl_messages.cc logimpl_messages.h
 libb10_log_la_SOURCES += log_dbglevels.h
 libb10_log_la_SOURCES += log_formatter.h log_formatter.cc
diff --git a/src/lib/log/dummylog.cc b/src/lib/log/dummylog.cc
deleted file mode 100644
index 5f025e1..0000000
--- a/src/lib/log/dummylog.cc
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include "dummylog.h"
-
-#include <iostream>
-
-using namespace std;
-
-namespace isc {
-namespace log {
-
-bool denabled = false;
-string dprefix;
-
-void dlog(const string& message,bool error_flag) {
-    if (denabled || error_flag) {
-        if (!dprefix.empty()) {
-            cerr << "[" << dprefix << "] ";
-        }
-        cerr << message << endl;
-    }
-}
-
-}
-}
diff --git a/src/lib/log/dummylog.h b/src/lib/log/dummylog.h
deleted file mode 100644
index 6f6ae97..0000000
--- a/src/lib/log/dummylog.h
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef ISC_DUMMYLOG_H
-#define ISC_DUMMYLOG_H 1
-
-#include <string>
-
-namespace isc {
-namespace log {
-
-/// Are we doing logging?
-extern bool denabled;
-/**
- * \short Prefix into logs.
- *
- * The prefix is printed in front of every log message in square brackets.
- * The usual convention is to put the name of program here.
- */
-extern std::string dprefix;
-
-/**
- * \short Temporary interface to logging.
- *
- * This is a temporary function to do logging. It has wrong interface currently
- * and should be replaced by something else. Its main purpose now is to mark
- * places where logging should happen. When it is removed, compiler will do
- * our work of finding the places.
- *
- * The only thing it does is printing the program prefix, message and
- * a newline if denabled is true.
- *
- * There are no tests for this function, since it is only temporary and
- * trivial. Tests will be written for the real logging framework when it is
- * created.
- *
- * It has the d in front of the name so it is unlikely anyone will create
- * a real logging function with the same name and the place wouldn't be found
- * as a compilation error.
- *
- * @param message The message to log. The real interface will probably have
- *     more parameters.
- * \param error_flag TODO
- */
-void dlog(const std::string& message, bool error_flag=false);
-
-}
-}
-
-#endif // ISC_DUMMYLOG_H
diff --git a/src/lib/python/isc/log_messages/Makefile.am b/src/lib/python/isc/log_messages/Makefile.am
index caf1e3b..3e265d7 100644
--- a/src/lib/python/isc/log_messages/Makefile.am
+++ b/src/lib/python/isc/log_messages/Makefile.am
@@ -4,6 +4,7 @@ EXTRA_DIST = __init__.py
 EXTRA_DIST += init_messages.py
 EXTRA_DIST += cmdctl_messages.py
 EXTRA_DIST += ddns_messages.py
+EXTRA_DIST += libmemmgr_messages.py
 EXTRA_DIST += memmgr_messages.py
 EXTRA_DIST += stats_messages.py
 EXTRA_DIST += stats_httpd_messages.py
@@ -25,6 +26,7 @@ CLEANFILES = __init__.pyc
 CLEANFILES += init_messages.pyc
 CLEANFILES += cmdctl_messages.pyc
 CLEANFILES += ddns_messages.pyc
+CLEANFILES += libmemmgr_messages.pyc
 CLEANFILES += memmgr_messages.pyc
 CLEANFILES += stats_messages.pyc
 CLEANFILES += stats_httpd_messages.pyc
diff --git a/src/lib/python/isc/log_messages/libmemmgr_messages.py b/src/lib/python/isc/log_messages/libmemmgr_messages.py
new file mode 100644
index 0000000..3aedc3f
--- /dev/null
+++ b/src/lib/python/isc/log_messages/libmemmgr_messages.py
@@ -0,0 +1 @@
+from work.libmemmgr_messages import *
diff --git a/src/lib/python/isc/memmgr/Makefile.am b/src/lib/python/isc/memmgr/Makefile.am
index f00dba6..5529570 100644
--- a/src/lib/python/isc/memmgr/Makefile.am
+++ b/src/lib/python/isc/memmgr/Makefile.am
@@ -1,9 +1,24 @@
 SUBDIRS = . tests
 
-python_PYTHON = __init__.py builder.py datasrc_info.py
+python_PYTHON = __init__.py builder.py datasrc_info.py logger.py
 
 pythondir = $(pyexecdir)/isc/memmgr
 
+BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/libmemmgr_messages.py
+
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/libmemmgr_messages.py
+
+pylogmessagedir = $(pyexecdir)/isc/log_messages/
+
+CLEANFILES = $(PYTHON_LOGMSGPKG_DIR)/work/libmemmgr_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/libmemmgr_messages.pyc
+
+EXTRA_DIST = libmemmgr_messages.mes
+
+$(PYTHON_LOGMSGPKG_DIR)/work/libmemmgr_messages.py : libmemmgr_messages.mes
+	$(top_builddir)/src/lib/log/compiler/message \
+	-d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/libmemmgr_messages.mes
+
 CLEANDIRS = __pycache__
 
 clean-local:
diff --git a/src/lib/python/isc/memmgr/builder.py b/src/lib/python/isc/memmgr/builder.py
index 5b4eed9..9c3738e 100644
--- a/src/lib/python/isc/memmgr/builder.py
+++ b/src/lib/python/isc/memmgr/builder.py
@@ -13,6 +13,13 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
+import json
+from isc.datasrc import ConfigurableClientList
+from isc.memmgr.datasrc_info import SegmentInfo
+
+from isc.log_messages.libmemmgr_messages import *
+from isc.memmgr.logger import logger
+
 class MemorySegmentBuilder:
     """The builder runs in a different thread in the memory manager. It
     waits for commands from the memory manager, and then executes them
@@ -50,6 +57,83 @@ class MemorySegmentBuilder:
         self._response_queue = response_queue
         self._shutdown = False
 
+    def __handle_shutdown(self):
+        # This method is called when handling the 'shutdown' command. The
+        # following tuple is passed:
+        #
+        # ('shutdown',)
+        self._shutdown = True
+
+    def __handle_bad_command(self, bad_command):
+        # A bad command was received. Raising an exception is not useful
+        # in this case as we are likely running in a different thread
+        # from the main thread which would need to be notified. Instead
+        # return this in the response queue.
+        logger.error(LIBMEMMGR_BUILDER_BAD_COMMAND_ERROR, bad_command)
+        self._response_queue.append(('bad_command',))
+        self._shutdown = True
+
+    def __handle_load(self, zone_name, dsrc_info, rrclass, dsrc_name):
+        # This method is called when handling the 'load' command. The
+        # following tuple is passed:
+        #
+        # ('load', zone_name, dsrc_info, rrclass, dsrc_name)
+        #
+        # where:
+        #
+        #  * zone_name is None or isc.dns.Name, specifying the zone name
+        #    to load. If it's None, it means all zones to be cached in
+        #    the specified data source (used for initialization).
+        #
+        #  * dsrc_info is a DataSrcInfo object corresponding to the
+        #    generation ID of the set of data sources for this loading.
+        #
+        #  * rrclass is an isc.dns.RRClass object, the RR class of the
+        #    data source.
+        #
+        #  * dsrc_name is a string, specifying a data source name.
+
+        clist = dsrc_info.clients_map[rrclass]
+        sgmt_info = dsrc_info.segment_info_map[(rrclass, dsrc_name)]
+        params = json.dumps(sgmt_info.get_reset_param(SegmentInfo.WRITER))
+        clist.reset_memory_segment(dsrc_name,
+                                   ConfigurableClientList.READ_WRITE,
+                                   params)
+
+        if zone_name is not None:
+            zones = [(None, zone_name)]
+        else:
+            zones = clist.get_zone_table_accessor(dsrc_name, True)
+
+        for _, zone_name in zones:
+            catch_load_error = (zone_name is None) # install empty zone initially
+            result, writer = clist.get_cached_zone_writer(zone_name, catch_load_error,
+                                                          dsrc_name)
+            if result != ConfigurableClientList.CACHE_STATUS_ZONE_SUCCESS:
+                logger.error(LIBMEMMGR_BUILDER_GET_ZONE_WRITER_ERROR, zone_name, dsrc_name)
+                continue
+
+            try:
+                error = writer.load()
+                if error is not None:
+                    logger.error(LIBMEMMGR_BUILDER_ZONE_WRITER_LOAD_1_ERROR, zone_name, dsrc_name, error)
+                    continue
+            except Exception as e:
+                logger.error(LIBMEMMGR_BUILDER_ZONE_WRITER_LOAD_2_ERROR, zone_name, dsrc_name, str(e))
+                continue
+            writer.install()
+            writer.cleanup()
+
+        # need to reset the segment so readers can read it (note: memmgr
+        # itself doesn't have to keep it open, but there's currently no
+        # public API to just clear the segment)
+        clist.reset_memory_segment(dsrc_name,
+                                   ConfigurableClientList.READ_ONLY,
+                                   params)
+
+        self._response_queue.append(('load-completed', dsrc_info, rrclass,
+                                     dsrc_name))
+
     def run(self):
         """ This is the method invoked when the builder thread is
             started.  In this thread, be careful when modifying
@@ -64,7 +148,7 @@ class MemorySegmentBuilder:
         # Acquire the condition variable while running the loop.
         with self._cv:
             while not self._shutdown:
-                while len(self._command_queue) == 0:
+                while not self._command_queue:
                     self._cv.wait()
                 # Move the queue content to a local queue. Be careful of
                 # not making assignments to reference variables.
@@ -74,26 +158,28 @@ class MemorySegmentBuilder:
                 # Run commands passed in the command queue sequentially
                 # in the given order.  For now, it only supports the
                 # "shutdown" command, which just exits the thread.
-                for command in local_command_queue:
-                    if command == 'shutdown':
-                        self._shutdown = True
+                for command_tuple in local_command_queue:
+                    command = command_tuple[0]
+                    if command == 'load':
+                        # See the comments for __handle_load() for
+                        # details of the tuple passed to the "load"
+                        # command.
+                        _, zone_name, dsrc_info, rrclass, dsrc_name = command_tuple
+                        self.__handle_load(zone_name, dsrc_info, rrclass, dsrc_name)
+                    elif command == 'shutdown':
+                        self.__handle_shutdown()
                         # When the shutdown command is received, we do
                         # not process any further commands.
                         break
                     else:
-                        # A bad command was received. Raising an
-                        # exception is not useful in this case as we are
-                        # likely running in a different thread from the
-                        # main thread which would need to be
-                        # notified. Instead return this in the response
-                        # queue.
-                        self._response_queue.append(('bad_command',))
-                        self._shutdown = True
+                        self.__handle_bad_command(command)
+                        # When a bad command is received, we do not
+                        # process any further commands.
                         break
 
                 # Notify (any main thread) on the socket about a
                 # response. Otherwise, the main thread may wait in its
                 # loop without knowing there was a problem.
-                if len(self._response_queue) > 0:
+                if self._response_queue:
                     while self._sock.send(b'x') != 1:
                         continue
diff --git a/src/lib/python/isc/memmgr/datasrc_info.py b/src/lib/python/isc/memmgr/datasrc_info.py
index 86f2d6a..db4a515 100644
--- a/src/lib/python/isc/memmgr/datasrc_info.py
+++ b/src/lib/python/isc/memmgr/datasrc_info.py
@@ -14,6 +14,7 @@
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 import os
+from collections import deque
 
 class SegmentInfoError(Exception):
     """An exception raised for general errors in the SegmentInfo class."""
@@ -33,16 +34,208 @@ class SegmentInfo:
     segment-type specific details.  Such details are expected to be
     delegated to subclasses corresponding to specific types of segments.
 
-    The implementation is still incomplete.  It will have more attributes
-    such as a set of current readers, methods for adding or deleting
-    the readers.  These will probably be implemented in this base class
-    as they will be independent from segment-type specific details.
+    A summarized (and simplified) state transition diagram (for __state)
+    would be as follows:
+                                                +--sync_reader()/remove_reader()
+                                                |  still have old readers
+                                                |          |
+                UPDATING-----complete_--->SYNCHRONIZING<---+
+                  ^          update()           |
+    start_update()|                             | sync_reader()/remove_reader()
+    events        |                             V no more old reader
+    exist       READY<------complete_----------COPYING
+                            update()
 
     """
     # Common constants of user type: reader or writer
     READER = 0
     WRITER = 1
 
+    # Enumerated values for state:
+    UPDATING = 0 # the segment is being updated (by the builder thread,
+                 # although SegmentInfo won't care about this level of
+                 # details).
+    SYNCHRONIZING = 1 # one pair of underlying segments has been
+                      # updated, and readers are now migrating to the
+                      # updated version of the segment.
+    COPYING = 2 # all readers that used the old version of segment have
+                # been migrated to the updated version, and the old
+                # segment is now being updated.
+    READY = 3 # both segments of the pair have been updated. it can now
+              # handle further updates (e.g., from xfrin).
+
+    def __init__(self):
+        # Holds the state of SegmentInfo. See the class description
+        # above for the state transition diagram.
+        self.__state = self.READY
+        # __readers is a set of 'reader_session_id' private to
+        # SegmentInfo. It consists of the (ID of) reader modules that
+        # are using the "current" reader version of the segment.
+        self.__readers = set()
+        # __old_readers is a set of 'reader_session_id' private to
+        # SegmentInfo for write (update), but publicly readable. It can
+        # be non empty only in the SYNCHRONIZING state, and consists of
+        # (ID of) reader modules that are using the old version of the
+        # segments (and have to migrate to the updated version).
+        self.__old_readers = set()
+        # __events is a FIFO queue of opaque data for pending update
+        # events. Update events can come at any time (e.g., after
+        # xfr-in), but can be only handled if SegmentInfo is in the
+        # READY state. This maintains such pending events in the order
+        # they arrived. SegmentInfo doesn't have to know the details of
+        # the stored data; it only matters for the memmgr.
+        self.__events = deque()
+
+    def get_state(self):
+        """Returns the state of SegmentInfo (UPDATING, SYNCHRONIZING,
+        COPYING or READY)."""
+        return self.__state
+
+    def get_readers(self):
+        """Returns a set of IDs of the reader modules that are using the
+        "current" reader version of the segment. This method is mainly
+        useful for testing purposes."""
+        return self.__readers
+
+    def get_old_readers(self):
+        """Returns a set of IDs of reader modules that are using the old
+        version of the segments and have to be migrated to the updated
+        version."""
+        return self.__old_readers
+
+    def get_events(self):
+        """Returns a list of pending events in the order they arrived."""
+        return list(self.__events)
+
+    # Helper method used in complete_update(), sync_reader() and
+    # remove_reader().
+    def __sync_reader_helper(self, new_state):
+        if not self.__old_readers:
+            self.__state = new_state
+            if self.__events:
+                return self.__events.popleft()
+
+        return None
+
+    def add_event(self, event_data):
+        """Add an event to the end of the pending events queue. The
+        event_data is not used internally by this class, and is returned
+        as-is by other methods. The format of event_data only matters in
+        the memmgr. This method must be called by memmgr when it
+        receives a request for reloading a zone. No state transition
+        happens."""
+        self.__events.append(event_data)
+
+    def add_reader(self, reader_session_id):
+        """Add the reader module ID to an internal set of reader modules
+        that are using the "current" reader version of the segment. It
+        must be called by memmgr when it first gets the pre-existing
+        readers or when it's notified of a new reader. No state
+        transition happens.
+
+        When the SegmentInfo is not in the READY state, if memmgr gets
+        notified of a new reader (such as b10-auth) subscribing to the
+        readers group and calls add_reader(), we assume the new reader
+        is using the new mapped file and not the old one. For making
+        sure there is no race, memmgr should make SegmentInfo updates in
+        the main thread itself (which also handles communications) and
+        only have the builder in a different thread."""
+        if reader_session_id in self.__readers:
+            raise SegmentInfoError('Reader session ID is already in readers set: ' +
+                                   str(reader_session_id))
+
+        self.__readers.add(reader_session_id)
+
+    def start_update(self):
+        """If the current state is READY and there are pending events,
+        it changes the state to UPDATING and returns the head (oldest)
+        event (without removing it from the pending events queue). This
+        tells the caller (memmgr) that it should initiate the update
+        process with the builder. In all other cases it returns None."""
+        if self.__state == self.READY:
+            if self.__events:
+                self.__state = self.UPDATING
+                return self.__events[0]
+            else:
+                return None
+
+        raise SegmentInfoError('start_update() called in ' +
+                               'incorrect state: ' + str(self.__state))
+
+    def complete_update(self):
+        """This method should be called when memmgr is notified by the
+        builder of the completion of segment update. It changes the
+        state from UPDATING to SYNCHRONIZING, and COPYING to READY. In
+        the former case, set of reader modules that are using the
+        "current" reader version of the segment are moved to the set
+        that are using an "old" version of segment. If there are no such
+        readers using the "old" version of segment, it pops the head
+        (oldest) event from the pending events queue and returns it. It
+        is an error if this method is called in other states than
+        UPDATING and COPYING."""
+        if self.__state == self.UPDATING:
+            self.__state = self.SYNCHRONIZING
+            self.__old_readers = self.__readers
+            self.__readers = set()
+            return self.__sync_reader_helper(self.READY)
+        elif self.__state == self.COPYING:
+            self.__state = self.READY
+            return None
+        else:
+            raise SegmentInfoError('complete_update() called in ' +
+                                   'incorrect state: ' + str(self.__state))
+
+    def sync_reader(self, reader_session_id):
+        """This method must only be called in the SYNCHRONIZING
+        state. memmgr should call it when it receives the
+        "segment_update_ack" message from a reader module. It moves the
+        given ID from the set of reader modules that are using the "old"
+        version of the segment to the set of reader modules that are
+        using the "current" version of the segment, and if there are no
+        reader modules using the "old" version of the segment, the state
+        is changed to COPYING. If the state has changed to COPYING, it
+        pops the head (oldest) event from the pending events queue and
+        returns it; otherwise it returns None."""
+        if self.__state != self.SYNCHRONIZING:
+            raise SegmentInfoError('sync_reader() called in ' +
+                                   'incorrect state: ' + str(self.__state))
+        if reader_session_id not in self.__old_readers:
+            raise SegmentInfoError('Reader session ID is not in old readers set: ' +
+                                   str(reader_session_id))
+        if reader_session_id in self.__readers:
+            raise SegmentInfoError('Reader session ID is already in readers set: ' +
+                                   str(reader_session_id))
+
+        self.__old_readers.remove(reader_session_id)
+        self.__readers.add(reader_session_id)
+
+        return self.__sync_reader_helper(self.COPYING)
+
+    def remove_reader(self, reader_session_id):
+        """This method must only be called in the SYNCHRONIZING
+        state. memmgr should call it when it's notified that an existing
+        reader has unsubscribed. It removes the given reader ID from
+        either the set of readers that use the "current" version of the
+        segment or the "old" version of the segment (wherever the reader
+        belonged), and in the latter case, if there are no reader
+        modules using the "old" version of the segment, the state is
+        changed to COPYING. If the state has changed to COPYING, it pops
+        the head (oldest) event from the pending events queue and
+        returns it; otherwise it returns None."""
+        if self.__state != self.SYNCHRONIZING:
+            raise SegmentInfoError('remove_reader() called in ' +
+                                   'incorrect state: ' + str(self.__state))
+        if reader_session_id in self.__old_readers:
+            self.__old_readers.remove(reader_session_id)
+            return self.__sync_reader_helper(self.COPYING)
+        elif reader_session_id in self.__readers:
+            self.__readers.remove(reader_session_id)
+            return None
+        else:
+            raise SegmentInfoError('Reader session ID is not in current ' +
+                                   'readers or old readers set: ' +
+                                   str(reader_session_id))
+
     def create(type, genid, rrclass, datasrc_name, mgr_config):
         """Factory of specific SegmentInfo subclass instance based on the
         segment type.
diff --git a/src/lib/python/isc/memmgr/libmemmgr_messages.mes b/src/lib/python/isc/memmgr/libmemmgr_messages.mes
new file mode 100644
index 0000000..c8fcf05
--- /dev/null
+++ b/src/lib/python/isc/memmgr/libmemmgr_messages.mes
@@ -0,0 +1,35 @@
+# Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# No namespace declaration - these constants go in the global namespace
+# of the config_messages python module.
+
+% LIBMEMMGR_BUILDER_BAD_COMMAND_ERROR MemorySegmentBuilder received bad command '%1'
+The MemorySegmentBuilder has received a bad command in its input command
+queue. This is likely a programming error. If the builder runs in a
+separate thread, this would cause it to exit the thread.
+
+% LIBMEMMGR_BUILDER_GET_ZONE_WRITER_ERROR Unable to get zone writer for zone '%1', data source '%2'. Skipping.
+The MemorySegmentBuilder was unable to get a ZoneWriter for the
+specified zone when handling the load command. This zone will be
+skipped.
+
+% LIBMEMMGR_BUILDER_ZONE_WRITER_LOAD_1_ERROR Error loading zone '%1', data source '%2': '%3'
+The MemorySegmentBuilder failed to load the specified zone when handling
+the load command. This zone will be skipped.
+
+% LIBMEMMGR_BUILDER_ZONE_WRITER_LOAD_2_ERROR Error loading zone '%1', data source '%2': '%3'
+An exception occured when the MemorySegmentBuilder tried to load the
+specified zone when handling the load command. This zone will be
+skipped.
diff --git a/src/lib/python/isc/memmgr/logger.py b/src/lib/python/isc/memmgr/logger.py
new file mode 100644
index 0000000..eb324cf
--- /dev/null
+++ b/src/lib/python/isc/memmgr/logger.py
@@ -0,0 +1,20 @@
+# Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+'''Common definitions regarding logging for the memmgr package.'''
+
+import isc.log
+
+logger = isc.log.Logger("libmemmgr")
diff --git a/src/lib/python/isc/memmgr/tests/Makefile.am b/src/lib/python/isc/memmgr/tests/Makefile.am
index 7a85083..b171cb1 100644
--- a/src/lib/python/isc/memmgr/tests/Makefile.am
+++ b/src/lib/python/isc/memmgr/tests/Makefile.am
@@ -1,3 +1,4 @@
+SUBDIRS = testdata
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 PYTESTS = builder_tests.py datasrc_info_tests.py
 EXTRA_DIST = $(PYTESTS)
@@ -26,7 +27,8 @@ endif
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
 	$(LIBRARY_PATH_PLACEHOLDER) \
-	TESTDATA_PATH=$(builddir) \
+	TESTDATA_PATH=$(abs_srcdir)/testdata \
+	TESTDATA_WRITE_PATH=$(builddir) \
 	B10_FROM_BUILD=$(abs_top_builddir) \
 	HAVE_SHARED_MEMORY=$(HAVE_SHARED_MEMORY) \
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/dns/python/.libs \
diff --git a/src/lib/python/isc/memmgr/tests/builder_tests.py b/src/lib/python/isc/memmgr/tests/builder_tests.py
index 328fd74..b5122cb 100644
--- a/src/lib/python/isc/memmgr/tests/builder_tests.py
+++ b/src/lib/python/isc/memmgr/tests/builder_tests.py
@@ -14,12 +14,28 @@
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 import unittest
+import os
 import socket
 import select
 import threading
 
 import isc.log
+from isc.dns import *
+import isc.datasrc
 from isc.memmgr.builder import *
+from isc.server_common.datasrc_clients_mgr import DataSrcClientsMgr
+from isc.memmgr.datasrc_info import *
+
+TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
+
+# Defined for easier tests with DataSrcClientsMgr.reconfigure(), which
+# only needs get_value() method
+class MockConfigData:
+    def __init__(self, data):
+        self.__data = data
+
+    def get_value(self, identifier):
+        return self.__data[identifier], False
 
 class TestMemorySegmentBuilder(unittest.TestCase):
     def _create_builder_thread(self):
@@ -29,7 +45,8 @@ class TestMemorySegmentBuilder(unittest.TestCase):
         self._builder_command_queue = []
         self._builder_response_queue = []
 
-        self._builder_cv = threading.Condition()
+        self._builder_lock = threading.Lock()
+        self._builder_cv = threading.Condition(lock=self._builder_lock)
 
         self._builder = MemorySegmentBuilder(self._builder_sock,
                                              self._builder_cv,
@@ -39,6 +56,7 @@ class TestMemorySegmentBuilder(unittest.TestCase):
 
     def setUp(self):
         self._create_builder_thread()
+        self.__mapped_file_path = None
 
     def tearDown(self):
         # It's the tests' responsibility to stop and join the builder
@@ -48,6 +66,10 @@ class TestMemorySegmentBuilder(unittest.TestCase):
         self._master_sock.close()
         self._builder_sock.close()
 
+        if self.__mapped_file_path is not None:
+            if os.path.exists(self.__mapped_file_path):
+                os.unlink(self.__mapped_file_path)
+
     def test_bad_command(self):
         """Tests what happens when a bad command is passed to the
         MemorySegmentBuilder.
@@ -58,7 +80,7 @@ class TestMemorySegmentBuilder(unittest.TestCase):
         # Now that the builder thread is running, send it a bad
         # command. The thread should exit its main loop and be joinable.
         with self._builder_cv:
-            self._builder_command_queue.append('bad_command')
+            self._builder_command_queue.append(('bad_command',))
             self._builder_cv.notify_all()
 
         # Wait 5 seconds to receive a notification on the socket from
@@ -95,13 +117,110 @@ class TestMemorySegmentBuilder(unittest.TestCase):
 
         self._builder_thread.start()
 
-        # Now that the builder thread is running, send it the shutdown
+        # Now that the builder thread is running, send it the "shutdown"
         # command. The thread should exit its main loop and be joinable.
         with self._builder_cv:
-            self._builder_command_queue.append('shutdown')
+            self._builder_command_queue.append(('shutdown',))
             # Commands after 'shutdown' must be ignored.
-            self._builder_command_queue.append('bad_command_1')
-            self._builder_command_queue.append('bad_command_2')
+            self._builder_command_queue.append(('bad_command_1',))
+            self._builder_command_queue.append(('bad_command_2',))
+            self._builder_cv.notify_all()
+
+        # Wait 5 seconds at most for the main loop of the builder to
+        # exit.
+        self._builder_thread.join(5)
+        self.assertFalse(self._builder_thread.isAlive())
+
+        # The command queue must be cleared, and the response queue must
+        # be untouched (we don't use it in this test). The thread is no
+        # longer running, so we can use the queues without a lock.
+        self.assertEqual(len(self._builder_command_queue), 0)
+        self.assertEqual(len(self._builder_response_queue), 0)
+
+    @unittest.skipIf(os.environ['HAVE_SHARED_MEMORY'] != 'yes',
+                     'shared memory is not available')
+    def test_load(self):
+        """
+        Test "load" command.
+        """
+
+        mapped_file_dir = os.environ['TESTDATA_WRITE_PATH']
+        mgr_config = {'mapped_file_dir': mapped_file_dir}
+
+        cfg_data = MockConfigData(
+            {"classes":
+                 {"IN": [{"type": "MasterFiles",
+                          "params": { "example.com": TESTDATA_PATH + "example.com.zone" },
+                          "cache-enable": True,
+                          "cache-type": "mapped"}]
+                  }
+             })
+        cmgr = DataSrcClientsMgr(use_cache=True)
+        cmgr.reconfigure({}, cfg_data)
+
+        genid, clients_map = cmgr.get_clients_map()
+        datasrc_info = DataSrcInfo(genid, clients_map, mgr_config)
+
+        self.assertEqual(1, datasrc_info.gen_id)
+        self.assertEqual(clients_map, datasrc_info.clients_map)
+        self.assertEqual(1, len(datasrc_info.segment_info_map))
+        sgmt_info = datasrc_info.segment_info_map[(RRClass.IN, 'MasterFiles')]
+        self.assertIsNone(sgmt_info.get_reset_param(SegmentInfo.READER))
+        self.assertIsNotNone(sgmt_info.get_reset_param(SegmentInfo.WRITER))
+
+        param = sgmt_info.get_reset_param(SegmentInfo.WRITER)
+        self.__mapped_file_path = param['mapped-file']
+
+        self._builder_thread.start()
+
+        # Now that the builder thread is running, send it the "load"
+        # command. We should be notified when the load operation is
+        # complete.
+        with self._builder_cv:
+            self._builder_command_queue.append(('load',
+                                                isc.dns.Name("example.com"),
+                                                datasrc_info, RRClass.IN,
+                                                'MasterFiles'))
+            self._builder_cv.notify_all()
+
+        # Wait 60 seconds to receive a notification on the socket from
+        # the builder.
+        (reads, _, _) = select.select([self._master_sock], [], [], 60)
+        self.assertTrue(self._master_sock in reads)
+
+        # Reading 1 byte should not block us here, especially as the
+        # socket is ready to read. It's a hack, but this is just a
+        # testcase.
+        got = self._master_sock.recv(1)
+        self.assertEqual(got, b'x')
+
+        with self._builder_lock:
+            # The command queue must be cleared, and the response queue
+            # must contain a response that a bad command was sent. The
+            # thread is no longer running, so we can use the queues
+            # without a lock.
+            self.assertEqual(len(self._builder_command_queue), 0)
+            self.assertEqual(len(self._builder_response_queue), 1)
+
+            response = self._builder_response_queue[0]
+            self.assertTrue(isinstance(response, tuple))
+            self.assertTupleEqual(response, ('load-completed', datasrc_info,
+                                             RRClass.IN, 'MasterFiles'))
+            del self._builder_response_queue[:]
+
+        # Now try looking for some loaded data
+        clist = datasrc_info.clients_map[RRClass.IN]
+        dsrc, finder, exact = clist.find(isc.dns.Name("example.com"))
+        self.assertIsNotNone(dsrc)
+        self.assertTrue(isinstance(dsrc, isc.datasrc.DataSourceClient))
+        self.assertIsNotNone(finder)
+        self.assertTrue(isinstance(finder, isc.datasrc.ZoneFinder))
+        self.assertTrue(exact)
+
+        # Send the builder thread the "shutdown" command. The thread
+        # should exit its main loop and be joinable.
+        with self._builder_cv:
+            self._builder_command_queue.append(('shutdown',))
             self._builder_cv.notify_all()
 
         # Wait 5 seconds at most for the main loop of the builder to
diff --git a/src/lib/python/isc/memmgr/tests/datasrc_info_tests.py b/src/lib/python/isc/memmgr/tests/datasrc_info_tests.py
index cc6307f..538f375 100644
--- a/src/lib/python/isc/memmgr/tests/datasrc_info_tests.py
+++ b/src/lib/python/isc/memmgr/tests/datasrc_info_tests.py
@@ -34,7 +34,7 @@ class MockConfigData:
 
 class TestSegmentInfo(unittest.TestCase):
     def setUp(self):
-        self.__mapped_file_dir = os.environ['TESTDATA_PATH']
+        self.__mapped_file_dir = os.environ['TESTDATA_WRITE_PATH']
         self.__sgmt_info = SegmentInfo.create('mapped', 0, RRClass.IN,
                                               'sqlite3',
                                               {'mapped_file_dir':
@@ -60,7 +60,284 @@ class TestSegmentInfo(unittest.TestCase):
         self.__check_sgmt_reset_param(SegmentInfo.WRITER, 0)
         self.__check_sgmt_reset_param(SegmentInfo.READER, None)
 
-    def test_swtich_versions(self):
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+        self.assertEqual(len(self.__sgmt_info.get_readers()), 0)
+        self.assertEqual(len(self.__sgmt_info.get_old_readers()), 0)
+        self.assertEqual(len(self.__sgmt_info.get_events()), 0)
+
+    def __si_to_ready_state(self):
+        # Go to a default starting state
+        self.__sgmt_info = SegmentInfo.create('mapped', 0, RRClass.IN,
+                                              'sqlite3',
+                                              {'mapped_file_dir':
+                                                   self.__mapped_file_dir})
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+
+    def __si_to_updating_state(self):
+        self.__si_to_ready_state()
+        self.__sgmt_info.add_reader(3)
+        self.__sgmt_info.add_event((42,))
+        e = self.__sgmt_info.start_update()
+        self.assertTupleEqual(e, (42,))
+        self.assertSetEqual(self.__sgmt_info.get_readers(), {3})
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.UPDATING)
+
+    def __si_to_synchronizing_state(self):
+        self.__si_to_updating_state()
+        self.__sgmt_info.complete_update()
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+    def __si_to_copying_state(self):
+        self.__si_to_synchronizing_state()
+        self.__sgmt_info.sync_reader(3)
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.COPYING)
+
+    def test_add_event(self):
+        self.assertEqual(len(self.__sgmt_info.get_events()), 0)
+        self.__sgmt_info.add_event(None)
+        self.assertEqual(len(self.__sgmt_info.get_events()), 1)
+        self.assertListEqual(self.__sgmt_info.get_events(), [None])
+
+    def test_add_reader(self):
+        self.assertSetEqual(self.__sgmt_info.get_readers(), set())
+        self.assertSetEqual(self.__sgmt_info.get_old_readers(), set())
+        self.__sgmt_info.add_reader(1)
+        self.assertSetEqual(self.__sgmt_info.get_readers(), {1})
+        self.__sgmt_info.add_reader(3)
+        self.assertSetEqual(self.__sgmt_info.get_readers(), {1, 3})
+        self.__sgmt_info.add_reader(2)
+        self.assertSetEqual(self.__sgmt_info.get_readers(), {1, 2, 3})
+
+        # adding the same existing reader must throw
+        self.assertRaises(SegmentInfoError, self.__sgmt_info.add_reader, (1))
+        # but the existing readers must be untouched
+        self.assertSetEqual(self.__sgmt_info.get_readers(), {1, 3, 2})
+
+        # none of this touches the old readers
+        self.assertSetEqual(self.__sgmt_info.get_old_readers(), set())
+
+    def test_start_update(self):
+        # in READY state
+        # a) there are no events
+        self.__si_to_ready_state()
+        e = self.__sgmt_info.start_update()
+        self.assertIsNone(e)
+        # if there are no events, there is nothing to update
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+
+        # b) there are events. this is the same as calling
+        # self.__si_to_updating_state(), but let's try to be
+        # descriptive.
+        self.__si_to_ready_state()
+        self.__sgmt_info.add_event((42,))
+        e = self.__sgmt_info.start_update()
+        self.assertTupleEqual(e, (42,))
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.UPDATING)
+
+        # in UPDATING state, it should always raise an exception and not
+        # change state.
+        self.__si_to_updating_state()
+        self.assertRaises(SegmentInfoError, self.__sgmt_info.start_update)
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.UPDATING)
+
+        # in SYNCHRONIZING state, it should always raise an exception
+        # and not change state.
+        self.__si_to_synchronizing_state()
+        self.assertRaises(SegmentInfoError, self.__sgmt_info.start_update)
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+        # in COPYING state, it should always raise an exception and not
+        # change state.
+        self.__si_to_copying_state()
+        self.assertRaises(SegmentInfoError, self.__sgmt_info.start_update)
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.COPYING)
+
+    def test_complete_update(self):
+        # in READY state
+        self.__si_to_ready_state()
+        self.assertRaises(SegmentInfoError, self.__sgmt_info.complete_update)
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+
+        # in UPDATING state this is the same as calling
+        # self.__si_to_synchronizing_state(), but let's try to be
+        # descriptive.
+        #
+        # a) with no events
+        self.__si_to_updating_state()
+        e = self.__sgmt_info.complete_update()
+        self.assertIsNone(e)
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+        # b) with events
+        self.__si_to_updating_state()
+        self.__sgmt_info.add_event((81,))
+        e = self.__sgmt_info.complete_update()
+        self.assertIsNone(e) # old_readers is not empty
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+        # c) with no readers, complete_update() from UPDATING must go
+        # directly to READY state
+        self.__si_to_ready_state()
+        self.__sgmt_info.add_event((42,))
+        e = self.__sgmt_info.start_update()
+        self.assertTupleEqual(e, (42,))
+        self.assertSetEqual(self.__sgmt_info.get_readers(), set())
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.UPDATING)
+        e = self.__sgmt_info.complete_update()
+        self.assertTupleEqual(e, (42,))
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+
+        # in SYNCHRONIZING state
+        self.__si_to_synchronizing_state()
+        self.assertRaises(SegmentInfoError, self.__sgmt_info.complete_update)
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+        # in COPYING state
+        self.__si_to_copying_state()
+        e = self.__sgmt_info.complete_update()
+        self.assertIsNone(e)
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+
+    def test_sync_reader(self):
+        # in READY state, it must raise an exception
+        self.__si_to_ready_state()
+        self.assertRaises(SegmentInfoError, self.__sgmt_info.sync_reader, (0))
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+
+        # in UPDATING state, it must raise an exception
+        self.__si_to_updating_state()
+        self.assertRaises(SegmentInfoError, self.__sgmt_info.sync_reader, (0))
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.UPDATING)
+
+        # in COPYING state, it must raise an exception
+        self.__si_to_copying_state()
+        self.assertRaises(SegmentInfoError, self.__sgmt_info.sync_reader, (0))
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.COPYING)
+
+        # in SYNCHRONIZING state:
+        #
+        # a) ID is not in old readers set. The following call sets up ID 3
+        # to be in the old readers set.
+        self.__si_to_synchronizing_state()
+        self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3})
+        self.assertSetEqual(self.__sgmt_info.get_readers(), set())
+        self.assertRaises(SegmentInfoError, self.__sgmt_info.sync_reader, (1))
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+        # b) ID is in old readers set, but also in readers set.
+        self.__si_to_synchronizing_state()
+        self.__sgmt_info.add_reader(3)
+        self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3})
+        self.assertSetEqual(self.__sgmt_info.get_readers(), {3})
+        self.assertRaises(SegmentInfoError, self.__sgmt_info.sync_reader, (3))
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+        # c) ID is in old readers set, but not in readers set, and
+        # old_readers becomes empty.
+        self.__si_to_synchronizing_state()
+        self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3})
+        self.assertSetEqual(self.__sgmt_info.get_readers(), set())
+        self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+        e = self.__sgmt_info.sync_reader(3)
+        self.assertTupleEqual(e, (42,))
+        # the ID should be moved from old readers to readers set
+        self.assertSetEqual(self.__sgmt_info.get_old_readers(), set())
+        self.assertSetEqual(self.__sgmt_info.get_readers(), {3})
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.COPYING)
+
+        # d) ID is in old readers set, but not in readers set, and
+        # old_readers doesn't become empty.
+        self.__si_to_updating_state()
+        self.__sgmt_info.add_reader(4)
+        self.__sgmt_info.complete_update()
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+        self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3, 4})
+        self.assertSetEqual(self.__sgmt_info.get_readers(), set())
+        self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+        e = self.__sgmt_info.sync_reader(3)
+        self.assertIsNone(e)
+        # the ID should be moved from old readers to readers set
+        self.assertSetEqual(self.__sgmt_info.get_old_readers(), {4})
+        self.assertSetEqual(self.__sgmt_info.get_readers(), {3})
+        # we should be left in SYNCHRONIZING state
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+    def test_remove_reader(self):
+        # in READY state, it must raise an exception
+        self.__si_to_ready_state()
+        self.assertRaises(SegmentInfoError, self.__sgmt_info.remove_reader, (0))
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.READY)
+
+        # in UPDATING state, it must raise an exception
+        self.__si_to_updating_state()
+        self.assertRaises(SegmentInfoError, self.__sgmt_info.remove_reader, (0))
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.UPDATING)
+
+        # in COPYING state, it must raise an exception
+        self.__si_to_copying_state()
+        self.assertRaises(SegmentInfoError, self.__sgmt_info.remove_reader, (0))
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.COPYING)
+
+        # in SYNCHRONIZING state:
+        #
+        # a) ID is not in old readers set or readers set.
+        self.__si_to_synchronizing_state()
+        self.__sgmt_info.add_reader(4)
+        self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3})
+        self.assertSetEqual(self.__sgmt_info.get_readers(), {4})
+        self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+        self.assertRaises(SegmentInfoError, self.__sgmt_info.remove_reader, (1))
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+        # b) ID is in readers set.
+        self.__si_to_synchronizing_state()
+        self.__sgmt_info.add_reader(4)
+        self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3})
+        self.assertSetEqual(self.__sgmt_info.get_readers(), {4})
+        self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+        e = self.__sgmt_info.remove_reader(4)
+        self.assertIsNone(e)
+        self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3})
+        self.assertSetEqual(self.__sgmt_info.get_readers(), set())
+        self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+        # we only change state if it was removed from old_readers
+        # specifically and it became empty.
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+        # c) ID is in old_readers set and it becomes empty.
+        self.__si_to_synchronizing_state()
+        self.__sgmt_info.add_reader(4)
+        self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3})
+        self.assertSetEqual(self.__sgmt_info.get_readers(), {4})
+        self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+        e = self.__sgmt_info.remove_reader(3)
+        self.assertTupleEqual(e, (42,))
+        self.assertSetEqual(self.__sgmt_info.get_old_readers(), set())
+        self.assertSetEqual(self.__sgmt_info.get_readers(), {4})
+        self.assertListEqual(self.__sgmt_info.get_events(), [])
+        # we only change state if it was removed from old_readers
+        # specifically and it became empty.
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.COPYING)
+
+        # d) ID is in old_readers set and it doesn't become empty.
+        self.__si_to_updating_state()
+        self.__sgmt_info.add_reader(4)
+        self.__sgmt_info.complete_update()
+        self.__sgmt_info.add_reader(5)
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+        self.assertSetEqual(self.__sgmt_info.get_old_readers(), {3, 4})
+        self.assertSetEqual(self.__sgmt_info.get_readers(), {5})
+        self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+        e = self.__sgmt_info.remove_reader(3)
+        self.assertIsNone(e)
+        self.assertSetEqual(self.__sgmt_info.get_old_readers(), {4})
+        self.assertSetEqual(self.__sgmt_info.get_readers(), {5})
+        self.assertListEqual(self.__sgmt_info.get_events(), [(42,)])
+        # we only change state if it was removed from old_readers
+        # specifically and it became empty.
+        self.assertEqual(self.__sgmt_info.get_state(), SegmentInfo.SYNCHRONIZING)
+
+    def test_switch_versions(self):
         self.__sgmt_info.switch_versions()
         self.__check_sgmt_reset_param(SegmentInfo.WRITER, 1)
         self.__check_sgmt_reset_param(SegmentInfo.READER, 0)
@@ -103,9 +380,9 @@ class MockClientList:
 
 class TestDataSrcInfo(unittest.TestCase):
     def setUp(self):
-        self.__mapped_file_dir = os.environ['TESTDATA_PATH']
+        self.__mapped_file_dir = os.environ['TESTDATA_WRITE_PATH']
         self.__mgr_config = {'mapped_file_dir': self.__mapped_file_dir}
-        self.__sqlite3_dbfile = os.environ['TESTDATA_PATH'] + '/' + 'zone.db'
+        self.__sqlite3_dbfile = os.environ['TESTDATA_WRITE_PATH'] + '/' + 'zone.db'
         self.__clients_map = {
             # mixture of 'local' and 'mapped' and 'unused' (type =None)
             # segments
diff --git a/src/lib/python/isc/memmgr/tests/testdata/Makefile.am b/src/lib/python/isc/memmgr/tests/testdata/Makefile.am
new file mode 100644
index 0000000..22e7ce3
--- /dev/null
+++ b/src/lib/python/isc/memmgr/tests/testdata/Makefile.am
@@ -0,0 +1,2 @@
+EXTRA_DIST = \
+	example.com.zone
diff --git a/src/lib/python/isc/memmgr/tests/testdata/example.com.zone b/src/lib/python/isc/memmgr/tests/testdata/example.com.zone
new file mode 100644
index 0000000..24e22e1
--- /dev/null
+++ b/src/lib/python/isc/memmgr/tests/testdata/example.com.zone
@@ -0,0 +1,8 @@
+example.com.         1000  IN  SOA a.dns.example.com. mail.example.com. 1 1 1 1 1
+example.com.         1000  IN  NS  a.dns.example.com.
+example.com.         1000  IN  NS  b.dns.example.com.
+example.com.         1000  IN  NS  c.dns.example.com.
+a.dns.example.com.   1000  IN  A    1.1.1.1
+b.dns.example.com.   1000  IN  A    3.3.3.3
+b.dns.example.com.   1000  IN  AAAA 4:4::4:4
+b.dns.example.com.   1000  IN  AAAA 5:5::5:5
diff --git a/src/lib/python/isc/notify/notify_out.py b/src/lib/python/isc/notify/notify_out.py
index 39c6708..9003ff5 100644
--- a/src/lib/python/isc/notify/notify_out.py
+++ b/src/lib/python/isc/notify/notify_out.py
@@ -25,7 +25,7 @@ from isc.datasrc import DataSourceClient
 from isc.net import addr
 import isc
 from isc.log_messages.notify_out_messages import *
-from isc.statistics import Counters
+from isc.statistics.dns import Counters
 from isc.util.address_formatter import AddressFormatter
 
 logger = isc.log.Logger("notify_out")
diff --git a/src/lib/python/isc/statistics/Makefile.am b/src/lib/python/isc/statistics/Makefile.am
index 9be1312..699004e 100644
--- a/src/lib/python/isc/statistics/Makefile.am
+++ b/src/lib/python/isc/statistics/Makefile.am
@@ -1,6 +1,6 @@
 SUBDIRS = . tests
 
-python_PYTHON = __init__.py counters.py
+python_PYTHON = __init__.py counters.py dns.py
 pythondir = $(pyexecdir)/isc/statistics
 
 CLEANDIRS = __pycache__
diff --git a/src/lib/python/isc/statistics/counters.py b/src/lib/python/isc/statistics/counters.py
index 0c2c827..87355be 100644
--- a/src/lib/python/isc/statistics/counters.py
+++ b/src/lib/python/isc/statistics/counters.py
@@ -17,52 +17,34 @@
 
 This module handles the statistics counters for BIND 10 modules.  For
 using the module `counter.py`, first a counters object should be created
-in each module (like b10-xfrin or b10-xfrout) after importing this
-module. A spec file can be specified as an argument when creating the
-counters object:
+in each module like b10-foo after importing this module. A spec file can
+be specified as an argument when creating the counters object:
 
   from isc.statistics import Counters
   self.counters = Counters("/path/to/foo.spec")
 
 The first argument of Counters() can be specified, which is the location
-of the specification file (like src/bin/xfrout/xfrout.spec). If Counters
-is constructed this way, statistics counters can be accessed from each
-module. For example, in case that the item `xfrreqdone` is defined in
-statistics_spec in xfrout.spec, the following methods are
-callable. Since these methods require the string of the zone name in the
-first argument, if we have the following code in b10-xfrout:
+of the specification file. If Counters is constructed this way,
+statistics counters can be accessed from each module. For example, in
+case that the item `counter1` is defined in statistics_spec in foo.spec,
+the following methods are callable.
 
-  self.counters.inc('zones', zone_name, 'xfrreqdone')
+  self.counters.inc('counter1')
 
-then the counter for xfrreqdone corresponding to zone_name is
-incremented. For getting the current number of this counter, we can use
-the following code:
+Then the counter for `counter1` is incremented. For getting the current
+number of this counter, we can use the following code:
 
-  number = self.counters.get('zones', zone_name, 'xfrreqdone')
+  number = self.counters.get('counter1')
 
-then the current count is obtained and set in the variable
+Then the current count is obtained and set in the variable
 `number`. Such a getter method would be mainly used for unit-testing.
-As other example, for the item `axfr_running`, the decrementer method is
-also callable.  This method is used for decrementing a counter.  For the
-item `axfr_running`, an argument like zone name is not required:
+The decrementer method is also callable.  This method is used for
+decrementing a counter as well as inc().
 
-  self.counters.dec('axfr_running')
+  self.counters.dec('counter2')
 
-These methods are effective in other modules. For example, in case that
-this module `counter.py` is once imported in a main module such as
-b10-xfrout, then for the item `notifyoutv4`, the `inc()` method can be
-invoked in another module such as notify_out.py, which is firstly
-imported in the main module.
-
-  self.counters.inc('zones', zone_name, 'notifyoutv4')
-
-In this example this is for incrementing the counter of the item
-`notifyoutv4`. Thus, such statement can be also written in another
-library like isc.notify.notify_out. If this module `counter.py` isn't
-imported in the main module but imported in such a library module as
-isc.notify.notify_out, in this example, empty methods would be invoked,
-which is directly defined in `counter.py`.
-"""
+Some other methods accessible to a counter are provided by this
+module."""
 
 import threading
 import isc.config
@@ -170,60 +152,13 @@ def _concat(*args, sep='/'):
     return sep.join(args)
 
 class _Statistics():
-    """Statistics data set"""
+    """Statistics data set. This class will be remove in the future
+    release."""
     # default statistics data
     _data = {}
     # default statistics spec used in case the specfile is omitted when
     # constructing a Counters() object
-    _spec = [
-      {
-        "item_name": "zones",
-        "item_type": "named_set",
-        "item_optional": False,
-        "item_default": {
-          "_SERVER_" : {
-            "notifyoutv4" : 0,
-            "notifyoutv6" : 0
-          }
-        },
-        "item_title": "Zone names",
-        "item_description": "Zone names",
-        "named_set_item_spec": {
-          "item_name": "classname",
-          "item_type": "named_set",
-          "item_optional": False,
-          "item_default": {},
-          "item_title": "RR class name",
-          "item_description": "RR class name",
-          "named_set_item_spec": {
-            "item_name": "zonename",
-            "item_type": "map",
-            "item_optional": False,
-            "item_default": {},
-            "item_title": "Zone name",
-            "item_description": "Zone name",
-            "map_item_spec": [
-              {
-                "item_name": "notifyoutv4",
-                "item_type": "integer",
-                "item_optional": False,
-                "item_default": 0,
-                "item_title": "IPv4 notifies",
-                "item_description": "Number of IPv4 notifies per zone name sent out"
-              },
-              {
-                "item_name": "notifyoutv6",
-                "item_type": "integer",
-                "item_optional": False,
-                "item_default": 0,
-                "item_title": "IPv6 notifies",
-                "item_description": "Number of IPv6 notifies per zone name sent out"
-              }
-            ]
-          }
-        }
-      }
-    ]
+    _spec = []
 
 class Counters():
     """A class for holding and manipulating all statistics counters
@@ -237,56 +172,8 @@ class Counters():
     stop_timer() and get() are useful for this. Saved counters can be
     cleared by the method clear_all(). Manipulating counters and
     timers can be temporarily disabled.  If disabled, counter values are
-    not changed even if methods to update them are invoked.  Including
-    per-zone counters, a list of counters which can be handled in the
-    class are like the following:
-
-        zones/IN/example.com./notifyoutv4
-        zones/IN/example.com./notifyoutv6
-        zones/IN/example.com./xfrrej
-        zones/IN/example.com./xfrreqdone
-        zones/IN/example.com./soaoutv4
-        zones/IN/example.com./soaoutv6
-        zones/IN/example.com./axfrreqv4
-        zones/IN/example.com./axfrreqv6
-        zones/IN/example.com./ixfrreqv4
-        zones/IN/example.com./ixfrreqv6
-        zones/IN/example.com./xfrsuccess
-        zones/IN/example.com./xfrfail
-        zones/IN/example.com./last_ixfr_duration
-        zones/IN/example.com./last_axfr_duration
-        ixfr_running
-        axfr_running
-        socket/unixdomain/open
-        socket/unixdomain/openfail
-        socket/unixdomain/close
-        socket/unixdomain/bindfail
-        socket/unixdomain/acceptfail
-        socket/unixdomain/accept
-        socket/unixdomain/senderr
-        socket/unixdomain/recverr
-        socket/ipv4/tcp/open
-        socket/ipv4/tcp/openfail
-        socket/ipv4/tcp/close
-        socket/ipv4/tcp/connfail
-        socket/ipv4/tcp/conn
-        socket/ipv4/tcp/senderr
-        socket/ipv4/tcp/recverr
-        socket/ipv6/tcp/open
-        socket/ipv6/tcp/openfail
-        socket/ipv6/tcp/close
-        socket/ipv6/tcp/connfail
-        socket/ipv6/tcp/conn
-        socket/ipv6/tcp/senderr
-        socket/ipv6/tcp/recverr
-    """
+    not changed even if methods to update them are invoked."""
 
-    # '_SERVER_' is a special zone name representing an entire
-    # count. It doesn't mean a specific zone, but it means an
-    # entire count in the server.
-    _entire_server = '_SERVER_'
-    # zone names are contained under this dirname in the spec file.
-    _perzone_prefix = 'zones'
     # default statistics data set
     _statistics = _Statistics()
 
@@ -296,7 +183,8 @@ class Counters():
         statistics spec can be accumulated if spec_file_name is
         specified. If omitted, a default statistics spec is used. The
         default statistics spec is defined in a hidden class named
-        _Statistics().
+        _Statistics(). But the hidden class won't be used and
+        spec_file_name will be required in the future release.
         """
         self._zones_item_list = []
         self._start_time = {}
@@ -307,13 +195,6 @@ class Counters():
         self._statistics._spec = \
             isc.config.module_spec_from_file(spec_file_name).\
             get_statistics_spec()
-        if self._perzone_prefix in \
-                isc.config.spec_name_list(self._statistics._spec):
-            self._zones_item_list = isc.config.spec_name_list(
-                isc.config.find_spec_part(
-                    self._statistics._spec,
-                    '%s/%s/%s' % (self._perzone_prefix,
-                                  '_CLASS_', self._entire_server)))
 
     def clear_all(self):
         """clears all statistics data"""
@@ -408,32 +289,9 @@ class Counters():
             del branch_map[leaf]
 
     def get_statistics(self):
-        """Calculates an entire server's counts, and returns statistics
-        data in a format to send out to the stats module, including each
-        counter. If nothing is counted yet, then it returns an empty
-        dictionary."""
+        """Returns statistics data in a format to send out to the
+        stats module, including each counter. If nothing is counted
+        yet, then it returns an empty dictionary."""
         # entire copy
         statistics_data = self._statistics._data.copy()
-        # If there is no 'zones' found in statistics_data,
-        # i.e. statistics_data contains no per-zone counter, it just
-        # returns statistics_data because calculating total counts
-        # across the zone names isn't necessary.
-        if self._perzone_prefix not in statistics_data:
-            return statistics_data
-        zones = statistics_data[self._perzone_prefix]
-        # Start calculation for '_SERVER_' counts
-        zones_spec = isc.config.find_spec_part(self._statistics._spec,
-                                               self._perzone_prefix)
-        zones_data = {}
-        for cls in zones.keys():
-            for zone in zones[cls].keys():
-                for (attr, val) in zones[cls][zone].items():
-                    id_str1 = '%s/%s/%s' % (cls, zone, attr)
-                    id_str2 = '%s/%s/%s' % (cls, self._entire_server, attr)
-                    _set_counter(zones_data, zones_spec, id_str1, val)
-                    _inc_counter(zones_data, zones_spec, id_str2, val)
-        # insert entire-server counts
-        statistics_data[self._perzone_prefix] = dict(
-            statistics_data[self._perzone_prefix],
-            **zones_data)
         return statistics_data
diff --git a/src/lib/python/isc/statistics/dns.py b/src/lib/python/isc/statistics/dns.py
new file mode 100644
index 0000000..c224687
--- /dev/null
+++ b/src/lib/python/isc/statistics/dns.py
@@ -0,0 +1,225 @@
+# Copyright (C) 2013  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""BIND 10 statistics counters module for DNS
+
+This module handles the statistics counters for BIND 10 modules for a
+DNS-specific purpose.  For using the module `counter.py`, first a
+counters object should be created in each module (like b10-xfrin or
+b10-xfrout) after importing this module. A spec file can be specified as
+an argument when creating the counters object:
+
+  from isc.statistics.dns import Counters
+  self.counters = Counters("/path/to/xfrout/xfrout.spec")
+
+The first argument of Counters() can be specified, which is the location
+of the specification file. If Counters is constructed this way,
+statistics counters can be accessed from each module. For example, in
+case that the item `xfrreqdone` is defined in statistics_spec in
+xfrout.spec, the following methods are callable. Since these methods
+require the string of the zone name in the first argument, if we have
+the following code in b10-xfrout:
+
+  self.counters.inc('zones', zone_name, 'xfrreqdone')
+
+then the counter for xfrreqdone corresponding to zone_name is
+incremented. For getting the current number of this counter, we can use
+the following code:
+
+  number = self.counters.get('zones', zone_name, 'xfrreqdone')
+
+then the current count is obtained and set in the variable
+`number`. Such a getter method would be mainly used for unit-testing.
+As other example, for the item `axfr_running`, the decrementer method is
+also callable.  This method is used for decrementing a counter.  For the
+item `axfr_running`, an argument like zone name is not required:
+
+  self.counters.dec('axfr_running')
+
+These methods are effective in other modules. For example, in case that
+this module `counters.py` is once imported in a main module such as
+b10-xfrout, then for the item `notifyoutv4`, the `inc()` method can be
+invoked in another module such as notify_out.py, which is firstly
+imported in the main module.
+
+  self.counters.inc('zones', zone_name, 'notifyoutv4')
+
+In this example this is for incrementing the counter of the item
+`notifyoutv4`. Thus, such statement can be also written in another
+library like isc.notify.notify_out. If this module `counter.py` isn't
+imported in the main module but imported in such a library module as
+isc.notify.notify_out, in this example, empty methods would be invoked,
+which is directly defined in `counter.py`.
+
+This module basically inherits isc.statistics.counters.  Also see
+documentation for isc.statistics.counters for details."""
+
+import isc.config
+from isc.statistics import counters
+
+class _Statistics():
+    """Statistics data set. This class will be removed in the future
+    release."""
+    # default statistics data
+    _data = {}
+    # default statistics spec used in case the specfile is omitted when
+    # constructing a Counters() object
+    _spec = [
+      {
+        "item_name": "zones",
+        "item_type": "named_set",
+        "item_optional": False,
+        "item_default": {
+          "_SERVER_" : {
+            "notifyoutv4" : 0,
+            "notifyoutv6" : 0
+          }
+        },
+        "item_title": "Zone names",
+        "item_description": "Zone names",
+        "named_set_item_spec": {
+          "item_name": "classname",
+          "item_type": "named_set",
+          "item_optional": False,
+          "item_default": {},
+          "item_title": "RR class name",
+          "item_description": "RR class name",
+          "named_set_item_spec": {
+            "item_name": "zonename",
+            "item_type": "map",
+            "item_optional": False,
+            "item_default": {},
+            "item_title": "Zone name",
+            "item_description": "Zone name",
+            "map_item_spec": [
+              {
+                "item_name": "notifyoutv4",
+                "item_type": "integer",
+                "item_optional": False,
+                "item_default": 0,
+                "item_title": "IPv4 notifies",
+                "item_description": "Number of IPv4 notifies per zone name sent out"
+              },
+              {
+                "item_name": "notifyoutv6",
+                "item_type": "integer",
+                "item_optional": False,
+                "item_default": 0,
+                "item_title": "IPv6 notifies",
+                "item_description": "Number of IPv6 notifies per zone name sent out"
+              }
+            ]
+          }
+        }
+      }
+    ]
+
+class Counters(counters.Counters):
+    """A list of counters which can be handled in the class are like
+    the following. Also see documentation for
+    isc.statistics.counters.Counters for details.
+
+        zones/IN/example.com./notifyoutv4
+        zones/IN/example.com./notifyoutv6
+        zones/IN/example.com./xfrrej
+        zones/IN/example.com./xfrreqdone
+        zones/IN/example.com./soaoutv4
+        zones/IN/example.com./soaoutv6
+        zones/IN/example.com./axfrreqv4
+        zones/IN/example.com./axfrreqv6
+        zones/IN/example.com./ixfrreqv4
+        zones/IN/example.com./ixfrreqv6
+        zones/IN/example.com./xfrsuccess
+        zones/IN/example.com./xfrfail
+        zones/IN/example.com./last_ixfr_duration
+        zones/IN/example.com./last_axfr_duration
+        ixfr_running
+        axfr_running
+        socket/unixdomain/open
+        socket/unixdomain/openfail
+        socket/unixdomain/close
+        socket/unixdomain/bindfail
+        socket/unixdomain/acceptfail
+        socket/unixdomain/accept
+        socket/unixdomain/senderr
+        socket/unixdomain/recverr
+        socket/ipv4/tcp/open
+        socket/ipv4/tcp/openfail
+        socket/ipv4/tcp/close
+        socket/ipv4/tcp/connfail
+        socket/ipv4/tcp/conn
+        socket/ipv4/tcp/senderr
+        socket/ipv4/tcp/recverr
+        socket/ipv6/tcp/open
+        socket/ipv6/tcp/openfail
+        socket/ipv6/tcp/close
+        socket/ipv6/tcp/connfail
+        socket/ipv6/tcp/conn
+        socket/ipv6/tcp/senderr
+        socket/ipv6/tcp/recverr
+    """
+    # '_SERVER_' is a special zone name representing an entire
+    # count. It doesn't mean a specific zone, but it means an
+    # entire count in the server.
+    _entire_server = '_SERVER_'
+    # zone names are contained under this dirname in the spec file.
+    _perzone_prefix = 'zones'
+    # default statistics data set
+    _statistics = _Statistics()
+
+    def __init__(self, spec_file_name=None):
+        """If the item `zones` is defined in the spec file, it obtains a
+        list of counter names under it when initiating.  For behaviors
+        other than this, see documentation for
+        isc.statistics.counters.Counters.__init__()"""
+        counters.Counters.__init__(self, spec_file_name)
+        if self._perzone_prefix in \
+                isc.config.spec_name_list(self._statistics._spec):
+            self._zones_item_list = isc.config.spec_name_list(
+                isc.config.find_spec_part(
+                    self._statistics._spec,
+                    '%s/%s/%s' % (self._perzone_prefix,
+                                  '_CLASS_', self._entire_server)))
+
+    def get_statistics(self):
+        """Calculates an entire server's counts, and returns statistics
+        data in a format to send out to the stats module, including each
+        counter. If nothing is counted yet, then it returns an empty
+        dictionary."""
+        # entire copy
+        statistics_data = self._statistics._data.copy()
+        # If there is no 'zones' found in statistics_data,
+        # i.e. statistics_data contains no per-zone counter, it just
+        # returns statistics_data because calculating total counts
+        # across the zone names isn't necessary.
+        if self._perzone_prefix not in statistics_data:
+            return statistics_data
+        zones = statistics_data[self._perzone_prefix]
+        # Start calculation for '_SERVER_' counts
+        zones_spec = isc.config.find_spec_part(self._statistics._spec,
+                                               self._perzone_prefix)
+        zones_data = {}
+        for cls in zones.keys():
+            for zone in zones[cls].keys():
+                for (attr, val) in zones[cls][zone].items():
+                    id_str1 = '%s/%s/%s' % (cls, zone, attr)
+                    id_str2 = '%s/%s/%s' % (cls, self._entire_server, attr)
+                    counters._set_counter(zones_data, zones_spec, id_str1, val)
+                    counters._inc_counter(zones_data, zones_spec, id_str2, val)
+        # insert entire-server counts
+        statistics_data[self._perzone_prefix] = dict(
+            statistics_data[self._perzone_prefix],
+            **zones_data)
+        return statistics_data
diff --git a/src/lib/python/isc/statistics/tests/Makefile.am b/src/lib/python/isc/statistics/tests/Makefile.am
index c38e0f5..0c02290 100644
--- a/src/lib/python/isc/statistics/tests/Makefile.am
+++ b/src/lib/python/isc/statistics/tests/Makefile.am
@@ -1,5 +1,5 @@
 PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
-PYTESTS = counters_test.py
+PYTESTS = counters_test.py dns_test.py
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST += testdata/test_spec1.spec
 EXTRA_DIST += testdata/test_spec2.spec
diff --git a/src/lib/python/isc/statistics/tests/counters_test.py b/src/lib/python/isc/statistics/tests/counters_test.py
index 718c9da..6b9dd8e 100644
--- a/src/lib/python/isc/statistics/tests/counters_test.py
+++ b/src/lib/python/isc/statistics/tests/counters_test.py
@@ -13,7 +13,7 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-'''Tests for isc.statistics.counter'''
+'''Tests for isc.statistics.counters'''
 
 import unittest
 import threading
@@ -22,8 +22,6 @@ import os
 import imp
 import isc.config
 
-TEST_ZONE_NAME_STR = "example.com."
-TEST_ZONE_CLASS_STR = "IN"
 TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
 
 from isc.statistics import counters
@@ -166,8 +164,6 @@ class BaseTestCounters():
         imp.reload(counters)
         self._statistics_data = {}
         self.counters = counters.Counters(self.TEST_SPECFILE_LOCATION)
-        self._entire_server    = self.counters._entire_server
-        self._perzone_prefix   = self.counters._perzone_prefix
 
     def tearDown(self):
         self.counters.clear_all()
@@ -194,107 +190,6 @@ class BaseTestCounters():
                     ).validate_statistics(
                     False, self._statistics_data))
 
-    def test_perzone_counters(self):
-        # for per-zone counters
-        for name in self.counters._zones_item_list:
-            args = (self._perzone_prefix, TEST_ZONE_CLASS_STR,
-                    TEST_ZONE_NAME_STR, name)
-            if name.find('last_') == 0 and name.endswith('_duration'):
-                self.counters.start_timer(*args)
-                self.counters.stop_timer(*args)
-                self.assertGreaterEqual(self.counters.get(*args), 0.0)
-                sec = self.counters.get(*args)
-                for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
-                    isc.cc.data.set(self._statistics_data,
-                                    '%s/%s/%s/%s' % (args[0], args[1],
-                                                     zone_str, name), sec)
-                # twice exec stopper, then second is not changed
-                self.counters.stop_timer(*args)
-                self.assertEqual(self.counters.get(*args), sec)
-            else:
-                self.counters.inc(*args)
-                self.assertEqual(self.counters.get(*args), 1)
-                # checks disable/enable
-                self.counters.disable()
-                self.counters.inc(*args)
-                self.assertEqual(self.counters.get(*args), 1)
-                self.counters.enable()
-                self.counters.inc(*args)
-                self.assertEqual(self.counters.get(*args), 2)
-                for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
-                    isc.cc.data.set(self._statistics_data,
-                                    '%s/%s/%s/%s' % (args[0], args[1],
-                                                     zone_str, name), 2)
-        self.check_get_statistics()
-
-    def test_xfrrunning_counters(self):
-        # for counters of xfer running
-        _suffix = 'xfr_running'
-        _xfrrunning_names = \
-            isc.config.spec_name_list(self.counters._statistics._spec,
-                                      "", True)
-        for name in _xfrrunning_names:
-            if name.find(_suffix) != 1: continue
-            args = name.split('/')
-            self.counters.inc(*args)
-            self.assertEqual(self.counters.get(*args), 1)
-            self.counters.dec(*args)
-            self.assertEqual(self.counters.get(*args), 0)
-            # checks disable/enable
-            self.counters.disable()
-            self.counters.inc(*args)
-            self.assertEqual(self.counters.get(*args), 0)
-            self.counters.enable()
-            self.counters.inc(*args)
-            self.assertEqual(self.counters.get(*args), 1)
-            self.counters.disable()
-            self.counters.dec(*args)
-            self.assertEqual(self.counters.get(*args), 1)
-            self.counters.enable()
-            self.counters.dec(*args)
-            self.assertEqual(self.counters.get(*args), 0)
-            self._statistics_data[name] = 0
-        self.check_get_statistics()
-
-    def test_socket_counters(self):
-        # for ipsocket/unixsocket counters
-        _prefix = 'socket/'
-        _socket_names = \
-            isc.config.spec_name_list(self.counters._statistics._spec,
-                                      "", True)
-        for name in _socket_names:
-            if name.find(_prefix) != 0: continue
-            args = name.split('/')
-            self.counters.inc(*args)
-            self.assertEqual(self.counters.get(*args), 1)
-            # checks disable/enable
-            self.counters.disable()
-            self.counters.inc(*args)
-            self.assertEqual(self.counters.get(*args), 1)
-            self.counters.enable()
-            self.counters.inc(*args)
-            self.assertEqual(self.counters.get(*args), 2)
-            isc.cc.data.set(
-                self._statistics_data, '/'.join(args), 2)
-        self.check_get_statistics()
-
-    def test_perzone_zero_counters(self):
-        # setting all counters to zero
-        for name in self.counters._zones_item_list:
-            args = (self._perzone_prefix, TEST_ZONE_CLASS_STR,
-                    TEST_ZONE_NAME_STR, name)
-            if name.find('last_') == 0 and name.endswith('_duration'):
-                zero = 0.0
-            else:
-                zero = 0
-            # set zero
-            self.counters._incdec(*args, step=zero)
-            for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
-                isc.cc.data.set(self._statistics_data,
-                                '%s/%s/%s/%s' % (args[0], args[1],
-                                                 zone_str, name), zero)
-        self.check_get_statistics()
-
     def test_undefined_item(self):
         # test DataNotFoundError raising when specifying item defined
         # in the specfile
@@ -322,123 +217,5 @@ class TestCounters1(unittest.TestCase, BaseTestCounters):
     def tearDown(self):
         BaseTestCounters.tearDown(self)
 
-class TestCounters2(unittest.TestCase, BaseTestCounters):
-    TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec2.spec'
-    def setUp(self):
-        BaseTestCounters.setUp(self)
-    def tearDown(self):
-        BaseTestCounters.tearDown(self)
-
-class TestCounters3(unittest.TestCase, BaseTestCounters):
-    TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec3.spec'
-    @classmethod
-    def setUpClass(cls):
-        imp.reload(counters)
-    def setUp(self):
-        BaseTestCounters.setUp(self)
-    def tearDown(self):
-        BaseTestCounters.tearDown(self)
-
-class BaseDummyModule():
-    """A base dummy class"""
-    TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec2.spec'
-    def __init__(self):
-        self.counters = counters.Counters(self.TEST_SPECFILE_LOCATION)
-
-    def get_counters(self):
-        return self.counters.get_statistics()
-
-    def clear_counters(self):
-        self.counters.clear_all()
-
-class DummyNotifyOut(BaseDummyModule):
-    """A dummy class equivalent to notify.notify_out.NotifyOut"""
-    def __init__(self):
-        self.counters = counters.Counters()
-
-    def inc_counters(self):
-        """increments counters"""
-        self.counters.inc('zones', TEST_ZONE_CLASS_STR,
-                          TEST_ZONE_NAME_STR, 'notifyoutv4')
-        self.counters.inc('zones', TEST_ZONE_CLASS_STR,
-                          TEST_ZONE_NAME_STR, 'notifyoutv6')
-
-class DummyXfroutSession(BaseDummyModule):
-    """A dummy class equivalent to XfroutSession in b10-xfrout"""
-    def inc_counters(self):
-        """increments counters"""
-        self.counters.inc('zones', TEST_ZONE_CLASS_STR,
-                          TEST_ZONE_NAME_STR, 'xfrreqdone')
-        self.counters.inc('zones', TEST_ZONE_CLASS_STR,
-                          TEST_ZONE_NAME_STR, 'xfrrej')
-        self.counters.inc('axfr_running')
-        self.counters.inc('ixfr_running')
-        self.counters.dec('axfr_running')
-        self.counters.dec('ixfr_running')
-
-class DummyUnixSockServer(BaseDummyModule):
-    """A dummy class equivalent to UnixSockServer in b10-xfrout"""
-    def inc_counters(self):
-        """increments counters"""
-        self.counters.inc('socket', 'unixdomain', 'open')
-        self.counters.inc('socket', 'unixdomain', 'close')
-
-class DummyXfroutServer(BaseDummyModule):
-    """A dummy class equivalent to XfroutServer in b10-xfrout"""
-    def __init__(self):
-        super().__init__()
-        self.xfrout_sess = DummyXfroutSession()
-        self.unix_socket_server = DummyUnixSockServer()
-        self.notifier = DummyNotifyOut()
-
-    def inc_counters(self):
-        self.xfrout_sess.inc_counters()
-        self.unix_socket_server.inc_counters()
-        self.notifier.inc_counters()
-
-class TestDummyNotifyOut(unittest.TestCase):
-    """Tests counters are incremented in which the spec file is not
-    loaded"""
-    def setUp(self):
-        imp.reload(counters)
-        self.notifier = DummyNotifyOut()
-        self.notifier.inc_counters()
-
-    def tearDown(self):
-        self.notifier.clear_counters()
-
-    def test_counters(self):
-        self.assertEqual(
-            {'zones': {TEST_ZONE_CLASS_STR: { '_SERVER_':
-                           {'notifyoutv4': 1, 'notifyoutv6': 1},
-                                              TEST_ZONE_NAME_STR:
-                           {'notifyoutv4': 1, 'notifyoutv6': 1}}}},
-            self.notifier.get_counters())
-
-class TestDummyXfroutServer(unittest.TestCase):
-    """Tests counters are incremented or decremented in which the same
-    spec file is multiply loaded in each child class"""
-    def setUp(self):
-        imp.reload(counters)
-        self.xfrout_server = DummyXfroutServer()
-        self.xfrout_server.inc_counters()
-
-    def tearDown(self):
-        self.xfrout_server.clear_counters()
-
-    def test_counters(self):
-        self.assertEqual(
-            {'axfr_running': 0, 'ixfr_running': 0,
-             'socket': {'unixdomain': {'open': 1, 'close': 1}},
-             'zones': {TEST_ZONE_CLASS_STR: {
-                        '_SERVER_': {'notifyoutv4': 1,
-                                    'notifyoutv6': 1,
-                                    'xfrrej': 1, 'xfrreqdone': 1},
-                        TEST_ZONE_NAME_STR: {'notifyoutv4': 1,
-                                        'notifyoutv6': 1,
-                                        'xfrrej': 1,
-                                        'xfrreqdone': 1}}}},
-            self.xfrout_server.get_counters())
-
 if __name__== "__main__":
     unittest.main()
diff --git a/src/lib/python/isc/statistics/tests/dns_test.py b/src/lib/python/isc/statistics/tests/dns_test.py
new file mode 100644
index 0000000..59187d9
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/dns_test.py
@@ -0,0 +1,260 @@
+# Copyright (C) 2013  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+'''Tests for isc.statistics.dns'''
+
+import unittest
+import os
+import imp
+import isc.config
+import counters_test
+
+TEST_ZONE_NAME_STR = "example.com."
+TEST_ZONE_CLASS_STR = "IN"
+TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
+
+from isc.statistics import dns
+
+class BaseTestCounters(counters_test.BaseTestCounters):
+
+    def setUp(self):
+        imp.reload(dns)
+        self._statistics_data = {}
+        self.counters = dns.Counters(self.TEST_SPECFILE_LOCATION)
+        self._entire_server    = self.counters._entire_server
+        self._perzone_prefix   = self.counters._perzone_prefix
+
+    def test_perzone_counters(self):
+        # for per-zone counters
+        for name in self.counters._zones_item_list:
+            args = (self._perzone_prefix, TEST_ZONE_CLASS_STR,
+                    TEST_ZONE_NAME_STR, name)
+            if name.find('last_') == 0 and name.endswith('_duration'):
+                self.counters.start_timer(*args)
+                self.counters.stop_timer(*args)
+                self.assertGreaterEqual(self.counters.get(*args), 0.0)
+                sec = self.counters.get(*args)
+                for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
+                    isc.cc.data.set(self._statistics_data,
+                                    '%s/%s/%s/%s' % (args[0], args[1],
+                                                     zone_str, name), sec)
+                # twice exec stopper, then second is not changed
+                self.counters.stop_timer(*args)
+                self.assertEqual(self.counters.get(*args), sec)
+            else:
+                self.counters.inc(*args)
+                self.assertEqual(self.counters.get(*args), 1)
+                # checks disable/enable
+                self.counters.disable()
+                self.counters.inc(*args)
+                self.assertEqual(self.counters.get(*args), 1)
+                self.counters.enable()
+                self.counters.inc(*args)
+                self.assertEqual(self.counters.get(*args), 2)
+                for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
+                    isc.cc.data.set(self._statistics_data,
+                                    '%s/%s/%s/%s' % (args[0], args[1],
+                                                     zone_str, name), 2)
+        self.check_get_statistics()
+
+    def test_xfrrunning_counters(self):
+        # for counters of xfer running
+        _suffix = 'xfr_running'
+        _xfrrunning_names = \
+            isc.config.spec_name_list(self.counters._statistics._spec,
+                                      "", True)
+        for name in _xfrrunning_names:
+            if name.find(_suffix) != 1: continue
+            args = name.split('/')
+            self.counters.inc(*args)
+            self.assertEqual(self.counters.get(*args), 1)
+            self.counters.dec(*args)
+            self.assertEqual(self.counters.get(*args), 0)
+            # checks disable/enable
+            self.counters.disable()
+            self.counters.inc(*args)
+            self.assertEqual(self.counters.get(*args), 0)
+            self.counters.enable()
+            self.counters.inc(*args)
+            self.assertEqual(self.counters.get(*args), 1)
+            self.counters.disable()
+            self.counters.dec(*args)
+            self.assertEqual(self.counters.get(*args), 1)
+            self.counters.enable()
+            self.counters.dec(*args)
+            self.assertEqual(self.counters.get(*args), 0)
+            self._statistics_data[name] = 0
+        self.check_get_statistics()
+
+    def test_socket_counters(self):
+        # for ipsocket/unixsocket counters
+        _prefix = 'socket/'
+        _socket_names = \
+            isc.config.spec_name_list(self.counters._statistics._spec,
+                                      "", True)
+        for name in _socket_names:
+            if name.find(_prefix) != 0: continue
+            args = name.split('/')
+            self.counters.inc(*args)
+            self.assertEqual(self.counters.get(*args), 1)
+            # checks disable/enable
+            self.counters.disable()
+            self.counters.inc(*args)
+            self.assertEqual(self.counters.get(*args), 1)
+            self.counters.enable()
+            self.counters.inc(*args)
+            self.assertEqual(self.counters.get(*args), 2)
+            isc.cc.data.set(
+                self._statistics_data, '/'.join(args), 2)
+        self.check_get_statistics()
+
+    def test_perzone_zero_counters(self):
+        # setting all counters to zero
+        for name in self.counters._zones_item_list:
+            args = (self._perzone_prefix, TEST_ZONE_CLASS_STR,
+                    TEST_ZONE_NAME_STR, name)
+            if name.find('last_') == 0 and name.endswith('_duration'):
+                zero = 0.0
+            else:
+                zero = 0
+            # set zero
+            self.counters._incdec(*args, step=zero)
+            for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
+                isc.cc.data.set(self._statistics_data,
+                                '%s/%s/%s/%s' % (args[0], args[1],
+                                                 zone_str, name), zero)
+        self.check_get_statistics()
+
+
+class TestCounters2(unittest.TestCase, BaseTestCounters):
+    TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec2.spec'
+    def setUp(self):
+        BaseTestCounters.setUp(self)
+    def tearDown(self):
+        BaseTestCounters.tearDown(self)
+
+class TestCounters3(unittest.TestCase, BaseTestCounters):
+    TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec3.spec'
+    @classmethod
+    def setUpClass(cls):
+        imp.reload(dns)
+    def setUp(self):
+        BaseTestCounters.setUp(self)
+    def tearDown(self):
+        BaseTestCounters.tearDown(self)
+
+class BaseDummyModule():
+    """A base dummy class"""
+    TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec2.spec'
+    def __init__(self):
+        self.counters = dns.Counters(self.TEST_SPECFILE_LOCATION)
+
+    def get_counters(self):
+        return self.counters.get_statistics()
+
+    def clear_counters(self):
+        self.counters.clear_all()
+
+class DummyNotifyOut(BaseDummyModule):
+    """A dummy class equivalent to notify.notify_out.NotifyOut"""
+    def __init__(self):
+        self.counters = dns.Counters()
+
+    def inc_counters(self):
+        """increments counters"""
+        self.counters.inc('zones', TEST_ZONE_CLASS_STR,
+                          TEST_ZONE_NAME_STR, 'notifyoutv4')
+        self.counters.inc('zones', TEST_ZONE_CLASS_STR,
+                          TEST_ZONE_NAME_STR, 'notifyoutv6')
+
+class DummyXfroutSession(BaseDummyModule):
+    """A dummy class equivalent to XfroutSession in b10-xfrout"""
+    def inc_counters(self):
+        """increments counters"""
+        self.counters.inc('zones', TEST_ZONE_CLASS_STR,
+                          TEST_ZONE_NAME_STR, 'xfrreqdone')
+        self.counters.inc('zones', TEST_ZONE_CLASS_STR,
+                          TEST_ZONE_NAME_STR, 'xfrrej')
+        self.counters.inc('axfr_running')
+        self.counters.inc('ixfr_running')
+        self.counters.dec('axfr_running')
+        self.counters.dec('ixfr_running')
+
+class DummyUnixSockServer(BaseDummyModule):
+    """A dummy class equivalent to UnixSockServer in b10-xfrout"""
+    def inc_counters(self):
+        """increments counters"""
+        self.counters.inc('socket', 'unixdomain', 'open')
+        self.counters.inc('socket', 'unixdomain', 'close')
+
+class DummyXfroutServer(BaseDummyModule):
+    """A dummy class equivalent to XfroutServer in b10-xfrout"""
+    def __init__(self):
+        super().__init__()
+        self.xfrout_sess = DummyXfroutSession()
+        self.unix_socket_server = DummyUnixSockServer()
+        self.notifier = DummyNotifyOut()
+
+    def inc_counters(self):
+        self.xfrout_sess.inc_counters()
+        self.unix_socket_server.inc_counters()
+        self.notifier.inc_counters()
+
+class TestDummyNotifyOut(unittest.TestCase):
+    """Tests counters are incremented in which the spec file is not
+    loaded"""
+    def setUp(self):
+        imp.reload(dns)
+        self.notifier = DummyNotifyOut()
+        self.notifier.inc_counters()
+
+    def tearDown(self):
+        self.notifier.clear_counters()
+
+    def test_counters(self):
+        self.assertEqual(
+            {'zones': {TEST_ZONE_CLASS_STR: { '_SERVER_':
+                           {'notifyoutv4': 1, 'notifyoutv6': 1},
+                                              TEST_ZONE_NAME_STR:
+                           {'notifyoutv4': 1, 'notifyoutv6': 1}}}},
+            self.notifier.get_counters())
+
+class TestDummyXfroutServer(unittest.TestCase):
+    """Tests counters are incremented or decremented in which the same
+    spec file is multiply loaded in each child class"""
+    def setUp(self):
+        imp.reload(dns)
+        self.xfrout_server = DummyXfroutServer()
+        self.xfrout_server.inc_counters()
+
+    def tearDown(self):
+        self.xfrout_server.clear_counters()
+
+    def test_counters(self):
+        self.assertEqual(
+            {'axfr_running': 0, 'ixfr_running': 0,
+             'socket': {'unixdomain': {'open': 1, 'close': 1}},
+             'zones': {TEST_ZONE_CLASS_STR: {
+                        '_SERVER_': {'notifyoutv4': 1,
+                                    'notifyoutv6': 1,
+                                    'xfrrej': 1, 'xfrreqdone': 1},
+                        TEST_ZONE_NAME_STR: {'notifyoutv4': 1,
+                                        'notifyoutv6': 1,
+                                        'xfrrej': 1,
+                                        'xfrreqdone': 1}}}},
+            self.xfrout_server.get_counters())
+
+if __name__== "__main__":
+    unittest.main()
diff --git a/src/lib/resolve/tests/recursive_query_unittest.cc b/src/lib/resolve/tests/recursive_query_unittest.cc
index acbbb03..48e5a31 100644
--- a/src/lib/resolve/tests/recursive_query_unittest.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest.cc
@@ -56,7 +56,6 @@
 #include <asiolink/io_service.h>
 #include <asiolink/io_message.h>
 #include <asiolink/io_error.h>
-#include <asiolink/simple_callback.h>
 
 using isc::UnitTestUtil;
 using namespace std;
@@ -333,8 +332,7 @@ protected:
     // Set up empty DNS Service
     // Set up an IO Service queue without any addresses
     void setDNSService() {
-        dns_service_.reset(new DNSService(io_service_, callback_.get(), NULL,
-                                          NULL));
+        dns_service_.reset(new DNSService(io_service_, callback_.get(), NULL));
     }
 
     // Run a simple server test, on either IPv4 or IPv6, and over either
@@ -478,10 +476,12 @@ protected:
     };
 
 private:
-    class ASIOCallBack : public SimpleCallback {
+    class ASIOCallBack : public DNSLookup {
     public:
         ASIOCallBack(RecursiveQueryTest* test_obj) : test_obj_(test_obj) {}
-        void operator()(const IOMessage& io_message) const {
+        void operator()(const IOMessage& io_message,
+                        isc::dns::MessagePtr, isc::dns::MessagePtr,
+                        isc::util::OutputBufferPtr, DNSServer*) const {
             test_obj_->callBack(io_message);
         }
     private:
diff --git a/src/lib/resolve/tests/recursive_query_unittest_2.cc b/src/lib/resolve/tests/recursive_query_unittest_2.cc
index 0b38c59..d6019d0 100644
--- a/src/lib/resolve/tests/recursive_query_unittest_2.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest_2.cc
@@ -159,7 +159,7 @@ public:
     RecursiveQueryTest2() :
         debug_(DEBUG_PRINT),
         service_(),
-        dns_service_(service_, NULL, NULL, NULL),
+        dns_service_(service_, NULL, NULL),
         question_(new Question(Name("www.example.org"), RRClass::IN(), RRType::A())),
         last_(NONE),
         expected_(NONE),
diff --git a/src/lib/resolve/tests/recursive_query_unittest_3.cc b/src/lib/resolve/tests/recursive_query_unittest_3.cc
index 92ec589..df48740 100644
--- a/src/lib/resolve/tests/recursive_query_unittest_3.cc
+++ b/src/lib/resolve/tests/recursive_query_unittest_3.cc
@@ -141,7 +141,7 @@ public:
     /// \brief Constructor
     RecursiveQueryTest3() :
         service_(),
-        dns_service_(service_, NULL, NULL, NULL),
+        dns_service_(service_, NULL, NULL),
         question_(new Question(Name("ednsfallback"),
                   RRClass::IN(), RRType::A())),
         last_(NONE),
diff --git a/src/lib/util/tests/memory_segment_mapped_unittest.cc b/src/lib/util/tests/memory_segment_mapped_unittest.cc
index c22b59e..8ae6fea 100644
--- a/src/lib/util/tests/memory_segment_mapped_unittest.cc
+++ b/src/lib/util/tests/memory_segment_mapped_unittest.cc
@@ -237,6 +237,15 @@ TEST_F(MemorySegmentMappedTest, allocate) {
 }
 
 TEST_F(MemorySegmentMappedTest, badAllocate) {
+    // If the test is run as the root user, the following allocate()
+    // call will result in a successful MemorySegmentGrown exception,
+    // instead of an abort (due to insufficient permissions during
+    // reopen).
+    if (getuid() == 0) {
+        std::cerr << "Skipping test as it's run as the root user" << std::endl;
+        return;
+    }
+
     // Make the mapped file non-writable; managed_mapped_file::grow() will
     // fail, resulting in abort.
     const int ret = chmod(mapped_file, 0444);
diff --git a/tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc b/tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc
index de134cc..02c009e 100644
--- a/tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc
+++ b/tests/tools/perfdhcp/tests/perf_pkt6_unittest.cc
@@ -111,8 +111,8 @@ TEST_F(PerfPkt6Test, Constructor) {
     // Test constructor to be used for incoming messages.
     // Use default (1) offset value and don't specify transaction id.
     boost::scoped_ptr<PerfPkt6> pkt1(new PerfPkt6(data, sizeof(data)));
-    EXPECT_EQ(sizeof(data), pkt1->getData().size());
-    EXPECT_EQ(0, memcmp(&pkt1->getData()[0], data, sizeof(data)));
+    EXPECT_EQ(sizeof(data), pkt1->data_.size());
+    EXPECT_EQ(0, memcmp(&pkt1->data_[0], data, sizeof(data)));
     EXPECT_EQ(1, pkt1->getTransidOffset());
 
     // Test constructor to be used for outgoing messages.
@@ -121,8 +121,8 @@ TEST_F(PerfPkt6Test, Constructor) {
     const uint32_t transid = 0x010203;
     boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(data, sizeof(data),
                                                   offset_transid, transid));
-    EXPECT_EQ(sizeof(data), pkt2->getData().size());
-    EXPECT_EQ(0, memcmp(&pkt2->getData()[0], data, sizeof(data)));
+    EXPECT_EQ(sizeof(data), pkt2->data_.size());
+    EXPECT_EQ(0, memcmp(&pkt2->data_[0], data, sizeof(data)));
     EXPECT_EQ(0x010203, pkt2->getTransid());
     EXPECT_EQ(10, pkt2->getTransidOffset());
 }
@@ -163,7 +163,7 @@ TEST_F(PerfPkt6Test, RawPackUnpack) {
     // Get output buffer from packet 1 to create new packet
     // that will be later validated.
     util::OutputBuffer pkt1_output = pkt1->getBuffer();
-    ASSERT_EQ(pkt1_output.getLength(), pkt1->getData().size());
+    ASSERT_EQ(pkt1_output.getLength(), pkt1->data_.size());
     const uint8_t* pkt1_output_data = static_cast<const uint8_t*>
         (pkt1_output.getData());
     boost::scoped_ptr<PerfPkt6> pkt2(new PerfPkt6(pkt1_output_data,
diff --git a/tests/tools/perfdhcp/tests/test_control_unittest.cc b/tests/tools/perfdhcp/tests/test_control_unittest.cc
index 3e0145c..6aff71c 100644
--- a/tests/tools/perfdhcp/tests/test_control_unittest.cc
+++ b/tests/tools/perfdhcp/tests/test_control_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -241,8 +241,9 @@ public:
         size_t matched_num = 0;
         for (size_t i = 0; i < buf.size(); i += 2) {
             for (int j = 0; j < requested_options.size(); j += 2) {
-                uint16_t opt_i = buf[i + 1] << 8 + buf[i] & 0xFF;
-                uint16_t opt_j = requested_options[j + 1] << 8 + requested_options[j] & 0xFF;
+                uint16_t opt_i = (buf[i + 1] << 8) + (buf[i] & 0xFF);
+                uint16_t opt_j = (requested_options[j + 1] << 8)
+                    + (requested_options[j] & 0xFF);
                 if (opt_i == opt_j) {
                     // Requested option has been found.
                     ++matched_num;
@@ -817,10 +818,12 @@ TEST_F(TestControlTest, Options6) {
     // Prepare the reference buffer with requested options.
     const uint8_t requested_options[] = {
         0, D6O_NAME_SERVERS,
-        0, D6O_DOMAIN_SEARCH,
+        0, D6O_DOMAIN_SEARCH
     };
-    int requested_options_num =
-        sizeof(requested_options) / sizeof(requested_options[0]);
+    // Each option code in ORO is 2 bytes long. We calculate the number of
+    // requested options by dividing the size of the buffer holding options
+    // by the size of each individual option.
+    int requested_options_num = sizeof(requested_options) / sizeof(uint16_t);
     OptionBuffer
         requested_options_ref(requested_options,
                               requested_options + sizeof(requested_options));



More information about the bind10-changes mailing list